diff --git a/BUILD.gn b/BUILD.gn
index 48eab0e..f69c7f62 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1291,6 +1291,12 @@
   }
 }
 
+# TODO(cassew): Add more OS's that don't support x86
+assert(
+    !(target_cpu == "x86" &&
+          (target_os == "ios" || target_os == "mac" || target_os == "linux")),
+    "'target_cpu=x86' is not supported for 'target_os=$target_os'. Consider omitting 'target_cpu' (default) or using 'target_cpu=x64' instead.")
+
 group("chromium_builder_perf") {
   testonly = true
 
diff --git a/DEPS b/DEPS
index ae914bab..bfee5e2 100644
--- a/DEPS
+++ b/DEPS
@@ -204,11 +204,11 @@
   # 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': '310dc4e0b9077c5b3a184e2a1e6b7cb9d5fd4105',
+  'skia_revision': '92748af1a5051e9fe329c8200f0fa3b47aadbdd7',
   # 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': '9becce304a5e9b05bff62eef6c5e53b944120eb2',
+  'v8_revision': '6b214099c60660a75b414785481afafc74a50628',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -216,7 +216,7 @@
   # 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': 'a11d65a172f885042cf4fdab5bfd124d174f5190',
+  'angle_revision': '06d194e2ae7b1d7e0eda0c0c911eff92dec7d3d1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -224,7 +224,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'a01468f95684536a17b41dc5db2f751bbf70a8ea',
+  'pdfium_revision': '5a4cc47b4b0b5cccae16ff386a08e4dc6f7e2578',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -283,7 +283,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': 'd9069b9a5477538b7da8c843191afd55718f8f47',
+  'devtools_frontend_revision': 'c1ef3e5ffa3b18285afe6bd7e4a70567954999cf',
   # 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.
@@ -327,7 +327,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.
-  'quiche_revision': 'c0215273f2dd351d4e427441fd3577bc86d853f2',
+  'quiche_revision': '91e0ce88f914bf9663dc6858c1a735c3d8829c47',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -930,7 +930,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e7dc8c3a863ce0cf24523431ae8ae535b237ece8',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '593a6b575b137c42c91ef2439dbf6c526e5c5980',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1302,7 +1302,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '4ed03f206457414bfcd3592afec4a0b3f71d821d',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '4973272defb4081572496c831ca3e55c6d12c99a',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1511,7 +1511,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '991335be3de503ef02cd9f8415e4242ad3f107f9',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@d70f9e670332b24303b835a7c1a517fc4668687e',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@2be540b3efbccbb11308b45ebbce9c01041b8684',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '732a76d9d3c70d6aa487216495eeb28518349c3a',
@@ -1610,7 +1610,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@5df04cae3dc3f04b5987bcc0f7b254330693db2a',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8a049f5cad963801d946d9d0dc078c514fe981e9',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index 2fc1e61..fbce96a6 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -130,8 +130,6 @@
 // On apps targeting API level O or later, check cleartext is enforced.
 bool g_check_cleartext_permitted = false;
 
-const uint32_t kAwContentsMessageFilteredClasses[] = {FrameMsgStart};
-
 // TODO(sgurun) move this to its own file.
 // This class handles android_webview.mojom.RenderMessageFilter Mojo interface's
 // methods on IO thread.
@@ -157,10 +155,7 @@
 };
 
 AwContentsMessageFilter::AwContentsMessageFilter(int process_id)
-    : content::BrowserMessageFilter(
-          kAwContentsMessageFilteredClasses,
-          base::size(kAwContentsMessageFilteredClasses)),
-      content::BrowserAssociatedInterface<mojom::RenderMessageFilter>(this),
+    : content::BrowserAssociatedInterface<mojom::RenderMessageFilter>(this),
       process_id_(process_id) {}
 
 AwContentsMessageFilter::~AwContentsMessageFilter() = default;
diff --git a/android_webview/browser/aw_feature_list_creator.cc b/android_webview/browser/aw_feature_list_creator.cc
index 3371b5e..c749c87a 100644
--- a/android_webview/browser/aw_feature_list_creator.cc
+++ b/android_webview/browser/aw_feature_list_creator.cc
@@ -106,14 +106,6 @@
   std::vector<base::FeatureList::FeatureOverrideInfo> feature_overrides =
       content::GetSwitchDependentFeatureOverrides(command_line);
 
-  // TODO(chlily): This can be removed when Schemeful Same-Site is enabled by
-  // default.
-  if (command_line.HasSwitch(switches::kWebViewEnableModernCookieSameSite)) {
-    feature_overrides.push_back(
-        std::make_pair(std::cref(net::features::kSchemefulSameSite),
-                       base::FeatureList::OVERRIDE_ENABLE_FEATURE));
-  }
-
   return feature_overrides;
 }
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
index fe3e31e..3936431 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwActivityTestRule.java
@@ -4,8 +4,6 @@
 
 package org.chromium.android_webview.test;
 
-import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
-
 import android.content.Context;
 import android.content.Intent;
 import android.support.test.InstrumentationRegistry;
@@ -34,6 +32,7 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.InMemorySharedPreferences;
+import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnPageFinishedHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -55,7 +54,10 @@
 
 /** Custom ActivityTestRunner for WebView instrumentation tests */
 public class AwActivityTestRule extends BaseActivityTestRule<AwTestRunnerActivity> {
-    public static final long WAIT_TIMEOUT_MS = scaleTimeout(15000L);
+    public static final long WAIT_TIMEOUT_MS = 15000L;
+
+    // Only use scaled timeout if you are certain it's not being called further up the call stack.
+    public static final long SCALED_WAIT_TIMEOUT_MS = ScalableTimeout.scaleTimeout(15000L);
 
     public static final int CHECK_INTERVAL = 100;
 
@@ -569,7 +571,7 @@
      */
     public static <T> T waitForFuture(Future<T> future) {
         try {
-            return future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         } catch (ExecutionException e) {
             // ExecutionException means this Future has an associated Exception that we should
             // re-throw on the current thread. We throw the cause instead of ExecutionException,
@@ -600,7 +602,7 @@
      * Takes an element out of the {@link BlockingQueue} (or times out).
      */
     public static <T> T waitForNextQueueElement(BlockingQueue<T> queue) throws Exception {
-        T value = queue.poll(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        T value = queue.poll(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         if (value == null) {
             // {@code null} is the special value which means {@link BlockingQueue#poll} has timed
             // out (also: there's no risk for collision with real values, because BlockingQueue does
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientOnRendererUnresponsiveTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientOnRendererUnresponsiveTest.java
index 847763a..3c9969c 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientOnRendererUnresponsiveTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientOnRendererUnresponsiveTest.java
@@ -59,11 +59,12 @@
         @JavascriptInterface
         public void block() throws Exception {
             mThreadWasBlockedLatch.countDown();
-            mBlockingLatch.await(AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            mBlockingLatch.await(AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         }
 
         public void waitUntilBlocked() throws Exception {
-            mThreadWasBlockedLatch.await(AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            mThreadWasBlockedLatch.await(
+                    AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         }
     }
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
index 9a1c3e5..dd8bfe5 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java
@@ -4,7 +4,7 @@
 
 package org.chromium.android_webview.test;
 
-import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;
+import static org.chromium.android_webview.test.AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS;
 
 import android.support.test.InstrumentationRegistry;
 import android.util.Pair;
@@ -1049,8 +1049,8 @@
         final String destinationUrl = mWebServer.setResponse("/hello.txt", "", headers);
 
         final Future<String> future = loadDataAndFetch(destinationUrl);
-        Assert.assertEquals(
-                "fetch should succeed", "cors", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch should succeed", "cors",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals(destinationUrl, mShouldInterceptRequestHelper.getUrls().get(1));
     }
@@ -1067,8 +1067,8 @@
 
         final Future<String> future = loadDataAndFetch(destinationUrl);
         // The request fails due to origin mismatch.
-        Assert.assertEquals(
-                "fetch should fail", "error", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch should fail", "error",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals(destinationUrl, mShouldInterceptRequestHelper.getUrls().get(1));
     }
@@ -1086,8 +1086,8 @@
 
         // PUT is not a safelisted method and triggers a preflight.
         final Future<String> future = loadDataAndFetch(destinationUrl, "PUT");
-        Assert.assertEquals(
-                "fetch should succeed", "cors", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch should succeed", "cors",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(3, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals("preflight request should be visible to shouldInterceptRequest",
                 destinationUrl, mShouldInterceptRequestHelper.getUrls().get(1));
@@ -1108,8 +1108,8 @@
         // PUT is not a safelisted method and triggers a preflight.
         final Future<String> future = loadDataAndFetch(destinationUrl, "PUT");
         // The request fails due to the lack of access-control-allow-methods.
-        Assert.assertEquals(
-                "fetch should fail", "error", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch should fail", "error",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals("preflight request should be visible to shouldInterceptRequest",
                 destinationUrl, mShouldInterceptRequestHelper.getUrls().get(1));
@@ -1135,8 +1135,8 @@
         mShouldInterceptRequestHelper.setReturnValueForUrl(destinationUrl, response);
 
         final Future<String> future = loadDataAndFetch(destinationUrl);
-        Assert.assertEquals(
-                "fetch should succeed", "cors", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch should succeed", "cors",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals(destinationUrl, mShouldInterceptRequestHelper.getUrls().get(1));
 
@@ -1162,8 +1162,8 @@
         mShouldInterceptRequestHelper.setReturnValueForUrl(destinationUrl, response);
 
         final Future<String> future = loadDataAndFetch(destinationUrl);
-        Assert.assertEquals(
-                "fetch should fail", "error", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch should fail", "error",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals(destinationUrl, mShouldInterceptRequestHelper.getUrls().get(1));
 
@@ -1192,8 +1192,8 @@
 
         // PUT is not a safelisted method and triggers a preflight.
         final Future<String> future = loadDataAndFetch(destinationUrl, "PUT");
-        Assert.assertEquals(
-                "fetch should succeed", "cors", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch should succeed", "cors",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(3, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals("preflight request should be visible to shouldInterceptRequest",
                 destinationUrl, mShouldInterceptRequestHelper.getUrls().get(1));
@@ -1224,8 +1224,8 @@
 
         // PUT is not a safelisted method and triggers a preflight.
         final Future<String> future = loadDataAndFetch(destinationUrl, "PUT");
-        Assert.assertEquals(
-                "fetch should fail", "error", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch should fail", "error",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals("preflight request should be visible to shouldInterceptRequest",
                 destinationUrl, mShouldInterceptRequestHelper.getUrls().get(1));
@@ -1247,7 +1247,7 @@
 
         final Future<String> future = loadUrlAndFetch(pageUrl, fetchUrl);
         Assert.assertEquals("fetch result check", fetchResult,
-                future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
         Assert.assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals(pageUrl, mShouldInterceptRequestHelper.getUrls().get(0));
@@ -1307,8 +1307,8 @@
         mShouldInterceptRequestHelper.setReturnValueForUrl(fetchUrl, response);
 
         final Future<String> future = loadUrlAndFetch(pageUrl, fetchUrl);
-        Assert.assertEquals(
-                "fetch result check", "error", future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertEquals("fetch result check", "error",
+                future.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
         // Only the main resource request reaches to the network stack.
         Assert.assertEquals(1, mShouldInterceptRequestHelper.getUrls().size());
@@ -1331,7 +1331,7 @@
         final Future<String> futureToFail =
                 loadUrlAndFetch(pageUrl, fetchUrlToFail, preflightTriggeringMethod);
         Assert.assertEquals("fetch result check", "error",
-                futureToFail.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                futureToFail.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
         Assert.assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals(pageUrl, mShouldInterceptRequestHelper.getUrls().get(0));
@@ -1365,7 +1365,7 @@
         final Future<String> futureToPass =
                 loadUrlAndFetch(pageUrl, fetchUrlToPass, preflightTriggeringMethod);
         Assert.assertEquals("fetch result check", "cors",
-                futureToPass.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                futureToPass.get(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
         Assert.assertEquals(3, mShouldInterceptRequestHelper.getUrls().size());
         Assert.assertEquals(pageUrl, mShouldInterceptRequestHelper.getUrls().get(0));
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java
index 27eca43..d28e962 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java
@@ -4,6 +4,7 @@
 
 package org.chromium.android_webview.test;
 
+import static org.chromium.android_webview.test.AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS;
 import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;
 
 import android.annotation.SuppressLint;
@@ -1124,7 +1125,7 @@
 
         public void waitForLatch() {
             try {
-                Assert.assertTrue(mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                Assert.assertTrue(mLatch.await(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
@@ -1146,7 +1147,7 @@
         mActivityTestRule.loadUrlAsync(mAwContents, fromUrl);
         client.waitForLatch();
         // Wait for an arbitrary amount of time to ensure onReceivedError is never called.
-        Thread.sleep(WAIT_TIMEOUT_MS / 3);
+        Thread.sleep(SCALED_WAIT_TIMEOUT_MS / 3);
     }
 
     private void verifyShouldOverrideUrlLoadingInPopup(String popupPath) throws Throwable {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
index d713d1d8..ac9d305 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java
@@ -128,7 +128,8 @@
                 }
             });
         });
-        Assert.assertTrue(latch.await(AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertTrue(
+                latch.await(AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
         final int width =
                 TestThreadUtils.runOnUiThreadBlockingNoException(() -> mContainerView.getWidth());
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
index 65377d6c..651f6f1 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
@@ -254,7 +254,8 @@
         });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(
                 () -> awContents.documentHasImages(msg));
-        Assert.assertTrue(s.tryAcquire(AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        Assert.assertTrue(
+                s.tryAcquire(AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         int result = val.get();
         return result;
     }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java
index e40aa118..f31aaba 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java
@@ -105,7 +105,7 @@
         Assert.assertNotNull(context.startService(intent));
         Assert.assertTrue(context.bindService(intent, mConnection, 0));
         Assert.assertTrue(mSecondBrowserProcessLatch.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         mSecondBrowserProcessLatch = null;
     }
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwUncaughtExceptionTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwUncaughtExceptionTest.java
index 1499c75c..b8979a2 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwUncaughtExceptionTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwUncaughtExceptionTest.java
@@ -4,7 +4,7 @@
 
 package org.chromium.android_webview.test;
 
-import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;
+import static org.chromium.android_webview.test.AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS;
 
 import android.os.Looper;
 import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
@@ -161,7 +161,8 @@
                     new StackTraceElement("android.webkit.WebView", "loadUrl", "<none>", 0)});
             throw exception;
         });
-        Assert.assertTrue(latch.await(WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
+        Assert.assertTrue(
+                latch.await(SCALED_WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
     }
 
     @Test
@@ -180,7 +181,8 @@
                     new StackTraceElement("java.lang.Object", "equals", "<none>", 0)});
             throw exception;
         });
-        Assert.assertTrue(latch.await(WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
+        Assert.assertTrue(
+                latch.await(SCALED_WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
     }
 
     @Test
@@ -209,6 +211,7 @@
                     "data:text/html,<script>window.location='https://www.google.com';</script>");
         });
 
-        Assert.assertTrue(latch.await(WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
+        Assert.assertTrue(
+                latch.await(SCALED_WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
     }
 };
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnPageFinishedTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnPageFinishedTest.java
index 6c04e4087..41ef710 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnPageFinishedTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnPageFinishedTest.java
@@ -4,7 +4,7 @@
 
 package org.chromium.android_webview.test;
 
-import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;
+import static org.chromium.android_webview.test.AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS;
 
 import android.support.test.InstrumentationRegistry;
 import android.util.Pair;
@@ -351,7 +351,7 @@
                     "/about.html", CommonResources.ABOUT_HTML, null,
                     () -> {
                         try {
-                            Assert.assertTrue(latch.await(WAIT_TIMEOUT_MS,
+                            Assert.assertTrue(latch.await(SCALED_WAIT_TIMEOUT_MS,
                                     java.util.concurrent.TimeUnit.MILLISECONDS));
                         } catch (InterruptedException e) {
                             Assert.fail("Caught InterruptedException " + e);
@@ -493,8 +493,8 @@
                         try {
                             // Delay the server response so that we guarantee stopLoading() comes
                             // before the server response.
-                            Assert.assertTrue(firstUrlLatch.await(
-                                    WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
+                            Assert.assertTrue(firstUrlLatch.await(SCALED_WAIT_TIMEOUT_MS,
+                                    java.util.concurrent.TimeUnit.MILLISECONDS));
                         } catch (InterruptedException e) {
                             Assert.fail("Caught InterruptedException " + e);
                         }
@@ -541,7 +541,7 @@
                     "/stallingImage.html", "", null /* headers */, () -> {
                         serverImageUrlLatch.countDown();
                         try {
-                            Assert.assertTrue(testDoneLatch.await(WAIT_TIMEOUT_MS,
+                            Assert.assertTrue(testDoneLatch.await(SCALED_WAIT_TIMEOUT_MS,
                                     java.util.concurrent.TimeUnit.MILLISECONDS));
                         } catch (InterruptedException e) {
                             Assert.fail("Caught InterruptedException " + e);
@@ -556,7 +556,7 @@
             mActivityTestRule.loadUrlAsync(mAwContents, mainPageUrl);
 
             Assert.assertTrue(serverImageUrlLatch.await(
-                    WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
+                    SCALED_WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
             Assert.assertEquals(0, onPageFinishedHelper.getCallCount());
             // Our load isn't done since we haven't loaded the image - now cancel the load.
             mActivityTestRule.stopLoading(mAwContents);
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedError2Test.java b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedError2Test.java
index dd3051c..7a3c8b4 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedError2Test.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/ClientOnReceivedError2Test.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertNotEquals;
 
+import static org.chromium.android_webview.test.AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS;
 import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;
 
 import android.webkit.WebSettings;
@@ -371,8 +372,7 @@
                 "/about.html", CommonResources.ABOUT_HTML, null,
                 () -> {
                     try {
-                        latch.await(
-                                WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                        latch.await(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
                     } catch (InterruptedException e) {
                         Assert.fail("Caught InterruptedException " + e);
                     }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
index cff02d0..3f2f69c2 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
@@ -193,7 +193,7 @@
         });
 
         Assert.assertTrue(testFinishedSignal.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
@@ -265,9 +265,9 @@
         });
 
         Assert.assertTrue(pageCommitCallbackOccurred.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertTrue(testFinishedSignal.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
@@ -313,7 +313,7 @@
                 awContents, awContentsClient.getOnPageFinishedHelper(), WAIT_FOR_JS_TEST_URL);
 
         Assert.assertTrue(readyToUpdateColor.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         DOMUtils.clickNode(webContents, UPDATE_COLOR_CONTROL_ID);
         Assert.assertTrue(jsObserver.waitForEvent(WAIT_TIMEOUT_MS));
 
@@ -330,7 +330,7 @@
                         }));
 
         Assert.assertTrue(testFinishedSignal.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     @Test
@@ -380,7 +380,7 @@
                 awContents, awContentsClient.getOnPageFinishedHelper(), FULLSCREEN_TEST_URL);
 
         Assert.assertTrue(readyToEnterFullscreenSignal.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
         DOMUtils.clickNode(webContents, ENTER_FULLSCREEN_CONTROL_ID);
         Assert.assertTrue(jsObserver.waitForEvent(WAIT_TIMEOUT_MS));
 
@@ -398,7 +398,7 @@
                 }));
 
         Assert.assertTrue(testFinishedSignal.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     private AwTestContainerView createDetachedTestContainerViewOnMainSync(
@@ -448,7 +448,7 @@
         });
 
         Assert.assertTrue(testFinishedSignal.await(
-                AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     private static final LoadUrlParams createTestPageUrl(String backgroundColor) {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/services/ComponentsProviderServiceTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/services/ComponentsProviderServiceTest.java
index 271ef45..93ae1d2 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/services/ComponentsProviderServiceTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/services/ComponentsProviderServiceTest.java
@@ -130,7 +130,7 @@
             }
         });
         Assert.assertTrue("Timeout waiting to receive result from getFilesForComponent",
-                latch.await(AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                latch.await(AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
         return result.isEmpty() ? null : result;
     }
diff --git a/ash/assistant/assistant_notification_controller_impl.cc b/ash/assistant/assistant_notification_controller_impl.cc
index 49f08f9..c13e737 100644
--- a/ash/assistant/assistant_notification_controller_impl.cc
+++ b/ash/assistant/assistant_notification_controller_impl.cc
@@ -107,11 +107,6 @@
 
 // AssistantNotificationController --------------------------------------
 
-void AssistantNotificationControllerImpl::AddOrUpdateNotification(
-    AssistantNotification&& notification) {
-  model_.AddOrUpdateNotification(std::move(notification));
-}
-
 void AssistantNotificationControllerImpl::RemoveNotificationById(
     const std::string& id,
     bool from_server) {
@@ -124,6 +119,11 @@
 
 // NotificationDelegate ------------------------------------------------------
 
+void AssistantNotificationControllerImpl::AddOrUpdateNotification(
+    AssistantNotification notification) {
+  model_.AddOrUpdateNotification(std::move(notification));
+}
+
 void AssistantNotificationControllerImpl::RemoveNotificationByGroupingKey(
     const std::string& grouping_key,
     bool from_server) {
diff --git a/ash/assistant/assistant_notification_controller_impl.h b/ash/assistant/assistant_notification_controller_impl.h
index 5187e3e..5821872 100644
--- a/ash/assistant/assistant_notification_controller_impl.h
+++ b/ash/assistant/assistant_notification_controller_impl.h
@@ -42,11 +42,11 @@
   void SetAssistant(chromeos::assistant::Assistant* assistant);
 
   // AssistantNotificationController:
-  void AddOrUpdateNotification(AssistantNotification&& notification) override;
   void RemoveNotificationById(const std::string& id, bool from_server) override;
   void SetQuietMode(bool enabled) override;
 
   // chromeos::libassistant::mojom::NotificationDelegate:
+  void AddOrUpdateNotification(AssistantNotification notification) override;
   void RemoveNotificationByGroupingKey(const std::string& grouping_id,
                                        bool from_server) override;
   void RemoveAllNotifications(bool from_server) override;
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc
index f150f499..5b692e2 100644
--- a/ash/capture_mode/capture_mode_controller.cc
+++ b/ash/capture_mode/capture_mode_controller.cc
@@ -68,8 +68,9 @@
 constexpr char kScreenCaptureStoppedNotificationId[] =
     "capture_mode_stopped_notification";
 constexpr char kScreenCaptureNotifierId[] = "ash.capture_mode_controller";
-constexpr char kScreenCaptureNotificationType[] =
-    "capture_mode_notification_type";
+constexpr char kScreenShotNotificationType[] = "screen_shot_notification_type";
+constexpr char kScreenRecordingNotificationType[] =
+    "screen_recording_notification_type";
 
 // The format strings of the file names of captured images.
 // TODO(afakhry): Discuss with UX localizing "Screenshot" and "Screen
@@ -162,6 +163,8 @@
 }
 
 // Shows a Capture Mode related notification with the given parameters.
+// |for_video_thumbnail| will be considered only if |optional_fields| contain
+// an image to show in the notification as a thumbnail for what was captured.
 void ShowNotification(
     const std::string& notification_id,
     int title_id,
@@ -170,7 +173,8 @@
     scoped_refptr<message_center::NotificationDelegate> delegate,
     message_center::SystemNotificationWarningLevel warning_level =
         message_center::SystemNotificationWarningLevel::NORMAL,
-    const gfx::VectorIcon& notification_icon = kCaptureModeIcon) {
+    const gfx::VectorIcon& notification_icon = kCaptureModeIcon,
+    bool for_video_thumbnail = false) {
   const auto type = optional_fields.image.IsEmpty()
                         ? message_center::NOTIFICATION_TYPE_SIMPLE
                         : message_center::NOTIFICATION_TYPE_CUSTOM;
@@ -184,8 +188,11 @@
               message_center::NotifierType::SYSTEM_COMPONENT,
               kScreenCaptureNotifierId),
           optional_fields, delegate, notification_icon, warning_level);
-  if (type == message_center::NOTIFICATION_TYPE_CUSTOM)
-    notification->set_custom_view_type(kScreenCaptureNotificationType);
+  if (type == message_center::NOTIFICATION_TYPE_CUSTOM) {
+    notification->set_custom_view_type(for_video_thumbnail
+                                           ? kScreenRecordingNotificationType
+                                           : kScreenShotNotificationType);
+  }
 
   // Remove the previous notification before showing the new one if there is
   // any.
@@ -304,10 +311,15 @@
           weak_ptr_factory_.GetWeakPtr()));
 
   DCHECK(!message_center::MessageViewFactory::HasCustomNotificationViewFactory(
-      kScreenCaptureNotificationType));
+      kScreenShotNotificationType));
+  DCHECK(!message_center::MessageViewFactory::HasCustomNotificationViewFactory(
+      kScreenRecordingNotificationType));
   message_center::MessageViewFactory::SetCustomNotificationViewFactory(
-      kScreenCaptureNotificationType,
-      base::BindRepeating(&CaptureModeNotificationView::Create));
+      kScreenShotNotificationType,
+      base::BindRepeating(&CaptureModeNotificationView::CreateForImage));
+  message_center::MessageViewFactory::SetCustomNotificationViewFactory(
+      kScreenRecordingNotificationType,
+      base::BindRepeating(&CaptureModeNotificationView::CreateForVideo));
 
   Shell::Get()->session_controller()->AddObserver(this);
   chromeos::PowerManagerClient::Get()->AddObserver(this);
@@ -316,9 +328,11 @@
 CaptureModeController::~CaptureModeController() {
   chromeos::PowerManagerClient::Get()->RemoveObserver(this);
   Shell::Get()->session_controller()->RemoveObserver(this);
-  // Remove the custom notification view factory.
+  // Remove the custom notification view factories.
   message_center::MessageViewFactory::ClearCustomNotificationViewFactory(
-      kScreenCaptureNotificationType);
+      kScreenShotNotificationType);
+  message_center::MessageViewFactory::ClearCustomNotificationViewFactory(
+      kScreenRecordingNotificationType);
 
   DCHECK_EQ(g_instance, this);
   g_instance = nullptr;
@@ -504,7 +518,7 @@
     // HDCP violation is also considered a failure, and we're not going to wait
     // for any buffered frames in the recording service.
     RecordEndRecordingReason(EndRecordingReason::kHdcpInterruption);
-    OnRecordingEnded(/*success=*/false);
+    OnRecordingEnded(/*success=*/false, gfx::ImageSkia());
     ShowVideoRecordingStoppedNotification(/*for_hdcp=*/true);
   }
 }
@@ -516,7 +530,8 @@
       .Then(on_video_file_status_);
 }
 
-void CaptureModeController::OnRecordingEnded(bool success) {
+void CaptureModeController::OnRecordingEnded(bool success,
+                                             const gfx::ImageSkia& thumbnail) {
   delegate_->StopObservingRestrictedContent();
 
   // If |success| is false, then recording has been force-terminated due to a
@@ -534,7 +549,7 @@
   DCHECK(video_file_handler_);
   video_file_handler_.AsyncCall(&VideoFileHandler::FlushBufferedChunks)
       .Then(base::BindOnce(&CaptureModeController::OnVideoFileSaved,
-                           weak_ptr_factory_.GetWeakPtr()));
+                           weak_ptr_factory_.GetWeakPtr(), thumbnail));
 }
 
 void CaptureModeController::OnActiveUserSessionChanged(
@@ -644,7 +659,7 @@
     // block the suspend until all chunks have been received, and then we can
     // resume it.
     RecordEndRecordingReason(EndRecordingReason::kImminentSuspend);
-    OnRecordingEnded(/*success=*/false);
+    OnRecordingEnded(/*success=*/false, gfx::ImageSkia());
     return;
   }
 
@@ -786,7 +801,7 @@
   // StopRecording(), and it calling us back with OnRecordingEnded(), so we call
   // OnRecordingEnded() in all cases.
   RecordEndRecordingReason(EndRecordingReason::kRecordingServiceDisconnected);
-  OnRecordingEnded(/*success=*/false);
+  OnRecordingEnded(/*success=*/false, gfx::ImageSkia());
 }
 
 CaptureAllowance CaptureModeController::IsCaptureAllowedByEnterprisePolicies(
@@ -819,7 +834,9 @@
 
 void CaptureModeController::CaptureImage(const CaptureParams& capture_params,
                                          const base::FilePath& path) {
-  DCHECK_EQ(CaptureModeType::kImage, type_);
+  // Note that |type_| may not necessarily be |kImage| here, since this may be
+  // called to take an instant fullscreen screenshot for the keyboard shortcut,
+  // which doesn't go through the capture mode UI, and doesn't change |type_|.
   DCHECK_EQ(CaptureAllowance::kAllowed,
             IsCaptureAllowedByEnterprisePolicies(capture_params));
 
@@ -909,14 +926,17 @@
   EndVideoRecording(EndRecordingReason::kFileIoError);
 }
 
-void CaptureModeController::OnVideoFileSaved(bool success) {
+void CaptureModeController::OnVideoFileSaved(
+    const gfx::ImageSkia& video_thumbnail,
+    bool success) {
   DCHECK(base::CurrentUIThread::IsSet());
   DCHECK(video_file_handler_);
 
   if (!success) {
     ShowFailureNotification();
   } else {
-    ShowPreviewNotification(current_video_file_path_, gfx::Image(),
+    ShowPreviewNotification(current_video_file_path_,
+                            gfx::Image(video_thumbnail),
                             CaptureModeType::kVideo);
     DCHECK(!recording_start_time_.is_null());
     RecordCaptureModeRecordTime(
@@ -965,7 +985,9 @@
       base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
           base::BindRepeating(&CaptureModeController::HandleNotificationClicked,
                               weak_ptr_factory_.GetWeakPtr(),
-                              screen_capture_path, type)));
+                              screen_capture_path, type)),
+      message_center::SystemNotificationWarningLevel::NORMAL, kCaptureModeIcon,
+      for_video);
 }
 
 void CaptureModeController::HandleNotificationClicked(
diff --git a/ash/capture_mode/capture_mode_controller.h b/ash/capture_mode/capture_mode_controller.h
index 77cfcf8..5d523ae 100644
--- a/ash/capture_mode/capture_mode_controller.h
+++ b/ash/capture_mode/capture_mode_controller.h
@@ -116,7 +116,7 @@
 
   // recording::mojom::RecordingServiceClient:
   void OnMuxerOutput(const std::string& chunk) override;
-  void OnRecordingEnded(bool success) override;
+  void OnRecordingEnded(bool success, const gfx::ImageSkia& thumbnail) override;
 
   // SessionObserver:
   void OnActiveUserSessionChanged(const AccountId& account_id) override;
@@ -237,8 +237,10 @@
   void OnVideoFileStatus(bool success);
 
   // Called back when the |video_file_handler_| flushes the remaining cached
-  // video chunks in its buffer. Called on the UI thread.
-  void OnVideoFileSaved(bool success);
+  // video chunks in its buffer. Called on the UI thread. |video_thumbnail| is
+  // an RGB image provided by the recording service that can be used as a
+  // thumbnail of the video in the notification.
+  void OnVideoFileSaved(const gfx::ImageSkia& video_thumbnail, bool success);
 
   // Shows a preview notification of the newly taken screenshot or screen
   // recording.
diff --git a/ash/capture_mode/capture_mode_notification_view.cc b/ash/capture_mode/capture_mode_notification_view.cc
index c60190c..07f5a6a 100644
--- a/ash/capture_mode/capture_mode_notification_view.cc
+++ b/ash/capture_mode/capture_mode_notification_view.cc
@@ -9,6 +9,7 @@
 #include "ash/style/ash_color_provider.h"
 #include "ash/style/scoped_light_mode_as_default.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/background.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/view.h"
@@ -17,15 +18,20 @@
 
 namespace {
 
-// Constants related to the banner view on the image capture notification.
+// Constants related to the banner view on the image capture notifications.
 constexpr int kBannerHeightDip = 36;
 constexpr int kBannerHorizontalInsetDip = 12;
 constexpr int kBannerVerticalInsetDip = 8;
 constexpr int kBannerIconTextSpacingDip = 8;
 constexpr int kBannerIconSizeDip = 20;
 
+// Constants related to the play icon view for video capture notifications.
+constexpr int kPlayIconSizeDip = 24;
+constexpr int kPlayIconBackgroundCornerRadiusDip = 20;
+constexpr gfx::Size kPlayIconViewSize{40, 40};
+
 // Creates the banner view that will show on top of the notification image.
-std::unique_ptr<views::View> CreateBannerViewImpl() {
+std::unique_ptr<views::View> CreateBannerView() {
   std::unique_ptr<views::View> banner_view = std::make_unique<views::View>();
 
   // Use the light mode as default as notification is still using light
@@ -61,15 +67,34 @@
   return banner_view;
 }
 
+// Creates the play icon view which shows on top of the video thumbnail in the
+// notification.
+std::unique_ptr<views::View> CreatePlayIconView() {
+  auto play_view = std::make_unique<views::ImageView>();
+  auto* color_provider = AshColorProvider::Get();
+  const SkColor icon_color = color_provider->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kIconColorPrimary);
+  play_view->SetImage(gfx::CreateVectorIcon(kCaptureModePlayIcon,
+                                            kPlayIconSizeDip, icon_color));
+  play_view->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
+  play_view->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
+  const SkColor background_color = color_provider->GetBaseLayerColor(
+      AshColorProvider::BaseLayerType::kTransparent80);
+  play_view->SetBackground(views::CreateRoundedRectBackground(
+      background_color, kPlayIconBackgroundCornerRadiusDip));
+  return play_view;
+}
+
 }  // namespace
 
 CaptureModeNotificationView::CaptureModeNotificationView(
-    const message_center::Notification& notification)
-    : message_center::NotificationViewMD(notification) {
-  // Create the banner view if notification image is not empty. The banner
-  // will show on top of the notification image.
+    const message_center::Notification& notification,
+    CaptureModeType capture_type)
+    : message_center::NotificationViewMD(notification),
+      capture_type_(capture_type) {
+  // Creates the extra view which will depend on the type of the notification.
   if (!notification.image().IsEmpty())
-    CreateBannerView();
+    CreateExtraView();
 
   // We need to observe this view as |this| view will be re-used for
   // notifications for with/without image scenarios if |this| is not destroyed
@@ -81,33 +106,51 @@
 
 // static
 std::unique_ptr<message_center::MessageView>
-CaptureModeNotificationView::Create(
+CaptureModeNotificationView::CreateForImage(
     const message_center::Notification& notification) {
-  return std::make_unique<CaptureModeNotificationView>(notification);
+  return std::make_unique<CaptureModeNotificationView>(notification,
+                                                       CaptureModeType::kImage);
+}
+
+// static
+std::unique_ptr<message_center::MessageView>
+CaptureModeNotificationView::CreateForVideo(
+    const message_center::Notification& notification) {
+  return std::make_unique<CaptureModeNotificationView>(notification,
+                                                       CaptureModeType::kVideo);
 }
 
 void CaptureModeNotificationView::Layout() {
   message_center::NotificationViewMD::Layout();
-  if (!banner_view_)
+  if (!extra_view_)
     return;
 
-  // Calculate the banner view's desired bounds.
-  gfx::Rect banner_bounds = image_container_view()->GetContentsBounds();
-  banner_bounds.set_y(banner_bounds.bottom() - kBannerHeightDip);
-  banner_bounds.set_height(kBannerHeightDip);
-  banner_view_->SetBoundsRect(banner_bounds);
+  gfx::Rect extra_view_bounds = image_container_view()->GetContentsBounds();
+
+  if (capture_type_ == CaptureModeType::kImage) {
+    // The extra view in this case is a banner laid out at the bottom of the
+    // image container.
+    extra_view_bounds.set_y(extra_view_bounds.bottom() - kBannerHeightDip);
+    extra_view_bounds.set_height(kBannerHeightDip);
+  } else {
+    DCHECK_EQ(capture_type_, CaptureModeType::kVideo);
+    // The extra view in this case is a play icon centered in the view.
+    extra_view_bounds.ClampToCenteredSize(kPlayIconViewSize);
+  }
+
+  extra_view_->SetBoundsRect(extra_view_bounds);
 }
 
 void CaptureModeNotificationView::OnChildViewAdded(views::View* observed_view,
                                                    views::View* child) {
   if (observed_view == this && child == image_container_view())
-    CreateBannerView();
+    CreateExtraView();
 }
 
 void CaptureModeNotificationView::OnChildViewRemoved(views::View* observed_view,
                                                      views::View* child) {
   if (observed_view == this && child == image_container_view())
-    banner_view_ = nullptr;
+    extra_view_ = nullptr;
 }
 
 void CaptureModeNotificationView::OnViewIsDeleting(View* observed_view) {
@@ -115,11 +158,13 @@
   views::View::RemoveObserver(this);
 }
 
-void CaptureModeNotificationView::CreateBannerView() {
+void CaptureModeNotificationView::CreateExtraView() {
   DCHECK(image_container_view());
   DCHECK(!image_container_view()->children().empty());
-  DCHECK(!banner_view_);
-  banner_view_ = image_container_view()->AddChildView(CreateBannerViewImpl());
+  DCHECK(!extra_view_);
+  extra_view_ = image_container_view()->AddChildView(
+      capture_type_ == CaptureModeType::kImage ? CreateBannerView()
+                                               : CreatePlayIconView());
 }
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_notification_view.h b/ash/capture_mode/capture_mode_notification_view.h
index f8b18562..81fa2e3 100644
--- a/ash/capture_mode/capture_mode_notification_view.h
+++ b/ash/capture_mode/capture_mode_notification_view.h
@@ -6,28 +6,36 @@
 #define ASH_CAPTURE_MODE_CAPTURE_MODE_NOTIFICATION_VIEW_H_
 
 #include "ash/ash_export.h"
+#include "ash/capture_mode/capture_mode_types.h"
 #include "ui/message_center/views/notification_view_md.h"
 #include "ui/views/view_observer.h"
 
 namespace ash {
 
-// A customized notification view for capture mode that can show a notification
-// with a banner on top of the notification image.
+// A customized notification view for capture mode that adjusts the capture
+// notification by either showing a banner on top of the notification image for
+// image captures, or a play icon on top of the video thumbnail.
 class ASH_EXPORT CaptureModeNotificationView
     : public message_center::NotificationViewMD,
       public views::ViewObserver {
  public:
-  explicit CaptureModeNotificationView(
-      const message_center::Notification& notification);
+  CaptureModeNotificationView(const message_center::Notification& notification,
+                              CaptureModeType capture_type);
   CaptureModeNotificationView(const CaptureModeNotificationView&) = delete;
   CaptureModeNotificationView& operator=(const CaptureModeNotificationView&) =
       delete;
   ~CaptureModeNotificationView() override;
 
   // Creates the custom capture mode notification for image capture
-  // notification. There is a banner on top of the image area of the
+  // notifications. There is a banner on top of the image area of the
   // notification to indicate the image has been copied to clipboard.
-  static std::unique_ptr<message_center::MessageView> Create(
+  static std::unique_ptr<message_center::MessageView> CreateForImage(
+      const message_center::Notification& notification);
+
+  // Creates the custom capture mode notification for video capture
+  // notifications. There is a superimposed "play" icon on top of the video
+  // thumbnail image.
+  static std::unique_ptr<message_center::MessageView> CreateForVideo(
       const message_center::Notification& notification);
 
   // message_center::NotificationViewMD:
@@ -41,11 +49,17 @@
   void OnViewIsDeleting(View* observed_view) override;
 
  private:
-  void CreateBannerView();
+  void CreateExtraView();
 
-  // The banner view that shows a banner string on top of the captured image.
-  // Owned by view hierarchy.
-  views::View* banner_view_ = nullptr;
+  // The type of capture this notification was created for.
+  const CaptureModeType capture_type_;
+
+  // The extra view created on top of the notification image. This will be a
+  // banner clarifying that the image was copied to the clipboard in case of
+  // image capture, or a superimposed "play" icon on top of the video thumbnail
+  // image.
+  // Owned by the view hierarchy.
+  views::View* extra_view_ = nullptr;
 };
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index c4b3dd526..5864733 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -64,6 +64,7 @@
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/image/image_unittest_util.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/message_center_observer.h"
 #include "ui/message_center/public/cpp/notification.h"
@@ -1663,6 +1664,36 @@
       kTabletHistogram, CaptureModeEntryType::kAccelTakePartialScreenshot, 2);
 }
 
+// Verifies that the video notification will show the same thumbnail image as
+// sent by recording service.
+TEST_F(CaptureModeTest, VideoNotificationThumbnail) {
+  auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
+                                         CaptureModeType::kVideo);
+  controller->StartVideoRecordingImmediatelyForTesting();
+  EXPECT_TRUE(controller->is_recording_in_progress());
+
+  auto* test_delegate =
+      static_cast<TestCaptureModeDelegate*>(controller->delegate_for_testing());
+
+  // Use a random bitmap as the fake thumbnail.
+  SkBitmap thumbnail;
+  thumbnail.allocN32Pixels(400, 300);
+  EXPECT_FALSE(thumbnail.drawsNothing());
+  test_delegate->SetVideoThumbnail(
+      gfx::ImageSkia::CreateFrom1xBitmap(thumbnail));
+
+  CaptureNotificationWaiter waiter;
+  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
+  EXPECT_FALSE(controller->is_recording_in_progress());
+  waiter.Wait();
+
+  const message_center::Notification* notification = GetPreviewNotification();
+  EXPECT_TRUE(notification);
+  EXPECT_FALSE(notification->image().IsEmpty());
+  const SkBitmap actual_thumbnail = notification->image().AsBitmap();
+  EXPECT_TRUE(gfx::test::AreBitmapsEqual(actual_thumbnail, thumbnail));
+}
+
 TEST_F(CaptureModeTest, WindowRecordingCaptureId) {
   auto window = CreateTestWindow(gfx::Rect(200, 200));
   StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
diff --git a/ash/capture_mode/test_capture_mode_delegate.cc b/ash/capture_mode/test_capture_mode_delegate.cc
index 40de09a..1082ad5 100644
--- a/ash/capture_mode/test_capture_mode_delegate.cc
+++ b/ash/capture_mode/test_capture_mode_delegate.cc
@@ -26,6 +26,9 @@
   }
   gfx::Size frame_sink_size() const { return frame_sink_size_; }
   const gfx::Size& video_size() const { return video_size_; }
+  void set_thumbnail(const gfx::ImageSkia& thumbnail) {
+    thumbnail_ = thumbnail;
+  }
 
   void Bind(
       mojo::PendingReceiver<recording::mojom::RecordingService> receiver) {
@@ -76,7 +79,7 @@
     video_size_ = crop_region.size();
   }
   void StopRecording() override {
-    remote_client_->OnRecordingEnded(/*success=*/true);
+    remote_client_->OnRecordingEnded(/*success=*/true, thumbnail_);
     remote_client_.FlushForTesting();
   }
   void OnRecordedWindowChangingRoot(
@@ -102,6 +105,7 @@
   CaptureModeSource current_capture_source_ = CaptureModeSource::kFullscreen;
   gfx::Size frame_sink_size_;
   gfx::Size video_size_;
+  gfx::ImageSkia thumbnail_;
 };
 
 // -----------------------------------------------------------------------------
@@ -129,6 +133,12 @@
   return fake_service_ ? fake_service_->video_size() : gfx::Size();
 }
 
+void TestCaptureModeDelegate::SetVideoThumbnail(
+    const gfx::ImageSkia& thumbnail) {
+  if (fake_service_)
+    fake_service_->set_thumbnail(thumbnail);
+}
+
 base::FilePath TestCaptureModeDelegate::GetScreenCaptureDir() const {
   return fake_downloads_dir_;
 }
diff --git a/ash/capture_mode/test_capture_mode_delegate.h b/ash/capture_mode/test_capture_mode_delegate.h
index b012f6b3..04f4e47 100644
--- a/ash/capture_mode/test_capture_mode_delegate.h
+++ b/ash/capture_mode/test_capture_mode_delegate.h
@@ -12,6 +12,7 @@
 #include "base/files/file_path.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "ui/gfx/geometry/size.h"
+#include "ui/gfx/image/image_skia.h"
 
 namespace ash {
 
@@ -33,6 +34,10 @@
   // Gets the current video size being captured by the fake service.
   gfx::Size GetCurrentVideoSize() const;
 
+  // Sets the thumbnail image that will be used by the fake service to provide
+  // it to the client.
+  void SetVideoThumbnail(const gfx::ImageSkia& thumbnail);
+
   // CaptureModeDelegate:
   base::FilePath GetScreenCaptureDir() const override;
   void ShowScreenCaptureItemInFolder(const base::FilePath& file_path) override;
diff --git a/ash/login/ui/lock_screen_media_controls_view.cc b/ash/login/ui/lock_screen_media_controls_view.cc
index 527db8c..d5d3345 100644
--- a/ash/login/ui/lock_screen_media_controls_view.cc
+++ b/ash/login/ui/lock_screen_media_controls_view.cc
@@ -124,6 +124,9 @@
     case MediaSessionAction::kEnterPictureInPicture:
     case MediaSessionAction::kExitPictureInPicture:
     case MediaSessionAction::kSwitchAudioDevice:
+    case MediaSessionAction::kToggleMicrophone:
+    case MediaSessionAction::kToggleCamera:
+    case MediaSessionAction::kHangUp:
       NOTREACHED();
       break;
   }
diff --git a/ash/public/cpp/assistant/controller/assistant_notification_controller.h b/ash/public/cpp/assistant/controller/assistant_notification_controller.h
index bd48f7d..5679dfc 100644
--- a/ash/public/cpp/assistant/controller/assistant_notification_controller.h
+++ b/ash/public/cpp/assistant/controller/assistant_notification_controller.h
@@ -19,12 +19,6 @@
  public:
   static AssistantNotificationController* Get();
 
-  // Requests that the specified |notification| be added or updated. If the
-  // |client_id| for |notification| matches that of an existing notification,
-  // an update will occur. Otherwise, a new notification will be added.
-  virtual void AddOrUpdateNotification(
-      chromeos::assistant::AssistantNotification&& notification) = 0;
-
   // Requests that the notification uniquely identified by |id| be removed. If
   // |from_server| is true the request to remove was initiated by the server.
   virtual void RemoveNotificationById(const std::string& id,
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index e6bc524..726c8c4 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -36,6 +36,7 @@
     "capture_mode_image.icon",
     "capture_mode_mic.icon",
     "capture_mode_mic_off.icon",
+    "capture_mode_play.icon",
     "capture_mode_region.icon",
     "capture_mode_settings.icon",
     "capture_mode_video.icon",
diff --git a/ash/resources/vector_icons/capture_mode_copied_to_clipboard.icon b/ash/resources/vector_icons/capture_mode_copied_to_clipboard.icon
index c5521d8..d6f9828 100644
--- a/ash/resources/vector_icons/capture_mode_copied_to_clipboard.icon
+++ b/ash/resources/vector_icons/capture_mode_copied_to_clipboard.icon
@@ -4,38 +4,38 @@
 
 CANVAS_DIMENSIONS, 20,
 MOVE_TO, 15, 3,
-H_LINE_TO, 11.82f,
+R_H_LINE_TO, -3.18f,
 CUBIC_TO, 11.4f, 1.84f, 10.3f, 1, 9, 1,
-CUBIC_TO, 7.7f, 1, 6.6f, 1.84f, 6.18f, 3,
+R_CUBIC_TO, -1.3f, 0, -2.4f, 0.84f, -2.82f, 2,
 H_LINE_TO, 3,
-CUBIC_TO, 1.9f, 3, 1, 3.9f, 1, 5,
-V_LINE_TO, 16,
-CUBIC_TO, 1, 17.1f, 1.9f, 18, 3, 18,
-H_LINE_TO, 9,
-V_LINE_TO, 16,
+R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
+R_V_LINE_TO, 11,
+R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+R_H_LINE_TO, 6,
+R_V_LINE_TO, -2,
 H_LINE_TO, 3,
 V_LINE_TO, 5,
-H_LINE_TO, 6,
-V_LINE_TO, 7,
-H_LINE_TO, 12,
+R_H_LINE_TO, 2,
+R_V_LINE_TO, 2,
+R_H_LINE_TO, 8,
 V_LINE_TO, 5,
-H_LINE_TO, 15,
-V_LINE_TO, 9,
-H_LINE_TO, 17,
+R_H_LINE_TO, 2,
+R_V_LINE_TO, 4,
+R_H_LINE_TO, 2,
 V_LINE_TO, 5,
-CUBIC_TO, 17, 3.9f, 16.1f, 3, 15, 3,
+R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
 CLOSE,
 MOVE_TO, 9, 3,
-CUBIC_TO, 9.55f, 3, 10, 3.45f, 10, 4,
-CUBIC_TO, 10, 4.55f, 9.55f, 5, 9, 5,
-CUBIC_TO, 8.45f, 5, 8, 4.55f, 8, 4,
-CUBIC_TO, 8, 3.45f, 8.45f, 3, 9, 3,
+R_CUBIC_TO, 0.55f, 0, 1, 0.45f, 1, 1,
+R_CUBIC_TO, 0, 0.55f, -0.45f, 1, -1, 1,
+R_CUBIC_TO, -0.55f, 0, -1, -0.45f, -1, -1,
+R_CUBIC_TO, 0, -0.55f, 0.45f, -1, 1, -1,
 CLOSE,
 MOVE_TO, 19.19f, 11.54f,
-LINE_TO, 13.54f, 17.19f,
+R_LINE_TO, -5.66f, 5.66f,
 LINE_TO, 10, 13.66f,
-LINE_TO, 11.41f, 12.24f,
-LINE_TO, 13.54f, 14.36f,
-LINE_TO, 17.78f, 10.12f,
-LINE_TO, 19.19f, 11.54f,
+R_LINE_TO, 1.41f, -1.41f,
+R_LINE_TO, 2.12f, 2.12f,
+R_LINE_TO, 4.24f, -4.24f,
+R_LINE_TO, 1.41f, 1.42f,
 CLOSE
diff --git a/ash/resources/vector_icons/capture_mode_play.icon b/ash/resources/vector_icons/capture_mode_play.icon
new file mode 100644
index 0000000..a5a7947
--- /dev/null
+++ b/ash/resources/vector_icons/capture_mode_play.icon
@@ -0,0 +1,10 @@
+// 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 6, 16,
+R_LINE_TO, 10, -6,
+LINE_TO, 6, 4,
+R_V_LINE_TO, 12,
+CLOSE
diff --git a/ash/services/recording/public/mojom/BUILD.gn b/ash/services/recording/public/mojom/BUILD.gn
index f1c259dd..9962692 100644
--- a/ash/services/recording/public/mojom/BUILD.gn
+++ b/ash/services/recording/public/mojom/BUILD.gn
@@ -10,5 +10,6 @@
   deps = [
     "//media/mojo/mojom",
     "//services/viz/privileged/mojom/compositing",
+    "//ui/gfx/image/mojom",
   ]
 }
diff --git a/ash/services/recording/public/mojom/recording_service.mojom b/ash/services/recording/public/mojom/recording_service.mojom
index e4f33d1..6744cde 100644
--- a/ash/services/recording/public/mojom/recording_service.mojom
+++ b/ash/services/recording/public/mojom/recording_service.mojom
@@ -10,6 +10,7 @@
 import "services/viz/public/mojom/compositing/frame_sink_id.mojom";
 import "services/viz/public/mojom/compositing/subtree_capture_id.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
+import "ui/gfx/image/mojom/image.mojom";
 
 // Defines the interface for the recording service client (e.g. ash), which
 // lives in a remote process other than that of the recording service itself.
@@ -25,8 +26,9 @@
   // Called by the service to inform the client that an in-progress video
   // recording ended, and no further frames will be provided. If |success| is
   // false, then recording is being terminated by the service itself due to an
-  // internal failure.
-  OnRecordingEnded(bool success);
+  // internal failure. The service can provide an RGB image |thumbnail|
+  // representing the recorded video. It can be empty in case of a failure.
+  OnRecordingEnded(bool success, gfx.mojom.ImageSkia? thumbnail);
 };
 
 // Defines the interface of the recording service which is implemented by
diff --git a/ash/services/recording/recording_service.cc b/ash/services/recording/recording_service.cc
index f91b5534..495f29dd 100644
--- a/ash/services/recording/recording_service.cc
+++ b/ash/services/recording/recording_service.cc
@@ -18,8 +18,11 @@
 #include "media/base/audio_codecs.h"
 #include "media/base/status.h"
 #include "media/base/video_frame.h"
+#include "media/base/video_util.h"
 #include "media/capture/mojom/video_capture_types.mojom.h"
+#include "media/renderers/paint_canvas_video_renderer.h"
 #include "services/audio/public/cpp/device_factory.h"
+#include "ui/gfx/image/image_skia_operations.h"
 
 namespace recording {
 
@@ -42,6 +45,11 @@
 // IPC call to the client.
 constexpr int kMaxBufferedChunks = 238;
 
+// The size within which we will try to fit a thumbnail image extracted from the
+// first valid video frame. The value was chosen to be suitable with the image
+// container in the notification UI.
+constexpr gfx::Size kThumbnailSize{328, 184};
+
 // Calculates the bitrate used to initialize the video encoder based on the
 // given |capture_size|.
 uint64_t CalculateVpxEncoderBitrate(const gfx::Size& capture_size) {
@@ -72,6 +80,34 @@
                                 kAudioSampleRate / 100);
 }
 
+// Extracts a potentially scaled-down RGB image from the given video |frame|,
+// which is suitable to use as a thumbnail for the video.
+gfx::ImageSkia ExtractImageFromVideoFrame(const media::VideoFrame& frame) {
+  const gfx::Size visible_size = frame.visible_rect().size();
+  media::PaintCanvasVideoRenderer renderer;
+  SkBitmap bitmap;
+  bitmap.allocN32Pixels(visible_size.width(), visible_size.height());
+  renderer.ConvertVideoFrameToRGBPixels(&frame, bitmap.getPixels(),
+                                        bitmap.rowBytes());
+
+  // Since this image will be used as a thumbnail, we can scale it down to save
+  // on memory if needed. For example, if recording a FHD display, that will be
+  // (for 12 bits/pixel):
+  // 1920 * 1080 * 12 / 8, which is approx. = 3 MB, which is a lot to keep
+  // around for a thumbnail.
+  const gfx::ImageSkia thumbnail = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+  if (visible_size.width() <= kThumbnailSize.width() &&
+      visible_size.height() <= kThumbnailSize.height()) {
+    return thumbnail;
+  }
+
+  const gfx::Size scaled_size =
+      media::ScaleSizeToFitWithinTarget(visible_size, kThumbnailSize);
+  return gfx::ImageSkiaOperations::CreateResizedImage(
+      thumbnail, skia::ImageOperations::ResizeMethod::RESIZE_BETTER,
+      scaled_size);
+}
+
 }  // namespace
 
 RecordingService::RecordingService(
@@ -256,6 +292,9 @@
   frame->set_metadata(info->metadata);
   frame->set_color_space(info->color_space.value());
 
+  if (video_thumbnail_.isNull())
+    video_thumbnail_ = ExtractImageFromVideoFrame(*frame);
+
   encoder_muxer_.AsyncCall(&RecordingEncoderMuxer::EncodeVideo).WithArgs(frame);
 }
 
@@ -453,7 +492,7 @@
   DCHECK(encoder_muxer_);
 
   encoder_muxer_.Reset();
-  client_remote_->OnRecordingEnded(success);
+  client_remote_->OnRecordingEnded(success, video_thumbnail_);
 }
 
 void RecordingService::OnMuxerWrite(base::StringPiece data) {
diff --git a/ash/services/recording/recording_service.h b/ash/services/recording/recording_service.h
index b47c8b31..fa6ad2a2 100644
--- a/ash/services/recording/recording_service.h
+++ b/ash/services/recording/recording_service.h
@@ -25,6 +25,7 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom-forward.h"
+#include "ui/gfx/image/image_skia.h"
 
 namespace recording {
 
@@ -183,6 +184,11 @@
   mojo::Remote<mojom::RecordingServiceClient> client_remote_
       GUARDED_BY_CONTEXT(main_thread_checker_);
 
+  // A cached scaled down rgb image of the first valid video frame which will be
+  // used to provide the client with an image thumbnail representing the
+  // recorded video.
+  gfx::ImageSkia video_thumbnail_ GUARDED_BY_CONTEXT(main_thread_checker_);
+
   // True if a failure has been propagated from |encoder_muxer_| that we will
   // end recording abruptly and ignore any incoming audio/video frames.
   bool did_failure_occur_ GUARDED_BY_CONTEXT(main_thread_checker_) = false;
diff --git a/ash/system/holding_space/holding_space_tray_unittest.cc b/ash/system/holding_space/holding_space_tray_unittest.cc
index df161ea5..df28bae3 100644
--- a/ash/system/holding_space/holding_space_tray_unittest.cc
+++ b/ash/system/holding_space/holding_space_tray_unittest.cc
@@ -76,24 +76,34 @@
   event_generator.GestureTapAt(view->GetBoundsInScreen().CenterPoint());
 }
 
+ui::GestureEvent BuildGestureEvent(const gfx::Point& event_location,
+                                   ui::EventType gesture_type) {
+  return ui::GestureEvent(event_location.x(), event_location.y(), ui::EF_NONE,
+                          ui::EventTimeForNow(),
+                          ui::GestureEventDetails(gesture_type));
+}
+
 void LongPress(const views::View* view) {
   auto* root_window = view->GetWidget()->GetNativeWindow()->GetRootWindow();
   ui::test::EventGenerator event_generator(root_window);
   event_generator.MoveTouch(view->GetBoundsInScreen().CenterPoint());
-  ui::GestureEvent long_press(
-      event_generator.current_screen_location().x(),
-      event_generator.current_screen_location().y(), ui::EF_NONE,
-      ui::EventTimeForNow(),
-      ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
+  const gfx::Point& press_location = event_generator.current_screen_location();
+  ui::GestureEvent long_press =
+      BuildGestureEvent(press_location, ui::ET_GESTURE_LONG_PRESS);
   event_generator.Dispatch(&long_press);
+
+  ui::GestureEvent gesture_end =
+      BuildGestureEvent(press_location, ui::ET_GESTURE_END);
+  event_generator.Dispatch(&gesture_end);
 }
 
-void PressKey(const views::View* view,
-              ui::KeyboardCode key_code,
-              int flags = ui::EF_NONE) {
+void PressAndReleaseKey(const views::View* view,
+                        ui::KeyboardCode key_code,
+                        int flags = ui::EF_NONE) {
   auto* root_window = view->GetWidget()->GetNativeWindow()->GetRootWindow();
   ui::test::EventGenerator event_generator(root_window);
   event_generator.PressKey(key_code, flags);
+  event_generator.ReleaseKey(key_code, flags);
 }
 
 bool PressTabUntilFocused(const views::View* view, int max_count = 10) {
@@ -1915,7 +1925,7 @@
   // app. There should be *no* attempts to open an holding space items.
   EXPECT_CALL(*client(), OpenItems).Times(0);
   EXPECT_CALL(*client(), OpenDownloads);
-  PressKey(downloads_section_header, ui::KeyboardCode::VKEY_RETURN);
+  PressAndReleaseKey(downloads_section_header, ui::KeyboardCode::VKEY_RETURN);
 }
 
 // User should be able to launch selected holding space items by pressing the
@@ -1942,7 +1952,7 @@
 
   // Press the enter key. The client should *not* attempt to open any items.
   EXPECT_CALL(*client(), OpenItems).Times(0);
-  PressKey(item_views[0], ui::KeyboardCode::VKEY_RETURN);
+  PressAndReleaseKey(item_views[0], ui::KeyboardCode::VKEY_RETURN);
   testing::Mock::VerifyAndClearExpectations(client());
 
   // Click an item. The view should be selected.
@@ -1954,7 +1964,7 @@
   // Press the enter key. We expect the client to open the selected item.
   EXPECT_CALL(*client(), OpenItems(testing::ElementsAre(item_views[0]->item()),
                                    testing::_));
-  PressKey(item_views[0], ui::KeyboardCode::VKEY_RETURN);
+  PressAndReleaseKey(item_views[0], ui::KeyboardCode::VKEY_RETURN);
   testing::Mock::VerifyAndClearExpectations(client());
 
   // Shift-click on the second item. Both views should be selected.
@@ -1966,7 +1976,7 @@
   EXPECT_CALL(*client(), OpenItems(testing::ElementsAre(item_views[0]->item(),
                                                         item_views[1]->item()),
                                    testing::_));
-  PressKey(item_views[1], ui::KeyboardCode::VKEY_RETURN);
+  PressAndReleaseKey(item_views[1], ui::KeyboardCode::VKEY_RETURN);
   testing::Mock::VerifyAndClearExpectations(client());
 
   // Tab traverse to the last item.
@@ -1976,7 +1986,7 @@
   // it was *not* selected prior to pressing the enter key.
   EXPECT_CALL(*client(), OpenItems(testing::ElementsAre(item_views[2]->item()),
                                    testing::_));
-  PressKey(item_views[2], ui::KeyboardCode::VKEY_RETURN);
+  PressAndReleaseKey(item_views[2], ui::KeyboardCode::VKEY_RETURN);
   EXPECT_FALSE(item_views[0]->selected());
   EXPECT_FALSE(item_views[1]->selected());
   EXPECT_TRUE(item_views[2]->selected());
@@ -2193,7 +2203,7 @@
 
   // Close the context menu. The view that was long pressed should still be
   // selected.
-  PressKey(item_views[0], ui::KeyboardCode::VKEY_ESCAPE);
+  PressAndReleaseKey(item_views[0], ui::KeyboardCode::VKEY_ESCAPE);
   EXPECT_FALSE(views::MenuController::GetActiveInstance());
   EXPECT_TRUE(item_views[0]->selected());
   EXPECT_FALSE(item_views[1]->selected());
@@ -2209,7 +2219,7 @@
 
   // Close the context menu. Both views that were long pressed should still be
   // selected.
-  PressKey(item_views[0], ui::KeyboardCode::VKEY_ESCAPE);
+  PressAndReleaseKey(item_views[0], ui::KeyboardCode::VKEY_ESCAPE);
   EXPECT_FALSE(views::MenuController::GetActiveInstance());
   EXPECT_TRUE(item_views[0]->selected());
   EXPECT_TRUE(item_views[1]->selected());
@@ -2227,9 +2237,20 @@
   GestureTap(item_views[0]);
   testing::Mock::VerifyAndClearExpectations(client());
 
-  // Long press the same two views to reselect them.
+  // Reselect the first item view and close the context menu triggered from
+  // long press.
   LongPress(item_views[0]);
+  EXPECT_TRUE(views::MenuController::GetActiveInstance());
+  PressAndReleaseKey(item_views[0], ui::VKEY_ESCAPE);
+  EXPECT_FALSE(views::MenuController::GetActiveInstance());
+
+  // Reselect the second item view and close the context menu triggered from
+  // long press.
   LongPress(item_views[1]);
+  EXPECT_TRUE(views::MenuController::GetActiveInstance());
+  PressAndReleaseKey(item_views[1], ui::VKEY_ESCAPE);
+  EXPECT_FALSE(views::MenuController::GetActiveInstance());
+
   EXPECT_TRUE(item_views[0]->selected());
   EXPECT_TRUE(item_views[1]->selected());
   EXPECT_FALSE(item_views[2]->selected());
diff --git a/ash/system/media/unified_media_controls_view.cc b/ash/system/media/unified_media_controls_view.cc
index 2fcd4ce..a28e7b2 100644
--- a/ash/system/media/unified_media_controls_view.cc
+++ b/ash/system/media/unified_media_controls_view.cc
@@ -83,6 +83,9 @@
     case MediaSessionAction::kEnterPictureInPicture:
     case MediaSessionAction::kExitPictureInPicture:
     case MediaSessionAction::kSwitchAudioDevice:
+    case MediaSessionAction::kToggleMicrophone:
+    case MediaSessionAction::kToggleCamera:
+    case MediaSessionAction::kHangUp:
       NOTREACHED();
       break;
   }
diff --git a/ash/system/tray/tray_background_view.cc b/ash/system/tray/tray_background_view.cc
index ff9304d..5a82892 100644
--- a/ash/system/tray/tray_background_view.cc
+++ b/ash/system/tray/tray_background_view.cc
@@ -70,7 +70,8 @@
 // Bounce animation constants
 const base::TimeDelta kAnimationDurationForBounceElement =
     base::TimeDelta::FromMilliseconds(250);
-const int kAnimationBounceDistance = 16;
+const int kAnimationBounceUpDistance = 16;
+const int kAnimationBounceDownDistance = 8;
 const float kAnimationBounceScaleFactor = 0.5;
 
 // When becoming visible delay the animation so that StatusAreaWidgetDelegate
@@ -417,12 +418,32 @@
                        1),
           gfx::Point3F(1, 1, 1));
 
+  gfx::PointF start_point = gfx::PointF(0, 0);
+  gfx::PointF bounce_up_point;
+  gfx::PointF bounce_down_point;
+
+  switch (shelf_->alignment()) {
+    case ShelfAlignment::kLeft:
+      bounce_up_point = gfx::PointF(kAnimationBounceUpDistance, 0);
+      bounce_down_point = gfx::PointF(-kAnimationBounceDownDistance, 0);
+      break;
+    case ShelfAlignment::kRight:
+      bounce_up_point = gfx::PointF(-kAnimationBounceUpDistance, 0);
+      bounce_down_point = gfx::PointF(kAnimationBounceDownDistance, 0);
+      break;
+    case ShelfAlignment::kBottom:
+    case ShelfAlignment::kBottomLocked:
+    default:
+      bounce_up_point = gfx::PointF(0, -kAnimationBounceUpDistance);
+      bounce_down_point = gfx::PointF(0, kAnimationBounceDownDistance);
+  }
+
   std::unique_ptr<ui::InterpolatedTransform> scale_about_pivot =
       std::make_unique<ui::InterpolatedTransformAboutPivot>(
           GetLocalBounds().CenterPoint(), std::move(scale));
 
   scale_about_pivot->SetChild(std::make_unique<ui::InterpolatedTranslation>(
-      gfx::PointF(0, 0), gfx::PointF(0, -kAnimationBounceDistance)));
+      start_point, bounce_up_point));
 
   std::unique_ptr<ui::LayerAnimationElement> scale_and_move_up =
       ui::LayerAnimationElement::CreateInterpolatedTransformElement(
@@ -431,16 +452,15 @@
 
   std::unique_ptr<ui::LayerAnimationElement> move_down =
       ui::LayerAnimationElement::CreateInterpolatedTransformElement(
-          std::make_unique<ui::InterpolatedTranslation>(
-              gfx::PointF(0, -kAnimationBounceDistance),
-              gfx::PointF(0, kAnimationBounceDistance)),
+          std::make_unique<ui::InterpolatedTranslation>(bounce_up_point,
+                                                        bounce_down_point),
           kAnimationDurationForBounceElement);
   move_down->set_tween_type(gfx::Tween::EASE_OUT_4);
 
   std::unique_ptr<ui::LayerAnimationElement> move_up =
       ui::LayerAnimationElement::CreateInterpolatedTransformElement(
-          std::make_unique<ui::InterpolatedTranslation>(
-              gfx::PointF(0, kAnimationBounceDistance), gfx::PointF(0, 0)),
+          std::make_unique<ui::InterpolatedTranslation>(bounce_down_point,
+                                                        start_point),
           kAnimationDurationForBounceElement);
   move_up->set_tween_type(gfx::Tween::FAST_OUT_SLOW_IN_3);
 
@@ -458,6 +478,20 @@
 }
 
 void TrayBackgroundView::HideAnimation() {
+  std::unique_ptr<ui::InterpolatedTransform> scale =
+      std::make_unique<ui::InterpolatedScale>(
+          gfx::Point3F(1, 1, 1), gfx::Point3F(kAnimationBounceScaleFactor,
+                                              kAnimationBounceScaleFactor, 1));
+  std::unique_ptr<ui::InterpolatedTransform> scale_about_pivot =
+      std::make_unique<ui::InterpolatedTransformAboutPivot>(
+          GetLocalBounds().CenterPoint(), std::move(scale));
+  std::unique_ptr<ui::LayerAnimationElement> scale_down =
+      ui::LayerAnimationElement::CreateInterpolatedTransformElement(
+          std::move(scale_about_pivot), kAnimationDurationForHideMs);
+  std::unique_ptr<ui::LayerAnimationSequence> scale_sequence =
+      std::make_unique<ui::LayerAnimationSequence>();
+  scale_sequence->AddElement(std::move(scale_down));
+
   std::unique_ptr<ui::LayerAnimationSequence> fade_sequence =
       std::make_unique<ui::LayerAnimationSequence>();
   std::unique_ptr<ui::LayerAnimationElement> fade_out =
@@ -466,7 +500,8 @@
   fade_sequence->AddElement(std::move(fade_out));
   fade_sequence->AddObserver(this);
 
-  layer()->GetAnimator()->StartAnimation(fade_sequence.release());
+  layer()->GetAnimator()->StartTogether(
+      {fade_sequence.release(), scale_sequence.release()});
 }
 
 void TrayBackgroundView::SetIsActive(bool is_active) {
diff --git a/ash/wm/overview/overview_grid_event_handler.cc b/ash/wm/overview/overview_grid_event_handler.cc
index ddf5dde..7b7e55d 100644
--- a/ash/wm/overview/overview_grid_event_handler.cc
+++ b/ash/wm/overview/overview_grid_event_handler.cc
@@ -24,6 +24,9 @@
 
 namespace {
 
+// The amount the grid's fling curve's offsets are scaled down.
+constexpr float kFlingScaleDown = 3.f;
+
 // Do not bother moving the grid until a series of scrolls has reached this
 // threshold.
 constexpr float kScrollOffsetThresholdDp = 1.f;
@@ -145,10 +148,10 @@
   bool continue_fling =
       fling_curve_->ComputeScrollOffset(timestamp, &offset, &fling_velocity_);
   offset.Scale(1 / kFlingScaleDown);
-  continue_fling = continue_fling &&
-                   grid_->UpdateScrollOffset(
+  continue_fling = grid_->UpdateScrollOffset(
                        fling_last_offset_ ? offset.x() - fling_last_offset_->x()
-                                          : offset.x());
+                                          : offset.x()) &&
+                   continue_fling;
   fling_last_offset_ = base::make_optional(offset);
 
   if (!continue_fling)
diff --git a/ash/wm/overview/overview_grid_event_handler.h b/ash/wm/overview/overview_grid_event_handler.h
index 844d5d99..d3b3bcd 100644
--- a/ash/wm/overview/overview_grid_event_handler.h
+++ b/ash/wm/overview/overview_grid_event_handler.h
@@ -35,9 +35,6 @@
   explicit OverviewGridEventHandler(OverviewGrid* grid);
   ~OverviewGridEventHandler() override;
 
-  // The amount the grid's fling curve's offsets are scaled down.
-  static constexpr float kFlingScaleDown = 3.f;
-
   // ui::EventHandler:
   void OnMouseEvent(ui::MouseEvent* event) override;
   void OnGestureEvent(ui::GestureEvent* event) override;
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index 2770579..389e942 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -3554,8 +3554,7 @@
   }
 };
 
-// TODO(crbug.com/1184114): Disabled for being flaky.
-TEST_F(OverviewSessionFlingTest, DISABLED_BasicFling) {
+TEST_F(OverviewSessionFlingTest, BasicFling) {
   std::vector<std::unique_ptr<aura::Window>> windows(16);
   for (int i = 15; i >= 0; --i)
     windows[i] = CreateTestWindow();
@@ -3569,8 +3568,7 @@
       gfx::ToRoundedPoint(item->target_bounds().CenterPoint());
 
   // Create a scroll sequence which results in a fling.
-  const gfx::Vector2d shift(-200 * OverviewGridEventHandler::kFlingScaleDown,
-                            0);
+  const gfx::Vector2d shift(-200, 0);
   GetEventGenerator()->GestureScrollSequence(
       item_center, item_center + shift, base::TimeDelta::FromMilliseconds(10),
       10);
diff --git a/ash/wm/overview/overview_window_drag_controller_unittest.cc b/ash/wm/overview/overview_window_drag_controller_unittest.cc
index 1636ccf..6c0bd360 100644
--- a/ash/wm/overview/overview_window_drag_controller_unittest.cc
+++ b/ash/wm/overview/overview_window_drag_controller_unittest.cc
@@ -191,6 +191,32 @@
   EXPECT_TRUE(overview_session->no_windows_widget_for_testing());
 }
 
+// Test that if window is destroyed during dragging, no crash should happen and
+// drag should be reset.
+TEST_F(OverviewWindowDragControllerTest, WindowDestroyedDuringDragging) {
+  std::unique_ptr<aura::Window> window =
+      CreateAppWindow(gfx::Rect(0, 0, 250, 100));
+  auto* overview_controller = Shell::Get()->overview_controller();
+  overview_controller->StartOverview();
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  auto* overview_session = overview_controller->overview_session();
+  auto* overview_item =
+      overview_session->GetOverviewItemForWindow(window.get());
+  ASSERT_TRUE(overview_item);
+
+  auto* event_generator = GetEventGenerator();
+  StartDraggingItemBy(overview_item, 30, 200, /*by_touch_gestures=*/false,
+                      event_generator);
+  OverviewWindowDragController* drag_controller =
+      overview_session->window_drag_controller();
+  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
+            drag_controller->current_drag_behavior());
+
+  window.reset();
+  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNoDrag,
+            drag_controller->current_drag_behavior());
+}
+
 // Tests the behavior of dragging a window in portrait tablet mode with virtual
 // desks enabled.
 class OverviewWindowDragControllerDesksPortraitTabletTest : public AshTestBase {
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index c4f5d0f..2ea9f93 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -4844,6 +4844,8 @@
         ->current_window_dragging_state();
   }
 
+  void DestroyWindow() { window_.reset(); }
+
   aura::Window* window() { return window_.get(); }
 
   std::unique_ptr<TabletModeWindowResizer> controller_;
@@ -5238,4 +5240,28 @@
   EXPECT_EQ(backdrop_window->bounds(), active_desk_container->bounds());
 }
 
+TEST_F(SplitViewAppDraggingTest, WindowDestroyedDuringDragging) {
+  UpdateDisplay("800x600");
+  InitializeWindow(/*can_resize=*/true);
+  EXPECT_TRUE(WindowState::Get(window())->IsMaximized());
+  gfx::Rect display_bounds =
+      screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
+          window());
+
+  // Start app dragging.
+  gfx::PointF location;
+  const float long_scroll_delta = display_bounds.height() / 4 + 5;
+  location.set_y(long_scroll_delta);
+  SendScrollStartAndUpdate(location);
+  OverviewController* overview_controller = Shell::Get()->overview_controller();
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  EXPECT_FALSE(
+      overview_controller->overview_session()->IsWindowInOverview(window()));
+
+  // Destroy the window.
+  DestroyWindow();
+  // Overview should still be active.
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+}
+
 }  // namespace ash
diff --git a/ash/wm/toplevel_window_event_handler.cc b/ash/wm/toplevel_window_event_handler.cc
index 0ff0659..426963e8 100644
--- a/ash/wm/toplevel_window_event_handler.cc
+++ b/ash/wm/toplevel_window_event_handler.cc
@@ -456,6 +456,74 @@
   }
 }
 
+wm::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop(
+    aura::Window* source,
+    const gfx::Vector2d& drag_offset,
+    ::wm::WindowMoveSource move_source) {
+  DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
+  aura::Window* root_window = source->GetRootWindow();
+  DCHECK(root_window);
+  gfx::PointF drag_location;
+  if (move_source == ::wm::WINDOW_MOVE_SOURCE_TOUCH &&
+      aura::Env::GetInstance()->is_touch_down()) {
+    gfx::PointF drag_location_f;
+    bool has_point = aura::Env::GetInstance()
+                         ->gesture_recognizer()
+                         ->GetLastTouchPointForTarget(source, &drag_location_f);
+    drag_location = drag_location_f;
+    DCHECK(has_point);
+  } else {
+    drag_location = gfx::PointF(
+        root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot());
+    aura::Window::ConvertPointToTarget(root_window, source->parent(),
+                                       &drag_location);
+  }
+  // Set the cursor before calling AttemptToStartDrag(), as that will
+  // eventually call LockCursor() and prevent the cursor from changing.
+  aura::client::CursorClient* cursor_client =
+      aura::client::GetCursorClient(root_window);
+  if (cursor_client)
+    cursor_client->SetCursor(ui::mojom::CursorType::kPointer);
+
+  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+
+  DragResult result = DragResult::SUCCESS;
+  if (!AttemptToStartDrag(source, drag_location, HTCAPTION, move_source,
+                          base::BindOnce(OnDragCompleted, &result, &run_loop),
+                          /*update_gesture_target=*/false)) {
+    return ::wm::MOVE_CANCELED;
+  }
+
+  in_move_loop_ = true;
+  base::WeakPtr<ToplevelWindowEventHandler> weak_ptr(
+      weak_factory_.GetWeakPtr());
+
+  // Disable window position auto management while dragging and restore it
+  // aftrewards.
+  WindowState* window_state = WindowState::Get(source);
+  const bool window_position_managed = window_state->GetWindowPositionManaged();
+  window_state->SetWindowPositionManaged(false);
+  aura::WindowTracker tracker({source});
+
+  run_loop.Run();
+
+  if (!weak_ptr)
+    return ::wm::MOVE_CANCELED;
+
+  // Make sure the window hasn't been deleted.
+  if (tracker.Contains(source))
+    window_state->SetWindowPositionManaged(window_position_managed);
+
+  in_move_loop_ = false;
+  return result == DragResult::SUCCESS ? ::wm::MOVE_SUCCESSFUL
+                                       : ::wm::MOVE_CANCELED;
+}
+
+void ToplevelWindowEventHandler::EndMoveLoop() {
+  if (in_move_loop_)
+    RevertDrag();
+}
+
 bool ToplevelWindowEventHandler::AttemptToStartDrag(
     aura::Window* window,
     const gfx::PointF& point_in_parent,
@@ -560,74 +628,6 @@
   return nullptr;
 }
 
-::wm::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop(
-    aura::Window* source,
-    const gfx::Vector2d& drag_offset,
-    ::wm::WindowMoveSource move_source) {
-  DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
-  aura::Window* root_window = source->GetRootWindow();
-  DCHECK(root_window);
-  gfx::PointF drag_location;
-  if (move_source == ::wm::WINDOW_MOVE_SOURCE_TOUCH &&
-      aura::Env::GetInstance()->is_touch_down()) {
-    gfx::PointF drag_location_f;
-    bool has_point = aura::Env::GetInstance()
-                         ->gesture_recognizer()
-                         ->GetLastTouchPointForTarget(source, &drag_location_f);
-    drag_location = drag_location_f;
-    DCHECK(has_point);
-  } else {
-    drag_location = gfx::PointF(
-        root_window->GetHost()->dispatcher()->GetLastMouseLocationInRoot());
-    aura::Window::ConvertPointToTarget(root_window, source->parent(),
-                                       &drag_location);
-  }
-  // Set the cursor before calling AttemptToStartDrag(), as that will
-  // eventually call LockCursor() and prevent the cursor from changing.
-  aura::client::CursorClient* cursor_client =
-      aura::client::GetCursorClient(root_window);
-  if (cursor_client)
-    cursor_client->SetCursor(ui::mojom::CursorType::kPointer);
-
-  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
-
-  DragResult result = DragResult::SUCCESS;
-  if (!AttemptToStartDrag(source, drag_location, HTCAPTION, move_source,
-                          base::BindOnce(OnDragCompleted, &result, &run_loop),
-                          /*update_gesture_target=*/false)) {
-    return ::wm::MOVE_CANCELED;
-  }
-
-  in_move_loop_ = true;
-  base::WeakPtr<ToplevelWindowEventHandler> weak_ptr(
-      weak_factory_.GetWeakPtr());
-
-  // Disable window position auto management while dragging and restore it
-  // aftrewards.
-  WindowState* window_state = WindowState::Get(source);
-  const bool window_position_managed = window_state->GetWindowPositionManaged();
-  window_state->SetWindowPositionManaged(false);
-  aura::WindowTracker tracker({source});
-
-  run_loop.Run();
-
-  if (!weak_ptr)
-    return ::wm::MOVE_CANCELED;
-
-  // Make sure the window hasn't been deleted.
-  if (tracker.Contains(source))
-    window_state->SetWindowPositionManaged(window_position_managed);
-
-  in_move_loop_ = false;
-  return result == DragResult::SUCCESS ? ::wm::MOVE_SUCCESSFUL
-                                       : ::wm::MOVE_CANCELED;
-}
-
-void ToplevelWindowEventHandler::EndMoveLoop() {
-  if (in_move_loop_)
-    RevertDrag();
-}
-
 bool ToplevelWindowEventHandler::PrepareForDrag(
     aura::Window* window,
     const gfx::PointF& point_in_parent,
diff --git a/ash/wm/toplevel_window_event_handler.h b/ash/wm/toplevel_window_event_handler.h
index f8c26da..8d9bd16 100644
--- a/ash/wm/toplevel_window_event_handler.h
+++ b/ash/wm/toplevel_window_event_handler.h
@@ -68,6 +68,12 @@
   void OnMouseEvent(ui::MouseEvent* event) override;
   void OnGestureEvent(ui::GestureEvent* event) override;
 
+  // wm::WindowMoveClient:
+  wm::WindowMoveResult RunMoveLoop(aura::Window* source,
+                                   const gfx::Vector2d& drag_offset,
+                                   ::wm::WindowMoveSource move_source) override;
+  void EndMoveLoop() override;
+
   // Attempts to start a drag if one is not already in progress. Returns true if
   // successful. |end_closure| is run when the drag completes, including if the
   // drag is not started. If |update_gesture_target| is true, the gesture
@@ -104,12 +110,8 @@
     return event_location_in_gesture_target_;
   }
 
-  // Overridden from wm::WindowMoveClient:
-  ::wm::WindowMoveResult RunMoveLoop(
-      aura::Window* source,
-      const gfx::Vector2d& drag_offset,
-      ::wm::WindowMoveSource move_source) override;
-  void EndMoveLoop() override;
+  // Returns true if there is a drag in progress.
+  bool is_drag_in_progress() const { return window_resizer_.get() != nullptr; }
 
  private:
   class ScopedWindowResizer;
diff --git a/ash/wm/workspace/workspace_window_resizer_unittest.cc b/ash/wm/workspace/workspace_window_resizer_unittest.cc
index f3d191e..6ce468b 100644
--- a/ash/wm/workspace/workspace_window_resizer_unittest.cc
+++ b/ash/wm/workspace/workspace_window_resizer_unittest.cc
@@ -10,6 +10,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/wm/toplevel_window_event_handler.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
@@ -2228,4 +2229,20 @@
   window_state = WindowState::Get(window_.get());
   EXPECT_FALSE(window_state->IsMaximized());
 }
+
+// Tests that window destroyed is properly handled during window resize.
+TEST_F(WorkspaceWindowResizerTest, WindowDestroyedDuringResize) {
+  InitTouchResizeWindow(gfx::Rect(100, 100, 600, kRootHeight - 200), HTRIGHT);
+  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+                                     touch_resize_window_.get());
+  generator.PressTouch();
+  EXPECT_TRUE(WindowState::Get(touch_resize_window_.get())->is_dragged());
+  ToplevelWindowEventHandler* event_handler =
+      Shell::Get()->toplevel_window_event_handler();
+  EXPECT_TRUE(event_handler->is_drag_in_progress());
+
+  touch_resize_window_.reset();
+  EXPECT_FALSE(event_handler->is_drag_in_progress());
+}
+
 }  // namespace ash
diff --git a/base/android/build_info.cc b/base/android/build_info.cc
index 5e9623f..12597e6 100644
--- a/base/android/build_info.cc
+++ b/base/android/build_info.cc
@@ -76,10 +76,9 @@
       firebase_app_id_(StrDupParam(params, 18)),
       custom_themes_(StrDupParam(params, 19)),
       resources_version_(StrDupParam(params, 20)),
-      extracted_file_suffix_(params[21]),
-      target_sdk_version_(GetIntParam(params, 22)),
-      is_debug_android_(GetIntParam(params, 23)),
-      is_tv_(GetIntParam(params, 24)) {}
+      target_sdk_version_(GetIntParam(params, 21)),
+      is_debug_android_(GetIntParam(params, 22)),
+      is_tv_(GetIntParam(params, 23)) {}
 
 // static
 BuildInfo* BuildInfo::GetInstance() {
diff --git a/base/android/build_info.h b/base/android/build_info.h
index 1a5fb14..cb9f2fe 100644
--- a/base/android/build_info.h
+++ b/base/android/build_info.h
@@ -118,8 +118,6 @@
 
   const char* abi_name() const { return abi_name_; }
 
-  std::string extracted_file_suffix() const { return extracted_file_suffix_; }
-
   int sdk_int() const {
     return sdk_int_;
   }
@@ -167,7 +165,6 @@
   const char* const custom_themes_;
   const char* const resources_version_;
   // Not needed by breakpad.
-  const std::string extracted_file_suffix_;
   const int target_sdk_version_;
   const bool is_debug_android_;
   const bool is_tv_;
diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java
index df405fd9..dafdce6 100644
--- a/base/android/java/src/org/chromium/base/BuildInfo.java
+++ b/base/android/java/src/org/chromium/base/BuildInfo.java
@@ -53,8 +53,6 @@
     public final String abiString;
     /** Truncated version of Build.FINGERPRINT (for crash reporting). */
     public final String androidBuildFingerprint;
-    /** A string that is different each time the apk changes. */
-    public final String extractedFileSuffix;
     /** Whether or not the device has apps installed for using custom themes. */
     public final String customThemes;
     /** Product version as stored in Android resources. */
@@ -90,7 +88,6 @@
                 sFirebaseAppId,
                 buildInfo.customThemes,
                 buildInfo.resourcesVersion,
-                buildInfo.extractedFileSuffix,
                 String.valueOf(
                         ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion),
                 isDebugAndroid() ? "1" : "0",
@@ -188,10 +185,6 @@
                 abiString = String.format("ABI1: %s, ABI2: %s", Build.CPU_ABI, Build.CPU_ABI2);
             }
 
-            // Append lastUpdateTime to versionCode, since versionCode is unlikely to change when
-            // developing locally but lastUpdateTime is.
-            extractedFileSuffix = String.format("@%x_%x", versionCode, pi.lastUpdateTime);
-
             // The value is truncated, as this is used for crash and UMA reporting.
             androidBuildFingerprint = Build.FINGERPRINT.substring(
                     0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH));
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
index a8a2662..84b00f0 100644
--- a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -71,9 +71,6 @@
     // Default sampling interval for reached code profiler in microseconds.
     private static final int DEFAULT_REACHED_CODE_SAMPLING_INTERVAL_US = 10000;
 
-    // Shared preferences key for the background thread pool setting.
-    private static final String BACKGROUND_THREAD_POOL_KEY = "background_thread_pool_enabled";
-
     // The singleton instance of LibraryLoader. Never null (not final for tests).
     private static LibraryLoader sInstance = new LibraryLoader();
 
@@ -590,33 +587,6 @@
         }
     }
 
-    /**
-     * Enables the background priority thread pool group. The value comes from the
-     * "BackgroundThreadPool" finch experiment, and is pushed on every run, to take effect on the
-     * subsequent run. I.e. the effect of the finch experiment lags by one run, which is the best we
-     * can do considering that the thread pool has to be configured before finch is initialized.
-     * Note that since LibraryLoader is in //base, it can't depend on ChromeFeatureList, and has to
-     * rely on external code pushing the value.
-     *
-     * @param enabled whether to enable the background priority thread pool group.
-     */
-    public static void setBackgroundThreadPoolEnabledOnNextRuns(boolean enabled) {
-        SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit();
-        editor.putBoolean(BACKGROUND_THREAD_POOL_KEY, enabled).apply();
-    }
-
-    /**
-     * @return whether the background priority thread pool group should be enabled. (see
-     *         setBackgroundThreadPoolEnabledOnNextRuns()).
-     */
-    @VisibleForTesting
-    public static boolean isBackgroundThreadPoolEnabled() {
-        try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
-            return ContextUtils.getAppSharedPreferences().getBoolean(
-                    BACKGROUND_THREAD_POOL_KEY, false);
-        }
-    }
-
     private void loadWithChromiumLinker(ApplicationInfo appInfo, String library) {
         Linker linker = getLinker();
 
@@ -786,10 +756,9 @@
         if (mInitialized) return;
         assert mLibraryProcessType != LibraryProcessType.PROCESS_UNINITIALIZED;
 
+        // Add a switch for the reached code profiler as late as possible since it requires a read
+        // from the shared preferences. At this point the shared preferences are usually warmed up.
         if (mLibraryProcessType == LibraryProcessType.PROCESS_BROWSER) {
-            // Add a switch for the reached code profiler as late as possible since it requires a
-            // read from the shared preferences. At this point the shared preferences are usually
-            // warmed up.
             int reachedCodeSamplingIntervalUs = getReachedCodeSamplingIntervalUs();
             if (reachedCodeSamplingIntervalUs > 0) {
                 CommandLine.getInstance().appendSwitch(BaseSwitches.ENABLE_REACHED_CODE_PROFILER);
@@ -797,12 +766,6 @@
                         BaseSwitches.REACHED_CODE_SAMPLING_INTERVAL_US,
                         Integer.toString(reachedCodeSamplingIntervalUs));
             }
-
-            // Similarly, append a switch to enable the background thread pool group if the cached
-            // preference indicates it should be enabled.
-            if (isBackgroundThreadPoolEnabled()) {
-                CommandLine.getInstance().appendSwitch(BaseSwitches.ENABLE_BACKGROUND_THREAD_POOL);
-            }
         }
 
         ensureCommandLineSwitchedAlreadyLocked();
diff --git a/base/base_switches.cc b/base/base_switches.cc
index 9bc2bb33..972bfb2d 100644
--- a/base/base_switches.cc
+++ b/base/base_switches.cc
@@ -31,11 +31,6 @@
 // Force low-end device mode when set.
 const char kEnableLowEndDeviceMode[]        = "enable-low-end-device-mode";
 
-// Enable the use of background thread priorities for background tasks in the
-// ThreadPool even on systems where it is disabled by default, e.g. due to
-// concerns about priority inversions.
-const char kEnableBackgroundThreadPool[] = "enable-background-thread-pool";
-
 // This option can be used to force field trials when testing changes locally.
 // The argument is a list of name and value pairs, separated by slashes. If a
 // trial name is prefixed with an asterisk, that trial will start activated.
diff --git a/base/base_switches.h b/base/base_switches.h
index 2a71415..af22c5118 100644
--- a/base/base_switches.h
+++ b/base/base_switches.h
@@ -19,7 +19,6 @@
 extern const char kEnableCrashReporter[];
 extern const char kEnableFeatures[];
 extern const char kEnableLowEndDeviceMode[];
-extern const char kEnableBackgroundThreadPool[];
 extern const char kForceFieldTrials[];
 extern const char kFullMemoryCrashReport[];
 extern const char kLogBestEffortTasks[];
diff --git a/base/task/thread_pool/environment_config.cc b/base/task/thread_pool/environment_config.cc
index 35547f1..2380f6a 100644
--- a/base/task/thread_pool/environment_config.cc
+++ b/base/task/thread_pool/environment_config.cc
@@ -4,8 +4,6 @@
 
 #include "base/task/thread_pool/environment_config.h"
 
-#include "base/base_switches.h"
-#include "base/command_line.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/platform_thread.h"
 #include "build/build_config.h"
@@ -16,12 +14,6 @@
 namespace {
 
 bool CanUseBackgroundPriorityForWorkerThreadImpl() {
-  // Commandline flag overrides (e.g. for experiments).
-  if (CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kEnableBackgroundThreadPool)) {
-    return true;
-  }
-
   // When Lock doesn't handle multiple thread priorities, run all
   // WorkerThread with a normal priority to avoid priority inversion when a
   // thread running with a normal priority tries to acquire a lock held by a
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
index d68366a..aad1c553 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
@@ -105,9 +105,9 @@
     // crashed.
     private static final String BUNDLE_STACK_ID = "stack";
 
-    private static final long WAIT_FOR_IDLE_TIMEOUT_MS = ScalableTimeout.scaleTimeout(10000L);
+    private static final long WAIT_FOR_IDLE_TIMEOUT_MS = 10000L;
 
-    private static final long FINISH_APP_TASKS_TIMEOUT_MS = ScalableTimeout.scaleTimeout(3000L);
+    private static final long FINISH_APP_TASKS_TIMEOUT_MS = 3000L;
     private static final long FINISH_APP_TASKS_POLL_INTERVAL_MS = 100;
 
     static InMemorySharedPreferencesContext sInMemorySharedPreferencesContext;
@@ -548,7 +548,8 @@
         for (ActivityManager.AppTask task : activityManager.getAppTasks()) {
             task.finishAndRemoveTask();
         }
-        long endTime = SystemClock.uptimeMillis() + FINISH_APP_TASKS_TIMEOUT_MS;
+        long endTime = SystemClock.uptimeMillis()
+                + ScalableTimeout.scaleTimeout(FINISH_APP_TASKS_TIMEOUT_MS);
         while (activityManager.getAppTasks().size() != 0 && SystemClock.uptimeMillis() < endTime) {
             try {
                 Thread.sleep(FINISH_APP_TASKS_POLL_INTERVAL_MS);
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
index 58988462..0896d98 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/ApplicationTestUtils.java
@@ -35,11 +35,9 @@
 
     /** Waits until the given activity transitions to the given state. */
     public static void waitForActivityState(String failureReason, Activity activity, Stage stage) {
-        CriteriaHelper.pollUiThread(
-                ()
-                        -> { return sMonitor.getLifecycleStageOf(activity) == stage; },
-                failureReason, ScalableTimeout.scaleTimeout(10000),
-                CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        CriteriaHelper.pollUiThread(() -> {
+            return sMonitor.getLifecycleStageOf(activity) == stage;
+        }, failureReason, 10000, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
     }
 
     /** Finishes the given activity and waits for its onDestroy() to be called. */
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java b/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
index e59da3fe8..3264014 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
@@ -126,7 +126,7 @@
  */
 public class CallbackHelper {
     /** The default timeout (in seconds) for a callback to wait. */
-    public static final long WAIT_TIMEOUT_SECONDS = ScalableTimeout.scaleTimeout(5L);
+    public static final long WAIT_TIMEOUT_SECONDS = 5L;
 
     private final Object mLock = new Object();
     private int mCallCount;
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto
index 5a8939e..04b3a27b 100644
--- a/base/tracing/protos/chrome_track_event.proto
+++ b/base/tracing/protos/chrome_track_event.proto
@@ -125,9 +125,16 @@
   optional bool has_speculative_render_frame_host = 3;
 }
 
+message ChromeHashedPerformanceMark {
+  optional uint32 site_hash = 1;
+  optional string site = 2;
+  optional uint32 mark_hash = 3;
+  optional string mark = 4;
+}
+
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
-  // Next ID: 1011
+  // Next ID: 1012
   extend TrackEvent {
     optional ChromeAppState chrome_app_state = 1000;
 
@@ -153,5 +160,7 @@
         should_swap_browsing_instances_result = 1009;
 
     optional FrameTreeNodeInfo frame_tree_node_info = 1010;
+
+    optional ChromeHashedPerformanceMark chrome_hashed_performance_mark = 1011;
   }
 }
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 0eb315b..47853545 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -723,18 +723,6 @@
     }
   }
 
-  # Pin to LLVM's legacy pass manager so we can switch independently of a Clang roll.
-  # TODO(https://crbug.com/1173188): remove once we've rolled a Clang with the new PM as the default PM.
-  if (is_android && is_clang && !is_nacl) {
-    cflags += [
-      "-Xclang",
-      "-fno-experimental-new-pass-manager",
-    ]
-    if (use_thin_lto) {
-      ldflags += [ "-Wl,--lto-legacy-pass-manager" ]
-    }
-  }
-
   # This flag enforces that member pointer base types are complete. It helps
   # prevent us from running into problems in the Microsoft C++ ABI (see
   # https://crbug.com/847724).
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 922620dd..f2ca277e 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-3.20210312.3.1
+3.20210315.0.1
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index 7bb27176f..650784bb 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -162,6 +162,14 @@
     } else {
       toolchain_uses_goma = use_goma
     }
+
+    # x86_64-nacl-* is ELF-32 and Goma/RBE won't support ELF-32.
+    if (toolchain_uses_goma &&
+        get_path_info(invoker.cc, "name") == "x86_64-nacl-gcc") {
+      # it will also disable x86_64-nacl-g++ since these are in
+      # the same toolchain.
+      toolchain_uses_goma = false
+    }
     if (defined(toolchain_args.cc_wrapper)) {
       toolchain_cc_wrapper = toolchain_args.cc_wrapper
     } else {
diff --git a/cc/base/features.cc b/cc/base/features.cc
index bab5f6ca..e094789 100644
--- a/cc/base/features.cc
+++ b/cc/base/features.cc
@@ -23,11 +23,7 @@
 // Enables impulse-style scroll animations in place of the default ones.
 const base::Feature kImpulseScrollAnimations = {
     "ImpulseScrollAnimations",
-#if defined(OS_MAC)
     base::FEATURE_DISABLED_BY_DEFAULT};
-#else
-    base::FEATURE_ENABLED_BY_DEFAULT};
-#endif
 
 // Whether the compositor should attempt to sync with the scroll handlers before
 // submitting a frame.
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 10e1bef..3e6161e2 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -1928,7 +1928,7 @@
 }
 
 void LayerTreeHost::SetDelegatedInkMetadata(
-    std::unique_ptr<viz::DelegatedInkMetadata> metadata) {
+    std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
   delegated_ink_metadata_ = std::move(metadata);
   SetNeedsCommit();
 }
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index bb2708d2..ec44f47b 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -52,10 +52,10 @@
 #include "cc/trees/swap_promise_manager.h"
 #include "cc/trees/target_property.h"
 #include "cc/trees/viewport_layers.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "components/viz/common/resources/resource_format.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/overlay_transform.h"
 
@@ -737,8 +737,8 @@
   }
 
   void SetDelegatedInkMetadata(
-      std::unique_ptr<viz::DelegatedInkMetadata> metadata);
-  viz::DelegatedInkMetadata* DelegatedInkMetadataForTesting() {
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata);
+  gfx::DelegatedInkMetadata* DelegatedInkMetadataForTesting() {
     return delegated_ink_metadata_.get();
   }
 
@@ -982,7 +982,7 @@
   // stroke. std::unique_ptr was specifically chosen so that it would be cleared
   // as it is forwarded along the pipeline to avoid old information incorrectly
   // sticking around and potentially being reused.
-  std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata_;
+  std::unique_ptr<gfx::DelegatedInkMetadata> delegated_ink_metadata_;
 
   // A list of document transitions that need to be transported from Blink to
   // Viz, as a CompositorFrameTransitionDirective.
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 973eeeb..6423a98 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -2220,7 +2220,7 @@
 
   metadata.display_transform_hint = active_tree_->display_transform_hint();
 
-  if (std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata =
+  if (std::unique_ptr<gfx::DelegatedInkMetadata> delegated_ink_metadata =
           active_tree_->take_delegated_ink_metadata()) {
     delegated_ink_metadata->set_frame_time(CurrentBeginFrameArgs().frame_time);
     TRACE_EVENT_INSTANT1(
@@ -2535,7 +2535,7 @@
 
   if (render_frame_metadata_observer_) {
     last_draw_render_frame_metadata_ = MakeRenderFrameMetadata(frame);
-    if (viz::DelegatedInkMetadata* ink_metadata =
+    if (gfx::DelegatedInkMetadata* ink_metadata =
             metadata.delegated_ink_metadata.get()) {
       last_draw_render_frame_metadata_->delegated_ink_metadata =
           DelegatedInkBrowserMetadata(ink_metadata->is_hovering());
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index ce37674..cca5516 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -8903,10 +8903,10 @@
     base::TimeTicks timestamp = base::TimeTicks::Now();
     bool is_hovering = true;
 
-    expected_metadata_ = viz::DelegatedInkMetadata(
+    expected_metadata_ = gfx::DelegatedInkMetadata(
         point, diameter, color, timestamp, area, is_hovering);
     layer_tree_host()->SetDelegatedInkMetadata(
-        std::make_unique<viz::DelegatedInkMetadata>(
+        std::make_unique<gfx::DelegatedInkMetadata>(
             expected_metadata_.value()));
   }
 
@@ -8928,7 +8928,7 @@
 
   void ExpectMetadata(base::Optional<DelegatedInkBrowserMetadata>
                           browser_delegated_ink_metadata,
-                      viz::DelegatedInkMetadata* actual_metadata) {
+                      gfx::DelegatedInkMetadata* actual_metadata) {
     if (expected_metadata_.has_value()) {
       EXPECT_TRUE(browser_delegated_ink_metadata.has_value());
       EXPECT_TRUE(actual_metadata);
@@ -8965,7 +8965,7 @@
   }
 
  private:
-  base::Optional<viz::DelegatedInkMetadata> expected_metadata_;
+  base::Optional<gfx::DelegatedInkMetadata> expected_metadata_;
   FakeContentLayerClient client_;
   scoped_refptr<Layer> layer_;
   bool set_needs_display_ = true;
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index cf4cacc..6d8a6ce 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -735,10 +735,10 @@
   // only be called after the JS API |updateInkTrailStartPoint| has been
   // called, which populates the metadata with provided information.
   void set_delegated_ink_metadata(
-      std::unique_ptr<viz::DelegatedInkMetadata> metadata) {
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
     delegated_ink_metadata_ = std::move(metadata);
   }
-  std::unique_ptr<viz::DelegatedInkMetadata> take_delegated_ink_metadata() {
+  std::unique_ptr<gfx::DelegatedInkMetadata> take_delegated_ink_metadata() {
     return std::move(delegated_ink_metadata_);
   }
 
@@ -918,7 +918,7 @@
   // Event metrics that are reported back from the main thread.
   EventMetrics::List events_metrics_from_main_thread_;
 
-  std::unique_ptr<viz::DelegatedInkMetadata> delegated_ink_metadata_;
+  std::unique_ptr<gfx::DelegatedInkMetadata> delegated_ink_metadata_;
 
   // Document transition requests to be transferred to Viz.
   std::vector<std::unique_ptr<DocumentTransitionRequest>>
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 1e035cc4..e001557 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -798,6 +798,7 @@
   "java/res/layout/new_tab_page_feed_v2_expandable_header.xml",
   "java/res/layout/new_tab_page_incognito.xml",
   "java/res/layout/new_tab_page_layout.xml",
+  "java/res/layout/new_tab_page_multi_feed_header.xml",
   "java/res/layout/new_tab_page_tile_grid_placeholder.xml",
   "java/res/layout/omnibox_answer_suggestion.xml",
   "java/res/layout/omnibox_basic_suggestion.xml",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index cf0a1e32..be1ea34 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -874,9 +874,9 @@
   "java/src/org/chromium/chrome/browser/ntp/search/SearchBoxMediator.java",
   "java/src/org/chromium/chrome/browser/ntp/search/SearchBoxProperties.java",
   "java/src/org/chromium/chrome/browser/ntp/search/SearchBoxViewBinder.java",
-  "java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeader.java",
-  "java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderList.java",
-  "java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderListMcp.java",
+  "java/src/org/chromium/chrome/browser/ntp/snippets/OnSectionHeaderSelectedListener.java",
+  "java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderListProperties.java",
+  "java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderProperties.java",
   "java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java",
   "java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java",
   "java/src/org/chromium/chrome/browser/offline/measurements/OfflineMeasurementsBackgroundTask.java",
@@ -1294,6 +1294,7 @@
   "java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java",
   "java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateTabHelper.java",
   "java/src/org/chromium/chrome/browser/tab/RedirectHandlerTabHelper.java",
+  "java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java",
   "java/src/org/chromium/chrome/browser/tab/TabAssociatedApp.java",
   "java/src/org/chromium/chrome/browser/tab/TabBrowserControlsConstraintsHelper.java",
   "java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 4028f9b5..29057726 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -206,7 +206,7 @@
   "junit/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandlerTest.java",
   "junit/src/org/chromium/chrome/browser/signin/SigninActivityLauncherImplTest.java",
   "junit/src/org/chromium/chrome/browser/signin/SigninBridgeTest.java",
-  "junit/src/org/chromium/chrome/browser/signin/SigninManagerTest.java",
+  "junit/src/org/chromium/chrome/browser/signin/SigninManagerImplTest.java",
   "junit/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorMediatorTest.java",
   "junit/src/org/chromium/chrome/browser/suggestions/SuggestionsImageFetcherTest.java",
   "junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerTest.java",
diff --git a/chrome/android/expectations/ccflags.expected b/chrome/android/expectations/ccflags.expected
index eca43af..06b7623 100644
--- a/chrome/android/expectations/ccflags.expected
+++ b/chrome/android/expectations/ccflags.expected
@@ -43,7 +43,6 @@
 -Wunreachable-code
 -Xclang -add-plugin -Xclang find-bad-constructs
 -Xclang -fdebug-compilation-dir -Xclang .
--Xclang -fno-experimental-new-pass-manager
 -Xclang -plugin-arg-find-bad-constructs -Xclang check-ipc
 -Xclang -plugin-arg-find-bad-constructs -Xclang checked-ptr-as-trivial-member
 -fPIC
diff --git a/chrome/android/expectations/ldflags.expected b/chrome/android/expectations/ldflags.expected
index ceb45f7..232554e 100644
--- a/chrome/android/expectations/ldflags.expected
+++ b/chrome/android/expectations/ldflags.expected
@@ -8,7 +8,6 @@
 -Wl,--gc-sections
 -Wl,--icf=all
 -Wl,--lto-O0
--Wl,--lto-legacy-pass-manager
 -Wl,--no-call-graph-profile-sort
 -Wl,--no-rosegment
 -Wl,--warn-shared-textrel
diff --git a/chrome/android/expectations/lint-suppressions.xml b/chrome/android/expectations/lint-suppressions.xml
index 76fe056..5affce00 100644
--- a/chrome/android/expectations/lint-suppressions.xml
+++ b/chrome/android/expectations/lint-suppressions.xml
@@ -249,8 +249,9 @@
     <ignore regexp="The resource `R.string.iph_infobar_add_to_home_screen_title` appears to be unused"/>
     <ignore regexp="The resource `R.string.iph_infobar_add_to_home_screen_description` appears to be unused"/>
     <ignore regexp="The resource `R.string.iph_infobar_add_to_home_screen_description_tablet` appears to be unused"/>
-    <!-- 1: resource is used in C++ components/ntp_snippets. -->
+    <!-- 2: resource is used in C++ components/ntp_snippets. -->
     <ignore regexp="The resource `R.string.ntp_article_suggestions_section_empty` appears to be unused"/>
+    <ignore regexp="The resource `R.string.ntp_article_suggestions_section_header` appears to be unused"/>
     <!-- 2: resource is used in C++ components/error_page. -->
     <ignore regexp="The resource `R.string.show_content` appears to be unused"/>
     <ignore regexp="The resource `R.string.hide_content` appears to be unused"/>
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
index 6fc8491e..41dd9e1 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.feed.StreamLifecycleManager;
 import org.chromium.chrome.browser.feed.shared.FeedSurfaceDelegate;
 import org.chromium.chrome.browser.feed.shared.stream.Stream;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.ntp.ScrollableContainerDelegate;
 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderView;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -100,8 +101,14 @@
         SectionHeaderView sectionHeaderView = null;
         if (hasHeader) {
             LayoutInflater inflater = LayoutInflater.from(mActivity);
-            sectionHeaderView = (SectionHeaderView) inflater.inflate(
-                    R.layout.new_tab_page_feed_v2_expandable_header, null, false);
+            if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_FEED)) {
+                sectionHeaderView = (SectionHeaderView) inflater.inflate(
+                        org.chromium.chrome.R.layout.new_tab_page_multi_feed_header, null, false);
+            } else {
+                sectionHeaderView = (SectionHeaderView) inflater.inflate(
+                        org.chromium.chrome.R.layout.new_tab_page_feed_v2_expandable_header, null,
+                        false);
+            }
         }
 
         FeedV1ActionOptions feedActionOptions = new FeedV1ActionOptions();
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index eef1b8c2..9392f63 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -84,7 +84,6 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.compositor.layouts.Layout;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone;
@@ -328,7 +327,7 @@
                 () -> mActivityTestRule.getActivity().startDelayedNativeInitializationForTests());
         CriteriaHelper.pollUiThread(
                 mActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized,
-                ScalableTimeout.scaleTimeout(10000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+                10000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         Assert.assertTrue(LibraryLoader.getInstance().isInitialized());
     }
 
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index fec5bb30..979a7fb 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -40,8 +40,7 @@
 import org.chromium.chrome.browser.ntp.ScrollableContainerDelegate;
 import org.chromium.chrome.browser.ntp.SnapScrollHelper;
 import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoController;
-import org.chromium.chrome.browser.ntp.snippets.SectionHeaderList;
-import org.chromium.chrome.browser.ntp.snippets.SectionHeaderListMcp;
+import org.chromium.chrome.browser.ntp.snippets.SectionHeaderListProperties;
 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderView;
 import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewBinder;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -59,6 +58,11 @@
 import org.chromium.ui.UiUtils;
 import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.ListModelChangeProcessor;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyListModel;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -128,9 +132,12 @@
     // Used when Feed is enabled.
     private @Nullable Stream mStream;
     private @Nullable StreamLifecycleManager mStreamLifecycleManager;
-    private @Nullable SectionHeaderList mSectionHeaderModel;
+    private @Nullable PropertyModel mSectionHeaderModel;
     private @Nullable SectionHeaderView mSectionHeaderView;
-    private @Nullable SectionHeaderListMcp mSectionHeaderChangeProcessor;
+    private @Nullable ListModelChangeProcessor<PropertyListModel<PropertyModel, PropertyKey>,
+            SectionHeaderView, PropertyKey> mSectionHeaderListModelChangeProcessor;
+    private @Nullable PropertyModelChangeProcessor<PropertyModel, SectionHeaderView, PropertyKey>
+            mSectionHeaderModelChangeProcessor;
     private @Nullable PersonalizedSigninPromoView mSigninPromoView;
     private @Nullable ViewResizer mStreamViewResizer;
     private @Nullable NativePageNavigationDelegate mPageNavigationDelegate;
@@ -319,14 +326,20 @@
         // MVC setup for feed header.
         mSectionHeaderView = sectionHeaderView;
         if (mSectionHeaderView != null) {
-            mSectionHeaderModel = new SectionHeaderList();
+            mSectionHeaderModel = SectionHeaderListProperties.create();
             SectionHeaderViewBinder binder = new SectionHeaderViewBinder();
-            mSectionHeaderChangeProcessor =
-                    new SectionHeaderListMcp(mSectionHeaderModel, mSectionHeaderView, binder);
+            mSectionHeaderModelChangeProcessor = PropertyModelChangeProcessor.create(
+                    mSectionHeaderModel, mSectionHeaderView, binder);
+            mSectionHeaderListModelChangeProcessor = new ListModelChangeProcessor<>(
+                    mSectionHeaderModel.get(SectionHeaderListProperties.SECTION_HEADERS_KEY),
+                    mSectionHeaderView, binder);
+            mSectionHeaderModel.get(SectionHeaderListProperties.SECTION_HEADERS_KEY)
+                    .addObserver(mSectionHeaderListModelChangeProcessor);
         }
 
         // Mediator should be created before any Stream changes.
-        mMediator = new FeedSurfaceMediator(this, snapScrollHelper, mPageNavigationDelegate);
+        mMediator = new FeedSurfaceMediator(
+                this, snapScrollHelper, mPageNavigationDelegate, mSectionHeaderModel);
 
         mUserEducationHelper =
                 new UserEducationHelper(mActivity, mHandler, TrackerFactory::getTrackerForProfile);
@@ -343,8 +356,10 @@
             mEnhancedProtectionPromoController.destroy();
         }
         mScrollableContainerDelegate = null;
-        if (mSectionHeaderChangeProcessor != null) {
-            mSectionHeaderChangeProcessor.destroy();
+        if (mSectionHeaderModelChangeProcessor != null) {
+            mSectionHeaderModelChangeProcessor.destroy();
+            mSectionHeaderModel.get(SectionHeaderListProperties.SECTION_HEADERS_KEY)
+                    .removeObserver(mSectionHeaderListModelChangeProcessor);
         }
     }
 
@@ -514,10 +529,10 @@
         return mSectionHeaderView;
     }
 
-    /** @return The {@link SectionHeaderList} model for the Feed section header. */
+    /** @return The {@link SectionHeaderListProperties} model for the Feed section header. */
     // TODO(chili): Make this package-private when we remove v2 folder.
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-    public SectionHeaderList getSectionHeaderModel() {
+    public PropertyModel getSectionHeaderModel() {
         return mSectionHeaderModel;
     }
 
@@ -655,7 +670,7 @@
             }
             @Override
             public boolean isFeedExpanded() {
-                return mSectionHeaderModel.isSectionEnabled();
+                return mSectionHeaderModel.get(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY);
             }
             @Override
             public boolean isSignedIn() {
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
index c3189a5..c29639aa 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
@@ -29,8 +29,8 @@
 import org.chromium.chrome.browser.ntp.SnapScrollHelper;
 import org.chromium.chrome.browser.ntp.cards.SignInPromo;
 import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoController.EnhancedProtectionPromoStateListener;
-import org.chromium.chrome.browser.ntp.snippets.SectionHeader;
-import org.chromium.chrome.browser.ntp.snippets.SectionHeaderList;
+import org.chromium.chrome.browser.ntp.snippets.SectionHeaderListProperties;
+import org.chromium.chrome.browser.ntp.snippets.SectionHeaderProperties;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -51,6 +51,7 @@
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.modelutil.MVCListAdapter;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.mojom.WindowOpenDisposition;
@@ -68,11 +69,13 @@
                    EnhancedProtectionPromoStateListener, IdentityManager.Observer {
     @VisibleForTesting
     public static final String FEED_CONTENT_FIRST_LOADED_TIME_MS_UMA = "FeedContentFirstLoadedTime";
+    private static final int INTEREST_FEED_HEADER_POSITION = 0;
 
     private final FeedSurfaceCoordinator mCoordinator;
     private final @Nullable SnapScrollHelper mSnapScrollHelper;
     private final PrefChangeRegistrar mPrefChangeRegistrar;
     private final SigninManager mSigninManager;
+    private final PropertyModel mSectionHeaderModel;
 
     private final NativePageNavigationDelegate mPageNavigationDelegate;
 
@@ -85,11 +88,13 @@
     private boolean mHasHeader;
     private boolean mTouchEnabled = true;
     private boolean mStreamContentChanged;
-    private boolean mHasHeaderMenu;
     private int mThumbnailWidth;
     private int mThumbnailHeight;
     private int mThumbnailScrollY;
 
+    /** The model representing feed-related cog menu items. */
+    private ModelList mFeedMenuModel;
+
     /** Whether the Feed content is loading. */
     private boolean mIsLoadingFeed;
     /** Cached parameters for recording the histogram of "FeedContentFirstLoadedTime". */
@@ -100,16 +105,19 @@
     // that Feed content is still loading at that time and the {@link mContentFirstAvailableTimeMs}
     // hasn't been set yet.
     private boolean mHasPendingUmaRecording;
+    private int mToggleswitchMenuIndex;
 
     /**
      * @param coordinator The {@link FeedSurfaceCoordinator} that interacts with this class.
      * @param snapScrollHelper The {@link SnapScrollHelper} that handles snap scrolling.
      * @param pageNavigationDelegate The {@link NativePageNavigationDelegate} that handles page
-     *                               navigation.
+     *         navigation.
+     * @param propertyModel The {@link PropertyModel} that contains this mediator should work with.
      */
     FeedSurfaceMediator(FeedSurfaceCoordinator coordinator,
             @Nullable SnapScrollHelper snapScrollHelper,
-            @Nullable NativePageNavigationDelegate pageNavigationDelegate) {
+            @Nullable NativePageNavigationDelegate pageNavigationDelegate,
+            PropertyModel propertyModel) {
         mCoordinator = coordinator;
         mSnapScrollHelper = snapScrollHelper;
         mSigninManager = IdentityServicesProvider.get().getSigninManager(
@@ -119,13 +127,14 @@
         mPrefChangeRegistrar = new PrefChangeRegistrar();
         mHasHeader = mCoordinator.getSectionHeaderView() != null;
         mPrefChangeRegistrar.addObserver(Pref.ENABLE_SNIPPETS, this::updateContent);
-        mHasHeaderMenu = FeedFeatures.isReportingUserActions();
 
         // Check that there is a navigation delegate when using the feed header menu.
-        if (mPageNavigationDelegate == null && mHasHeaderMenu) {
+        if (mPageNavigationDelegate == null) {
             assert false : "Need navigation delegate for header menu";
         }
 
+        mSectionHeaderModel = propertyModel;
+
         initialize();
         // Create the content.
         updateContent();
@@ -143,6 +152,10 @@
     }
 
     private void initialize() {
+        if (mSectionHeaderModel != null) {
+            mSectionHeaderModel.set(
+                    SectionHeaderListProperties.ON_TAB_SELECTED_CALLBACK_KEY, this::onTabSelected);
+        }
         if (mSnapScrollHelper == null) return;
 
         // Listen for layout changes on the NewTabPageView itself to catch changes in scroll
@@ -249,29 +262,43 @@
         stream.addOnContentChangedListener(mStreamContentChangedListener);
 
         if (mHasHeader) {
-            boolean suggestionsVisible = getPrefService().getBoolean(Pref.ARTICLES_LIST_VISIBLE);
-
-            mCoordinator.getSectionHeaderModel().set(
-                    SectionHeaderList.IS_SECTION_ENABLED_KEY, suggestionsVisible);
-
-            SectionHeader interestFeedHeader =
-                    new SectionHeader(getSectionHeaderText(suggestionsVisible));
-            mCoordinator.getSectionHeaderModel().addHeader(interestFeedHeader);
-
             mPrefChangeRegistrar.addObserver(Pref.ARTICLES_LIST_VISIBLE, this::updateSectionHeader);
             TemplateUrlServiceFactory.get().addObserver(this);
 
-            if (mHasHeaderMenu) {
-                interestFeedHeader.set(SectionHeader.MENU_MODEL_LIST_KEY, buildMenuItems());
-                interestFeedHeader.set(SectionHeader.MENU_DELEGATE_KEY, this::onItemSelected);
-                mCoordinator.initializeIph();
-                mSigninManager.getIdentityManager().addObserver(this);
+            boolean suggestionsVisible = getPrefService().getBoolean(Pref.ARTICLES_LIST_VISIBLE);
+            mSectionHeaderModel.set(
+                    SectionHeaderListProperties.IS_SECTION_ENABLED_KEY, suggestionsVisible);
+            // Build menu after section enabled key is set.
+            mFeedMenuModel = buildMenuItems();
+
+            PropertyModel interestFeedHeader = SectionHeaderProperties.createSectionHeader(
+                    getSectionHeaderText(suggestionsVisible));
+            mSectionHeaderModel.get(SectionHeaderListProperties.SECTION_HEADERS_KEY)
+                    .add(interestFeedHeader);
+
+            mCoordinator.initializeIph();
+            mSigninManager.getIdentityManager().addObserver(this);
+
+            mSectionHeaderModel.set(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY, 0);
+            mSectionHeaderModel.set(
+                    SectionHeaderListProperties.MENU_MODEL_LIST_KEY, mFeedMenuModel);
+            mSectionHeaderModel.set(
+                    SectionHeaderListProperties.MENU_DELEGATE_KEY, this::onItemSelected);
+
+            if (FeedFeatures.isWebFeedUIEnabled()) {
+                PropertyModel webFeedHeader = SectionHeaderProperties.createSectionHeader(
+                        mCoordinator.getSectionHeaderView().getResources().getString(
+                                R.string.ntp_following));
+                mSectionHeaderModel.get(SectionHeaderListProperties.SECTION_HEADERS_KEY)
+                        .add(webFeedHeader);
             }
         }
         // Show feed if there is no header that would allow user to hide feed.
         // This is currently only relevant for the two panes start surface.
-        stream.setStreamContentVisibility(
-                mHasHeader ? mCoordinator.getSectionHeaderModel().isSectionEnabled() : true);
+        stream.setStreamContentVisibility(mHasHeader
+                        ? mSectionHeaderModel.get(
+                                SectionHeaderListProperties.IS_SECTION_ENABLED_KEY)
+                        : true);
 
         initStreamHeaderViews();
 
@@ -359,17 +386,18 @@
     /** Update whether the section header should be expanded and its text contents. */
     private void updateSectionHeader() {
         boolean suggestionsVisible = getPrefService().getBoolean(Pref.ARTICLES_LIST_VISIBLE);
-        if (mCoordinator.getSectionHeaderModel().isSectionEnabled() != suggestionsVisible) {
-            mCoordinator.getSectionHeaderModel().toggleSection();
+        mSectionHeaderModel.set(
+                SectionHeaderListProperties.IS_SECTION_ENABLED_KEY, suggestionsVisible);
+
+        if (!FeedFeatures.isWebFeedUIEnabled()) {
+            mSectionHeaderModel.get(SectionHeaderListProperties.SECTION_HEADERS_KEY)
+                    .get(INTEREST_FEED_HEADER_POSITION)
+                    .set(SectionHeaderProperties.HEADER_TEXT_KEY,
+                            getSectionHeaderText(suggestionsVisible));
         }
 
-        // TODO(chili): Support multiple headers.
-        mCoordinator.getSectionHeaderModel().getHeaders().get(0).set(SectionHeader.HEADER_TEXT_KEY,
-                getSectionHeaderText(mCoordinator.getSectionHeaderModel().isSectionEnabled()));
-        if (mHasHeaderMenu) {
-            mCoordinator.getSectionHeaderModel().getHeaders().get(0).set(
-                    SectionHeader.MENU_MODEL_LIST_KEY, buildMenuItems());
-        }
+        // Update toggleswitch item, which is last item in list.
+        mFeedMenuModel.update(mToggleswitchMenuIndex, getMenuToggleSwitch(suggestionsVisible, 0));
 
         if (mSignInPromo != null) {
             mSignInPromo.setCanShowPersonalizedSuggestions(suggestionsVisible);
@@ -383,10 +411,12 @@
      * expand icon on the section header view.
      */
     private void onSectionHeaderToggled() {
-        // Update model.
-        mCoordinator.getSectionHeaderModel().toggleSection();
+        boolean isExpanded =
+                !mSectionHeaderModel.get(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY);
 
-        boolean isExpanded = mCoordinator.getSectionHeaderModel().isSectionEnabled();
+        // Update model.
+        mSectionHeaderModel.set(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY, isExpanded);
+
         // Record in prefs and UMA.
         getPrefService().setBoolean(Pref.ARTICLES_LIST_VISIBLE, isExpanded);
         mCoordinator.getStream().toggledArticlesListVisible(isExpanded);
@@ -401,48 +431,58 @@
         final boolean isDefaultSearchEngineGoogle =
                 TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle();
         final int sectionHeaderStringId;
-        if (mHasHeaderMenu) {
-            if (isDefaultSearchEngineGoogle) {
-                sectionHeaderStringId =
-                        isExpanded ? R.string.ntp_discover_on : R.string.ntp_discover_off;
-            } else {
-                sectionHeaderStringId = isExpanded ? R.string.ntp_discover_on_branded
-                                                   : R.string.ntp_discover_off_branded;
-            }
+
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_FEED)) {
+            sectionHeaderStringId = R.string.ntp_for_you;
+        } else if (isDefaultSearchEngineGoogle) {
+            sectionHeaderStringId =
+                    isExpanded ? R.string.ntp_discover_on : R.string.ntp_discover_off;
         } else {
-            sectionHeaderStringId = isDefaultSearchEngineGoogle
-                    ? R.string.ntp_article_suggestions_section_header
-                    : R.string.ntp_article_suggestions_section_header_branded;
+            sectionHeaderStringId = isExpanded ? R.string.ntp_discover_on_branded
+                                               : R.string.ntp_discover_off_branded;
         }
+
         return res.getString(sectionHeaderStringId);
     }
 
     private ModelList buildMenuItems() {
         ModelList itemList = new ModelList();
-        int icon_id = 0;
+        int iconId = 0;
         if (mSigninManager.getIdentityManager().hasPrimaryAccount()) {
             itemList.add(buildMenuListItem(R.string.ntp_manage_my_activity,
-                    R.id.ntp_feed_header_menu_item_activity, icon_id));
+                    R.id.ntp_feed_header_menu_item_activity, iconId));
             itemList.add(buildMenuListItem(R.string.ntp_manage_interests,
-                    R.id.ntp_feed_header_menu_item_interest, icon_id));
+                    R.id.ntp_feed_header_menu_item_interest, iconId));
             if (ChromeFeatureList.isEnabled(ChromeFeatureList.INTEREST_FEED_V2_HEARTS)) {
                 itemList.add(buildMenuListItem(R.string.ntp_manage_reactions,
-                        R.id.ntp_feed_header_menu_item_reactions, icon_id));
+                        R.id.ntp_feed_header_menu_item_reactions, iconId));
             }
         }
         itemList.add(buildMenuListItem(
-                R.string.learn_more, R.id.ntp_feed_header_menu_item_learn, icon_id));
-        if (mCoordinator.getSectionHeaderModel().isSectionEnabled()) {
-            itemList.add(buildMenuListItem(R.string.ntp_turn_off_feed,
-                    R.id.ntp_feed_header_menu_item_toggle_switch, icon_id));
-        } else {
-            itemList.add(buildMenuListItem(R.string.ntp_turn_on_feed,
-                    R.id.ntp_feed_header_menu_item_toggle_switch, icon_id));
-        }
+                R.string.learn_more, R.id.ntp_feed_header_menu_item_learn, iconId));
+        itemList.add(getMenuToggleSwitch(
+                mSectionHeaderModel.get(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY),
+                iconId));
+        mToggleswitchMenuIndex = itemList.size() - 1;
         return itemList;
     }
 
     /**
+     * Returns the menu list item that represents turning the feed on/off.
+     *
+     * @param isEnabled Whether the feed section is currently enabled.
+     * @param iconId IconId for the list item if any.
+     */
+    private MVCListAdapter.ListItem getMenuToggleSwitch(boolean isEnabled, int iconId) {
+        if (isEnabled) {
+            return buildMenuListItem(R.string.ntp_turn_off_feed,
+                    R.id.ntp_feed_header_menu_item_toggle_switch, iconId);
+        }
+        return buildMenuListItem(
+                R.string.ntp_turn_on_feed, R.id.ntp_feed_header_menu_item_toggle_switch, iconId);
+    }
+
+    /**
      * Callback on sign-in promo is dismissed.
      */
     void onSignInPromoDismissed() {
@@ -666,4 +706,17 @@
                 mContentFirstAvailableTimeMs - mActivityCreationTimeMs, mIsInstantStart);
         return true;
     }
+
+    private void onTabSelected(int index) {
+        mSectionHeaderModel.set(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY, index);
+        Runnable onSelectCallback =
+                mSectionHeaderModel.get(SectionHeaderListProperties.SECTION_HEADERS_KEY)
+                        .get(index)
+                        .get(SectionHeaderProperties.ON_SELECT_CALLBACK_KEY);
+        if (onSelectCallback != null) {
+            onSelectCallback.run();
+        }
+        // TODO(chili): Register observers for new datastream; de-register observer for old
+        // datastream.
+    }
 }
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2NewTabPageTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2NewTabPageTest.java
index 380cc63..5274bfc 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2NewTabPageTest.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/FeedV2NewTabPageTest.java
@@ -61,6 +61,7 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.ntp.cards.SignInPromo;
+import org.chromium.chrome.browser.ntp.snippets.SectionHeaderListProperties;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
@@ -343,8 +344,8 @@
         TextView headerStatusView = sectionHeaderView.findViewById(R.id.header_title);
 
         // Assert that the feed is expanded and that the header title text is correct.
-        Assert.assertTrue(
-                mNtp.getCoordinatorForTesting().getSectionHeaderModel().isSectionEnabled());
+        Assert.assertTrue(mNtp.getCoordinatorForTesting().getSectionHeaderModel().get(
+                SectionHeaderListProperties.IS_SECTION_ENABLED_KEY));
         Assert.assertEquals(sectionHeaderView.getContext().getString(R.string.ntp_discover_on),
                 headerStatusView.getText());
 
@@ -352,8 +353,8 @@
         toggleHeader(false);
 
         // Assert that the feed is collapsed and that the header title text is correct.
-        Assert.assertFalse(
-                mNtp.getCoordinatorForTesting().getSectionHeaderModel().isSectionEnabled());
+        Assert.assertFalse(mNtp.getCoordinatorForTesting().getSectionHeaderModel().get(
+                SectionHeaderListProperties.IS_SECTION_ENABLED_KEY));
         Assert.assertEquals(sectionHeaderView.getContext().getString(R.string.ntp_discover_off),
                 headerStatusView.getText());
     }
diff --git a/chrome/android/java/res/layout/new_tab_page_multi_feed_header.xml b/chrome/android/java/res/layout/new_tab_page_multi_feed_header.xml
new file mode 100644
index 0000000..cd2624bf
--- /dev/null
+++ b/chrome/android/java/res/layout/new_tab_page_multi_feed_header.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<org.chromium.chrome.browser.ntp.snippets.SectionHeaderView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/snippets_article_header_height"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:layoutDirection="locale"
+    app:animatePaddingWhenDisabled="true">
+
+    <!-- Null content description for now because UX in flux. -->
+    <ImageView
+        android:id="@+id/visibility_indicator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_visibility_off_black"
+        android:visibility="invisible"
+        tools:ignore="contentDescription" />
+
+    <com.google.android.material.tabs.TabLayout
+        android:id="@+id/tab_list_view"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        style="@style/NtpHeaderTabLayoutStyle" />
+
+    <org.chromium.components.browser_ui.widget.listmenu.ListMenuButton
+        android:id="@+id/header_menu"
+        android:layout_width="@dimen/feed_v2_header_menu_width"
+        android:layout_height="@dimen/snippets_article_header_menu_size"
+        android:scaleType="fitXY"
+        android:paddingVertical="15dp"
+        android:layout_marginStart="15dp"
+        android:background="@null"
+        android:src="@drawable/ic_settings_gear_24dp"
+        android:contentDescription="@string/accessibility_ntp_feed_menu_button"
+        app:menuMaxWidth="@dimen/feed_header_menu_max_width"
+        app:tint="@null" />
+
+</org.chromium.chrome.browser.ntp.snippets.SectionHeaderView>
\ No newline at end of file
diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml
index 258f8288..961a3b16 100644
--- a/chrome/android/java/res/values/styles.xml
+++ b/chrome/android/java/res/values/styles.xml
@@ -370,6 +370,10 @@
         <item name="android:background">@color/default_bg_color</item>
     </style>
 
+    <style name="NtpHeaderTabLayoutStyle" parent="TabLayoutStyle">
+        <item name="tabGravity">center</item>
+    </style>
+
     <!-- Misc styles -->
     <style name="LocationBarButton">
         <item name="android:background">@null</item>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity2.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity2.java
index a9d13e0b..e1556502 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity2.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity2.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser;
 
-import org.chromium.base.metrics.RecordUserAction;
-
 /**
  * A subclass of ChromeTabbedActivity, used in Android N multi-window mode.
  *
@@ -19,18 +17,7 @@
  */
 public class ChromeTabbedActivity2 extends ChromeTabbedActivity {
     @Override
-    protected void onDeferredStartupForMultiWindowMode() {
-        RecordUserAction.record("Android.MultiWindowMode.MultiInstance.Enter");
-    }
-
-    @Override
-    protected void recordMultiWindowModeChangedUserAction(boolean isInMultiWindowMode) {
-        // Record separate user actions for entering/exiting multi-window mode to avoid recording
-        // the same action twice when two instances are running.
-        if (isInMultiWindowMode) {
-            RecordUserAction.record("Android.MultiWindowMode.Enter-SecondInstance");
-        } else {
-            RecordUserAction.record("Android.MultiWindowMode.Exit-SecondInstance");
-        }
+    protected boolean isFirstActivity() {
+        return false;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 29c96f6..5c0257a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -143,6 +143,7 @@
 import org.chromium.chrome.browser.share.ShareDelegateSupplier;
 import org.chromium.chrome.browser.sync.ProfileSyncService;
 import org.chromium.chrome.browser.tab.AccessibilityVisibilityHandler;
+import org.chromium.chrome.browser.tab.RequestDesktopUtils;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabHidingType;
 import org.chromium.chrome.browser.tab.TabLaunchType;
@@ -900,6 +901,8 @@
             }
         }
         VrModuleProvider.getDelegate().onActivityShown(this);
+
+        MultiWindowUtils.getInstance().recordMultiWindowStateUkm(this, tab);
     }
 
     private void onActivityHidden() {
@@ -1229,9 +1232,10 @@
      * Actions that may be run at some point after startup for Android N multi-window mode. Should
      * be called from #onDeferredStartup() if the activity is in multi-window mode.
      */
-    protected void onDeferredStartupForMultiWindowMode() {
+    private void onDeferredStartupForMultiWindowMode() {
         // If the Activity was launched in multi-window mode, record a user action.
-        recordMultiWindowModeChangedUserAction(true);
+        recordMultiWindowModeChanged(
+                /* isInMultiWindowMode= */ true, /* isDeferredStartup= */ true);
     }
 
     /**
@@ -1269,6 +1273,7 @@
         if (mCompositorViewHolderSupplier.hasValue()) mCompositorViewHolderSupplier.get().onStart();
 
         mConfig = getResources().getConfiguration();
+
         mStarted = true;
     }
 
@@ -2040,6 +2045,12 @@
                 return;
             }
         }
+
+        if (newConfig.orientation != mConfig.orientation) {
+            RequestDesktopUtils.recordScreenOrientationChangedUkm(
+                    newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE, getActivityTab());
+        }
+
         mConfig = newConfig;
     }
 
@@ -2069,7 +2080,7 @@
         // will be called in #onResumeWithNative(). Both of these methods require native to be
         // initialized, so do not call here to avoid crashing. See https://crbug.com/797921.
         if (mNativeInitialized) {
-            recordMultiWindowModeChangedUserAction(isInMultiWindowMode);
+            recordMultiWindowModeChanged(isInMultiWindowMode, /* isDeferredStartup= */ false);
 
             if (!isInMultiWindowMode
                     && ApplicationStatus.getStateForActivity(this) == ActivityState.RESUMED) {
@@ -2091,15 +2102,23 @@
     }
 
     /**
-     * Records user actions associated with entering and exiting Android N multi-window mode
+     * Records user actions and ukms associated with entering and exiting Android N multi-window
+     * mode.
      * @param isInMultiWindowMode True if the activity is in multi-window mode.
+     * @param isDeferredStartup True if the activity is deferred startup.
      */
-    protected void recordMultiWindowModeChangedUserAction(boolean isInMultiWindowMode) {
-        if (isInMultiWindowMode) {
-            RecordUserAction.record("Android.MultiWindowMode.Enter");
-        } else {
-            RecordUserAction.record("Android.MultiWindowMode.Exit");
-        }
+    private void recordMultiWindowModeChanged(
+            boolean isInMultiWindowMode, boolean isDeferredStartup) {
+        MultiWindowUtils.getInstance().recordMultiWindowModeChanged(
+                isInMultiWindowMode, isDeferredStartup, isFirstActivity(), getActivityTab());
+    }
+
+    /**
+     * This method serves to distinguish windows in multi-window mode.
+     * @return True if this activity is the first created activity.
+     */
+    protected boolean isFirstActivity() {
+        return true;
     }
 
     @Override
@@ -2341,9 +2360,7 @@
                     currentTab.getWebContents().getNavigationController().getUseDesktopUserAgent();
             usingDesktopUserAgent = !usingDesktopUserAgent;
             TabUtils.switchUserAgent(currentTab, usingDesktopUserAgent, /* forcedByUser */ true);
-            RecordUserAction.record("MobileMenuRequestDesktopSite");
-            RecordHistogram.recordBooleanHistogram(
-                    "Android.RequestDesktopSite.UserSwitchToDesktop", usingDesktopUserAgent);
+            RequestDesktopUtils.recordUserChangeUserAgent(usingDesktopUserAgent, getActivityTab());
             return true;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
index ab23935..022b939c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java
@@ -17,6 +17,7 @@
 import android.text.TextUtils;
 import android.view.Display;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -25,12 +26,17 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.ApplicationStatus.ActivityStateListener;
 import org.chromium.base.IntentUtils;
+import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
 import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.util.AndroidTaskUtils;
+import org.chromium.components.ukm.UkmRecorder;
 import org.chromium.ui.display.DisplayAndroidManager;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
@@ -52,6 +58,22 @@
     private WeakReference<ChromeTabbedActivity> mLastResumedTabbedActivity;
     private boolean mIsInMultiWindowModeForTesting;
 
+    // Note: these values must match the AndroidMultiWindowActivityType enum in enums.xml.
+    @IntDef({MultiWindowActivityType.ENTER, MultiWindowActivityType.EXIT})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface MultiWindowActivityType {
+        int ENTER = 0;
+        int EXIT = 1;
+    }
+
+    // Note: these values must match the AndroidMultiWindowState enum in enums.xml.
+    @IntDef({MultiWindowState.SINGLE_WINDOW, MultiWindowState.MULTI_WINDOW})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface MultiWindowState {
+        int SINGLE_WINDOW = 0;
+        int MULTI_WINDOW = 1;
+    }
+
     private MultiWindowUtils() {}
 
     /**
@@ -445,4 +467,54 @@
                     & ~(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT));
         }
     }
+
+    /**
+     * Records user actions and ukms associated with entering and exiting Android N multi-window
+     * mode.
+     * For second activity, records separate user actions for entering/exiting multi-window mode to
+     * avoid recording the same action twice when two instances are running, but still records same
+     * UKM since two instances have two different tabs.
+     * @param isInMultiWindowMode True if the activity is in multi-window mode.
+     * @param isDeferredStartup True if the activity is deferred startup.
+     * @param isFirstActivity True if the activity is the first activity in multi-window mode.
+     * @param tab The current activity {@link Tab}.
+     */
+    public void recordMultiWindowModeChanged(boolean isInMultiWindowMode, boolean isDeferredStartup,
+            boolean isFirstActivity, @Nullable Tab tab) {
+        if (isFirstActivity) {
+            if (isInMultiWindowMode) {
+                RecordUserAction.record("Android.MultiWindowMode.Enter");
+            } else {
+                RecordUserAction.record("Android.MultiWindowMode.Exit");
+            }
+        } else {
+            if (isDeferredStartup) {
+                RecordUserAction.record("Android.MultiWindowMode.MultiInstance.Enter");
+            } else if (isInMultiWindowMode) {
+                RecordUserAction.record("Android.MultiWindowMode.Enter-SecondInstance");
+            } else {
+                RecordUserAction.record("Android.MultiWindowMode.Exit-SecondInstance");
+            }
+        }
+
+        if (tab == null || tab.isIncognito() || tab.getWebContents() == null) return;
+
+        new UkmRecorder.Bridge().recordEventWithIntegerMetric(tab.getWebContents(),
+                "Android.MultiWindowChangeActivity", "ActivityType",
+                isInMultiWindowMode ? MultiWindowActivityType.ENTER : MultiWindowActivityType.EXIT);
+    }
+
+    /**
+     * Records the ukms about if the activity is in multi-window mode when the activity is shown.
+     * @param activity The current Context, used to retrieve the ActivityManager system service.
+     * @param tab The current activity {@link Tab}.
+     */
+    public void recordMultiWindowStateUkm(Activity activity, Tab tab) {
+        if (tab == null || tab.isIncognito() || tab.getWebContents() == null) return;
+
+        new UkmRecorder.Bridge().recordEventWithIntegerMetric(tab.getWebContents(),
+                "Android.MultiWindowState", "WindowState",
+                isInMultiWindowMode(activity) ? MultiWindowState.MULTI_WINDOW
+                                              : MultiWindowState.SINGLE_WINDOW);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/net/spdyproxy/DataReductionProxySettings.java b/chrome/android/java/src/org/chromium/chrome/browser/net/spdyproxy/DataReductionProxySettings.java
index b785ac4..a905bf24 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/net/spdyproxy/DataReductionProxySettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/net/spdyproxy/DataReductionProxySettings.java
@@ -312,7 +312,7 @@
     }
 
     @NativeMethods
-    interface Natives {
+    public interface Natives {
         long init(DataReductionProxySettings caller);
         boolean isDataReductionProxyPromoAllowed(
                 long nativeDataReductionProxySettingsAndroid, DataReductionProxySettings caller);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
index 50d3846..9e3fbc4e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptor.java
@@ -60,11 +60,10 @@
     }
 
     /**
-     * Deprecated, now we use {@link TrampolineActivity} to do the logging. Temporarily kept or
-     * existing notification will crash. Receives the event when the user taps on the notification
-     * body, notification action, or dismiss notification.
-     * {@link Notification#contentIntent}, {@link Notification#deleteIntent}
-     * {@link Notification.Action#actionIntent} will be delivered to this broadcast receiver.
+     * Receives the event when the user dismisses notification. {@link Notification#deleteIntent}
+     * will be delivered to this broadcast receiver. Starting from Android S, the click events and
+     * action click events must be handled by activity. Starting from Android Q, the dismiss event
+     * can't be handled by {@link TrampolineActivity} due to background activity start restriction.
      */
     public static final class Receiver extends BroadcastReceiver {
         @Override
@@ -105,7 +104,7 @@
     }
 
     /**
-     * A trampoline activity that handles notification events logging.
+     * A trampoline activity that handles logging metrics for click events and action click events.
      */
     public static class TrampolineActivity extends Activity {
         @Override
@@ -137,8 +136,15 @@
             pendingIntent = pendingIntentProvider.getPendingIntent();
             flags = pendingIntentProvider.getFlags();
         }
+
+        // The delete intent needs to be handled by broadcast receiver from Q due to background
+        // activity start restriction.
+        boolean shouldUseBroadcast =
+                intentType == NotificationIntentInterceptor.IntentType.DELETE_INTENT;
         Context applicationContext = ContextUtils.getApplicationContext();
-        Intent intent = new Intent(applicationContext, TrampolineActivity.class);
+        Intent intent = shouldUseBroadcast
+                ? new Intent(applicationContext, Receiver.class)
+                : new Intent(applicationContext, TrampolineActivity.class);
 
         intent.setAction(INTENT_ACTION);
         intent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
@@ -151,7 +157,7 @@
 
         // This flag ensures the broadcast is delivered with foreground priority to speed up the
         // broadcast delivery.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+        if (shouldUseBroadcast && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         }
         // Use request code to distinguish different PendingIntents on Android.
@@ -159,7 +165,9 @@
                 pendingIntentProvider != null ? pendingIntentProvider.getRequestCode() : 0;
         int requestCode = computeHashCode(metadata, intentType, intentId, originalRequestCode);
 
-        return PendingIntent.getActivity(applicationContext, requestCode, intent, flags);
+        return shouldUseBroadcast
+                ? PendingIntent.getBroadcast(applicationContext, requestCode, intent, flags)
+                : PendingIntent.getActivity(applicationContext, requestCode, intent, flags);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index e91fe93..e415c07 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -41,6 +41,7 @@
 import org.chromium.chrome.browser.feed.shared.FeedSurfaceDelegate;
 import org.chromium.chrome.browser.feed.shared.FeedSurfaceProvider;
 import org.chromium.chrome.browser.feed.shared.stream.Stream;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.LifecycleObserver;
 import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
@@ -409,8 +410,14 @@
         LayoutInflater inflater = LayoutInflater.from(activity);
         mNewTabPageLayout = (NewTabPageLayout) inflater.inflate(R.layout.new_tab_page_layout, null);
 
-        final SectionHeaderView sectionHeaderView = (SectionHeaderView) inflater.inflate(
-                R.layout.new_tab_page_feed_v2_expandable_header, null, false);
+        final SectionHeaderView sectionHeaderView;
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_FEED)) {
+            sectionHeaderView = (SectionHeaderView) inflater.inflate(
+                    R.layout.new_tab_page_multi_feed_header, null, false);
+        } else {
+            sectionHeaderView = (SectionHeaderView) inflater.inflate(
+                    R.layout.new_tab_page_feed_v2_expandable_header, null, false);
+        }
 
         mFeedSurfaceProvider =
                 new FeedSurfaceCoordinator(activity, snackbarManager, tabModelSelector,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/OnSectionHeaderSelectedListener.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/OnSectionHeaderSelectedListener.java
new file mode 100644
index 0000000..ee71a1f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/OnSectionHeaderSelectedListener.java
@@ -0,0 +1,14 @@
+// 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.
+
+package org.chromium.chrome.browser.ntp.snippets;
+
+/** Callback for when a section header is selected. */
+public interface OnSectionHeaderSelectedListener {
+    /**
+     * Callback for when a header tab is selected.
+     * @param index the index of the tab selected.
+     */
+    void onSectionHeaderSelected(int index);
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeader.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeader.java
deleted file mode 100644
index b3a21139..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeader.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2016 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.ntp.snippets;
-
-import android.view.View;
-
-import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
-import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
-import org.chromium.ui.modelutil.PropertyModel;
-
-/**
- * Represents the data for a header of a group of snippets.
- */
-public class SectionHeader extends PropertyModel {
-    /** The header text to be shown. */
-    public static final WritableObjectPropertyKey<String> HEADER_TEXT_KEY =
-            new WritableObjectPropertyKey<>();
-    /** The model of the menu items to show in the overflow menu to manage the feed. */
-    public static final WritableObjectPropertyKey<ModelList> MENU_MODEL_LIST_KEY =
-            new WritableObjectPropertyKey<>();
-    public static final WritableObjectPropertyKey<ListMenu.Delegate> MENU_DELEGATE_KEY =
-            new WritableObjectPropertyKey<>();
-    public static final WritableObjectPropertyKey<View.OnClickListener> ON_CLICK_HANDLER_KEY =
-            new WritableObjectPropertyKey<>();
-
-    /**
-     * Constructor for non-expandable header.
-     * @param headerText The title of the header.
-     */
-    public SectionHeader(String headerText) {
-        super(ON_CLICK_HANDLER_KEY, HEADER_TEXT_KEY, MENU_DELEGATE_KEY, MENU_MODEL_LIST_KEY);
-        set(HEADER_TEXT_KEY, headerText);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderList.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderList.java
deleted file mode 100644
index 760bc18..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderList.java
+++ /dev/null
@@ -1,52 +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.
-
-package org.chromium.chrome.browser.ntp.snippets;
-
-import org.chromium.ui.modelutil.PropertyKey;
-import org.chromium.ui.modelutil.PropertyListModel;
-import org.chromium.ui.modelutil.PropertyModel;
-
-/**
- * Represents a list of SectionHeaders.
- */
-public class SectionHeaderList extends PropertyModel {
-    public static final PropertyModel.WritableBooleanPropertyKey IS_SECTION_ENABLED_KEY =
-            new PropertyModel.WritableBooleanPropertyKey();
-
-    // Private writable version because cannot set initial values on the super model with Readable
-    // keys.
-    private static final PropertyModel
-            .WritableObjectPropertyKey<PropertyListModel<SectionHeader, PropertyKey>>
-                    SECTION_HEADERS_KEY = new PropertyModel.WritableObjectPropertyKey<>();
-
-    public SectionHeaderList() {
-        super(IS_SECTION_ENABLED_KEY, SECTION_HEADERS_KEY);
-        PropertyListModel<SectionHeader, PropertyKey> headerList = new PropertyListModel<>();
-        set(SECTION_HEADERS_KEY, headerList);
-    }
-
-    public void addHeader(SectionHeader header) {
-        get(SECTION_HEADERS_KEY).add(header);
-    }
-
-    public PropertyListModel<SectionHeader, PropertyKey> getHeaders() {
-        return get(SECTION_HEADERS_KEY);
-    }
-
-    /**
-     * @return Whether or not the contents below this header is shown or not.
-     */
-    public boolean isSectionEnabled() {
-        return get(IS_SECTION_ENABLED_KEY);
-    }
-
-    /**
-     * Toggle the expanded state of the header.
-     * Toggling to off state should collapse the entire section.
-     */
-    public void toggleSection() {
-        set(IS_SECTION_ENABLED_KEY, !get(IS_SECTION_ENABLED_KEY));
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderListMcp.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderListMcp.java
deleted file mode 100644
index 197eb7c5..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderListMcp.java
+++ /dev/null
@@ -1,52 +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.
-
-package org.chromium.chrome.browser.ntp.snippets;
-
-import org.chromium.ui.modelutil.ListObservable;
-import org.chromium.ui.modelutil.PropertyKey;
-import org.chromium.ui.modelutil.PropertyObservable;
-
-/** A customized model change processor for the SectionHeaderList. */
-public class SectionHeaderListMcp implements ListObservable.ListObserver<PropertyKey>,
-                                             PropertyObservable.PropertyObserver<PropertyKey> {
-    private final SectionHeaderView mView;
-    private final SectionHeaderViewBinder mBinder;
-    private final SectionHeaderList mModel;
-
-    public SectionHeaderListMcp(
-            SectionHeaderList model, SectionHeaderView view, SectionHeaderViewBinder viewBinder) {
-        mView = view;
-        mBinder = viewBinder;
-        mModel = model;
-        mModel.addObserver(this);
-        mModel.getHeaders().addObserver(this);
-    }
-
-    public void destroy() {
-        mModel.removeObserver(this);
-        mModel.getHeaders().removeObserver(this);
-    }
-
-    @Override
-    public void onPropertyChanged(PropertyObservable<PropertyKey> source, PropertyKey propertyKey) {
-        mBinder.bind(mModel, mView, propertyKey);
-    }
-
-    @Override
-    public void onItemRangeInserted(ListObservable source, int index, int count) {
-        mBinder.onItemsInserted(mModel, mView, index, count);
-    }
-
-    @Override
-    public void onItemRangeRemoved(ListObservable source, int index, int count) {
-        mBinder.onItemsRemoved(mModel, mView, index, count);
-    }
-
-    @Override
-    public void onItemRangeChanged(
-            ListObservable<PropertyKey> source, int index, int count, PropertyKey payload) {
-        mBinder.onItemsChanged(mModel, mView, index, count, payload);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderListProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderListProperties.java
new file mode 100644
index 0000000..7abde27
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderListProperties.java
@@ -0,0 +1,41 @@
+// 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.
+
+package org.chromium.chrome.browser.ntp.snippets;
+
+import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
+import org.chromium.ui.modelutil.MVCListAdapter;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyListModel;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Properties for a list of {@link SectionHeaderProperties} models.
+ */
+public class SectionHeaderListProperties {
+    public static final PropertyModel.WritableBooleanPropertyKey IS_SECTION_ENABLED_KEY =
+            new PropertyModel.WritableBooleanPropertyKey();
+    public static final PropertyModel
+            .ReadableObjectPropertyKey<PropertyListModel<PropertyModel, PropertyKey>>
+                    SECTION_HEADERS_KEY = new PropertyModel.ReadableObjectPropertyKey<>();
+    public static final PropertyModel.WritableIntPropertyKey CURRENT_TAB_INDEX_KEY =
+            new PropertyModel.WritableIntPropertyKey();
+    public static final PropertyModel.WritableObjectPropertyKey<OnSectionHeaderSelectedListener>
+            ON_TAB_SELECTED_CALLBACK_KEY = new PropertyModel.WritableObjectPropertyKey<>();
+    /** The model of the menu items to show in the overflow menu to manage the feed. */
+    public static final PropertyModel
+            .WritableObjectPropertyKey<MVCListAdapter.ModelList> MENU_MODEL_LIST_KEY =
+            new PropertyModel.WritableObjectPropertyKey<>();
+    public static final PropertyModel
+            .WritableObjectPropertyKey<ListMenu.Delegate> MENU_DELEGATE_KEY =
+            new PropertyModel.WritableObjectPropertyKey<>();
+
+    public static PropertyModel create() {
+        return new PropertyModel
+                .Builder(IS_SECTION_ENABLED_KEY, SECTION_HEADERS_KEY, CURRENT_TAB_INDEX_KEY,
+                        ON_TAB_SELECTED_CALLBACK_KEY, MENU_MODEL_LIST_KEY, MENU_DELEGATE_KEY)
+                .with(SECTION_HEADERS_KEY, new PropertyListModel<>())
+                .build();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderProperties.java
new file mode 100644
index 0000000..837284d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderProperties.java
@@ -0,0 +1,24 @@
+// Copyright 2016 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.ntp.snippets;
+
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Represents the data for a header of a group of snippets.
+ */
+public class SectionHeaderProperties {
+    /** The header text to be shown. */
+    public static final PropertyModel.WritableObjectPropertyKey<String> HEADER_TEXT_KEY =
+            new PropertyModel.WritableObjectPropertyKey<>();
+    public static final PropertyModel.WritableObjectPropertyKey<Runnable> ON_SELECT_CALLBACK_KEY =
+            new PropertyModel.WritableObjectPropertyKey<>();
+
+    public static PropertyModel createSectionHeader(String headerText) {
+        return new PropertyModel.Builder(ON_SELECT_CALLBACK_KEY, HEADER_TEXT_KEY)
+                .with(HEADER_TEXT_KEY, headerText)
+                .build();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java
index b13cd0d1..4d660bc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderView.java
@@ -13,11 +13,14 @@
 import android.util.AttributeSet;
 import android.view.TouchDelegate;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 
+import com.google.android.material.tabs.TabLayout;
+
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.feed.FeedUma;
 import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
@@ -39,10 +42,35 @@
 public class SectionHeaderView extends LinearLayout {
     private static final int ANIMATION_DURATION_MS = 200;
 
+    /** OnTabSelectedListener that delegates calls to the SectionHeadSelectedListener. */
+    private class SectionHeaderTabListener implements TabLayout.OnTabSelectedListener {
+        private @Nullable OnSectionHeaderSelectedListener mListener;
+
+        @Override
+        public void onTabSelected(TabLayout.Tab tab) {
+            if (mListener != null) {
+                mListener.onSectionHeaderSelected(tab.getPosition());
+            }
+        }
+
+        @Override
+        public void onTabUnselected(TabLayout.Tab tab) {
+            // Do nothing; Not supported.
+        }
+
+        @Override
+        public void onTabReselected(TabLayout.Tab tab) {
+            // Do nothing; Not supported.
+        }
+    }
+
     // Views in the header layout that are set during inflate.
-    private TextView mTitleView;
+    private @Nullable ImageView mVisibilityIndicator;
+    private @Nullable TabLayout mTabLayout;
+    private @Nullable TextView mTitleView;
     private ListMenuButton mMenuView;
 
+    private @Nullable SectionHeaderTabListener mTabListener;
     private boolean mAnimatePaddingWhenDisabled;
 
     public SectionHeaderView(Context context, @Nullable AttributeSet attrs) {
@@ -64,6 +92,13 @@
 
         mTitleView = findViewById(R.id.header_title);
         mMenuView = findViewById(R.id.header_menu);
+        mVisibilityIndicator = findViewById(R.id.visibility_indicator);
+        mTabLayout = findViewById(R.id.tab_list_view);
+
+        if (mTabLayout != null) {
+            mTabListener = new SectionHeaderTabListener();
+            mTabLayout.addOnTabSelectedListener(mTabListener);
+        }
 
         int touchPadding;
         // If we are animating padding, add additional touch area around the menu.
@@ -87,20 +122,62 @@
     }
 
     /** Updates header text for this view. */
-    public void setHeaderText(String text) {
-        mTitleView.setText(text);
+    void setHeaderText(String text) {
+        if (mTitleView != null) {
+            mTitleView.setText(text);
+        }
+    }
+
+    /** Adds a blank tab. */
+    void addTab() {
+        if (mTabLayout != null) {
+            mTabLayout.addTab(mTabLayout.newTab());
+        }
+    }
+
+    /**
+     * Set text for the header tab at a particular index to text.
+     *
+     * Does nothing if index is invalid. Make sure to call addTab() beforehand.
+     *
+     * @param text Text to set the tab to.
+     * @param index Index of the tab to set.
+     */
+    void setHeaderTextAt(String text, int index) {
+        if (mTabLayout != null && mTabLayout.getTabCount() > index && index >= 0) {
+            mTabLayout.getTabAt(index).setText(text);
+        }
+    }
+
+    /**
+     * @param index The index of the tab to set as active. Does nothing if index is invalid.
+     */
+    void setActiveTab(int index) {
+        if (mTabLayout != null && mTabLayout.getTabCount() > index && index >= 0) {
+            mTabLayout.selectTab(mTabLayout.getTabAt(index));
+        }
+    }
+
+    /** Sets the listener for tab changes. */
+    void setTabChangeListener(OnSectionHeaderSelectedListener listener) {
+        if (mTabListener != null) {
+            mTabListener.mListener = listener;
+        }
     }
 
     /** Sets the delegate for the gear/settings icon. */
-    public void setMenuDelegate(ModelList listItems, ListMenu.Delegate listMenuDelegate) {
+    void setMenuDelegate(ModelList listItems, ListMenu.Delegate listMenuDelegate) {
         mMenuView.setOnClickListener((v) -> { displayMenu(listItems, listMenuDelegate); });
     }
 
     /** Expand the header to indicate the section has been enabled. */
-    public void expandHeader() {
+    void expandHeader() {
         if (mAnimatePaddingWhenDisabled) {
             int finalHorizontalPadding = 0;
             setBackgroundResource(0);
+            if (mVisibilityIndicator != null) {
+                mVisibilityIndicator.setVisibility(View.INVISIBLE);
+            }
             ValueAnimator animator = ValueAnimator.ofInt(getPaddingLeft(), finalHorizontalPadding);
             animator.addUpdateListener((ValueAnimator animation) -> {
                 int horizontalPadding = (Integer) animation.getAnimatedValue();
@@ -115,7 +192,7 @@
     }
 
     /** Collapse the header to indicate the section has been disabled. */
-    public void collapseHeader() {
+    void collapseHeader() {
         if (mAnimatePaddingWhenDisabled) {
             int finalHorizontalPadding = getResources().getDimensionPixelSize(
                     R.dimen.feed_v2_header_menu_disabled_padding);
@@ -130,6 +207,9 @@
                 public void onAnimationEnd(Animator animation) {
                     // Add the hairline after animation.
                     setBackgroundResource(R.drawable.hairline_border_card_background);
+                    if (mVisibilityIndicator != null) {
+                        mVisibilityIndicator.setVisibility(View.VISIBLE);
+                    }
                 }
             });
             animator.setDuration(ANIMATION_DURATION_MS);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java
index be8b522a..07832651 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SectionHeaderViewBinder.java
@@ -4,54 +4,81 @@
 
 package org.chromium.chrome.browser.ntp.snippets;
 
+import org.chromium.ui.modelutil.ListModelChangeProcessor;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyListModel;
+import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
 /**
- * View binder for {@link SectionHeaderList}, {@link SectionHeader} and {@link SectionHeaderView}.
+ * View binder for {@link SectionHeaderListProperties}, {@link SectionHeaderProperties} and {@link
+ * SectionHeaderView}.
  */
 public class SectionHeaderViewBinder
         implements PropertyModelChangeProcessor
-                           .ViewBinder<SectionHeaderList, SectionHeaderView, PropertyKey> {
+                           .ViewBinder<PropertyModel, SectionHeaderView, PropertyKey>,
+                   ListModelChangeProcessor
+                           .ViewBinder<PropertyListModel<PropertyModel, PropertyKey>,
+                                   SectionHeaderView, PropertyKey> {
     @Override
-    public void bind(SectionHeaderList model, SectionHeaderView view, PropertyKey key) {
-        if (key == SectionHeaderList.IS_SECTION_ENABLED_KEY) {
-            boolean isExpanding = model.get(SectionHeaderList.IS_SECTION_ENABLED_KEY);
+    public void bind(PropertyModel model, SectionHeaderView view, PropertyKey key) {
+        if (key == SectionHeaderListProperties.IS_SECTION_ENABLED_KEY) {
+            boolean isExpanding = model.get(SectionHeaderListProperties.IS_SECTION_ENABLED_KEY);
             if (isExpanding) {
                 view.expandHeader();
+                setActiveTab(model, view);
             } else {
                 view.collapseHeader();
             }
+        } else if (key == SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY) {
+            setActiveTab(model, view);
+        } else if (key == SectionHeaderListProperties.ON_TAB_SELECTED_CALLBACK_KEY) {
+            view.setTabChangeListener(
+                    model.get(SectionHeaderListProperties.ON_TAB_SELECTED_CALLBACK_KEY));
+        } else if (key == SectionHeaderListProperties.MENU_DELEGATE_KEY
+                || key == SectionHeaderListProperties.MENU_MODEL_LIST_KEY) {
+            view.setMenuDelegate(model.get(SectionHeaderListProperties.MENU_MODEL_LIST_KEY),
+                    model.get(SectionHeaderListProperties.MENU_DELEGATE_KEY));
         }
     }
 
-    void onItemsInserted(SectionHeaderList model, SectionHeaderView view, int index, int count) {
-        PropertyListModel<SectionHeader, PropertyKey> headers = model.getHeaders();
-
-        // TODO(chili): Make this support more than 1 header.
-        SectionHeader header = headers.get(0);
-        view.setHeaderText(header.get(SectionHeader.HEADER_TEXT_KEY));
-        view.setMenuDelegate(header.get(SectionHeader.MENU_MODEL_LIST_KEY),
-                header.get(SectionHeader.MENU_DELEGATE_KEY));
+    private void setActiveTab(PropertyModel model, SectionHeaderView view) {
+        int index = model.get(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY);
+        // TODO(chili): Figure out whether check needed in scroll state restore.
+        if (index <= model.get(SectionHeaderListProperties.SECTION_HEADERS_KEY).size()) {
+            view.setActiveTab(model.get(SectionHeaderListProperties.CURRENT_TAB_INDEX_KEY));
+        }
     }
 
-    void onItemsRemoved(SectionHeaderList model, SectionHeaderView view, int index, int count) {
-        // TODO(chili): Implement.
+    @Override
+    public void onItemsInserted(PropertyListModel<PropertyModel, PropertyKey> headers,
+            SectionHeaderView view, int index, int count) {
+        for (int i = index; i < count + index; i++) {
+            view.addTab();
+        }
+        onItemsChanged(headers, view, index, count, null);
     }
 
-    void onItemsChanged(SectionHeaderList model, SectionHeaderView view, int index, int count,
-            PropertyKey payload) {
-        PropertyListModel<SectionHeader, PropertyKey> headers = model.getHeaders();
+    @Override
+    public void onItemsRemoved(PropertyListModel<PropertyModel, PropertyKey> model,
+            SectionHeaderView view, int index, int count) {
+        // Do nothing. We don't expect to remove tabs.
+        assert false;
+    }
 
-        // TODO(chili): Make this support more than 1 header.
-        SectionHeader header = headers.get(0);
-        if (payload == SectionHeader.HEADER_TEXT_KEY) {
-            view.setHeaderText(header.get(SectionHeader.HEADER_TEXT_KEY));
-        } else if (payload == SectionHeader.MENU_MODEL_LIST_KEY
-                || payload == SectionHeader.MENU_DELEGATE_KEY) {
-            view.setMenuDelegate(header.get(SectionHeader.MENU_MODEL_LIST_KEY),
-                    header.get(SectionHeader.MENU_DELEGATE_KEY));
+    @Override
+    public void onItemsChanged(PropertyListModel<PropertyModel, PropertyKey> headers,
+            SectionHeaderView view, int index, int count, PropertyKey payload) {
+        if (payload == null || payload == SectionHeaderProperties.HEADER_TEXT_KEY) {
+            PropertyModel header = headers.get(0);
+            // Only use 1st tab for legacy headerText;
+            view.setHeaderText(header.get(SectionHeaderProperties.HEADER_TEXT_KEY));
+
+            // Update header text properly.
+            for (int i = index; i < index + count; i++) {
+                view.setHeaderTextAt(
+                        headers.get(i).get(SectionHeaderProperties.HEADER_TEXT_KEY), i);
+            }
         }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
index 32dc8e0..55128c4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
@@ -99,8 +99,8 @@
      * We are using an internal interface, so that instance methods can have the same names as
      * static methods.
      */
-    @VisibleForTesting
-    interface Internal {
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public interface Internal {
         /** Returns offline page bridge for specified profile. */
         OfflinePageBridge getOfflinePageBridge(Profile profile);
 
@@ -950,8 +950,8 @@
                 "OfflinePages.TabRestore", tabRestoreType, TabRestoreType.NUM_ENTRIES);
     }
 
-    @VisibleForTesting
-    static void setInstanceForTesting(Internal instance) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public static void setInstanceForTesting(Internal instance) {
         sInstance = instance;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
index adb6c7b..491b90c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusMediator.java
@@ -497,20 +497,15 @@
     /** @return True if the security icon has been set for the search engine icon. */
     @VisibleForTesting
     boolean maybeUpdateStatusIconForSearchEngineIcon() {
-        boolean showIconWhenFocused = mUrlHasFocus && mShowStatusIconWhenUrlFocused;
-        boolean showIconWhenScrollingOnNTP =
-                mSearchEngineLogoUtils.currentlyOnNTP(mLocationBarDataProvider)
-                && mUrlFocusPercent > 0 && !mUrlHasFocus && !mLocationBarDataProvider.isLoading()
-                && mShowStatusIconWhenUrlFocused;
-
         // Show the logo unfocused if we're on the NTP.
-        if (mSearchEngineLogoUtils.shouldShowSearchEngineLogo(
-                    mLocationBarDataProvider.isIncognito())
-                && mIsSearchEngineStateSetup
-                && (showIconWhenFocused || showIconWhenScrollingOnNTP)) {
+        if (shouldUpdateStatusIconForSearchEngineIcon()) {
             getStatusIconResourceForSearchEngineIcon(
                     mLocationBarDataProvider.isIncognito(), (statusIconRes) -> {
-                        mModel.set(StatusProperties.STATUS_ICON_RESOURCE, statusIconRes);
+                        // Check again in case the conditions have changed since this callback was
+                        // created.
+                        if (shouldUpdateStatusIconForSearchEngineIcon()) {
+                            mModel.set(StatusProperties.STATUS_ICON_RESOURCE, statusIconRes);
+                        }
                     });
             return true;
         } else {
@@ -519,6 +514,18 @@
         }
     }
 
+    private boolean shouldUpdateStatusIconForSearchEngineIcon() {
+        boolean showIconWhenFocused = mUrlHasFocus && mShowStatusIconWhenUrlFocused;
+        boolean showIconWhenScrollingOnNTP =
+                mSearchEngineLogoUtils.currentlyOnNTP(mLocationBarDataProvider)
+                && mUrlFocusPercent > 0 && !mUrlHasFocus && !mLocationBarDataProvider.isLoading()
+                && mShowStatusIconWhenUrlFocused;
+
+        return mSearchEngineLogoUtils.shouldShowSearchEngineLogo(
+                       mLocationBarDataProvider.isIncognito())
+                && mIsSearchEngineStateSetup && (showIconWhenFocused || showIconWhenScrollingOnNTP);
+    }
+
     /**
      * Set the security icon resource for the search engine icon and invoke the callback to inform
      * the caller which resource has been set.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
index 8dd5346d..df127e39 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
@@ -647,15 +647,14 @@
 
     @Override
     public void onAccountsCookieDeletedByUserAction() {
-        // No need to sign out if the user is already signed out.
-        if (mIdentityManager.getPrimaryAccountInfo(ConsentLevel.NOT_REQUIRED) == null) return;
-
-        // If the user consented for sync, then the user should not be signed out.
-        // Account cookies will be rebuilt by the account reconcilor.
-        if (mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SYNC) != null) return;
-
-        // Clearing account cookies should also sign the user out if the user was not syncing.
-        signOut(SignoutReason.USER_DELETED_ACCOUNT_COOKIES);
+        if (mIdentityManager.getPrimaryAccountInfo(ConsentLevel.NOT_REQUIRED) != null
+                && mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SYNC) == null) {
+            // Clearing account cookies should trigger sign-out only when user is signed in
+            // without sync.
+            // If the user consented for sync, then the user should not be signed out,
+            // since account cookies will be rebuilt by the account reconcilor.
+            signOut(SignoutReason.USER_DELETED_ACCOUNT_COOKIES);
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
index c5a63a35..bb116c7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
@@ -10,6 +10,7 @@
   "+chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/TrackerFactory.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoUtils.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationServiceImpl.java",
+  "+chrome/android/java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/paint_preview/PaintPreviewTabHelper.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/policy/PolicyAuditor.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/tab",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java
new file mode 100644
index 0000000..169ce11
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java
@@ -0,0 +1,66 @@
+// 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.
+package org.chromium.chrome.browser.tab;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.components.ukm.UkmRecorder;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utilities for requesting desktop sites support.
+ */
+public class RequestDesktopUtils {
+    // Note: these values must match the UserAgentRequestType enum in enums.xml.
+    @IntDef({UserAgentRequestType.REQUEST_DESKTOP, UserAgentRequestType.REQUEST_MOBILE})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface UserAgentRequestType {
+        int REQUEST_DESKTOP = 0;
+        int REQUEST_MOBILE = 1;
+    }
+
+    // Note: these values must match the DeviceOrientation2 enum in enums.xml.
+    @IntDef({DeviceOrientation2.LANDSCAPE, DeviceOrientation2.PORTRAIT})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface DeviceOrientation2 {
+        int LANDSCAPE = 0;
+        int PORTRAIT = 1;
+    }
+
+    /**
+     * Records the metrics associated with changing the user agent by user agent.
+     * @param isDesktop True if the user agent is the desktop.
+     * @param tab The current activity {@link Tab}.
+     */
+    public static void recordUserChangeUserAgent(boolean isDesktop, @Nullable Tab tab) {
+        RecordUserAction.record("MobileMenuRequestDesktopSite");
+        RecordHistogram.recordBooleanHistogram(
+                "Android.RequestDesktopSite.UserSwitchToDesktop", isDesktop);
+
+        if (tab == null || tab.isIncognito() || tab.getWebContents() == null) return;
+
+        new UkmRecorder.Bridge().recordEventWithIntegerMetric(tab.getWebContents(),
+                "Android.UserRequestedUserAgentChange", "UserAgentType",
+                isDesktop ? UserAgentRequestType.REQUEST_DESKTOP
+                          : UserAgentRequestType.REQUEST_MOBILE);
+    }
+
+    /**
+     * Records the ukms associated with changing screen orientation.
+     * @param isLandscape True if the orientation is landscape.
+     * @param tab The current activity {@link Tab}.
+     */
+    public static void recordScreenOrientationChangedUkm(boolean isLandscape, @Nullable Tab tab) {
+        if (tab == null || tab.isIncognito() || tab.getWebContents() == null) return;
+
+        new UkmRecorder.Bridge().recordEventWithIntegerMetric(tab.getWebContents(),
+                "Android.ScreenRotation", "TargetDeviceOrientation",
+                isLandscape ? DeviceOrientation2.LANDSCAPE : DeviceOrientation2.PORTRAIT);
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
index 01f87fd..6a403b8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
@@ -22,12 +22,14 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
+import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
+import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.favicon.LargeIconBridge;
 
 /**
@@ -53,10 +55,16 @@
     }
 
     private boolean shouldShowWebFeedMenuItem() {
-        // TODO(crbug/1152592): Restrict when to show the footer based on tab (eg. not on chrome://
-        // etc.).
-        return FeedFeatures.isWebFeedUIEnabled() && mActivityTabProvider.get() != null
-                && !mActivityTabProvider.get().isIncognito();
+        if (!FeedFeatures.isWebFeedUIEnabled()) {
+            return false;
+        }
+        Tab tab = mActivityTabProvider.get();
+        if (tab == null || tab.isIncognito() || OfflinePageUtils.isOfflinePage(tab)) {
+            return false;
+        }
+        String url = tab.getOriginalUrl().getSpec();
+        return url.startsWith(UrlConstants.HTTP_URL_PREFIX)
+                || url.startsWith(UrlConstants.HTTPS_URL_PREFIX);
     }
 
     @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
index 0b4c79bb..8e94580f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/metrics/TabbedActivityLaunchCauseMetricsTest.java
@@ -45,7 +45,6 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.JniMocker;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.LauncherShortcutActivity;
@@ -180,7 +179,7 @@
             Criteria.checkThat(
                     histogramCountForValue(LaunchCauseMetrics.LaunchCause.HOME_SCREEN_WIDGET),
                     Matchers.is(count));
-        }, ScalableTimeout.scaleTimeout(5000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }, 5000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
     }
 
     private static class TestContext extends ContextWrapper {
@@ -230,7 +229,7 @@
                     histogramCountForValue(
                             LaunchCauseMetrics.LaunchCause.EXTERNAL_SEARCH_ACTION_INTENT),
                     Matchers.is(count));
-        }, ScalableTimeout.scaleTimeout(5000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }, 5000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
 
         ApplicationTestUtils.finishActivity(cta);
         ApplicationTestUtils.finishActivity(searchActivity);
@@ -251,6 +250,6 @@
         CriteriaHelper.pollInstrumentationThread(() -> {
             Criteria.checkThat(histogramCountForValue(LaunchCauseMetrics.LaunchCause.NOTIFICATION),
                     Matchers.is(count));
-        }, ScalableTimeout.scaleTimeout(5000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }, 5000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index 604fbbf..18f00a17 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -2478,6 +2478,7 @@
     @Test
     @SmallTest
     @Feature({"ContextualSearch"})
+    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.Q, message = "crbug.com/1037667")
     @ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
     public void testExternalNavigationWithUserGesture(@EnabledFeature int enabledFeature) {
         final ExternalNavigationDelegateImpl delegate =
@@ -2511,6 +2512,7 @@
     @Test
     @SmallTest
     @Feature({"ContextualSearch"})
+    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.Q, message = "crbug.com/1037667")
     @ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
     public void testRedirectedExternalNavigationWithUserGesture(
             @EnabledFeature int enabledFeature) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index eb2d320..98b9235 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -83,7 +83,6 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeApplication;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -801,7 +800,7 @@
                                        LaunchCauseMetrics.LAUNCH_CAUSE_HISTOGRAM,
                                        LaunchCauseMetrics.LaunchCause.OPEN_IN_BROWSER_FROM_MENU),
                     Matchers.is(1));
-        }, ScalableTimeout.scaleTimeout(5000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }, 5000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         activity.finish();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index c61c5bbe..6c9c897 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -37,7 +37,6 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -447,7 +446,7 @@
 
         CriteriaHelper.pollUiThread(() -> {
             Criteria.checkThat(mActivityMonitor.getHits(), Matchers.is(1));
-        }, ScalableTimeout.scaleTimeout(10000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }, 10000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         ApplicationTestUtils.waitForActivityState(activity, Stage.STOPPED);
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
index a89d641..bf0fd21 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
@@ -4,11 +4,16 @@
 
 package org.chromium.chrome.browser.omnibox;
 
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
 import android.annotation.SuppressLint;
 import android.support.test.InstrumentationRegistry;
 import android.view.KeyEvent;
 import android.widget.ImageButton;
 
+import androidx.test.espresso.Espresso;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 
@@ -24,6 +29,7 @@
 import org.chromium.base.test.params.SkipCommandLineParameterization;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.EnormousTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.R;
@@ -31,6 +37,7 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.omnibox.status.StatusCoordinator;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController.OnSuggestionsReceivedListener;
+import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabObserver;
@@ -43,11 +50,15 @@
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.omnibox.AutocompleteResult;
+import org.chromium.components.search_engines.TemplateUrl;
+import org.chromium.components.search_engines.TemplateUrlService;
 import org.chromium.content_public.browser.test.util.KeyUtils;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.ServerCertificate;
 
+import java.util.List;
+
 /**
  * Tests of the Omnibox.
  *
@@ -250,6 +261,101 @@
     }
 
     /**
+     * Test to verify that the security icon is present after
+     * <ol>
+     *   <li>visiting a https:// URL
+     *   <li>focusing the url bar
+     *   <li>pressing back
+     * </ol>
+     * All while the search engine is not the default one. See https://crbug.com/1173447
+     */
+    @Test
+    @MediumTest
+    @SkipCommandLineParameterization
+    public void testSecurityIconOnHTTPSFocusAndBack() throws Exception {
+        setNonDefaultSearchEngine();
+
+        EmbeddedTestServer httpsTestServer = EmbeddedTestServer.createAndStartHTTPSServer(
+                InstrumentationRegistry.getContext(), ServerCertificate.CERT_OK);
+        CallbackHelper onSSLStateUpdatedCallbackHelper = new CallbackHelper();
+        TabObserver observer = new EmptyTabObserver() {
+            @Override
+            public void onSSLStateUpdated(Tab tab) {
+                onSSLStateUpdatedCallbackHelper.notifyCalled();
+            }
+        };
+        mActivityTestRule.getActivity().getActivityTab().addObserver(observer);
+
+        try {
+            final String testHttpsUrl =
+                    httpsTestServer.getURL("/chrome/test/data/android/omnibox/one.html");
+
+            ImageButton securityButton = (ImageButton) mActivityTestRule.getActivity().findViewById(
+                    R.id.location_bar_status_icon);
+
+            mActivityTestRule.loadUrl(testHttpsUrl);
+            onSSLStateUpdatedCallbackHelper.waitForCallback(0);
+            final LocationBarLayout locationBar =
+                    (LocationBarLayout) mActivityTestRule.getActivity().findViewById(
+                            R.id.location_bar);
+            final StatusCoordinator statusCoordinator =
+                    locationBar.getStatusCoordinatorForTesting();
+            final int firstIcon = statusCoordinator.getSecurityIconResourceIdForTesting();
+
+            onView(withId(R.id.url_bar)).perform(click());
+            CriteriaHelper.pollUiThread(
+                    () -> statusCoordinator.getSecurityIconResourceIdForTesting() != firstIcon);
+            final int secondIcon = statusCoordinator.getSecurityIconResourceIdForTesting();
+            Espresso.pressBack();
+            CriteriaHelper.pollUiThread(
+                    () -> statusCoordinator.getSecurityIconResourceIdForTesting() != secondIcon);
+
+            boolean securityIcon = statusCoordinator.isSecurityButtonShown();
+            Assert.assertTrue("Omnibox should have a Security icon", securityIcon);
+            Assert.assertEquals("location_bar_status_icon with wrong resource-id",
+                    R.id.location_bar_status_icon, securityButton.getId());
+            Assert.assertTrue(securityButton.isShown());
+            Assert.assertEquals(R.drawable.omnibox_https_valid,
+                    statusCoordinator.getSecurityIconResourceIdForTesting());
+        } finally {
+            httpsTestServer.stopAndDestroyServer();
+            restoreDefaultSearchEngine();
+        }
+    }
+
+    private void setNonDefaultSearchEngine() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> TemplateUrlServiceFactory.get().load());
+        CriteriaHelper.pollUiThread(() -> TemplateUrlServiceFactory.get().isLoaded());
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TemplateUrlService service = TemplateUrlServiceFactory.get();
+
+            List<TemplateUrl> searchEngines = service.getTemplateUrls();
+            TemplateUrl defaultEngine = service.getDefaultSearchEngineTemplateUrl();
+
+            TemplateUrl notDefault = null;
+            for (TemplateUrl searchEngine : searchEngines) {
+                if (!searchEngine.equals(defaultEngine)) {
+                    notDefault = searchEngine;
+                    break;
+                }
+            }
+
+            Assert.assertNotNull(notDefault);
+
+            service.setSearchEngine(notDefault.getKeyword());
+        });
+    }
+
+    private void restoreDefaultSearchEngine() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TemplateUrlService service = TemplateUrlServiceFactory.get();
+            TemplateUrl defaultEngine = service.getDefaultSearchEngineTemplateUrl();
+            service.setSearchEngine(defaultEngine.getKeyword());
+        });
+    }
+
+    /**
      * Test whether the color of the Location bar is correct for HTTPS scheme.
      */
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
index 20b13c3..a15a2bc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
@@ -575,6 +575,7 @@
      */
     @Test
     @MediumTest
+    @FlakyTest(message = "crbug.com/1184155")
     public void testInOrderRestore() throws TimeoutException {
         TabModel model = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
         ChromeTabCreator tabCreator = TestThreadUtils.runOnUiThreadBlockingNoException(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTestRule.java
index 505f667e..d58a6a2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTestRule.java
@@ -13,7 +13,6 @@
 
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.browser.ShortcutHelper;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.test.ChromeActivityTestRule;
@@ -23,7 +22,7 @@
 /** Custom {@link ChromeActivityTestRule} for tests using a WebAPK {@link WebappActivity}. */
 public class WebApkActivityTestRule extends ChromeActivityTestRule<WebappActivity> {
     /** Time in milliseconds to wait for page to be loaded. */
-    private static final long STARTUP_TIMEOUT = ScalableTimeout.scaleTimeout(10000);
+    private static final long STARTUP_TIMEOUT = 10000;
 
     public WebApkActivityTestRule() {
         super(WebappActivity.class);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/test/smoke/AndroidManifest_bundle.xml b/chrome/android/javatests/src/org/chromium/chrome/test/smoke/AndroidManifest_bundle.xml
index 1da48e3..8812f6e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/test/smoke/AndroidManifest_bundle.xml
+++ b/chrome/android/javatests/src/org/chromium/chrome/test/smoke/AndroidManifest_bundle.xml
@@ -24,4 +24,12 @@
     <instrumentation android:name="org.chromium.base.test.BaseChromiumAndroidJUnitRunner"
         android:targetPackage="org.chromium.chrome.test.smoke.bundle"
         android:label="Runner for org.chromium.chrome.test.smoke.bundle"/>
+
+    <!-- In Android-11 or newer, apks need to have the <queries> element when
+         querying and interfacting with other packages. See
+         https://developer.android.com/training/basics/intents/package-visibility#package-name
+         for more details. -->
+    <queries>
+      <package android:name="org.chromium.chrome" />
+    </queries>
 </manifest>
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptorTest.java
index b65c7160..44038c53c 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptorTest.java
@@ -6,6 +6,8 @@
 
 import static org.robolectric.Shadows.shadowOf;
 
+import static org.chromium.chrome.browser.notifications.NotificationIntentInterceptor.INTENT_ACTION;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -85,6 +87,8 @@
         ContextUtils.initApplicationContextForTests(mContext);
         mShadowNotificationManager = shadowOf(
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE));
+        mContext.registerReceiver(
+                new NotificationIntentInterceptor.Receiver(), new IntentFilter(INTENT_ACTION));
         mReceiver = new TestReceiver();
         mContext.registerReceiver(mReceiver, new IntentFilter(TestReceiver.TEST_ACTION));
     }
@@ -177,7 +181,7 @@
         Notification notification = mShadowNotificationManager.getAllNotifications().get(0);
         Assert.assertEquals(TEST_NOTIFICATION_TITLE,
                 notification.extras.getCharSequence(Notification.EXTRA_TITLE).toString());
-        sendPendingIntent(notification.deleteIntent);
+        notification.deleteIntent.send();
 
         // Verify the histogram.
         Assert.assertEquals(1,
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerImplTest.java
similarity index 67%
rename from chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerTest.java
rename to chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerImplTest.java
index 4ef2f58..45af7d1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerImplTest.java
@@ -12,12 +12,10 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import org.junit.After;
 import org.junit.Before;
@@ -25,7 +23,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.stubbing.Answer;
-import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.JniMocker;
@@ -41,6 +38,7 @@
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.identitymanager.ConsentLevel;
 import org.chromium.components.signin.identitymanager.IdentityManager;
+import org.chromium.components.signin.identitymanager.IdentityManagerJni;
 import org.chromium.components.signin.identitymanager.IdentityMutator;
 import org.chromium.components.signin.identitymanager.PrimaryAccountChangeEvent;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
@@ -51,71 +49,68 @@
 
 /** Tests for {@link SigninManagerImpl}. */
 @RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
 @Features.DisableFeatures(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)
-public class SigninManagerTest {
-    @Rule
-    public final JniMocker mocker = new JniMocker();
-    @Rule
-    public final Features.JUnitProcessor processor = new Features.JUnitProcessor();
-
+public class SigninManagerImplTest {
+    private static final long NATIVE_SIGNIN_MANAGER = 10001L;
+    private static final long NATIVE_IDENTITY_MANAGER = 10002L;
     private static final AccountInfo ACCOUNT_INFO =
             new AccountInfo(new CoreAccountId("gaia-id-user"), "user@domain.com", "gaia-id-user",
                     "full name", "given name", null);
 
+    @Rule
+    public final JniMocker mocker = new JniMocker();
+
+    @Rule
+    public final Features.JUnitProcessor processor = new Features.JUnitProcessor();
+
     private final SigninManagerImpl.Natives mNativeMock = mock(SigninManagerImpl.Natives.class);
+    private final IdentityManager.Natives mIdentityManagerNativeMock =
+            mock(IdentityManager.Natives.class);
     private final AccountTrackerService mAccountTrackerService = mock(AccountTrackerService.class);
     private final IdentityMutator mIdentityMutator = mock(IdentityMutator.class);
     private final AndroidSyncSettings mAndroidSyncSettings = mock(AndroidSyncSettings.class);
     private final ExternalAuthUtils mExternalAuthUtils = mock(ExternalAuthUtils.class);
     private final ProfileSyncService mProfileSyncService = mock(ProfileSyncService.class);
-    private IdentityManager mIdentityManager;
 
+    private final IdentityManager mIdentityManager =
+            new IdentityManager(NATIVE_IDENTITY_MANAGER, null /* OAuth2TokenService */);
     private SigninManagerImpl mSigninManager;
 
     @Before
     public void setUp() {
         mocker.mock(SigninManagerImplJni.TEST_HOOKS, mNativeMock);
+        mocker.mock(IdentityManagerJni.TEST_HOOKS, mIdentityManagerNativeMock);
         ProfileSyncService.overrideForTests(mProfileSyncService);
-        doReturn(true).when(mNativeMock).isSigninAllowedByPolicy(anyLong());
+        when(mNativeMock.isSigninAllowedByPolicy(NATIVE_SIGNIN_MANAGER)).thenReturn(true);
         // Pretend Google Play services are available as it is required for the sign-in
-        doReturn(false).when(mExternalAuthUtils).isGooglePlayServicesMissing(any());
+        when(mExternalAuthUtils.isGooglePlayServicesMissing(any())).thenReturn(false);
+        // Suppose that the accounts are already seeded
+        when(mAccountTrackerService.checkAndSeedSystemAccounts()).thenReturn(true);
+        when(mIdentityManagerNativeMock
+                        .findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
+                                NATIVE_IDENTITY_MANAGER, ACCOUNT_INFO.getEmail()))
+                .thenReturn(ACCOUNT_INFO);
 
-        mIdentityManager = spy(
-                new IdentityManager(0 /* nativeIdentityManager */, null /* OAuth2TokenService */));
-        doReturn(null).when(mIdentityManager).getPrimaryAccountInfo(anyInt());
+        mSigninManager = new SigninManagerImpl(NATIVE_SIGNIN_MANAGER, mAccountTrackerService,
+                mIdentityManager, mIdentityMutator, mAndroidSyncSettings, mExternalAuthUtils);
     }
 
     @After
     public void tearDown() {
-        if (mSigninManager != null) {
-            mSigninManager.destroy();
-            mSigninManager = null;
-        }
-    }
-
-    private void createSigninManager() {
-        mSigninManager = new SigninManagerImpl(0 /* nativeSigninManagerAndroid */,
-                mAccountTrackerService, mIdentityManager, mIdentityMutator, mAndroidSyncSettings,
-                mExternalAuthUtils);
+        mSigninManager.destroy();
     }
 
     @Test
     public void signinAndTurnSyncOn() {
-        doReturn(true).when(mAccountTrackerService).checkAndSeedSystemAccounts();
-        doReturn(ACCOUNT_INFO)
-                .when(mIdentityManager)
-                .findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
-                        eq(ACCOUNT_INFO.getEmail()));
-        doReturn(true).when(mIdentityMutator).setPrimaryAccount(any(), anyInt());
+        when(mIdentityMutator.setPrimaryAccount(any(), anyInt())).thenReturn(true);
 
-        createSigninManager();
         mSigninManager.onFirstRunCheckDone();
 
         SigninManager.SignInCallback callback = mock(SigninManager.SignInCallback.class);
         mSigninManager.signinAndEnableSync(SigninAccessPoint.START_PAGE, ACCOUNT_INFO, callback);
 
-        verify(mNativeMock).fetchAndApplyCloudPolicy(anyLong(), eq(ACCOUNT_INFO), any());
+        verify(mNativeMock)
+                .fetchAndApplyCloudPolicy(eq(NATIVE_SIGNIN_MANAGER), eq(ACCOUNT_INFO), any());
 
         mSigninManager.finishSignInAfterPolicyEnforced();
         verify(mIdentityMutator).setPrimaryAccount(ACCOUNT_INFO.getId(), ConsentLevel.SYNC);
@@ -129,14 +124,8 @@
 
     @Test
     public void signinNoTurnSyncOn() {
-        doReturn(true).when(mAccountTrackerService).checkAndSeedSystemAccounts();
-        doReturn(ACCOUNT_INFO)
-                .when(mIdentityManager)
-                .findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
-                        eq(ACCOUNT_INFO.getEmail()));
-        doReturn(true).when(mIdentityMutator).setPrimaryAccount(any(), anyInt());
+        when(mIdentityMutator.setPrimaryAccount(any(), anyInt())).thenReturn(true);
 
-        createSigninManager();
         mSigninManager.onFirstRunCheckDone();
 
         SigninManager.SignInCallback callback = mock(SigninManager.SignInCallback.class);
@@ -156,10 +145,8 @@
 
     @Test
     public void signOutNonSyncingAccountFromJavaWithManagedDomain() {
-        // See verification of nativeWipeProfileData below.
-        doReturn("TestDomain").when(mNativeMock).getManagementDomain(anyLong());
+        when(mNativeMock.getManagementDomain(NATIVE_SIGNIN_MANAGER)).thenReturn("TestDomain");
 
-        createSigninManager();
         // Trigger the sign out flow!
         mSigninManager.signOut(SignoutReason.SIGNOUT_TEST);
 
@@ -173,16 +160,14 @@
                 PrimaryAccountChangeEvent.Type.CLEARED, PrimaryAccountChangeEvent.Type.NONE));
 
         // Sign-out should only clear the profile when the user is managed.
-        verify(mNativeMock, times(1)).wipeProfileData(anyLong(), any());
+        verify(mNativeMock).wipeProfileData(eq(NATIVE_SIGNIN_MANAGER), any());
         verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
     }
 
     @Test
     public void signOutSyncingAccountFromJavaWithManagedDomain() {
-        // See verification of nativeWipeProfileData below.
-        doReturn("TestDomain").when(mNativeMock).getManagementDomain(anyLong());
+        when(mNativeMock.getManagementDomain(NATIVE_SIGNIN_MANAGER)).thenReturn("TestDomain");
 
-        createSigninManager();
         // Trigger the sign out flow!
         mSigninManager.signOut(SignoutReason.SIGNOUT_TEST);
 
@@ -196,16 +181,12 @@
                 PrimaryAccountChangeEvent.Type.CLEARED, PrimaryAccountChangeEvent.Type.CLEARED));
 
         // Sign-out should only clear the profile when the user is managed.
-        verify(mNativeMock, times(1)).wipeProfileData(anyLong(), any());
+        verify(mNativeMock).wipeProfileData(eq(NATIVE_SIGNIN_MANAGER), any());
         verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
     }
 
     @Test
     public void signOutNonSyncingAccountFromJavaWithNullDomain() {
-        // Simulate sign-out with non-managed account.
-        doReturn(null).when(mNativeMock).getManagementDomain(anyLong());
-
-        createSigninManager();
         mSigninManager.signOut(SignoutReason.SIGNOUT_TEST);
 
         // PrimaryAccountChanged should be called *before* clearing any account data.
@@ -220,16 +201,16 @@
         // Sign-out should only clear the service worker cache when the user is neither managed or
         // syncing.
         verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
-        verify(mNativeMock, times(1)).wipeGoogleServiceWorkerCaches(anyLong(), any());
+        verify(mNativeMock).wipeGoogleServiceWorkerCaches(eq(NATIVE_SIGNIN_MANAGER), any());
     }
 
     @Test
     public void signOutSyncingAccountFromJavaWithNullDomain() {
         // Simulate sign-out with non-managed account.
-        doReturn(null).when(mNativeMock).getManagementDomain(anyLong());
-        doReturn(true).when(mIdentityManager).hasPrimaryAccount();
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     eq(NATIVE_IDENTITY_MANAGER), anyInt()))
+                .thenReturn(ACCOUNT_INFO);
 
-        createSigninManager();
         mSigninManager.signOut(SignoutReason.SIGNOUT_TEST);
 
         // PrimaryAccountCleared should be called *before* clearing any account data.
@@ -244,16 +225,15 @@
         // Sign-out should only clear the service worker cache when the user has decided not to
         // wipe data.
         verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
-        verify(mNativeMock, times(1)).wipeGoogleServiceWorkerCaches(anyLong(), any());
+        verify(mNativeMock).wipeGoogleServiceWorkerCaches(eq(NATIVE_SIGNIN_MANAGER), any());
     }
 
     @Test
     public void signOutSyncingAccountFromJavaWithNullDomainAndForceWipe() {
-        // See verification of nativeWipeGoogleServiceWorkerCaches below.
-        doReturn(null).when(mNativeMock).getManagementDomain(anyLong());
-        doReturn(true).when(mIdentityManager).hasPrimaryAccount();
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     eq(NATIVE_IDENTITY_MANAGER), anyInt()))
+                .thenReturn(ACCOUNT_INFO);
 
-        createSigninManager();
         mSigninManager.signOut(SignoutReason.SIGNOUT_TEST, null, true);
 
         // PrimaryAccountCleared should be called *before* clearing any account data.
@@ -268,97 +248,99 @@
 
         // Sign-out should only clear the profile when the user is syncing and has decided to
         // wipe data.
-        verify(mNativeMock, times(1)).wipeProfileData(anyLong(), any());
+        verify(mNativeMock).wipeProfileData(eq(NATIVE_SIGNIN_MANAGER), any());
         verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
     }
 
     @Test
     public void signOutNonSyncingAccountFromNative() {
-        createSigninManager();
         // Simulate native initiating the sign-out.
         mIdentityManager.onPrimaryAccountChanged(new PrimaryAccountChangeEvent(
                 PrimaryAccountChangeEvent.Type.CLEARED, PrimaryAccountChangeEvent.Type.NONE));
 
         // Sign-out should only clear the service worker cache when the user is not syncing.
         verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
-        verify(mNativeMock, times(1)).wipeGoogleServiceWorkerCaches(anyLong(), any());
+        verify(mNativeMock).wipeGoogleServiceWorkerCaches(eq(NATIVE_SIGNIN_MANAGER), any());
     }
 
     @Test
     public void signOutSyncingAccountFromNativeWithManagedDomain() {
-        doReturn("TestDomain").when(mNativeMock).getManagementDomain(anyLong());
+        when(mNativeMock.getManagementDomain(NATIVE_SIGNIN_MANAGER)).thenReturn("TestDomain");
 
-        createSigninManager();
         // Simulate native initiating the sign-out.
         mIdentityManager.onPrimaryAccountChanged(new PrimaryAccountChangeEvent(
                 PrimaryAccountChangeEvent.Type.CLEARED, PrimaryAccountChangeEvent.Type.CLEARED));
 
         // Turning off sync should only clear the profile data when the account is managed.
-        verify(mNativeMock).wipeProfileData(anyLong(), any());
+        verify(mNativeMock).wipeProfileData(eq(NATIVE_SIGNIN_MANAGER), any());
         verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
     }
 
     @Test
     public void signOutSyncingAccountFromNativeWithNullDomain() {
-        doReturn(null).when(mNativeMock).getManagementDomain(anyLong());
-
-        createSigninManager();
         // Simulate native initiating the sign-out.
         mIdentityManager.onPrimaryAccountChanged(new PrimaryAccountChangeEvent(
                 PrimaryAccountChangeEvent.Type.CLEARED, PrimaryAccountChangeEvent.Type.CLEARED));
 
         // Turning off sync should only clear service worker caches when the account is not managed.
         verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
-        verify(mNativeMock).wipeGoogleServiceWorkerCaches(anyLong(), any());
+        verify(mNativeMock).wipeGoogleServiceWorkerCaches(eq(NATIVE_SIGNIN_MANAGER), any());
     }
 
     @Test
-    public void clearingAccountCookiesTriggersSignout() {
-        // Create SigninManager so it adds an observer for onAccountsCookieDeletedByUserAction.
-        createSigninManager();
-
-        // Clearing cookies shouldn't do anything when there's no primary account.
-        doReturn(null).when(mIdentityManager).getPrimaryAccountInfo(anyInt());
+    public void clearingAccountCookieDoesNotTriggerSignoutWhenUserIsSignedOut() {
         mIdentityManager.onAccountsCookieDeletedByUserAction();
+
         verify(mIdentityMutator, never()).clearPrimaryAccount(anyInt(), anyInt());
+        verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
+        verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
+    }
 
-        // Clearing cookies shouldn't do anything when there's sync account.
-        doReturn(ACCOUNT_INFO).when(mIdentityManager).getPrimaryAccountInfo(anyInt());
+    @Test
+    public void clearingAccountCookieDoesNotTriggerSignoutWhenUserIsSignedInAndSync() {
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     eq(NATIVE_IDENTITY_MANAGER), anyInt()))
+                .thenReturn(ACCOUNT_INFO);
+
         mIdentityManager.onAccountsCookieDeletedByUserAction();
+
         verify(mIdentityMutator, never()).clearPrimaryAccount(anyInt(), anyInt());
+        verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
+        verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
+    }
 
-        // Clearing cookies when there's only an unconsented account should trigger sign-out.
-        doReturn(ACCOUNT_INFO)
-                .when(mIdentityManager)
-                .getPrimaryAccountInfo(ConsentLevel.NOT_REQUIRED);
-        doReturn(null).when(mIdentityManager).getPrimaryAccountInfo(ConsentLevel.SYNC);
+    @Test
+    public void clearingAccountCookieTriggersSignoutWhenUserIsSignedInWithoutSync() {
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     NATIVE_IDENTITY_MANAGER, ConsentLevel.NOT_REQUIRED))
+                .thenReturn(ACCOUNT_INFO);
+
         mIdentityManager.onAccountsCookieDeletedByUserAction();
+
         verify(mIdentityMutator)
                 .clearPrimaryAccount(
                         SignoutReason.USER_DELETED_ACCOUNT_COOKIES, SignoutDelete.IGNORE_METRIC);
-
         // Sign-out triggered by wiping account cookies shouldn't wipe data.
         verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
         verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
     }
 
     @Test
-    public void testRollbackForMobileIdentityConsitency() {
-        doReturn(ACCOUNT_INFO)
-                .when(mIdentityManager)
-                .getPrimaryAccountInfo(ConsentLevel.NOT_REQUIRED);
-        doReturn(null).when(mIdentityManager).getPrimaryAccountInfo(ConsentLevel.SYNC);
-        mIdentityManager.onAccountsCookieDeletedByUserAction();
+    public void rollbackWhenMobileIdentityConsistencyIsDisabled() {
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     NATIVE_IDENTITY_MANAGER, ConsentLevel.NOT_REQUIRED))
+                .thenReturn(ACCOUNT_INFO);
+
+        mSigninManager = new SigninManagerImpl(NATIVE_SIGNIN_MANAGER, mAccountTrackerService,
+                mIdentityManager, mIdentityMutator, mAndroidSyncSettings, mExternalAuthUtils);
 
         // SignedIn state (without sync consent) doesn't exist pre-MobileIdentityConsistency. If the
         // feature is disabled while in this state, SigninManager ctor should trigger sign-out.
-        createSigninManager();
-
         verify(mIdentityMutator)
                 .clearPrimaryAccount(SignoutReason.MOBILE_IDENTITY_CONSISTENCY_ROLLBACK,
                         SignoutDelete.IGNORE_METRIC);
-        verify(mNativeMock).logOutAllAccountsForMobileIdentityConsistencyRollback(anyLong());
-
+        verify(mNativeMock)
+                .logOutAllAccountsForMobileIdentityConsistencyRollback(NATIVE_SIGNIN_MANAGER);
         // This sign-out shouldn't wipe data.
         verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
         verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
@@ -366,14 +348,13 @@
 
     @Test
     @Features.EnableFeatures(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)
-    public void testNoRollbackIfMobileIdentityConsitencyIsEnabled() {
-        doReturn(ACCOUNT_INFO)
-                .when(mIdentityManager)
-                .getPrimaryAccountInfo(ConsentLevel.NOT_REQUIRED);
-        doReturn(null).when(mIdentityManager).getPrimaryAccountInfo(ConsentLevel.SYNC);
-        mIdentityManager.onAccountsCookieDeletedByUserAction();
+    public void noRollbackWhenMobileIdentityConsistencyIsEnabled() {
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     NATIVE_IDENTITY_MANAGER, ConsentLevel.NOT_REQUIRED))
+                .thenReturn(ACCOUNT_INFO);
 
-        createSigninManager();
+        mSigninManager = new SigninManagerImpl(NATIVE_SIGNIN_MANAGER, mAccountTrackerService,
+                mIdentityManager, mIdentityMutator, mAndroidSyncSettings, mExternalAuthUtils);
 
         verify(mIdentityMutator, never()).clearPrimaryAccount(anyInt(), anyInt());
         verify(mNativeMock, never())
@@ -382,10 +363,9 @@
 
     @Test
     public void callbackNotifiedWhenNoOperationIsInProgress() {
-        createSigninManager();
         assertFalse(mSigninManager.isOperationInProgress());
-
         AtomicInteger callCount = new AtomicInteger(0);
+
         mSigninManager.runAfterOperationInProgress(callCount::incrementAndGet);
         assertEquals(1, callCount.get());
     }
@@ -400,7 +380,6 @@
                 .when(mIdentityMutator)
                 .clearPrimaryAccount(anyInt(), anyInt());
 
-        createSigninManager();
         mSigninManager.signOut(SignoutReason.SIGNOUT_TEST);
         assertTrue(mSigninManager.isOperationInProgress());
         AtomicInteger callCount = new AtomicInteger(0);
@@ -414,29 +393,20 @@
 
     @Test
     public void callbackNotifiedOnSignin() {
-        AccountInfo account = new AccountInfo(new CoreAccountId("test_at_gmail.com"),
-                "test@gmail.com", "test_at_gmail.com", "full name", "given name", null);
-
-        // No need to seed accounts to the native code.
-        doReturn(true).when(mAccountTrackerService).checkAndSeedSystemAccounts();
-
-        doReturn(account)
-                .when(mIdentityManager)
-                .findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(any());
-        doReturn(null).when(mIdentityManager).getPrimaryAccountInfo(anyInt());
-        Answer<Boolean> setPrimaryAccountAnswer = invocation -> {
+        final Answer<Boolean> setPrimaryAccountAnswer = invocation -> {
             // From now on getPrimaryAccountInfo should return account.
-            doReturn(account).when(mIdentityManager).getPrimaryAccountInfo(anyInt());
+            when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                         eq(NATIVE_IDENTITY_MANAGER), anyInt()))
+                    .thenReturn(ACCOUNT_INFO);
             return true;
         };
         doAnswer(setPrimaryAccountAnswer)
                 .when(mIdentityMutator)
-                .setPrimaryAccount(account.getId(), ConsentLevel.SYNC);
+                .setPrimaryAccount(ACCOUNT_INFO.getId(), ConsentLevel.SYNC);
 
-        createSigninManager();
         mSigninManager.onFirstRunCheckDone(); // Allow sign-in.
 
-        mSigninManager.signinAndEnableSync(SigninAccessPoint.UNKNOWN, account, null);
+        mSigninManager.signinAndEnableSync(SigninAccessPoint.UNKNOWN, ACCOUNT_INFO, null);
         assertTrue(mSigninManager.isOperationInProgress());
         AtomicInteger callCount = new AtomicInteger(0);
         mSigninManager.runAfterOperationInProgress(callCount::incrementAndGet);
@@ -448,25 +418,10 @@
     }
 
     @Test(expected = AssertionError.class)
-    public void failIfAlreadySignedin() {
-        AccountInfo account = new AccountInfo(new CoreAccountId("test_at_gmail.com"),
-                "test@gmail.com", "test_at_gmail.com", "full name", "given name", null);
-
-        // No need to seed accounts to the native code.
-        doReturn(true).when(mAccountTrackerService).checkAndSeedSystemAccounts();
-
-        doReturn(account)
-                .when(mIdentityManager)
-                .findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(any());
-        doReturn(true).when(mIdentityManager).hasPrimaryAccount();
-
-        createSigninManager();
-        mSigninManager.onFirstRunCheckDone(); // Allow sign-in.
-
-        mSigninManager.signinAndEnableSync(SigninAccessPoint.UNKNOWN, account, null);
-        assertTrue(mSigninManager.isOperationInProgress());
-
-        // The following should throw an assertion error
-        mSigninManager.finishSignInAfterPolicyEnforced();
+    public void signinfailsWhenAlreadySignedIn() {
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     eq(NATIVE_IDENTITY_MANAGER), anyInt()))
+                .thenReturn(ACCOUNT_INFO);
+        mSigninManager.signinAndEnableSync(SigninAccessPoint.UNKNOWN, ACCOUNT_INFO, null);
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
index 6363695a..dcc345b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
@@ -4,8 +4,11 @@
 
 package org.chromium.chrome.browser.tabbed_mode;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
@@ -39,6 +42,9 @@
 import org.chromium.chrome.browser.enterprise.util.ManagedBrowserUtilsJni;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
+import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
+import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettingsJni;
+import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileJni;
@@ -58,6 +64,7 @@
 import org.chromium.content_public.browser.ContentFeatureList;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.url.JUnitTestGURLs;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -66,6 +73,7 @@
  * Unit tests for {@link TabbedAppMenuPropertiesDelegate}.
  */
 @RunWith(BaseRobolectricTestRunner.class)
+@Features.EnableFeatures({ChromeFeatureList.WEB_FEED})
 public class TabbedAppMenuPropertiesDelegateUnitTest {
     @Rule
     public JniMocker jniMocker = new JniMocker();
@@ -106,7 +114,11 @@
     @Mock
     private PrefService mPrefServiceMock;
     @Mock
+    private DataReductionProxySettings.Natives mDataReductionJniMock;
+    @Mock
     private ContentFeatureListImpl.Natives mContentFeatureListJniMock;
+    @Mock
+    private OfflinePageUtils.Internal mOfflinePageUtils;
 
     private OneshotSupplierImpl<OverviewModeBehavior> mOverviewModeSupplier =
             new OneshotSupplierImpl<>();
@@ -133,10 +145,13 @@
         when(mPrefServiceMock.getBoolean(Pref.ENABLE_WEB_FEED_UI)).thenReturn(true);
         jniMocker.mock(ManagedBrowserUtilsJni.TEST_HOOKS, mManagedBrowserUtilsJniMock);
         Profile.setLastUsedProfileForTesting(mProfile);
+        jniMocker.mock(DataReductionProxySettingsJni.TEST_HOOKS, mDataReductionJniMock);
+        when(mDataReductionJniMock.isDataReductionProxyEnabled(anyLong(), any())).thenReturn(false);
         jniMocker.mock(ContentFeatureListImplJni.TEST_HOOKS, mContentFeatureListJniMock);
         when(mContentFeatureListJniMock.isEnabled(
                      ContentFeatureList.EXPERIMENTAL_ACCESSIBILITY_LABELS))
                 .thenReturn(false);
+        OfflinePageUtils.setInstanceForTesting(mOfflinePageUtils);
         FeatureList.setTestCanUseDefaultsForTesting();
 
         mTabbedAppMenuPropertiesDelegate = Mockito.spy(new TabbedAppMenuPropertiesDelegate(
@@ -177,6 +192,52 @@
         assertMenuItemsAreEqual(menu, expectedItems);
     }
 
+    @Test
+    public void getFooterResourceId_incognito_doesNotReturnWebFeedMenuItem() {
+        setUpMocksForWebFeedFooter();
+        when(mTab.isIncognito()).thenReturn(true);
+
+        assertNotEquals("Footer Resource ID should not be web_feed_main_menu_item.",
+                R.layout.web_feed_main_menu_item,
+                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
+    }
+
+    @Test
+    public void getFooterResourceId_offlinePage_doesNotReturnWebFeedMenuItem() {
+        setUpMocksForWebFeedFooter();
+        when(mOfflinePageUtils.isOfflinePage(mTab)).thenReturn(true);
+
+        assertNotEquals("Footer Resource ID should not be web_feed_main_menu_item.",
+                R.layout.web_feed_main_menu_item,
+                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
+    }
+
+    @Test
+    public void getFooterResourceId_nonHttpUrl_doesNotReturnWebFeedMenuItem() {
+        setUpMocksForWebFeedFooter();
+        when(mTab.getOriginalUrl()).thenReturn(JUnitTestGURLs.getGURL(JUnitTestGURLs.NTP_URL));
+
+        assertNotEquals("Footer Resource ID should not be web_feed_main_menu_item.",
+                R.layout.web_feed_main_menu_item,
+                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
+    }
+
+    @Test
+    public void getFooterResourceId_httpsUrl_returnsWebFeedMenuItem() {
+        setUpMocksForWebFeedFooter();
+
+        assertEquals("Footer Resource ID should be web_feed_main_menu_item.",
+                R.layout.web_feed_main_menu_item,
+                mTabbedAppMenuPropertiesDelegate.getFooterResourceId());
+    }
+
+    private void setUpMocksForWebFeedFooter() {
+        when(mActivityTabProvider.get()).thenReturn(mTab);
+        when(mTab.isIncognito()).thenReturn(false);
+        when(mTab.getOriginalUrl()).thenReturn(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL));
+        when(mOfflinePageUtils.isOfflinePage(mTab)).thenReturn(false);
+    }
+
     private void setUpMocksForPageMenu() {
         when(mActivityTabProvider.get()).thenReturn(mTab);
         when(mOverviewModeBehavior.overviewVisible()).thenReturn(false);
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index b5b024d..26b1456 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -273,8 +273,11 @@
   <message name="IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_ERROR_TITLE" desc="Title for cellular setup when Chrome OS encounters an error preparing the cellular device for setup.">
     No network found
   </message>
-  <message name="IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_ERROR_MESSAGE" desc="Message displayed under title in cellular setup when Chrome OS encounters an error preparing the cellular device for setup. Prompts user to insert SIM and try again.">
-    Please insert your SIM and try again
+  <message name="IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_ERROR_MESSAGE" desc="Message displayed under title in cellular setup when Chrome OS encounters an error preparing the cellular device for setup and has retry attempts left. Prompts user to insert SIM and try again.">
+    No network found. Please insert your SIM and try again.
+  </message>
+  <message name="IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_FINAL_ERROR_MESSAGE" desc="Message displayed under title in cellular setup when Chrome OS encounters an error preparing the cellular device for setup and has no retry attempts left. Prompts user to insert SIM, reboot and try again.">
+    No network found. Please insert your SIM and reboot your device before trying again.
   </message>
   <message name="IDS_CELLULAR_SETUP_PROVISIONING_PAGE_LOADING_TITLE" desc="Title for cellular setup step which indicates that a connection is in progress to the user's mobile data provider (e.g., Verizon).">
     Connecting to <ph name="CARRIER_NAME">$1<ex>Google Fi</ex></ph>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_ERROR_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_ERROR_MESSAGE.png.sha1
new file mode 100644
index 0000000..c2d4a88
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_ERROR_MESSAGE.png.sha1
@@ -0,0 +1 @@
+ecadfde5f81d83ff5de041befd4f8906b685df19
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_FINAL_ERROR_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_FINAL_ERROR_MESSAGE.png.sha1
new file mode 100644
index 0000000..0d600717
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_FINAL_ERROR_MESSAGE.png.sha1
@@ -0,0 +1 @@
+b94d5af291f4d811847fe10df18f6db65fd2cf73
\ No newline at end of file
diff --git a/chrome/app/theme/chrome_unscaled_resources.grd b/chrome/app/theme/chrome_unscaled_resources.grd
index b4e4545..75b9214 100644
--- a/chrome/app/theme/chrome_unscaled_resources.grd
+++ b/chrome/app/theme/chrome_unscaled_resources.grd
@@ -118,6 +118,7 @@
         <include name="IDR_APPS_FOLDER_OVERLAY_512" file="mac/apps_folder_overlay_512.png" type="BINDATA" />
       </if>
       <if expr="chromeos">
+        <include name="IDR_LOGO_CROSH" file="chromium/chromeos/crosh_app_icon_256.png" type="BINDATA" />
         <!-- Borealis icons -->
         <include name="IDR_LOGO_BOREALIS_DEFAULT_192" file="borealis/logo_borealis_default_192.png" type="BINDATA" />
         <!-- Crostini icons -->
diff --git a/chrome/app/theme/chromium/chromeos/crosh_app_icon_256.png b/chrome/app/theme/chromium/chromeos/crosh_app_icon_256.png
new file mode 100644
index 0000000..2e3521c
--- /dev/null
+++ b/chrome/app/theme/chromium/chromeos/crosh_app_icon_256.png
Binary files differ
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index de3b455ed..a7e1d41 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3946,6 +3946,8 @@
       "serial/serial_chooser_context_factory.cc",
       "serial/serial_chooser_context_factory.h",
       "serial/serial_chooser_histograms.h",
+      "serial/serial_policy_allowed_ports.cc",
+      "serial/serial_policy_allowed_ports.h",
       "sessions/closed_tab_cache_service.cc",
       "sessions/closed_tab_cache_service.h",
       "sessions/closed_tab_cache_service_factory.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a8400e6..54bd0db 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -6066,8 +6066,13 @@
      flag_descriptions::kUseNotificationCompatBuilderDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(features::kUseNotificationCompatBuilder)},
 
-    {"use-chime-android-sdk", flag_descriptions::kUseChimeAndroidSdkName,
-     flag_descriptions::kUseChimeAndroidSdkDescription, kOsAndroid,
+    {"debug-chime-notification",
+     flag_descriptions::kChimeAlwaysShowNotificationName,
+     flag_descriptions::kChimeAlwaysShowNotificationDescription, kOsAndroid,
+     SINGLE_VALUE_TYPE(notifications::switches::kDebugChimeNotification)},
+
+    {"use-chime-android-sdk", flag_descriptions::kChimeAndroidSdkName,
+     flag_descriptions::kChimeAndroidSdkDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(notifications::features::kUseChimeAndroidSdk)},
 
 #endif  // defined(OS_ANDROID)
diff --git a/chrome/browser/android/feed/v2/feed_image_fetch_client.cc b/chrome/browser/android/feed/v2/feed_image_fetch_client.cc
index 4e9b520e..fa7fbcb3 100644
--- a/chrome/browser/android/feed/v2/feed_image_fetch_client.cc
+++ b/chrome/browser/android/feed/v2/feed_image_fetch_client.cc
@@ -10,8 +10,8 @@
 #include "chrome/browser/android/feed/v2/feed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/public/feed_service.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
 #include "components/feed/core/v2/public/types.h"
 
 using base::android::JavaParamRef;
@@ -27,7 +27,7 @@
       base::android::ToJavaByteArray(env, response.response_bytes));
 }
 
-FeedStreamApi* GetFeedStream() {
+FeedApi* GetFeedStream() {
   Profile* profile = ProfileManager::GetLastUsedProfile();
   if (!profile)
     return nullptr;
@@ -49,7 +49,7 @@
   // with OnFetchFinished.
   base::android::ScopedJavaGlobalRef<jobject> callback(j_response_callback);
 
-  FeedStreamApi* stream = GetFeedStream();
+  FeedApi* stream = GetFeedStream();
   if (!stream) {
     OnFetchFinished(env, std::move(callback), {});
     return 0;
@@ -62,7 +62,7 @@
 }
 
 void JNI_FeedImageFetchClient_Cancel(JNIEnv* env, jint j_request_id) {
-  FeedStreamApi* stream = GetFeedStream();
+  FeedApi* stream = GetFeedStream();
   if (!stream)
     return;
 
diff --git a/chrome/browser/android/feed/v2/feed_persistent_key_value_cache.cc b/chrome/browser/android/feed/v2/feed_persistent_key_value_cache.cc
index ca8b363a9..2d3abf8 100644
--- a/chrome/browser/android/feed/v2/feed_persistent_key_value_cache.cc
+++ b/chrome/browser/android/feed/v2/feed_persistent_key_value_cache.cc
@@ -11,8 +11,8 @@
 #include "chrome/browser/android/feed/v2/feed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/public/feed_service.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
 #include "components/feed/core/v2/public/persistent_key_value_store.h"
 
 namespace feed {
diff --git a/chrome/browser/android/feed/v2/feed_stream_surface.cc b/chrome/browser/android/feed/v2/feed_stream_surface.cc
index 0ba080d9..b326ee8 100644
--- a/chrome/browser/android/feed/v2/feed_stream_surface.cc
+++ b/chrome/browser/android/feed/v2/feed_stream_surface.cc
@@ -17,8 +17,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "components/feed/core/proto/v2/ui.pb.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/public/feed_service.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
 #include "components/variations/variations_ids_provider.h"
 
 using base::android::JavaParamRef;
diff --git a/chrome/browser/android/feed/v2/feed_stream_surface.h b/chrome/browser/android/feed/v2/feed_stream_surface.h
index e7ef86bf..ccd1d38 100644
--- a/chrome/browser/android/feed/v2/feed_stream_surface.h
+++ b/chrome/browser/android/feed/v2/feed_stream_surface.h
@@ -8,7 +8,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 
 namespace feedui {
 class StreamUpdate;
@@ -78,7 +78,7 @@
       const base::android::JavaParamRef<jobject>& obj);
 
   // Event reporting functions. These have no side-effect beyond recording
-  // metrics. See |FeedStreamApi| for definitions.
+  // metrics. See |FeedApi| for definitions.
   void ReportSliceViewed(JNIEnv* env,
                          const base::android::JavaParamRef<jobject>& obj,
                          const base::android::JavaParamRef<jstring>& slice_id);
@@ -111,7 +111,7 @@
 
  private:
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
-  FeedStreamApi* feed_stream_api_;
+  FeedApi* feed_stream_api_;
   bool attached_ = false;
 };
 
diff --git a/chrome/browser/android/feed/v2/refresh_task_scheduler_impl.cc b/chrome/browser/android/feed/v2/refresh_task_scheduler_impl.cc
index c33ce2f2..324eb05 100644
--- a/chrome/browser/android/feed/v2/refresh_task_scheduler_impl.cc
+++ b/chrome/browser/android/feed/v2/refresh_task_scheduler_impl.cc
@@ -11,8 +11,8 @@
 #include "components/background_task_scheduler/task_ids.h"
 #include "components/background_task_scheduler/task_info.h"
 #include "components/feed/core/v2/config.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/public/feed_service.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
 
 namespace feed {
 
diff --git a/chrome/browser/ash/login/app_mode/kiosk_browsertest.cc b/chrome/browser/ash/login/app_mode/kiosk_browsertest.cc
index da1b230..bb5ec82 100644
--- a/chrome/browser/ash/login/app_mode/kiosk_browsertest.cc
+++ b/chrome/browser/ash/login/app_mode/kiosk_browsertest.cc
@@ -24,6 +24,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "base/scoped_observer.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
diff --git a/chrome/browser/ash/login/demo_mode/demo_session.cc b/chrome/browser/ash/login/demo_mode/demo_session.cc
index aff22b4..0ba7f9b 100644
--- a/chrome/browser/ash/login/demo_mode/demo_session.cc
+++ b/chrome/browser/ash/login/demo_mode/demo_session.cc
@@ -174,7 +174,7 @@
        "nb", "nl", "sv"});
 
   const std::vector<std::string>& available_locales =
-      l10n_util::GetAvailableLocales();
+      l10n_util::GetLocalesWithStrings();
   const std::string current_locale_iso_code =
       ProfileManager::GetActiveUserProfile()->GetPrefs()->GetString(
           language::prefs::kApplicationLocale);
diff --git a/chrome/browser/ash/login/webview_login_browsertest.cc b/chrome/browser/ash/login/webview_login_browsertest.cc
index 8578429..ca3a74c 100644
--- a/chrome/browser/ash/login/webview_login_browsertest.cc
+++ b/chrome/browser/ash/login/webview_login_browsertest.cc
@@ -18,6 +18,7 @@
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
+#include "base/scoped_observer.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
diff --git a/chrome/browser/ash/scanning/scan_service.cc b/chrome/browser/ash/scanning/scan_service.cc
index 86accc3..5ec62d0d 100644
--- a/chrome/browser/ash/scanning/scan_service.cc
+++ b/chrome/browser/ash/scanning/scan_service.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/check.h"
+#include "base/check_op.h"
 #include "base/files/file_util.h"
 #include "base/location.h"
 #include "base/metrics/histogram_functions.h"
@@ -121,6 +122,8 @@
 // PDF to |file_path|. Returns whether the PDF was successfully saved.
 bool SaveAsPdf(const std::vector<std::string>& png_images,
                const base::FilePath& file_path) {
+  DCHECK(!file_path.empty());
+
   SkFILEWStream pdf_outfile(file_path.value().c_str());
   if (!pdf_outfile.isValid()) {
     LOG(ERROR) << "Unable to open output file.";
@@ -369,9 +372,13 @@
   // scanned images are received.
   if (file_type == mojo_ipc::FileType::kPdf) {
     scanned_images_.push_back(std::move(scanned_image));
-    if (last_scanned_file_path_.empty()) {
-      last_scanned_file_path_ = scan_to_path.Append(CreateFilename(
-          start_time_, /*not used*/ 0, mojo_ipc::FileType::kPdf));
+
+    // The output of multi-page PDF scans is a single file so only create and
+    // append a single file path.
+    if (scanned_file_paths_.empty()) {
+      DCHECK_EQ(1, page_number);
+      scanned_file_paths_.push_back(scan_to_path.Append(CreateFilename(
+          start_time_, /*not used*/ 0, mojo_ipc::FileType::kPdf)));
     }
     return;
   }
@@ -385,10 +392,12 @@
 }
 
 void ScanService::OnScanCompleted(bool success) {
+  // |scanned_images_| only has data for PDF scans.
   if (success && !scanned_images_.empty()) {
+    DCHECK(!scanned_file_paths_.empty());
     base::PostTaskAndReplyWithResult(
         task_runner_.get(), FROM_HERE,
-        base::BindOnce(&SaveAsPdf, scanned_images_, last_scanned_file_path_),
+        base::BindOnce(&SaveAsPdf, scanned_images_, scanned_file_paths_.back()),
         base::BindOnce(&ScanService::OnPdfSaved,
                        weak_ptr_factory_.GetWeakPtr()));
   }
@@ -414,29 +423,32 @@
 
 void ScanService::OnPageSaved(const base::FilePath& saved_file_path) {
   page_save_failed_ = page_save_failed_ || saved_file_path.empty();
-  last_scanned_file_path_ =
-      page_save_failed_ ? base::FilePath() : saved_file_path;
+  if (page_save_failed_) {
+    return;
+  }
+
+  scanned_file_paths_.push_back(saved_file_path);
 }
 
 void ScanService::OnAllPagesSaved(bool success) {
   base::Optional<scanning::ScanJobFailureReason> failure_reason = base::nullopt;
   if (!success) {
     failure_reason = scanning::ScanJobFailureReason::kUnknownScannerError;
-    last_scanned_file_path_.clear();
+    scanned_file_paths_.clear();
   } else if (page_save_failed_) {
     failure_reason = scanning::ScanJobFailureReason::kSaveToDiskFailed;
-    last_scanned_file_path_.clear();
+    scanned_file_paths_.clear();
   }
 
   scan_job_observer_->OnScanComplete(success && !page_save_failed_,
-                                     last_scanned_file_path_);
+                                     scanned_file_paths_);
   RecordScanJobResult(success && !page_save_failed_, failure_reason,
                       num_pages_scanned_);
 }
 
 void ScanService::ClearScanState() {
   page_save_failed_ = false;
-  last_scanned_file_path_.clear();
+  scanned_file_paths_.clear();
   scanned_images_.clear();
   num_pages_scanned_ = 0;
 }
diff --git a/chrome/browser/ash/scanning/scan_service.h b/chrome/browser/ash/scanning/scan_service.h
index 10abf61..7641b6c 100644
--- a/chrome/browser/ash/scanning/scan_service.h
+++ b/chrome/browser/ash/scanning/scan_service.h
@@ -154,8 +154,8 @@
   // The time a scan was started. Used in filenames when saving scanned images.
   base::Time::Exploded start_time_;
 
-  // The file path of the last page scanned in a scan job.
-  base::FilePath last_scanned_file_path_;
+  // The file paths of the pages scanned in a scan job.
+  std::vector<base::FilePath> scanned_file_paths_;
 
   // Task runner used to convert and save scanned images.
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/chrome/browser/ash/scanning/scan_service_unittest.cc b/chrome/browser/ash/scanning/scan_service_unittest.cc
index 9269151..ea0161e 100644
--- a/chrome/browser/ash/scanning/scan_service_unittest.cc
+++ b/chrome/browser/ash/scanning/scan_service_unittest.cc
@@ -134,10 +134,11 @@
     page_complete_ = true;
   }
 
-  void OnScanComplete(bool success,
-                      const base::FilePath& last_scanned_file_path) override {
+  void OnScanComplete(
+      bool success,
+      const std::vector<base::FilePath>& scanned_file_paths) override {
     scan_success_ = success;
-    last_scanned_file_path_ = last_scanned_file_path;
+    scanned_file_paths_ = scanned_file_paths;
   }
 
   void OnCancelComplete(bool success) override {
@@ -163,9 +164,9 @@
   // Returns true if the cancel scan request completed successfully.
   bool cancel_scan_success() const { return cancel_scan_success_; }
 
-  // Returns the file path of the file saved last.
-  base::FilePath last_scanned_file_path() const {
-    return last_scanned_file_path_;
+  // Returns file paths of the saved scan files.
+  std::vector<base::FilePath> scanned_file_paths() const {
+    return scanned_file_paths_;
   }
 
  private:
@@ -173,7 +174,7 @@
   bool page_complete_ = false;
   bool scan_success_ = false;
   bool cancel_scan_success_ = false;
-  base::FilePath last_scanned_file_path_;
+  std::vector<base::FilePath> scanned_file_paths_;
   mojo::Receiver<mojo_ipc::ScanJobObserver> receiver_{this};
 };
 
@@ -376,8 +377,7 @@
       EXPECT_TRUE(base::PathExists(saved_scan_path));
 
     EXPECT_TRUE(fake_scan_job_observer_.scan_success());
-    EXPECT_EQ(saved_scan_paths.back(),
-              fake_scan_job_observer_.last_scanned_file_path());
+    EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
   }
 }
 
@@ -404,7 +404,10 @@
   EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
   EXPECT_TRUE(base::PathExists(saved_scan_path));
   EXPECT_TRUE(fake_scan_job_observer_.scan_success());
-  EXPECT_EQ(saved_scan_path, fake_scan_job_observer_.last_scanned_file_path());
+  const std::vector<base::FilePath> scanned_file_paths =
+      fake_scan_job_observer_.scanned_file_paths();
+  EXPECT_EQ(1u, scanned_file_paths.size());
+  EXPECT_EQ(saved_scan_path, scanned_file_paths.front());
 }
 
 // Test that when a scan fails, the scan job is marked as failed.
@@ -422,7 +425,7 @@
 
   EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
   EXPECT_FALSE(fake_scan_job_observer_.scan_success());
-  EXPECT_TRUE(fake_scan_job_observer_.last_scanned_file_path().empty());
+  EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
 }
 
 // Test that when a page fails to save during the scan, the scan job is marked
@@ -442,7 +445,7 @@
 
   EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
   EXPECT_FALSE(fake_scan_job_observer_.scan_success());
-  EXPECT_TRUE(fake_scan_job_observer_.last_scanned_file_path().empty());
+  EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
 }
 
 // Tests that a new scan job can succeed after the previous scan failed.
@@ -460,7 +463,7 @@
 
   EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
   EXPECT_FALSE(fake_scan_job_observer_.scan_success());
-  EXPECT_TRUE(fake_scan_job_observer_.last_scanned_file_path().empty());
+  EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
 
   // Set scan data so next scan is successful.
   const std::vector<std::string> scan_data = {"TestData1", "TestData2",
@@ -480,8 +483,7 @@
     EXPECT_TRUE(base::PathExists(saved_scan_path));
 
   EXPECT_TRUE(fake_scan_job_observer_.scan_success());
-  EXPECT_EQ(saved_scan_paths.back(),
-            fake_scan_job_observer_.last_scanned_file_path());
+  EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
 }
 
 // Tests that a failed scan does not retain values from the previous successful
@@ -512,8 +514,7 @@
     EXPECT_TRUE(base::PathExists(saved_scan_path));
 
   EXPECT_TRUE(fake_scan_job_observer_.scan_success());
-  EXPECT_EQ(saved_scan_paths.back(),
-            fake_scan_job_observer_.last_scanned_file_path());
+  EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
 
   // Remove the scan data from FakeLorgnetteScannerManager so the scan will
   // fail.
@@ -521,7 +522,7 @@
 
   EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
   EXPECT_FALSE(fake_scan_job_observer_.scan_success());
-  EXPECT_TRUE(fake_scan_job_observer_.last_scanned_file_path().empty());
+  EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
 }
 
 // Test that canceling sends an update to the observer OnCancelComplete().
diff --git a/chrome/browser/ash/web_applications/crosh_system_web_app_info.cc b/chrome/browser/ash/web_applications/crosh_system_web_app_info.cc
index 96e23829..003e9a6af 100644
--- a/chrome/browser/ash/web_applications/crosh_system_web_app_info.cc
+++ b/chrome/browser/ash/web_applications/crosh_system_web_app_info.cc
@@ -22,8 +22,7 @@
   info->scope = GURL(chrome::kChromeUIUntrustedCroshURL);
   info->title = std::u16string(base::ASCIIToUTF16("crosh"));
   web_app::CreateIconInfoForSystemWebApp(
-      info->start_url, {{"app_icon_256.png", 256, IDR_LOGO_CROSTINI_TERMINAL}},
-      *info);
+      info->start_url, {{"app_icon_256.png", 256, IDR_LOGO_CROSH}}, *info);
   info->background_color = 0xFF202124;
   info->display_mode = blink::mojom::DisplayMode::kStandalone;
   return info;
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index 06787333..82f4982 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -452,6 +452,10 @@
 
   sessions::SessionIdGenerator::GetInstance()->Shutdown();
 
+  // Resetting the status tray will result in calls to
+  // |g_browser_process->local_state()|. See crbug.com/1187418
+  status_tray_.reset();
+
   if (local_state_)
     local_state_->CommitPendingWrite();
 
diff --git a/chrome/browser/chrome_browser_field_trials.cc b/chrome/browser/chrome_browser_field_trials.cc
index e1e85da0..0c4ccb47b 100644
--- a/chrome/browser/chrome_browser_field_trials.cc
+++ b/chrome/browser/chrome_browser_field_trials.cc
@@ -29,10 +29,8 @@
 #if defined(OS_ANDROID)
 #include "base/android/build_info.h"
 #include "base/android/bundle_utils.h"
-#include "base/task/thread_pool/environment_config.h"
 #include "chrome/browser/chrome_browser_field_trials_mobile.h"
 #include "chrome/browser/flags/android/cached_feature_flags.h"
-#include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "chrome/common/chrome_features.h"
 #endif
 
@@ -112,59 +110,26 @@
         kReachedCodeProfilerTrial, reached_code_profiler_group);
   }
 
-  {
-    // EarlyLibraryLoadSynthetic field trial.
-    const char* group_name;
-    bool java_feature_enabled = chrome::android::IsJavaDrivenFeatureEnabled(
-        features::kEarlyLibraryLoad);
-    bool feature_enabled =
-        base::FeatureList::IsEnabled(features::kEarlyLibraryLoad);
-    // Use the default group if cc and java feature values don't agree (can
-    // happen on first startup after feature is enabled by Finch), or the
-    // feature is not overridden by Finch.
-    if (feature_enabled != java_feature_enabled ||
-        !base::FeatureList::GetInstance()->IsFeatureOverridden(
-            features::kEarlyLibraryLoad.name)) {
-      group_name = "Default";
-    } else if (java_feature_enabled) {
-      group_name = "Enabled";
-    } else {
-      group_name = "Disabled";
-    }
-    static constexpr char kEarlyLibraryLoadTrial[] =
-        "EarlyLibraryLoadSynthetic";
-    ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
-        kEarlyLibraryLoadTrial, group_name);
+  const char* group_name;
+  bool java_feature_enabled =
+      chrome::android::IsJavaDrivenFeatureEnabled(features::kEarlyLibraryLoad);
+  bool feature_enabled =
+      base::FeatureList::IsEnabled(features::kEarlyLibraryLoad);
+  // Use the default group if cc and java feature values don't agree (can happen
+  // on first startup after feature is enabled by Finch), or the feature is not
+  // overridden by Finch.
+  if (feature_enabled != java_feature_enabled ||
+      !base::FeatureList::GetInstance()->IsFeatureOverridden(
+          features::kEarlyLibraryLoad.name)) {
+    group_name = "Default";
+  } else if (java_feature_enabled) {
+    group_name = "Enabled";
+  } else {
+    group_name = "Disabled";
   }
-
-  {
-    // BackgroundThreadPoolSynthetic field trial.
-    const char* group_name;
-    // Target group as indicated by finch feature.
-    bool feature_enabled =
-        base::FeatureList::IsEnabled(chrome::android::kBackgroundThreadPool);
-    bool feature_overridden =
-        base::FeatureList::GetInstance()->IsFeatureOverridden(
-            chrome::android::kBackgroundThreadPool.name);
-    // The finch feature value is cached by Java in a setting and applied via a
-    // command line flag. Check if this has happened -- it may not have happened
-    // if this is the first startup after the feature is enabled.
-    bool actually_enabled =
-        base::internal::CanUseBackgroundPriorityForWorkerThread();
-    // Use the default group if either the feature wasn't overridden by Finch,
-    // or if the feature target state and actual state don't agree.
-    if (actually_enabled != feature_enabled || !feature_overridden) {
-      group_name = "Default";
-    } else if (feature_enabled) {
-      group_name = "Enabled";
-    } else {
-      group_name = "Disabled";
-    }
-    static constexpr char kBackgroundThreadPoolTrial[] =
-        "BackgroundThreadPoolSynthetic";
-    ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
-        kBackgroundThreadPoolTrial, group_name);
-  }
+  static constexpr char kEarlyLibraryLoadTrial[] = "EarlyLibraryLoadSynthetic";
+  ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
+      kEarlyLibraryLoadTrial, group_name);
 
   // If isolated splits are enabled at build time, Monochrome and Trichrome will
   // have a different bundle layout, so measure N+ even though isolated splits
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 72bea6a..4571937b 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -204,6 +204,8 @@
     "//chromeos/dbus/u2f",
     "//chromeos/dbus/u2f:u2f_proto",
     "//chromeos/dbus/upstart",
+    "//chromeos/dbus/userdataauth",
+    "//chromeos/dbus/userdataauth:userdataauth_proto",
     "//chromeos/disks",
     "//chromeos/geolocation",
     "//chromeos/ime:gencode",
@@ -3574,6 +3576,7 @@
     "../ash/login/version_updater/version_updater_unittest.cc",
     "../ash/mobile/mobile_activator_unittest.cc",
     "../ash/notifications/deprecation_notification_controller_unittest.cc",
+    "../ash/ownership/owner_settings_service_chromeos_unittest.cc",
     "../ash/plugin_vm/mock_plugin_vm_manager.cc",
     "../ash/plugin_vm/mock_plugin_vm_manager.h",
     "../ash/plugin_vm/plugin_vm_features_unittest.cc",
@@ -3583,7 +3586,6 @@
     "../ash/plugin_vm/plugin_vm_test_helper.cc",
     "../ash/plugin_vm/plugin_vm_test_helper.h",
     "../ash/plugin_vm/plugin_vm_util_unittest.cc",
-    "../ash/ownership/owner_settings_service_chromeos_unittest.cc",
     "../ash/scanning/fake_lorgnette_scanner_manager.cc",
     "../ash/scanning/fake_lorgnette_scanner_manager.h",
     "../ash/scanning/lorgnette_scanner_manager_unittest.cc",
diff --git a/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_manager_impl.cc b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_manager_impl.cc
index 267defa..919f2261 100644
--- a/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_manager_impl.cc
+++ b/chrome/browser/chromeos/platform_keys/key_permissions/key_permissions_manager_impl.cc
@@ -236,7 +236,14 @@
 
 void KeyPermissionsManagerImpl::KeyPermissionsInChapsUpdater::
     OnKeyPermissionsUpdated(Status permissions_update_status) {
-  if (permissions_update_status != Status::kSuccess) {
+  if (permissions_update_status == Status::kErrorKeyNotFound) {
+    // Some public keys are not removed from chaps although their corresponding
+    // private keys are removed. We continue the migration process if we
+    // received kKeyNotFound as a workaround until the keys-clean-up problem is
+    // solved (crbug.com/1096051).
+    LOG(WARNING) << "Corresponding private key not found. Continuing the "
+                    "migration process...";
+  } else if (permissions_update_status != Status::kSuccess) {
     LOG(ERROR) << "Couldn't update permissions for a key: "
                << StatusToString(permissions_update_status);
     std::move(callback_).Run(permissions_update_status);
diff --git a/chrome/browser/download/internal/android/BUILD.gn b/chrome/browser/download/internal/android/BUILD.gn
index 7d32fc96..6a11571 100644
--- a/chrome/browser/download/internal/android/BUILD.gn
+++ b/chrome/browser/download/internal/android/BUILD.gn
@@ -28,10 +28,6 @@
     "java/src/org/chromium/chrome/browser/download/home/filter/OfflineItemFilterSource.java",
     "java/src/org/chromium/chrome/browser/download/home/filter/SearchOfflineItemFilter.java",
     "java/src/org/chromium/chrome/browser/download/home/filter/TypeOfflineItemFilter.java",
-    "java/src/org/chromium/chrome/browser/download/home/filter/chips/Chip.java",
-    "java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsCoordinator.java",
-    "java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsProvider.java",
-    "java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsViewHolder.java",
     "java/src/org/chromium/chrome/browser/download/home/glue/OfflineContentProviderGlue.java",
     "java/src/org/chromium/chrome/browser/download/home/glue/ThumbnailRequestGlue.java",
     "java/src/org/chromium/chrome/browser/download/home/list/BatchListModel.java",
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java
index 162c7a6..616831c 100644
--- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java
+++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java
@@ -9,10 +9,10 @@
 
 import org.chromium.base.ObserverList;
 import org.chromium.chrome.browser.download.home.filter.Filters.FilterType;
-import org.chromium.chrome.browser.download.home.filter.chips.Chip;
-import org.chromium.chrome.browser.download.home.filter.chips.ChipsProvider;
 import org.chromium.chrome.browser.download.home.list.UiUtils;
 import org.chromium.chrome.browser.download.internal.R;
+import org.chromium.components.browser_ui.widget.chips.Chip;
+import org.chromium.components.browser_ui.widget.chips.ChipsProvider;
 import org.chromium.components.offline_items_collection.OfflineItem;
 
 import java.util.ArrayList;
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterCoordinator.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterCoordinator.java
index 85b59d1..e1f7ee6 100644
--- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterCoordinator.java
+++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/FilterCoordinator.java
@@ -13,7 +13,7 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.browser.download.home.filter.Filters.FilterType;
-import org.chromium.chrome.browser.download.home.filter.chips.ChipsCoordinator;
+import org.chromium.components.browser_ui.widget.chips.ChipsCoordinator;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/Chip.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/Chip.java
deleted file mode 100644
index 89be267..0000000
--- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/Chip.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.download.home.filter.chips;
-
-import androidx.annotation.DrawableRes;
-import androidx.annotation.StringRes;
-
-import org.chromium.ui.widget.ChipView;
-
-/**
- * A generic visual representation of a Chip. Most of the visuals are immutable, but the selection
- * and enable states are not.
- */
-public class Chip {
-    /** An id to use for {@link #icon} when there is no icon on the chip. */
-    public static final int INVALID_ICON_ID = ChipView.INVALID_ICON_ID;
-
-    /** An id used to identify this chip. */
-    public final int id;
-
-    /** The resource id for the text to show in the chip. */
-    public final @StringRes int text;
-
-    /** The accessibility text to use for the chip. */
-    public String contentDescription;
-
-    /** The resource id for the icon to use in the chip. */
-    public final @DrawableRes int icon;
-
-    /** The {@link Runnable} to trigger when this chip is selected by the UI. */
-    public final Runnable chipSelectedListener;
-
-    /** Whether or not this Chip is enabled. */
-    public boolean enabled;
-
-    /** Whether or not this Chip is selected. */
-    public boolean selected;
-
-    /** Builds a new {@link Chip} instance.  These properties cannot be changed. */
-    public Chip(int id, @StringRes int text, @DrawableRes int icon, Runnable chipSelectedListener) {
-        this.id = id;
-        this.text = text;
-        this.icon = icon;
-        this.chipSelectedListener = chipSelectedListener;
-    }
-}
\ No newline at end of file
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsCoordinator.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsCoordinator.java
deleted file mode 100644
index 0412a61c..0000000
--- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsCoordinator.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.download.home.filter.chips;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.View;
-
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
-import androidx.recyclerview.widget.RecyclerView.State;
-
-import org.chromium.chrome.browser.download.internal.R;
-import org.chromium.ui.modelutil.ListModel;
-import org.chromium.ui.modelutil.RecyclerViewAdapter;
-import org.chromium.ui.modelutil.SimpleRecyclerViewMcp;
-
-import java.util.List;
-
-/**
- * The coordinator responsible for managing a list of chips.  To get the {@link View} that
- * represents this coordinator use {@link #getView()}.
- */
-public class ChipsCoordinator implements ChipsProvider.Observer {
-    private final ChipsProvider mProvider;
-    private final ListModel<Chip> mModel = new ListModel<>();
-    private final RecyclerView mView;
-
-    /**
-     * Builds and initializes this coordinator, including all sub-components.
-     * @param context The {@link Context} to use to grab all of the resources.
-     * @param provider The source for the underlying Chip state.
-     */
-    public ChipsCoordinator(Context context, ChipsProvider provider) {
-        assert context != null;
-        assert provider != null;
-
-        mProvider = provider;
-
-        // Build the underlying components.
-        mView = createView(context);
-
-        mView.setAdapter(new RecyclerViewAdapter<>(
-                new SimpleRecyclerViewMcp<>(mModel, null, ChipsViewHolder::bind),
-                ChipsViewHolder::create));
-
-        mProvider.addObserver(this);
-        mModel.set(mProvider.getChips());
-    }
-
-    /**
-     * Destroys the coordinator.  This should be called when the coordinator is no longer in use.
-     * The coordinator should not be used after that point.
-     */
-    public void destroy() {
-        mProvider.removeObserver(this);
-    }
-
-    /** @return The {@link View} that represents this coordinator. */
-    public View getView() {
-        return mView;
-    }
-
-    // ChipsProvider.Observer implementation.
-    @Override
-    public void onChipsChanged() {
-        List<Chip> chips = mProvider.getChips();
-        mModel.set(chips);
-    }
-
-    private static RecyclerView createView(Context context) {
-        RecyclerView view = new RecyclerView(context);
-        view.setLayoutManager(
-                new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
-        view.addItemDecoration(new SpaceItemDecoration(context));
-        view.getItemAnimator().setChangeDuration(0);
-        return view;
-    }
-
-    private static class SpaceItemDecoration extends ItemDecoration {
-        private final int mInterPaddingPx;
-        private final int mSidePaddingPx;
-
-        public SpaceItemDecoration(Context context) {
-            mInterPaddingPx = (int) context.getResources().getDimensionPixelSize(
-                    R.dimen.chip_list_inter_chip_padding);
-            mSidePaddingPx = (int) context.getResources().getDimensionPixelSize(
-                    R.dimen.chip_list_side_padding);
-        }
-
-        @Override
-        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
-            int position = parent.getChildAdapterPosition(view);
-            boolean isFirst = position == 0;
-            boolean isLast = position == parent.getAdapter().getItemCount() - 1;
-
-            outRect.left = isFirst ? mSidePaddingPx : mInterPaddingPx;
-            outRect.right = isLast ? mSidePaddingPx : mInterPaddingPx;
-        }
-    }
-}
\ No newline at end of file
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsProvider.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsProvider.java
deleted file mode 100644
index 8af0ff1..0000000
--- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsProvider.java
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.download.home.filter.chips;
-
-import java.util.List;
-
-/** A source of Chips meant to be visually represented by a {@link ChipCoordinator}. */
-public interface ChipsProvider {
-    /** Interface to be called a Chip's state changes. */
-    interface Observer {
-        /** Called whenever the list of Chips or selection changes. */
-        void onChipsChanged();
-    }
-
-    /** Adds an {@link Observer} to be notified of Chip state changes. */
-    void addObserver(Observer observer);
-
-    /** Removes an {@link Observer} to be notified of Chip state changes. */
-    void removeObserver(Observer observer);
-
-    /** @return A list of {@link Chip} objects that are currently visible. */
-    List<Chip> getChips();
-}
\ No newline at end of file
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsViewHolder.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsViewHolder.java
deleted file mode 100644
index 4f8b3ff3..0000000
--- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsViewHolder.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-package org.chromium.chrome.browser.download.home.filter.chips;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-
-import org.chromium.chrome.browser.download.internal.R;
-import org.chromium.ui.modelutil.RecyclerViewAdapter;
-import org.chromium.ui.widget.ChipView;
-
-/** The {@link ViewHolder} responsible for reflecting a {@link Chip} to a {@link View}. */
-public class ChipsViewHolder extends ViewHolder {
-    /** Builds a ChipsViewHolder around a specific {@link View}. */
-    private ChipsViewHolder(View itemView) {
-        super(itemView);
-    }
-
-    private ChipView getChipView() {
-        assert itemView
-                instanceof ChipView : "ChipViewHolder doesn't hold ChipView but "
-                                      + itemView.getClass();
-        return (ChipView) itemView;
-    }
-
-    /**
-     * Used as a method reference for ViewHolderFactory.
-     * @see RecyclerViewAdapter
-     *         .ViewHolderFactory#createViewHolder
-     */
-    public static ChipsViewHolder create(ViewGroup parent, int viewType) {
-        assert viewType == 0;
-        return new ChipsViewHolder(
-                new ChipView(parent.getContext(), R.style.SuggestionChipThemeOverlay));
-    }
-
-    /**
-     * Used as a method reference for ViewBinder, to push the properties of {@code chip} to
-     * {@link #itemView}.
-     * @param chip The {@link Chip} to visually reflect in the stored {@link View}.
-     * @see SimpleRecyclerViewMcp.ViewBinder#onBindViewHolder
-     */
-    public void bind(Chip chip) {
-        getChipView().setEnabled(chip.enabled);
-        getChipView().setSelected(chip.selected);
-        getChipView().setOnClickListener(v -> chip.chipSelectedListener.run());
-        getChipView().getPrimaryTextView().setText(chip.text);
-        getChipView().setIcon(chip.selected ? R.drawable.ic_check_googblue_24dp : chip.icon, true);
-        getChipView().getPrimaryTextView().setContentDescription(chip.contentDescription);
-    }
-}
diff --git a/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc b/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
index 8ecdedda..ba79294db 100644
--- a/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
+++ b/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
@@ -234,8 +234,8 @@
 
 std::string GetChromeVersionString() {
   // Version of the current running browser.
-  std::string browser_version = chrome::GetVersionString();
-
+  std::string browser_version =
+      chrome::GetVersionString(chrome::WithExtendedStable(true));
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // If the device is receiving LTS updates, add a prefix to the version string.
diff --git a/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source_unittest.cc b/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source_unittest.cc
index 655e09b..225bf2e 100644
--- a/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source_unittest.cc
+++ b/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source_unittest.cc
@@ -36,8 +36,10 @@
 
 TEST_F(ChromeInternalLogSourceTest, VersionTagContainsActualVersion) {
   auto response = GetChromeInternalLogs();
-  EXPECT_PRED_FORMAT2(testing::IsSubstring, chrome::GetVersionString(),
-                      response->at("CHROME VERSION"));
+  EXPECT_PRED_FORMAT2(
+      testing::IsSubstring,
+      chrome::GetVersionString(chrome::WithExtendedStable(true)),
+      response->at("CHROME VERSION"));
 }
 
 #if defined(OS_MAC)
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 5e33d16e..dfc63ca 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -898,6 +898,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "debug-chime-notification",
+    "owners": [ "xingliu" ],
+    "expiry_milestone": 95
+  },
+  {
     "name": "debug-packed-apps",
     "owners": [ "benwells", "raymes" ],
     "expiry_milestone": 88
@@ -1230,12 +1235,6 @@
     "expiry_milestone": 90
   },
   {
-    "name": "embedder-block-restore-url",
-    "owners": [ "justincohen", "rohitrao", "bling-flags@google.com" ],
-    // Needed for manual testing of fallback flow on iOS.
-    "expiry_milestone": -1
-  },
-  {
     "name": "enable-accelerated-video-decode",
     "owners": [ "videostack-eng@google.com" ],
     "expiry_milestone": 90
@@ -4493,6 +4492,12 @@
     "expiry_milestone": 96
   },
   {
+    "name": "restore-session-from-cache",
+    "owners": [ "justincohen", "gambard", "bling-flags@google.com" ],
+    // Needed for manual testing of native session restore flow on iOS.
+    "expiry_milestone": 97
+  },
+  {
     "name": "restrict-gamepad-access",
     "owners": [ "//device/gamepad/OWNERS", "jameshollyer@chromium.org" ],
     "expiry_milestone": 89
diff --git a/chrome/browser/flag-never-expire-list.json b/chrome/browser/flag-never-expire-list.json
index d2796a4..ae2b606 100644
--- a/chrome/browser/flag-never-expire-list.json
+++ b/chrome/browser/flag-never-expire-list.json
@@ -29,7 +29,6 @@
   "disable-webrtc-hw-decoding",
   "disable-webrtc-hw-encoding",
   "disallow-doc-written-script-loads",
-  "embedder-block-restore-url",
   "enable-autofill-credit-card-upload",
   "enable-command-line-on-non-rooted-devices",
   "enable-data-reduction-proxy-server-experiment",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 43af0d9..762936c9 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2781,6 +2781,15 @@
     "Enables specify target language the page should be translated to "
     "in Chrome Custom Tabs.";
 
+const char kChimeAlwaysShowNotificationDescription[] =
+    "A debug flag to always show Chime notification after receiving a payload.";
+const char kChimeAlwaysShowNotificationName[] =
+    "Always show Chime notification";
+
+const char kChimeAndroidSdkDescription[] =
+    "Enable Chime SDK to receive push notification.";
+const char kChimeAndroidSdkName[] = "Use Chime SDK";
+
 const char kContinuousSearchName[] = "Continuous Search Navigation";
 const char kContinuousSearchDescription[] =
     "Enables caching of search results to permit a more seamless search "
@@ -3381,10 +3390,6 @@
     "if enable prefetch notification service and background task will hook up "
     "to notification scheduling system in native side";
 
-const char kUseChimeAndroidSdkDescription[] =
-    "Enable Chime SDK to receive push notification.";
-const char kUseChimeAndroidSdkName[] = "Use Chime SDK";
-
 const char kVideoTutorialsName[] = "Enable video tutorials";
 const char kVideoTutorialsDescription[] = "Show video tutorials in Chrome";
 const char kVideoTutorialsInstantFetchName[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 45d96c7..e59a205 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1631,6 +1631,12 @@
 extern const char kCCTTargetTranslateLanguageName[];
 extern const char kCCTTargetTranslateLanguageDescription[];
 
+extern const char kChimeAlwaysShowNotificationDescription[];
+extern const char kChimeAlwaysShowNotificationName[];
+
+extern const char kChimeAndroidSdkDescription[];
+extern const char kChimeAndroidSdkName[];
+
 extern const char kContinuousSearchName[];
 extern const char kContinuousSearchDescription[];
 
@@ -1971,9 +1977,6 @@
 extern const char kPrefetchNotificationSchedulingIntegrationName[];
 extern const char kPrefetchNotificationSchedulingIntegrationDescription[];
 
-extern const char kUseChimeAndroidSdkDescription[];
-extern const char kUseChimeAndroidSdkName[];
-
 extern const char kInlineUpdateFlowName[];
 extern const char kInlineUpdateFlowDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 77a10a4..d85f0841 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -138,7 +138,6 @@
     &kAssistantIntentPageUrl,
     &kAssistantIntentTranslateInfo,
     &kAppLaunchpad,
-    &kBackgroundThreadPool,
     &kBentoOffline,
     &kCastDeviceFilter,
     &kCloseTabSuggestions,
@@ -364,9 +363,6 @@
 const base::Feature kBackgroundTaskComponentUpdate{
     "BackgroundTaskComponentUpdate", base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kBackgroundThreadPool{"BackgroundThreadPool",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kBentoOffline{"BentoOffline",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
@@ -707,7 +703,7 @@
 
 // If enabled, keep logging and reporting UMA while chrome is backgrounded.
 const base::Feature kUmaBackgroundSessions{"UMABackgroundSessions",
-                                           base::FEATURE_DISABLED_BY_DEFAULT};
+                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kUpdateNotificationSchedulingIntegration{
     "UpdateNotificationSchedulingIntegration",
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index caeab562..980f1cf 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -24,7 +24,6 @@
 extern const base::Feature kAssistantIntentTranslateInfo;
 extern const base::Feature kAppLaunchpad;
 extern const base::Feature kBackgroundTaskComponentUpdate;
-extern const base::Feature kBackgroundThreadPool;
 extern const base::Feature kBentoOffline;
 extern const base::Feature kCloseTabSuggestions;
 extern const base::Feature kCriticalPersistedTabData;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
index 9eb963bd1..3825a8b 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
@@ -256,10 +256,6 @@
                 ChromeFeatureList.isEnabled(ChromeFeatureList.REACHED_CODE_PROFILER),
                 ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
                         ChromeFeatureList.REACHED_CODE_PROFILER, "sampling_interval_us", 0));
-
-        // Similarly, propagate the BACKGROUND_THREAD_POOL feature value to LibraryLoader.
-        LibraryLoader.setBackgroundThreadPoolEnabledOnNextRuns(
-                ChromeFeatureList.isEnabled(ChromeFeatureList.BACKGROUND_THREAD_POOL));
     }
 
     /**
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 183274b..25acc16 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
@@ -232,7 +232,6 @@
     public static final String AUTOFILL_REFRESH_STYLE_ANDROID = "AutofillRefreshStyleAndroid";
     public static final String AUTOFILL_KEYBOARD_ACCESSORY = "AutofillKeyboardAccessory";
     public static final String APP_LAUNCHPAD = "AppLaunchpad";
-    public static final String BACKGROUND_THREAD_POOL = "BackgroundThreadPool";
     public static final String BENTO_OFFLINE = "BentoOffline";
     public static final String BIOMETRIC_TOUCH_TO_FILL = "BiometricTouchToFill";
     public static final String CAPTIVE_PORTAL_CERTIFICATE_LIST = "CaptivePortalCertificateList";
diff --git a/chrome/browser/l10n_util_browsertest.cc b/chrome/browser/l10n_util_browsertest.cc
new file mode 100644
index 0000000..555cf32
--- /dev/null
+++ b/chrome/browser/l10n_util_browsertest.cc
@@ -0,0 +1,68 @@
+// 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 <string>
+#include <vector>
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+class L10nUtilBrowserTest : public InProcessBrowserTest {
+ public:
+  L10nUtilBrowserTest() = default;
+  ~L10nUtilBrowserTest() override = default;
+  L10nUtilBrowserTest(const L10nUtilBrowserTest&) = delete;
+  L10nUtilBrowserTest& operator=(const L10nUtilBrowserTest&) = delete;
+};
+
+}  // namespace
+
+// Tests whether CheckAndResolveLocale returns the same result with and without
+// I/O.
+IN_PROC_BROWSER_TEST_F(L10nUtilBrowserTest, CheckAndResolveLocaleIO) {
+  base::ScopedAllowBlockingForTesting allow_io;
+  std::vector<std::string> accept_languages;
+  l10n_util::GetAcceptLanguages(&accept_languages);
+
+  for (const std::string& locale : accept_languages) {
+    std::string resolved_locale;
+    bool resolved = l10n_util::CheckAndResolveLocale(locale, &resolved_locale,
+                                                     /*perform_io=*/false);
+    std::string resolved_locale_with_io;
+    bool resolved_with_io = l10n_util::CheckAndResolveLocale(
+        locale, &resolved_locale_with_io, /*perform_io=*/true);
+
+#if defined(OS_ANDROID)
+    // False positives may occur on Android and iOS (and chrome/ isn't used on
+    // iOS, so we only need to check for Android).
+    // False negatives should never occur - so if the call without IO returns
+    // false, the call with I/O must return false too.
+    if (!resolved) {
+      EXPECT_FALSE(resolved_with_io)
+          << "Couldn't resolve " << locale
+          << " without IO, but resolving with IO successfully returned "
+          << resolved_locale_with_io;
+    }
+    // If CheckAndResolveLocale returns the same locale as the input, that means
+    // that we have strings for that locale. False negatives should never occur
+    // like this as well - if the call without I/O returns something different
+    // to the input, the same should apply to the call with I/O.
+    if (resolved_locale != locale) {
+      EXPECT_NE(resolved_locale_with_io, locale)
+          << "Resolving " << locale
+          << " without IO returned a different locale ("
+          << (resolved_locale.empty() ? "an empty string" : resolved_locale)
+          << "), but resolving with IO returned the same locale";
+    }
+#else
+    // On other platforms, the two function calls should be identical.
+    EXPECT_EQ(resolved, resolved_with_io);
+    EXPECT_EQ(resolved_locale, resolved_locale_with_io);
+#endif
+  }
+}
diff --git a/chrome/browser/lacros/lacros_chrome_service_delegate_impl.cc b/chrome/browser/lacros/lacros_chrome_service_delegate_impl.cc
index e721f00..5885062 100644
--- a/chrome/browser/lacros/lacros_chrome_service_delegate_impl.cc
+++ b/chrome/browser/lacros/lacros_chrome_service_delegate_impl.cc
@@ -89,7 +89,7 @@
 }
 
 std::string LacrosChromeServiceDelegateImpl::GetChromeVersion() {
-  return chrome::GetVersionString();
+  return chrome::GetVersionString(chrome::WithExtendedStable(true));
 }
 
 void LacrosChromeServiceDelegateImpl::GetFeedbackData(
diff --git a/chrome/browser/media/webrtc/current_tab_desktop_media_list.cc b/chrome/browser/media/webrtc/current_tab_desktop_media_list.cc
index a4c3d16..ab8df64 100644
--- a/chrome/browser/media/webrtc/current_tab_desktop_media_list.cc
+++ b/chrome/browser/media/webrtc/current_tab_desktop_media_list.cc
@@ -84,7 +84,6 @@
     base::TimeDelta period,
     DesktopMediaListObserver* observer)
     : DesktopMediaListBase(period),
-      view_(web_contents->GetRenderWidgetHostView()),
       media_id_(content::DesktopMediaID::TYPE_WEB_CONTENTS,
                 content::DesktopMediaID::kNullId,
                 content::WebContentsMediaCaptureId(
@@ -93,7 +92,6 @@
       thumbnail_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::USER_VISIBLE})) {
   DCHECK(web_contents);
-  DCHECK(view_);
 
   type_ = DesktopMediaList::Type::kCurrentTab;
 
@@ -107,11 +105,23 @@
 
 CurrentTabDesktopMediaList::~CurrentTabDesktopMediaList() = default;
 
-void CurrentTabDesktopMediaList::Refresh(bool update_thumnails) {
+void CurrentTabDesktopMediaList::Refresh(bool update_thumbnails) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(can_refresh());
 
-  if (refresh_in_progress_ || !update_thumnails || thumbnail_size_.IsEmpty()) {
+  if (refresh_in_progress_ || !update_thumbnails || thumbnail_size_.IsEmpty()) {
+    return;
+  }
+
+  content::RenderFrameHost* const host = content::RenderFrameHost::FromID(
+      media_id_.web_contents_id.render_process_id,
+      media_id_.web_contents_id.main_render_frame_id);
+  if (!host) {
+    return;
+  }
+
+  content::RenderWidgetHostView* const view = host->GetView();
+  if (!view) {
     return;
   }
 
@@ -120,7 +130,7 @@
   auto reply = base::BindOnce(&CurrentTabDesktopMediaList::OnCaptureHandled,
                               weak_factory_.GetWeakPtr());
 
-  view_->CopyFromSurface(
+  view->CopyFromSurface(
       gfx::Rect(), gfx::Size(),
       base::BindPostTask(thumbnail_task_runner_,
                          base::BindOnce(&HandleCapturedBitmap, std::move(reply),
diff --git a/chrome/browser/media/webrtc/current_tab_desktop_media_list.h b/chrome/browser/media/webrtc/current_tab_desktop_media_list.h
index ec12b80..61fb2b5 100644
--- a/chrome/browser/media/webrtc/current_tab_desktop_media_list.h
+++ b/chrome/browser/media/webrtc/current_tab_desktop_media_list.h
@@ -23,7 +23,7 @@
                              base::TimeDelta period,
                              DesktopMediaListObserver* observer);
 
-  void Refresh(bool update_thumnails) override;
+  void Refresh(bool update_thumbnails) override;
 
   // Called on the UI thread after the captured image is handled. If the
   // image was new, it's rescaled to the desired size and sent back in |image|.
@@ -38,7 +38,6 @@
   void ResetLastHashForTesting();
 
   // This "list" tracks a single view - the one represented by these variables.
-  content::RenderWidgetHostView* const view_;
   const content::DesktopMediaID media_id_;
 
   // Avoid two concurrent refreshes.
diff --git a/chrome/browser/media/webrtc/current_tab_desktop_media_list_unittest.cc b/chrome/browser/media/webrtc/current_tab_desktop_media_list_unittest.cc
index cd5d2d7..964b52dc 100644
--- a/chrome/browser/media/webrtc/current_tab_desktop_media_list_unittest.cc
+++ b/chrome/browser/media/webrtc/current_tab_desktop_media_list_unittest.cc
@@ -165,6 +165,8 @@
 
   void ResetLastHash() { list_->ResetLastHashForTesting(); }
 
+  void RefreshList() { list_->Refresh(/*update_thumbnails=*/true); }
+
   // The path to temporary directory used to contain the test operations.
   base::ScopedTempDir temp_dir_;
   ScopedTestingLocalState local_state_;
@@ -268,4 +270,23 @@
   task_environment_.AdvanceClock(kUpdatePeriod);
 }
 
+TEST_F(CurrentTabDesktopMediaListTest, CallingRefreshAfterTabFreedIsSafe) {
+  constexpr size_t kMainTab = 3;
+  WebContents* const web_contents = all_web_contents_[kMainTab];
+
+  // Setup.
+  EXPECT_CALL(observer_, OnSourceAdded(_, 0)).Times(1);
+  EXPECT_CALL(observer_, OnSourceThumbnailChanged(_, 0))
+      .Times(1)
+      .WillOnce(QuitMessageLoop(run_loop_.get()));
+  list_ = CreateCurrentTabDesktopMediaList(web_contents);
+  Wait();
+
+  // Simulate tab closing.
+  RemoveWebContents(web_contents);
+
+  // Test focus - no crash.
+  RefreshList();
+}
+
 // TODO(crbug.com/1136942): Test rescaling of the thumbnails.
diff --git a/chrome/browser/media_galleries/chromeos/mtp_device_delegate_impl_chromeos.cc b/chrome/browser/media_galleries/chromeos/mtp_device_delegate_impl_chromeos.cc
index db2cc4ee..08048a3 100644
--- a/chrome/browser/media_galleries/chromeos/mtp_device_delegate_impl_chromeos.cc
+++ b/chrome/browser/media_galleries/chromeos/mtp_device_delegate_impl_chromeos.cc
@@ -39,6 +39,18 @@
 // File path separator constant.
 const char kRootPath[] = "/";
 
+// ErrorCallback should only be called once, but in some cases, there's more
+// than one branch that may invoke it. This utility converts an ErrorCallback to
+// a RepeatingCallback so ownership can be passed off more than once. There will
+// still be a runtime error if the ErrorCallback is called more than once.
+base::RepeatingCallback<void(base::File::Error error)>
+MakeErrorCallbackRepeating(MTPDeviceAsyncDelegate::ErrorCallback callback) {
+  return base::BindRepeating(
+      [](MTPDeviceAsyncDelegate::ErrorCallback callback,
+         base::File::Error error) { std::move(callback).Run(error); },
+      base::Passed(&callback));
+}
+
 // Helper function to create |MTPDeviceDelegateImplLinux::storage_name_|.
 std::string CreateStorageName(const std::string& device_location) {
   std::string storage_name;
@@ -123,15 +135,16 @@
     const bool read_only,
     const uint32_t parent_id,
     const std::string& directory_name,
-    const MTPDeviceTaskHelper::CreateDirectorySuccessCallback& success_callback,
-    const MTPDeviceTaskHelper::ErrorCallback& error_callback) {
+    MTPDeviceTaskHelper::CreateDirectorySuccessCallback success_callback,
+    MTPDeviceTaskHelper::ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
-  task_helper->CreateDirectory(parent_id, directory_name, success_callback,
-                               error_callback);
+  task_helper->CreateDirectory(parent_id, directory_name,
+                               std::move(success_callback),
+                               std::move(error_callback));
 }
 
 // Enumerates the |directory_id| directory file entries.
@@ -148,14 +161,15 @@
     const std::string& storage_name,
     const bool read_only,
     const uint32_t directory_id,
-    const MTPDeviceTaskHelper::ReadDirectorySuccessCallback& success_callback,
-    const MTPDeviceTaskHelper::ErrorCallback& error_callback) {
+    MTPDeviceTaskHelper::ReadDirectorySuccessCallback success_callback,
+    MTPDeviceTaskHelper::ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
-  task_helper->ReadDirectory(directory_id, success_callback, error_callback);
+  task_helper->ReadDirectory(directory_id, std::move(success_callback),
+                             std::move(error_callback));
 }
 
 // Checks if the |directory_id| directory is empty.
@@ -173,14 +187,14 @@
     bool read_only,
     uint32_t directory_id,
     MTPDeviceTaskHelper::CheckDirectoryEmptySuccessCallback success_callback,
-    const MTPDeviceTaskHelper::ErrorCallback& error_callback) {
+    MTPDeviceTaskHelper::ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
   task_helper->CheckDirectoryEmpty(directory_id, std::move(success_callback),
-                                   error_callback);
+                                   std::move(error_callback));
 }
 
 // Gets the |file_path| details.
@@ -197,14 +211,14 @@
     const bool read_only,
     uint32_t file_id,
     MTPDeviceTaskHelper::GetFileInfoSuccessCallback success_callback,
-    const MTPDeviceTaskHelper::ErrorCallback& error_callback) {
+    MTPDeviceTaskHelper::ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
   task_helper->GetFileInfo(file_id, std::move(success_callback),
-                           error_callback);
+                           std::move(error_callback));
 }
 
 // Copies the contents of |device_file_path| to |snapshot_file_path|.
@@ -223,14 +237,15 @@
 void WriteDataIntoSnapshotFileOnUIThread(
     const std::string& storage_name,
     const bool read_only,
-    const SnapshotRequestInfo& request_info,
+    SnapshotRequestInfo request_info,
     const base::File::Info& snapshot_file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
-  task_helper->WriteDataIntoSnapshotFile(request_info, snapshot_file_info);
+  task_helper->WriteDataIntoSnapshotFile(std::move(request_info),
+                                         snapshot_file_info);
 }
 
 // Copies the contents of |device_file_path| to |snapshot_file_path|.
@@ -240,16 +255,15 @@
 // |storage_name| specifies the name of the storage device.
 // |read_only| specifies the mode of the storage device.
 // |request| is a struct containing details about the byte read request.
-void ReadBytesOnUIThread(
-    const std::string& storage_name,
-    const bool read_only,
-    const MTPDeviceAsyncDelegate::ReadBytesRequest& request) {
+void ReadBytesOnUIThread(const std::string& storage_name,
+                         const bool read_only,
+                         MTPDeviceAsyncDelegate::ReadBytesRequest request) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
-  task_helper->ReadBytes(request);
+  task_helper->ReadBytes(std::move(request));
 }
 
 // Renames |object_id| to |new_name|.
@@ -266,15 +280,15 @@
     const bool read_only,
     const uint32_t object_id,
     const std::string& new_name,
-    const MTPDeviceTaskHelper::RenameObjectSuccessCallback& success_callback,
-    const MTPDeviceTaskHelper::ErrorCallback& error_callback) {
+    MTPDeviceTaskHelper::RenameObjectSuccessCallback success_callback,
+    MTPDeviceTaskHelper::ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
-  task_helper->RenameObject(object_id, new_name, success_callback,
-                            error_callback);
+  task_helper->RenameObject(object_id, new_name, std::move(success_callback),
+                            std::move(error_callback));
 }
 
 // Copies the file |source_file_descriptor| to |file_name| in |parent_id|.
@@ -294,17 +308,16 @@
     const int source_file_descriptor,
     const uint32_t parent_id,
     const std::string& file_name,
-    const MTPDeviceTaskHelper::CopyFileFromLocalSuccessCallback&
-        success_callback,
-    const MTPDeviceTaskHelper::ErrorCallback& error_callback) {
+    MTPDeviceTaskHelper::CopyFileFromLocalSuccessCallback success_callback,
+    MTPDeviceTaskHelper::ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
-  task_helper->CopyFileFromLocal(storage_name, source_file_descriptor,
-                                 parent_id, file_name, success_callback,
-                                 error_callback);
+  task_helper->CopyFileFromLocal(
+      storage_name, source_file_descriptor, parent_id, file_name,
+      std::move(success_callback), std::move(error_callback));
 }
 
 // Deletes |object_id|.
@@ -321,14 +334,15 @@
     const std::string storage_name,
     const bool read_only,
     const uint32_t object_id,
-    const MTPDeviceTaskHelper::DeleteObjectSuccessCallback success_callback,
-    const MTPDeviceTaskHelper::ErrorCallback error_callback) {
+    const MTPDeviceTaskHelper::DeleteObjectSuccessCallback& success_callback,
+    MTPDeviceTaskHelper::ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   MTPDeviceTaskHelper* task_helper =
       GetDeviceTaskHelperForStorage(storage_name, read_only);
   if (!task_helper)
     return;
-  task_helper->DeleteObject(object_id, success_callback, error_callback);
+  task_helper->DeleteObject(object_id, success_callback,
+                            std::move(error_callback));
 }
 
 // Closes the device storage specified by the |storage_name| and destroys the
@@ -543,14 +557,14 @@
     const base::FilePath& directory_path,
     const bool exclusive,
     const bool recursive,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!directory_path.empty());
 
   // If |directory_path| is not the path in this device, fails with error.
   if (!device_path_.IsParent(directory_path)) {
-    error_callback.Run(base::File::FILE_ERROR_FAILED);
+    std::move(error_callback).Run(base::File::FILE_ERROR_FAILED);
     return;
   }
 
@@ -569,7 +583,7 @@
   base::OnceClosure closure =
       base::BindOnce(&MTPDeviceDelegateImplLinux::CreateDirectoryInternal,
                      weak_ptr_factory_.GetWeakPtr(), components, exclusive,
-                     success_callback, error_callback);
+                     std::move(success_callback), std::move(error_callback));
   EnsureInitAndRunTask(PendingTaskInfo(directory_path,
                                        content::BrowserThread::IO, FROM_HERE,
                                        std::move(closure)));
@@ -578,7 +592,7 @@
 void MTPDeviceDelegateImplLinux::GetFileInfo(
     const base::FilePath& file_path,
     GetFileInfoSuccessCallback success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!file_path.empty());
 
@@ -595,20 +609,21 @@
   base::OnceClosure closure =
       base::BindOnce(&MTPDeviceDelegateImplLinux::GetFileInfoInternal,
                      weak_ptr_factory_.GetWeakPtr(), file_path,
-                     std::move(success_callback), error_callback);
+                     std::move(success_callback), std::move(error_callback));
   EnsureInitAndRunTask(PendingTaskInfo(file_path, content::BrowserThread::IO,
                                        FROM_HERE, std::move(closure)));
 }
 
 void MTPDeviceDelegateImplLinux::ReadDirectory(
     const base::FilePath& root,
-    const ReadDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ReadDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!root.empty());
-  base::OnceClosure closure = base::BindOnce(
-      &MTPDeviceDelegateImplLinux::ReadDirectoryInternal,
-      weak_ptr_factory_.GetWeakPtr(), root, success_callback, error_callback);
+  base::OnceClosure closure =
+      base::BindOnce(&MTPDeviceDelegateImplLinux::ReadDirectoryInternal,
+                     weak_ptr_factory_.GetWeakPtr(), root,
+                     std::move(success_callback), std::move(error_callback));
   EnsureInitAndRunTask(PendingTaskInfo(root, content::BrowserThread::IO,
                                        FROM_HERE, std::move(closure)));
 }
@@ -616,15 +631,15 @@
 void MTPDeviceDelegateImplLinux::CreateSnapshotFile(
     const base::FilePath& device_file_path,
     const base::FilePath& local_path,
-    const CreateSnapshotFileSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateSnapshotFileSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!device_file_path.empty());
   DCHECK(!local_path.empty());
-  base::OnceClosure closure =
-      base::BindOnce(&MTPDeviceDelegateImplLinux::CreateSnapshotFileInternal,
-                     weak_ptr_factory_.GetWeakPtr(), device_file_path,
-                     local_path, success_callback, error_callback);
+  base::OnceClosure closure = base::BindOnce(
+      &MTPDeviceDelegateImplLinux::CreateSnapshotFileInternal,
+      weak_ptr_factory_.GetWeakPtr(), device_file_path, local_path,
+      std::move(success_callback), std::move(error_callback));
   EnsureInitAndRunTask(PendingTaskInfo(device_file_path,
                                        content::BrowserThread::IO, FROM_HERE,
                                        std::move(closure)));
@@ -639,14 +654,14 @@
     const scoped_refptr<net::IOBuffer>& buf,
     int64_t offset,
     int buf_len,
-    const ReadBytesSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ReadBytesSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!device_file_path.empty());
   base::OnceClosure closure = base::BindOnce(
       &MTPDeviceDelegateImplLinux::ReadBytesInternal,
       weak_ptr_factory_.GetWeakPtr(), device_file_path, base::RetainedRef(buf),
-      offset, buf_len, success_callback, error_callback);
+      offset, buf_len, std::move(success_callback), std::move(error_callback));
   EnsureInitAndRunTask(PendingTaskInfo(device_file_path,
                                        content::BrowserThread::IO, FROM_HERE,
                                        std::move(closure)));
@@ -661,8 +676,8 @@
     const base::FilePath& device_file_path,
     const CreateTemporaryFileCallback& create_temporary_file_callback,
     const CopyFileProgressCallback& progress_callback,
-    const CopyFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CopyFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!source_file_path.empty());
   DCHECK(!device_file_path.empty());
@@ -676,28 +691,31 @@
       base::BindOnce(
           &MTPDeviceDelegateImplLinux::OnDidCreateTemporaryFileToCopyFileLocal,
           weak_ptr_factory_.GetWeakPtr(), source_file_path, device_file_path,
-          progress_callback, success_callback, error_callback));
+          progress_callback, std::move(success_callback),
+          std::move(error_callback)));
 }
 
 void MTPDeviceDelegateImplLinux::MoveFileLocal(
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
     const CreateTemporaryFileCallback& create_temporary_file_callback,
-    const MoveFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    MoveFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!source_file_path.empty());
   DCHECK(!device_file_path.empty());
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
   // Get file info to move file on local.
-  GetFileInfoSuccessCallback success_callback_wrapper = base::BindOnce(
-      &MTPDeviceDelegateImplLinux::MoveFileLocalInternal,
-      weak_ptr_factory_.GetWeakPtr(), source_file_path, device_file_path,
-      create_temporary_file_callback, success_callback, error_callback);
+  GetFileInfoSuccessCallback success_callback_wrapper =
+      base::BindOnce(&MTPDeviceDelegateImplLinux::MoveFileLocalInternal,
+                     weak_ptr_factory_.GetWeakPtr(), source_file_path,
+                     device_file_path, create_temporary_file_callback,
+                     std::move(success_callback), repeating_error);
   base::OnceClosure closure =
       base::BindOnce(&MTPDeviceDelegateImplLinux::GetFileInfoInternal,
                      weak_ptr_factory_.GetWeakPtr(), source_file_path,
-                     std::move(success_callback_wrapper), error_callback);
+                     std::move(success_callback_wrapper), repeating_error);
   EnsureInitAndRunTask(PendingTaskInfo(source_file_path,
                                        content::BrowserThread::IO, FROM_HERE,
                                        std::move(closure)));
@@ -706,24 +724,26 @@
 void MTPDeviceDelegateImplLinux::CopyFileFromLocal(
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
-    const CopyFileFromLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CopyFileFromLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!source_file_path.empty());
   DCHECK(!device_file_path.empty());
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
+
   // Get file info of destination file path.
   GetFileInfoSuccessCallback success_callback_wrapper = base::BindOnce(
       &MTPDeviceDelegateImplLinux::OnDidGetDestFileInfoToCopyFileFromLocal,
-      weak_ptr_factory_.GetWeakPtr(), error_callback);
-  const ErrorCallback error_callback_wrapper = base::Bind(
+      weak_ptr_factory_.GetWeakPtr(), repeating_error);
+  ErrorCallback error_callback_wrapper = base::BindOnce(
       &MTPDeviceDelegateImplLinux::OnGetDestFileInfoErrorToCopyFileFromLocal,
       weak_ptr_factory_.GetWeakPtr(), source_file_path, device_file_path,
-      success_callback, error_callback);
+      std::move(success_callback), repeating_error);
   base::OnceClosure closure = base::BindOnce(
       &MTPDeviceDelegateImplLinux::GetFileInfoInternal,
       weak_ptr_factory_.GetWeakPtr(), device_file_path,
-      std::move(success_callback_wrapper), error_callback_wrapper);
+      std::move(success_callback_wrapper), std::move(error_callback_wrapper));
   EnsureInitAndRunTask(PendingTaskInfo(device_file_path,
                                        content::BrowserThread::IO, FROM_HERE,
                                        std::move(closure)));
@@ -731,40 +751,42 @@
 
 void MTPDeviceDelegateImplLinux::DeleteFile(
     const base::FilePath& file_path,
-    const DeleteFileSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    DeleteFileSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!file_path.empty());
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
   GetFileInfoSuccessCallback success_callback_wrapper =
       base::BindOnce(&MTPDeviceDelegateImplLinux::DeleteFileInternal,
                      weak_ptr_factory_.GetWeakPtr(), file_path,
-                     success_callback, error_callback);
+                     std::move(success_callback), repeating_error);
 
   base::OnceClosure closure =
       base::BindOnce(&MTPDeviceDelegateImplLinux::GetFileInfoInternal,
                      weak_ptr_factory_.GetWeakPtr(), file_path,
-                     std::move(success_callback_wrapper), error_callback);
+                     std::move(success_callback_wrapper), repeating_error);
   EnsureInitAndRunTask(PendingTaskInfo(file_path, content::BrowserThread::IO,
                                        FROM_HERE, std::move(closure)));
 }
 
 void MTPDeviceDelegateImplLinux::DeleteDirectory(
     const base::FilePath& file_path,
-    const DeleteDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    DeleteDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!file_path.empty());
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
   GetFileInfoSuccessCallback success_callback_wrapper =
       base::BindOnce(&MTPDeviceDelegateImplLinux::DeleteDirectoryInternal,
                      weak_ptr_factory_.GetWeakPtr(), file_path,
-                     success_callback, error_callback);
+                     std::move(success_callback), repeating_error);
 
   base::OnceClosure closure =
       base::BindOnce(&MTPDeviceDelegateImplLinux::GetFileInfoInternal,
                      weak_ptr_factory_.GetWeakPtr(), file_path,
-                     std::move(success_callback_wrapper), error_callback);
+                     std::move(success_callback_wrapper), repeating_error);
   EnsureInitAndRunTask(PendingTaskInfo(file_path, content::BrowserThread::IO,
                                        FROM_HERE, std::move(closure)));
 }
@@ -850,7 +872,7 @@
 void MTPDeviceDelegateImplLinux::GetFileInfoInternal(
     const base::FilePath& file_path,
     GetFileInfoSuccessCallback success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   base::Optional<uint32_t> file_id = CachedPathToId(file_path);
@@ -858,18 +880,18 @@
     GetFileInfoSuccessCallback success_callback_wrapper = base::BindOnce(
         &MTPDeviceDelegateImplLinux::OnDidGetFileInfo,
         weak_ptr_factory_.GetWeakPtr(), std::move(success_callback));
-    ErrorCallback error_callback_wrapper =
-        base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
-                   weak_ptr_factory_.GetWeakPtr(), error_callback, *file_id);
+    ErrorCallback error_callback_wrapper = base::BindOnce(
+        &MTPDeviceDelegateImplLinux::HandleDeviceFileError,
+        weak_ptr_factory_.GetWeakPtr(), std::move(error_callback), *file_id);
 
     base::OnceClosure closure = base::BindOnce(
         &GetFileInfoOnUIThread, storage_name_, read_only_, *file_id,
-        std::move(success_callback_wrapper), error_callback_wrapper);
+        std::move(success_callback_wrapper), std::move(error_callback_wrapper));
     EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                          content::BrowserThread::UI, FROM_HERE,
                                          std::move(closure)));
   } else {
-    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
   }
   PendingRequestDone();
 }
@@ -877,8 +899,8 @@
 void MTPDeviceDelegateImplLinux::CreateDirectoryInternal(
     const std::vector<base::FilePath>& components,
     const bool exclusive,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   const base::FilePath current_component = components.back();
@@ -891,15 +913,15 @@
     base::Optional<uint32_t> parent_id =
         CachedPathToId(current_component.DirName());
     if (parent_id) {
-      base::OnceClosure closure =
-          base::BindOnce(&MTPDeviceDelegateImplLinux::CreateSingleDirectory,
-                         weak_ptr_factory_.GetWeakPtr(), current_component,
-                         exclusive, success_callback, error_callback);
+      base::OnceClosure closure = base::BindOnce(
+          &MTPDeviceDelegateImplLinux::CreateSingleDirectory,
+          weak_ptr_factory_.GetWeakPtr(), current_component, exclusive,
+          std::move(success_callback), std::move(error_callback));
       EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                            content::BrowserThread::IO,
                                            FROM_HERE, std::move(closure)));
     } else {
-      error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+      std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
     }
   } else {
     // Ensures that parent directories are created for recursive case.
@@ -907,32 +929,33 @@
     if (directory_id) {
       // Parent directory |current_component| already exists, continue creating
       // directories.
-      base::OnceClosure closure =
-          base::BindOnce(&MTPDeviceDelegateImplLinux::CreateDirectoryInternal,
-                         weak_ptr_factory_.GetWeakPtr(), other_components,
-                         exclusive, success_callback, error_callback);
+      base::OnceClosure closure = base::BindOnce(
+          &MTPDeviceDelegateImplLinux::CreateDirectoryInternal,
+          weak_ptr_factory_.GetWeakPtr(), other_components, exclusive,
+          std::move(success_callback), std::move(error_callback));
       EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                            content::BrowserThread::IO,
                                            FROM_HERE, std::move(closure)));
     } else {
+      auto repeating_error =
+          MakeErrorCallbackRepeating(std::move(error_callback));
       // If parent directory |current_component| does not exist, create it.
-      const CreateDirectorySuccessCallback success_callback_wrapper =
-          base::Bind(&MTPDeviceDelegateImplLinux::
-                         OnDidCreateParentDirectoryToCreateDirectory,
-                     weak_ptr_factory_.GetWeakPtr(), current_component,
-                     other_components, exclusive, success_callback,
-                     error_callback);
+      CreateDirectorySuccessCallback success_callback_wrapper = base::BindOnce(
+          &MTPDeviceDelegateImplLinux::
+              OnDidCreateParentDirectoryToCreateDirectory,
+          weak_ptr_factory_.GetWeakPtr(), current_component, other_components,
+          exclusive, std::move(success_callback), repeating_error);
       // Wraps error callback to return all errors of creating parent
       // directories as FILE_ERROR_FAILED.
-      const ErrorCallback error_callback_wrapper =
-          base::Bind(&MTPDeviceDelegateImplLinux::
-                         OnCreateParentDirectoryErrorToCreateDirectory,
-                     weak_ptr_factory_.GetWeakPtr(), error_callback);
-      base::OnceClosure closure =
-          base::BindOnce(&MTPDeviceDelegateImplLinux::CreateSingleDirectory,
-                         weak_ptr_factory_.GetWeakPtr(), current_component,
-                         false /* not exclusive */, success_callback_wrapper,
-                         error_callback_wrapper);
+      ErrorCallback error_callback_wrapper =
+          base::BindOnce(&MTPDeviceDelegateImplLinux::
+                             OnCreateParentDirectoryErrorToCreateDirectory,
+                         weak_ptr_factory_.GetWeakPtr(), repeating_error);
+      base::OnceClosure closure = base::BindOnce(
+          &MTPDeviceDelegateImplLinux::CreateSingleDirectory,
+          weak_ptr_factory_.GetWeakPtr(), current_component,
+          false /* not exclusive */, std::move(success_callback_wrapper),
+          std::move(error_callback_wrapper));
       EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                            content::BrowserThread::IO,
                                            FROM_HERE, std::move(closure)));
@@ -944,28 +967,30 @@
 
 void MTPDeviceDelegateImplLinux::ReadDirectoryInternal(
     const base::FilePath& root,
-    const ReadDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ReadDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(task_in_progress_);
 
   base::Optional<uint32_t> dir_id = CachedPathToId(root);
   if (!dir_id) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
     PendingRequestDone();
     return;
   }
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
+
   GetFileInfoSuccessCallback success_callback_wrapper = base::BindOnce(
       &MTPDeviceDelegateImplLinux::OnDidGetFileInfoToReadDirectory,
-      weak_ptr_factory_.GetWeakPtr(), *dir_id, success_callback,
-      error_callback);
+      weak_ptr_factory_.GetWeakPtr(), *dir_id, std::move(success_callback),
+      repeating_error);
   ErrorCallback error_callback_wrapper =
-      base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
-                 weak_ptr_factory_.GetWeakPtr(), error_callback, *dir_id);
+      base::BindOnce(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
+                     weak_ptr_factory_.GetWeakPtr(), repeating_error, *dir_id);
   base::OnceClosure closure = base::BindOnce(
       &GetFileInfoOnUIThread, storage_name_, read_only_, *dir_id,
-      std::move(success_callback_wrapper), error_callback_wrapper);
+      std::move(success_callback_wrapper), std::move(error_callback_wrapper));
 
   content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(closure));
 }
@@ -973,28 +998,30 @@
 void MTPDeviceDelegateImplLinux::CreateSnapshotFileInternal(
     const base::FilePath& device_file_path,
     const base::FilePath& local_path,
-    const CreateSnapshotFileSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateSnapshotFileSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   base::Optional<uint32_t> file_id = CachedPathToId(device_file_path);
   if (file_id) {
+    auto repeating_error =
+        MakeErrorCallbackRepeating(std::move(error_callback));
     auto request_info = std::make_unique<SnapshotRequestInfo>(
-        *file_id, local_path, success_callback, error_callback);
+        *file_id, local_path, std::move(success_callback), repeating_error);
     GetFileInfoSuccessCallback success_callback_wrapper = base::BindOnce(
         &MTPDeviceDelegateImplLinux::OnDidGetFileInfoToCreateSnapshotFile,
         weak_ptr_factory_.GetWeakPtr(), std::move(request_info));
-    ErrorCallback error_callback_wrapper =
-        base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
-                   weak_ptr_factory_.GetWeakPtr(), error_callback, *file_id);
+    ErrorCallback error_callback_wrapper = base::BindOnce(
+        &MTPDeviceDelegateImplLinux::HandleDeviceFileError,
+        weak_ptr_factory_.GetWeakPtr(), repeating_error, *file_id);
     base::OnceClosure closure = base::BindOnce(
         &GetFileInfoOnUIThread, storage_name_, read_only_, *file_id,
-        std::move(success_callback_wrapper), error_callback_wrapper);
+        std::move(success_callback_wrapper), std::move(error_callback_wrapper));
     EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                          content::BrowserThread::UI, FROM_HERE,
                                          std::move(closure)));
   } else {
-    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
   }
   PendingRequestDone();
 }
@@ -1004,27 +1031,28 @@
     net::IOBuffer* buf,
     int64_t offset,
     int buf_len,
-    const ReadBytesSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ReadBytesSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   base::Optional<uint32_t> file_id = CachedPathToId(device_file_path);
   if (file_id) {
     ReadBytesRequest request(
         *file_id, buf, offset, buf_len,
-        base::Bind(&MTPDeviceDelegateImplLinux::OnDidReadBytes,
-                   weak_ptr_factory_.GetWeakPtr(), success_callback),
-        base::BindRepeating(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
-                            weak_ptr_factory_.GetWeakPtr(), error_callback,
-                            *file_id));
+        base::BindOnce(&MTPDeviceDelegateImplLinux::OnDidReadBytes,
+                       weak_ptr_factory_.GetWeakPtr(),
+                       std::move(success_callback)),
+        base::BindOnce(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
+                       weak_ptr_factory_.GetWeakPtr(),
+                       std::move(error_callback), *file_id));
 
     base::OnceClosure closure = base::BindOnce(
-        &ReadBytesOnUIThread, storage_name_, read_only_, request);
+        &ReadBytesOnUIThread, storage_name_, read_only_, std::move(request));
     EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                          content::BrowserThread::UI, FROM_HERE,
                                          std::move(closure)));
   } else {
-    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
   }
   PendingRequestDone();
 }
@@ -1033,13 +1061,13 @@
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
     const CreateTemporaryFileCallback& create_temporary_file_callback,
-    const MoveFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    MoveFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::File::Info& source_file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (source_file_info.is_directory) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_A_FILE);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_A_FILE);
     return;
   }
 
@@ -1048,48 +1076,50 @@
     base::Optional<uint32_t> file_id = CachedPathToId(source_file_path);
     if (file_id) {
       const MTPDeviceTaskHelper::RenameObjectSuccessCallback
-          success_callback_wrapper = base::Bind(
+          success_callback_wrapper = base::BindRepeating(
               &MTPDeviceDelegateImplLinux::OnDidMoveFileLocalWithRename,
-              weak_ptr_factory_.GetWeakPtr(), success_callback,
+              weak_ptr_factory_.GetWeakPtr(), base::Passed(&success_callback),
               source_file_path, *file_id);
-      const MTPDeviceTaskHelper::ErrorCallback error_callback_wrapper =
-          base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
-                     weak_ptr_factory_.GetWeakPtr(), error_callback, *file_id);
-      base::OnceClosure closure =
-          base::BindOnce(&RenameObjectOnUIThread, storage_name_, read_only_,
-                         *file_id, device_file_path.BaseName().value(),
-                         success_callback_wrapper, error_callback_wrapper);
+      MTPDeviceTaskHelper::ErrorCallback error_callback_wrapper =
+          base::BindOnce(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
+                         weak_ptr_factory_.GetWeakPtr(),
+                         std::move(error_callback), *file_id);
+      base::OnceClosure closure = base::BindOnce(
+          &RenameObjectOnUIThread, storage_name_, read_only_, *file_id,
+          device_file_path.BaseName().value(), success_callback_wrapper,
+          std::move(error_callback_wrapper));
       EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                            content::BrowserThread::UI,
                                            FROM_HERE, std::move(closure)));
     } else {
-      error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+      std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
     }
     return;
   }
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
   // If a file is moved to a different directory, create a copy to the
   // destination path, and remove source file.
-  const CopyFileLocalSuccessCallback& success_callback_wrapper =
-      base::Bind(&MTPDeviceDelegateImplLinux::DeleteFileInternal,
-                 weak_ptr_factory_.GetWeakPtr(), source_file_path,
-                 success_callback, error_callback, source_file_info);
+  CopyFileLocalSuccessCallback success_callback_wrapper = base::BindOnce(
+      &MTPDeviceDelegateImplLinux::DeleteFileInternal,
+      weak_ptr_factory_.GetWeakPtr(), source_file_path,
+      std::move(success_callback), repeating_error, source_file_info);
   // TODO(yawano): Avoid to call external method from internal code.
   CopyFileLocal(source_file_path, device_file_path,
                 create_temporary_file_callback,
                 base::Bind(&FakeCopyFileProgressCallback),
-                success_callback_wrapper, error_callback);
+                std::move(success_callback_wrapper), repeating_error);
 }
 
 void MTPDeviceDelegateImplLinux::OnDidOpenFDToCopyFileFromLocal(
     const base::FilePath& device_file_path,
-    const CopyFileFromLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    CopyFileFromLocalSuccessCallback success_callback,
+    ErrorCallback error_callback,
     const std::pair<int, base::File::Error>& open_fd_result) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (open_fd_result.second != base::File::FILE_OK) {
-    error_callback.Run(open_fd_result.second);
+    std::move(error_callback).Run(open_fd_result.second);
     return;
   }
 
@@ -1097,24 +1127,27 @@
   base::Optional<uint32_t> parent_id =
       CachedPathToId(device_file_path.DirName());
   if (!parent_id) {
-    HandleCopyFileFromLocalError(error_callback, source_file_descriptor,
+    HandleCopyFileFromLocalError(std::move(error_callback),
+                                 source_file_descriptor,
                                  base::File::FILE_ERROR_NOT_FOUND);
     return;
   }
 
-  CopyFileFromLocalSuccessCallback success_callback_wrapper =
-      base::Bind(&MTPDeviceDelegateImplLinux::OnDidCopyFileFromLocal,
-                 weak_ptr_factory_.GetWeakPtr(), success_callback,
-                 device_file_path, source_file_descriptor);
+  MTPDeviceTaskHelper::CopyFileFromLocalSuccessCallback
+      success_callback_wrapper = base::BindRepeating(
+          &MTPDeviceDelegateImplLinux::OnDidCopyFileFromLocal,
+          weak_ptr_factory_.GetWeakPtr(), base::Passed(&success_callback),
+          device_file_path, source_file_descriptor);
 
-  ErrorCallback error_callback_wrapper = base::Bind(
-      &MTPDeviceDelegateImplLinux::HandleCopyFileFromLocalError,
-      weak_ptr_factory_.GetWeakPtr(), error_callback, source_file_descriptor);
+  ErrorCallback error_callback_wrapper =
+      base::BindOnce(&MTPDeviceDelegateImplLinux::HandleCopyFileFromLocalError,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(error_callback),
+                     source_file_descriptor);
 
   base::OnceClosure closure = base::BindOnce(
       &CopyFileFromLocalOnUIThread, storage_name_, read_only_,
       source_file_descriptor, *parent_id, device_file_path.BaseName().value(),
-      success_callback_wrapper, error_callback_wrapper);
+      success_callback_wrapper, std::move(error_callback_wrapper));
 
   EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                        content::BrowserThread::UI, FROM_HERE,
@@ -1123,41 +1156,41 @@
 
 void MTPDeviceDelegateImplLinux::DeleteFileInternal(
     const base::FilePath& file_path,
-    const DeleteFileSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    DeleteFileSuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::File::Info& file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (file_info.is_directory) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_A_FILE);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_A_FILE);
     return;
   }
 
   base::Optional<uint32_t> file_id = CachedPathToId(file_path);
   if (!file_id) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
     return;
   }
 
-  RunDeleteObjectOnUIThread(file_path, *file_id, success_callback,
-                            error_callback);
+  RunDeleteObjectOnUIThread(file_path, *file_id, std::move(success_callback),
+                            std::move(error_callback));
 }
 
 void MTPDeviceDelegateImplLinux::DeleteDirectoryInternal(
     const base::FilePath& file_path,
-    const DeleteDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    DeleteDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::File::Info& file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (!file_info.is_directory) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_A_DIRECTORY);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_A_DIRECTORY);
     return;
   }
 
   base::Optional<uint32_t> directory_id = CachedPathToId(file_path);
   if (!directory_id) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
     return;
   }
 
@@ -1166,24 +1199,26 @@
   FileIdToMTPFileNodeMap::const_iterator it =
       file_id_to_node_map_.find(*directory_id);
   if (it != file_id_to_node_map_.end() && it->second->HasChildren()) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_EMPTY);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_EMPTY);
     return;
   }
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
+
   // Since the directory can contain a file even if the cache returns it as
   // empty, explicitly check the directory and confirm it is actually empty.
   MTPDeviceTaskHelper::CheckDirectoryEmptySuccessCallback
-      success_callback_wrapper =
-          base::BindOnce(&MTPDeviceDelegateImplLinux::
-                             OnDidCheckDirectoryEmptyToDeleteDirectory,
-                         weak_ptr_factory_.GetWeakPtr(), file_path,
-                         *directory_id, success_callback, error_callback);
-  const MTPDeviceTaskHelper::ErrorCallback error_callback_wrapper =
-      base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
-                 weak_ptr_factory_.GetWeakPtr(), error_callback, *directory_id);
+      success_callback_wrapper = base::BindOnce(
+          &MTPDeviceDelegateImplLinux::
+              OnDidCheckDirectoryEmptyToDeleteDirectory,
+          weak_ptr_factory_.GetWeakPtr(), file_path, *directory_id,
+          std::move(success_callback), repeating_error);
+  MTPDeviceTaskHelper::ErrorCallback error_callback_wrapper = base::BindOnce(
+      &MTPDeviceDelegateImplLinux::HandleDeviceFileError,
+      weak_ptr_factory_.GetWeakPtr(), repeating_error, *directory_id);
   base::OnceClosure closure = base::BindOnce(
       &CheckDirectoryEmptyOnUIThread, storage_name_, read_only_, *directory_id,
-      std::move(success_callback_wrapper), error_callback_wrapper);
+      std::move(success_callback_wrapper), std::move(error_callback_wrapper));
   EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                        content::BrowserThread::UI, FROM_HERE,
                                        std::move(closure)));
@@ -1192,22 +1227,27 @@
 void MTPDeviceDelegateImplLinux::CreateSingleDirectory(
     const base::FilePath& directory_path,
     const bool exclusive,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
+  auto repeating_success = base::BindRepeating(
+      [](base::OnceClosure closure) { std::move(closure).Run(); },
+      base::Passed(&success_callback));
+
   GetFileInfoSuccessCallback success_callback_wrapper = base::BindOnce(
       &MTPDeviceDelegateImplLinux::OnPathAlreadyExistsForCreateSingleDirectory,
-      weak_ptr_factory_.GetWeakPtr(), exclusive, success_callback,
-      error_callback);
-  const ErrorCallback error_callback_wrapper = base::Bind(
+      weak_ptr_factory_.GetWeakPtr(), exclusive, repeating_success,
+      repeating_error);
+  ErrorCallback error_callback_wrapper = base::BindOnce(
       &MTPDeviceDelegateImplLinux::OnPathDoesNotExistForCreateSingleDirectory,
-      weak_ptr_factory_.GetWeakPtr(), directory_path, success_callback,
-      error_callback);
+      weak_ptr_factory_.GetWeakPtr(), directory_path, repeating_success,
+      repeating_error);
   base::OnceClosure closure = base::BindOnce(
       &MTPDeviceDelegateImplLinux::GetFileInfoInternal,
       weak_ptr_factory_.GetWeakPtr(), directory_path,
-      std::move(success_callback_wrapper), error_callback_wrapper);
+      std::move(success_callback_wrapper), std::move(error_callback_wrapper));
   EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                        content::BrowserThread::IO, FROM_HERE,
                                        std::move(closure)));
@@ -1217,8 +1257,8 @@
 void MTPDeviceDelegateImplLinux::OnDidReadDirectoryToCreateDirectory(
     const std::vector<base::FilePath>& components,
     const bool exclusive,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback,
     storage::AsyncFileUtil::EntryList /* entries */,
     const bool has_more) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -1229,7 +1269,7 @@
   base::OnceClosure closure =
       base::BindOnce(&MTPDeviceDelegateImplLinux::CreateDirectoryInternal,
                      weak_ptr_factory_.GetWeakPtr(), components, exclusive,
-                     success_callback, error_callback);
+                     std::move(success_callback), std::move(error_callback));
   EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                        content::BrowserThread::IO, FROM_HERE,
                                        std::move(closure)));
@@ -1238,16 +1278,17 @@
 void MTPDeviceDelegateImplLinux::OnDidCheckDirectoryEmptyToDeleteDirectory(
     const base::FilePath& directory_path,
     uint32_t directory_id,
-    const DeleteDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    DeleteDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback,
     bool is_empty) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (is_empty) {
-    RunDeleteObjectOnUIThread(directory_path, directory_id, success_callback,
-                              error_callback);
+    RunDeleteObjectOnUIThread(directory_path, directory_id,
+                              std::move(success_callback),
+                              std::move(error_callback));
   } else {
-    error_callback.Run(base::File::FILE_ERROR_NOT_EMPTY);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_EMPTY);
   }
 
   PendingRequestDone();
@@ -1256,21 +1297,20 @@
 void MTPDeviceDelegateImplLinux::RunDeleteObjectOnUIThread(
     const base::FilePath& object_path,
     const uint32_t object_id,
-    const DeleteObjectSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
-  const MTPDeviceTaskHelper::DeleteObjectSuccessCallback
-      success_callback_wrapper =
-          base::Bind(&MTPDeviceDelegateImplLinux::OnDidDeleteObject,
-                     weak_ptr_factory_.GetWeakPtr(), object_path, object_id,
-                     success_callback);
+    DeleteObjectSuccessCallback success_callback,
+    ErrorCallback error_callback) {
+  MTPDeviceTaskHelper::DeleteObjectSuccessCallback success_callback_wrapper =
+      base::BindRepeating(&MTPDeviceDelegateImplLinux::OnDidDeleteObject,
+                          weak_ptr_factory_.GetWeakPtr(), object_path,
+                          object_id, base::Passed(&success_callback));
 
-  const MTPDeviceTaskHelper::ErrorCallback error_callback_wrapper =
-      base::Bind(&MTPDeviceDelegateImplLinux::HandleDeleteFileOrDirectoryError,
-                 weak_ptr_factory_.GetWeakPtr(), error_callback);
+  MTPDeviceTaskHelper::ErrorCallback error_callback_wrapper = base::BindOnce(
+      &MTPDeviceDelegateImplLinux::HandleDeleteFileOrDirectoryError,
+      weak_ptr_factory_.GetWeakPtr(), std::move(error_callback));
 
   base::OnceClosure closure = base::BindOnce(
       &DeleteObjectOnUIThread, storage_name_, read_only_, object_id,
-      success_callback_wrapper, error_callback_wrapper);
+      success_callback_wrapper, std::move(error_callback_wrapper));
   EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                        content::BrowserThread::UI, FROM_HERE,
                                        std::move(closure)));
@@ -1333,15 +1373,16 @@
   SnapshotRequestInfo request_info(
       current_snapshot_request_info_->file_id,
       current_snapshot_request_info_->snapshot_file_path,
-      base::Bind(&MTPDeviceDelegateImplLinux::OnDidWriteDataIntoSnapshotFile,
-                 weak_ptr_factory_.GetWeakPtr()),
+      base::BindRepeating(
+          &MTPDeviceDelegateImplLinux::OnDidWriteDataIntoSnapshotFile,
+          weak_ptr_factory_.GetWeakPtr()),
       base::BindRepeating(
           &MTPDeviceDelegateImplLinux::OnWriteDataIntoSnapshotFileError,
           weak_ptr_factory_.GetWeakPtr()));
 
   base::OnceClosure task_closure =
       base::BindOnce(&WriteDataIntoSnapshotFileOnUIThread, storage_name_,
-                     read_only_, request_info, file_info);
+                     read_only_, std::move(request_info), file_info);
   content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
                                                std::move(task_closure));
 }
@@ -1380,46 +1421,47 @@
 
 void MTPDeviceDelegateImplLinux::OnPathAlreadyExistsForCreateSingleDirectory(
     const bool exclusive,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::File::Info& file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (!file_info.is_directory || exclusive)
-    error_callback.Run(base::File::FILE_ERROR_EXISTS);
+    std::move(error_callback).Run(base::File::FILE_ERROR_EXISTS);
   else
-    success_callback.Run();
+    std::move(success_callback).Run();
 }
 
 void MTPDeviceDelegateImplLinux::OnPathDoesNotExistForCreateSingleDirectory(
     const base::FilePath& directory_path,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (error != base::File::FILE_ERROR_NOT_FOUND) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
     return;
   }
 
   base::Optional<uint32_t> parent_id = CachedPathToId(directory_path.DirName());
   if (!parent_id) {
-    error_callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+    std::move(error_callback).Run(base::File::FILE_ERROR_NOT_FOUND);
     return;
   }
 
   const MTPDeviceTaskHelper::CreateDirectorySuccessCallback
-      success_callback_wrapper = base::Bind(
-          &MTPDeviceDelegateImplLinux::OnDidCreateSingleDirectory,
-          weak_ptr_factory_.GetWeakPtr(), directory_path, success_callback);
-  const MTPDeviceTaskHelper::ErrorCallback error_callback_wrapper =
-      base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
-                 weak_ptr_factory_.GetWeakPtr(), error_callback, *parent_id);
-  base::OnceClosure closure =
-      base::BindOnce(&CreateDirectoryOnUIThread, storage_name_, read_only_,
-                     *parent_id, directory_path.BaseName().value(),
-                     success_callback_wrapper, error_callback_wrapper);
+      success_callback_wrapper =
+          base::Bind(&MTPDeviceDelegateImplLinux::OnDidCreateSingleDirectory,
+                     weak_ptr_factory_.GetWeakPtr(), directory_path,
+                     base::Passed(&success_callback));
+  MTPDeviceTaskHelper::ErrorCallback error_callback_wrapper = base::BindOnce(
+      &MTPDeviceDelegateImplLinux::HandleDeviceFileError,
+      weak_ptr_factory_.GetWeakPtr(), std::move(error_callback), *parent_id);
+  base::OnceClosure closure = base::BindOnce(
+      &CreateDirectoryOnUIThread, storage_name_, read_only_, *parent_id,
+      directory_path.BaseName().value(), success_callback_wrapper,
+      std::move(error_callback_wrapper));
   EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                        content::BrowserThread::UI, FROM_HERE,
                                        std::move(closure)));
@@ -1427,23 +1469,24 @@
 
 void MTPDeviceDelegateImplLinux::OnDidGetFileInfoToReadDirectory(
     uint32_t dir_id,
-    const ReadDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    ReadDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::File::Info& file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(task_in_progress_);
   if (!file_info.is_directory) {
-    return HandleDeviceFileError(error_callback,
-                                 dir_id,
+    return HandleDeviceFileError(std::move(error_callback), dir_id,
                                  base::File::FILE_ERROR_NOT_A_DIRECTORY);
   }
 
   base::OnceClosure task_closure = base::BindOnce(
       &ReadDirectoryOnUIThread, storage_name_, read_only_, dir_id,
-      base::Bind(&MTPDeviceDelegateImplLinux::OnDidReadDirectory,
-                 weak_ptr_factory_.GetWeakPtr(), dir_id, success_callback),
-      base::Bind(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
-                 weak_ptr_factory_.GetWeakPtr(), error_callback, dir_id));
+      base::BindRepeating(&MTPDeviceDelegateImplLinux::OnDidReadDirectory,
+                          weak_ptr_factory_.GetWeakPtr(), dir_id,
+                          base::Passed(&success_callback)),
+      base::BindOnce(&MTPDeviceDelegateImplLinux::HandleDeviceFileError,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(error_callback),
+                     dir_id));
   content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
                                                std::move(task_closure));
 }
@@ -1462,10 +1505,11 @@
            file_info.size > std::numeric_limits<uint32_t>::max())
     error = base::File::FILE_ERROR_FAILED;
 
-  if (error != base::File::FILE_OK)
-    return HandleDeviceFileError(snapshot_request_info->error_callback,
-                                 snapshot_request_info->file_id,
-                                 error);
+  if (error != base::File::FILE_OK) {
+    return HandleDeviceFileError(
+        std::move(snapshot_request_info->error_callback),
+        snapshot_request_info->file_id, error);
+  }
 
   base::File::Info snapshot_file_info(file_info);
   // Modify the last modified time to null. This prevents the time stamp
@@ -1482,26 +1526,26 @@
 }
 
 void MTPDeviceDelegateImplLinux::OnDidGetDestFileInfoToCopyFileFromLocal(
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const base::File::Info& file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (file_info.is_directory)
-    error_callback.Run(base::File::FILE_ERROR_INVALID_OPERATION);
+    std::move(error_callback).Run(base::File::FILE_ERROR_INVALID_OPERATION);
   else
-    error_callback.Run(base::File::FILE_ERROR_FAILED);
+    std::move(error_callback).Run(base::File::FILE_ERROR_FAILED);
 }
 
 void MTPDeviceDelegateImplLinux::OnGetDestFileInfoErrorToCopyFileFromLocal(
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
-    const CopyFileFromLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    CopyFileFromLocalSuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (error != base::File::FILE_ERROR_NOT_FOUND) {
-    error_callback.Run(error);
+    std::move(error_callback).Run(error);
     return;
   }
 
@@ -1510,16 +1554,16 @@
       base::BindOnce(&OpenFileDescriptor, source_file_path, O_RDONLY),
       base::BindOnce(
           &MTPDeviceDelegateImplLinux::OnDidOpenFDToCopyFileFromLocal,
-          weak_ptr_factory_.GetWeakPtr(), device_file_path, success_callback,
-          error_callback));
+          weak_ptr_factory_.GetWeakPtr(), device_file_path,
+          std::move(success_callback), std::move(error_callback)));
 }
 
 void MTPDeviceDelegateImplLinux::OnDidCreateSingleDirectory(
     const base::FilePath& directory_path,
-    const CreateDirectorySuccessCallback& success_callback) {
+    CreateDirectorySuccessCallback success_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
-  success_callback.Run();
+  std::move(success_callback).Run();
   NotifyFileChange(directory_path.DirName(),
                    storage::WatcherManager::ChangeType::CHANGED);
   PendingRequestDone();
@@ -1529,37 +1573,38 @@
     const base::FilePath& created_directory,
     const std::vector<base::FilePath>& components,
     const bool exclusive,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
   // Calls ReadDirectoryInternal to fill the cache for created directory.
   // Calls ReadDirectoryInternal in this method to call it via
   // EnsureInitAndRunTask.
-  const ReadDirectorySuccessCallback& success_callback_wrapper = base::Bind(
+  ReadDirectorySuccessCallback success_callback_wrapper = base::BindOnce(
       &MTPDeviceDelegateImplLinux::OnDidReadDirectoryToCreateDirectory,
-      weak_ptr_factory_.GetWeakPtr(), components, exclusive, success_callback,
-      error_callback);
+      weak_ptr_factory_.GetWeakPtr(), components, exclusive,
+      std::move(success_callback), repeating_error);
   base::OnceClosure closure = base::BindOnce(
       &MTPDeviceDelegateImplLinux::ReadDirectoryInternal,
       weak_ptr_factory_.GetWeakPtr(), created_directory.DirName(),
-      success_callback_wrapper, error_callback);
+      std::move(success_callback_wrapper), repeating_error);
   EnsureInitAndRunTask(PendingTaskInfo(base::FilePath(),
                                        content::BrowserThread::IO, FROM_HERE,
                                        std::move(closure)));
 }
 
 void MTPDeviceDelegateImplLinux::OnCreateParentDirectoryErrorToCreateDirectory(
-    const ErrorCallback& callback,
+    ErrorCallback callback,
     const base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
-  callback.Run(base::File::FILE_ERROR_FAILED);
+  std::move(callback).Run(base::File::FILE_ERROR_FAILED);
 }
 
 void MTPDeviceDelegateImplLinux::OnDidReadDirectory(
     uint32_t dir_id,
-    const ReadDirectorySuccessCallback& success_callback,
+    ReadDirectorySuccessCallback success_callback,
     const MTPDeviceTaskHelper::MTPEntries& mtp_entries,
     bool has_more) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -1596,7 +1641,7 @@
     file_info_cache_[dir_path.Append(entry.name)] = mtp_entry;
   }
 
-  success_callback.Run(file_list, has_more);
+  std::move(success_callback).Run(file_list, has_more);
   if (has_more)
     return;  // Wait to be called again.
 
@@ -1613,8 +1658,8 @@
     const base::FilePath& snapshot_file_path) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(current_snapshot_request_info_.get());
-  current_snapshot_request_info_->success_callback.Run(
-      file_info, snapshot_file_path);
+  std::move(current_snapshot_request_info_->success_callback)
+      .Run(file_info, snapshot_file_path);
   current_snapshot_request_info_.reset();
   PendingRequestDone();
 }
@@ -1623,16 +1668,17 @@
     base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(current_snapshot_request_info_.get());
-  current_snapshot_request_info_->error_callback.Run(error);
+  std::move(current_snapshot_request_info_->error_callback).Run(error);
   current_snapshot_request_info_.reset();
   PendingRequestDone();
 }
 
 void MTPDeviceDelegateImplLinux::OnDidReadBytes(
-    const ReadBytesSuccessCallback& success_callback,
-    const base::File::Info& file_info, int bytes_read) {
+    ReadBytesSuccessCallback success_callback,
+    const base::File::Info& file_info,
+    int bytes_read) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-  success_callback.Run(file_info, bytes_read);
+  std::move(success_callback).Run(file_info, bytes_read);
   PendingRequestDone();
 }
 
@@ -1660,32 +1706,33 @@
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
     const CopyFileProgressCallback& progress_callback,
-    const CopyFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    CopyFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::FilePath& temporary_file_path) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   if (temporary_file_path.empty()) {
-    error_callback.Run(base::File::FILE_ERROR_FAILED);
+    std::move(error_callback).Run(base::File::FILE_ERROR_FAILED);
     return;
   }
 
+  auto repeating_error = MakeErrorCallbackRepeating(std::move(error_callback));
   CreateSnapshotFile(
       source_file_path, temporary_file_path,
-      base::Bind(
+      base::BindOnce(
           &MTPDeviceDelegateImplLinux::OnDidCreateSnapshotFileOfCopyFileLocal,
           weak_ptr_factory_.GetWeakPtr(), device_file_path, progress_callback,
-          success_callback, error_callback),
-      base::BindRepeating(&MTPDeviceDelegateImplLinux::HandleCopyFileLocalError,
-                          weak_ptr_factory_.GetWeakPtr(), error_callback,
-                          temporary_file_path));
+          std::move(success_callback), repeating_error),
+      base::BindOnce(&MTPDeviceDelegateImplLinux::HandleCopyFileLocalError,
+                     weak_ptr_factory_.GetWeakPtr(), repeating_error,
+                     temporary_file_path));
 }
 
 void MTPDeviceDelegateImplLinux::OnDidCreateSnapshotFileOfCopyFileLocal(
     const base::FilePath& device_file_path,
     const CopyFileProgressCallback& progress_callback,
-    const CopyFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    CopyFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback,
     const base::File::Info& file_info,
     const base::FilePath& temporary_file_path) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -1696,32 +1743,32 @@
   // TODO(yawano): Avoid to call external method from internal code.
   CopyFileFromLocal(
       temporary_file_path, device_file_path,
-      base::Bind(
+      base::BindOnce(
           &MTPDeviceDelegateImplLinux::OnDidCopyFileFromLocalOfCopyFileLocal,
-          weak_ptr_factory_.GetWeakPtr(), success_callback,
+          weak_ptr_factory_.GetWeakPtr(), std::move(success_callback),
           temporary_file_path),
-      base::BindRepeating(&MTPDeviceDelegateImplLinux::HandleCopyFileLocalError,
-                          weak_ptr_factory_.GetWeakPtr(), error_callback,
-                          temporary_file_path));
+      base::BindOnce(&MTPDeviceDelegateImplLinux::HandleCopyFileLocalError,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(error_callback),
+                     temporary_file_path));
 }
 
 void MTPDeviceDelegateImplLinux::OnDidCopyFileFromLocalOfCopyFileLocal(
-    const CopyFileFromLocalSuccessCallback success_callback,
+    CopyFileFromLocalSuccessCallback success_callback,
     const base::FilePath& temporary_file_path) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   DeleteTemporaryFile(temporary_file_path);
-  success_callback.Run();
+  std::move(success_callback).Run();
 }
 
 void MTPDeviceDelegateImplLinux::OnDidMoveFileLocalWithRename(
-    const MoveFileLocalSuccessCallback& success_callback,
+    MoveFileLocalSuccessCallback success_callback,
     const base::FilePath& source_file_path,
     const uint32_t file_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   EvictCachedPathToId(file_id);
-  success_callback.Run();
+  std::move(success_callback).Run();
   NotifyFileChange(source_file_path,
                    storage::WatcherManager::ChangeType::DELETED);
   NotifyFileChange(source_file_path.DirName(),
@@ -1730,7 +1777,7 @@
 }
 
 void MTPDeviceDelegateImplLinux::OnDidCopyFileFromLocal(
-    const CopyFileFromLocalSuccessCallback& success_callback,
+    CopyFileFromLocalSuccessCallback success_callback,
     const base::FilePath& file_path,
     const int source_file_descriptor) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -1741,24 +1788,24 @@
   base::ThreadPool::PostTask(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, closure);
 
-  success_callback.Run();
+  std::move(success_callback).Run();
   NotifyFileChange(file_path.DirName(),
                    storage::WatcherManager::ChangeType::CHANGED);
   PendingRequestDone();
 }
 
 void MTPDeviceDelegateImplLinux::HandleCopyFileLocalError(
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const base::FilePath& temporary_file_path,
     const base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   DeleteTemporaryFile(temporary_file_path);
-  error_callback.Run(error);
+  std::move(error_callback).Run(error);
 }
 
 void MTPDeviceDelegateImplLinux::HandleCopyFileFromLocalError(
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const int source_file_descriptor,
     base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -1769,18 +1816,18 @@
   base::ThreadPool::PostTask(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, closure);
 
-  error_callback.Run(error);
+  std::move(error_callback).Run(error);
   PendingRequestDone();
 }
 
 void MTPDeviceDelegateImplLinux::OnDidDeleteObject(
     const base::FilePath& object_path,
     const uint32_t object_id,
-    const DeleteObjectSuccessCallback success_callback) {
+    DeleteObjectSuccessCallback success_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   EvictCachedPathToId(object_id);
-  success_callback.Run();
+  std::move(success_callback).Run();
   NotifyFileChange(object_path, storage::WatcherManager::ChangeType::DELETED);
   NotifyFileChange(object_path.DirName(),
                    storage::WatcherManager::ChangeType::CHANGED);
@@ -1788,22 +1835,22 @@
 }
 
 void MTPDeviceDelegateImplLinux::HandleDeleteFileOrDirectoryError(
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
-  error_callback.Run(error);
+  std::move(error_callback).Run(error);
   PendingRequestDone();
 }
 
 void MTPDeviceDelegateImplLinux::HandleDeviceFileError(
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     uint32_t file_id,
     base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   EvictCachedPathToId(file_id);
-  error_callback.Run(error);
+  std::move(error_callback).Run(error);
   PendingRequestDone();
 }
 
@@ -1846,13 +1893,13 @@
   DCHECK(task_in_progress_);
 
   ReadDirectorySuccessCallback success_callback =
-      base::Bind(&MTPDeviceDelegateImplLinux::OnDidFillFileCache,
-                 weak_ptr_factory_.GetWeakPtr(),
-                 uncached_path);
+      base::BindOnce(&MTPDeviceDelegateImplLinux::OnDidFillFileCache,
+                     weak_ptr_factory_.GetWeakPtr(), uncached_path);
   ErrorCallback error_callback =
-      base::BindRepeating(&MTPDeviceDelegateImplLinux::OnFillFileCacheFailed,
-                          weak_ptr_factory_.GetWeakPtr());
-  ReadDirectoryInternal(uncached_path, success_callback, error_callback);
+      base::BindOnce(&MTPDeviceDelegateImplLinux::OnFillFileCacheFailed,
+                     weak_ptr_factory_.GetWeakPtr());
+  ReadDirectoryInternal(uncached_path, std::move(success_callback),
+                        std::move(error_callback));
 }
 
 base::Optional<uint32_t> MTPDeviceDelegateImplLinux::CachedPathToId(
diff --git a/chrome/browser/media_galleries/chromeos/mtp_device_delegate_impl_chromeos.h b/chrome/browser/media_galleries/chromeos/mtp_device_delegate_impl_chromeos.h
index 90b16c3..232caedf 100644
--- a/chrome/browser/media_galleries/chromeos/mtp_device_delegate_impl_chromeos.h
+++ b/chrome/browser/media_galleries/chromeos/mtp_device_delegate_impl_chromeos.h
@@ -73,7 +73,7 @@
   // Maps file paths to file info.
   typedef std::map<base::FilePath, MTPDeviceTaskHelper::MTPEntry> FileInfoCache;
 
-  typedef base::Closure DeleteObjectSuccessCallback;
+  using DeleteObjectSuccessCallback = base::OnceClosure;
 
   // Should only be called by CreateMTPDeviceAsyncDelegate() factory call.
   // Defer the device initializations until the first file operation request.
@@ -87,52 +87,50 @@
   // MTPDeviceAsyncDelegate:
   void GetFileInfo(const base::FilePath& file_path,
                    GetFileInfoSuccessCallback success_callback,
-                   const ErrorCallback& error_callback) override;
+                   ErrorCallback error_callback) override;
   void CreateDirectory(const base::FilePath& directory_path,
                        const bool exclusive,
                        const bool recursive,
-                       const CreateDirectorySuccessCallback& success_callback,
-                       const ErrorCallback& error_callback) override;
+                       CreateDirectorySuccessCallback success_callback,
+                       ErrorCallback error_callback) override;
   void ReadDirectory(const base::FilePath& root,
-                     const ReadDirectorySuccessCallback& success_callback,
-                     const ErrorCallback& error_callback) override;
-  void CreateSnapshotFile(
-      const base::FilePath& device_file_path,
-      const base::FilePath& local_path,
-      const CreateSnapshotFileSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+                     ReadDirectorySuccessCallback success_callback,
+                     ErrorCallback error_callback) override;
+  void CreateSnapshotFile(const base::FilePath& device_file_path,
+                          const base::FilePath& local_path,
+                          CreateSnapshotFileSuccessCallback success_callback,
+                          ErrorCallback error_callback) override;
   bool IsStreaming() override;
   void ReadBytes(const base::FilePath& device_file_path,
                  const scoped_refptr<net::IOBuffer>& buf,
                  int64_t offset,
                  int buf_len,
-                 const ReadBytesSuccessCallback& success_callback,
-                 const ErrorCallback& error_callback) override;
+                 ReadBytesSuccessCallback success_callback,
+                 ErrorCallback error_callback) override;
   bool IsReadOnly() const override;
   void CopyFileLocal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
       const CopyFileProgressCallback& progress_callback,
-      const CopyFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+      CopyFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) override;
   void MoveFileLocal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
-      const MoveFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
-  void CopyFileFromLocal(
-      const base::FilePath& source_file_path,
-      const base::FilePath& device_file_path,
-      const CopyFileFromLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+      MoveFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) override;
+  void CopyFileFromLocal(const base::FilePath& source_file_path,
+                         const base::FilePath& device_file_path,
+                         CopyFileFromLocalSuccessCallback success_callback,
+                         ErrorCallback error_callback) override;
   void DeleteFile(const base::FilePath& file_path,
-                  const DeleteFileSuccessCallback& success_callback,
-                  const ErrorCallback& error_callback) override;
+                  DeleteFileSuccessCallback success_callback,
+                  ErrorCallback error_callback) override;
   void DeleteDirectory(const base::FilePath& file_path,
-                       const DeleteDirectorySuccessCallback& success_callback,
-                       const ErrorCallback& error_callback) override;
+                       DeleteDirectorySuccessCallback success_callback,
+                       ErrorCallback error_callback) override;
   void AddWatcher(const GURL& origin,
                   const base::FilePath& file_path,
                   const bool recursive,
@@ -149,64 +147,60 @@
   // The |root_node_| cache should be filled at this point.
   void GetFileInfoInternal(const base::FilePath& file_path,
                            GetFileInfoSuccessCallback success_callback,
-                           const ErrorCallback& error_callback);
-  void CreateDirectoryInternal(
-      const std::vector<base::FilePath>& components,
-      const bool exclusive,
-      const CreateDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
-  void ReadDirectoryInternal(
-      const base::FilePath& root,
-      const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
+                           ErrorCallback error_callback);
+  void CreateDirectoryInternal(const std::vector<base::FilePath>& components,
+                               const bool exclusive,
+                               CreateDirectorySuccessCallback success_callback,
+                               ErrorCallback error_callback);
+  void ReadDirectoryInternal(const base::FilePath& root,
+                             ReadDirectorySuccessCallback success_callback,
+                             ErrorCallback error_callback);
   void CreateSnapshotFileInternal(
       const base::FilePath& device_file_path,
       const base::FilePath& local_path,
-      const CreateSnapshotFileSuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
+      CreateSnapshotFileSuccessCallback success_callback,
+      ErrorCallback error_callback);
   void ReadBytesInternal(const base::FilePath& device_file_path,
                          net::IOBuffer* buf,
                          int64_t offset,
                          int buf_len,
-                         const ReadBytesSuccessCallback& success_callback,
-                         const ErrorCallback& error_callback);
+                         ReadBytesSuccessCallback success_callback,
+                         ErrorCallback error_callback);
   void MoveFileLocalInternal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
-      const MoveFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      MoveFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback,
       const base::File::Info& source_file_info);
   void OnDidOpenFDToCopyFileFromLocal(
       const base::FilePath& device_file_path,
-      const CopyFileFromLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      CopyFileFromLocalSuccessCallback success_callback,
+      ErrorCallback error_callback,
       const std::pair<int, base::File::Error>& open_fd_result);
   void DeleteFileInternal(const base::FilePath& file_path,
-                          const DeleteFileSuccessCallback& success_callback,
-                          const ErrorCallback& error_callback,
+                          DeleteFileSuccessCallback success_callback,
+                          ErrorCallback error_callback,
                           const base::File::Info& file_info);
-  void DeleteDirectoryInternal(
-      const base::FilePath& file_path,
-      const DeleteDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
-      const base::File::Info& file_info);
+  void DeleteDirectoryInternal(const base::FilePath& file_path,
+                               DeleteDirectorySuccessCallback success_callback,
+                               ErrorCallback error_callback,
+                               const base::File::Info& file_info);
 
   // Creates a single directory to |directory_path|. The caller must ensure that
   // parent directory |directory_path.DirName()| already exists.
-  void CreateSingleDirectory(
-      const base::FilePath& directory_path,
-      const bool exclusive,
-      const CreateDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
+  void CreateSingleDirectory(const base::FilePath& directory_path,
+                             const bool exclusive,
+                             CreateDirectorySuccessCallback success_callback,
+                             ErrorCallback error_callback);
 
   // Called when ReadDirectoryInternal() completes for filling cache as part of
   // creating directories.
   void OnDidReadDirectoryToCreateDirectory(
       const std::vector<base::FilePath>& components,
       const bool exclusive,
-      const CreateDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      CreateDirectorySuccessCallback success_callback,
+      ErrorCallback error_callback,
       storage::AsyncFileUtil::EntryList entries,
       const bool has_more);
 
@@ -214,16 +208,15 @@
   void OnDidCheckDirectoryEmptyToDeleteDirectory(
       const base::FilePath& directory_path,
       uint32_t directory_id,
-      const DeleteDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      DeleteDirectorySuccessCallback success_callback,
+      ErrorCallback error_callback,
       bool is_empty);
 
   // Calls DeleteObjectOnUIThread on UI thread.
-  void RunDeleteObjectOnUIThread(
-      const base::FilePath& object_path,
-      const uint32_t object_id,
-      const DeleteObjectSuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
+  void RunDeleteObjectOnUIThread(const base::FilePath& object_path,
+                                 const uint32_t object_id,
+                                 DeleteObjectSuccessCallback success_callback,
+                                 ErrorCallback error_callback);
 
   // Notifies |chage_type| of |file_path| to watchers.
   void NotifyFileChange(const base::FilePath& file_path,
@@ -272,16 +265,16 @@
   // path already exists.
   void OnPathAlreadyExistsForCreateSingleDirectory(
       const bool exclusive,
-      const CreateDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      CreateDirectorySuccessCallback success_callback,
+      ErrorCallback error_callback,
       const base::File::Info& file_info);
 
   // Called when GetFileInfo() of |directory_path| failed to check the path
   // already exists.
   void OnPathDoesNotExistForCreateSingleDirectory(
       const base::FilePath& directory_path,
-      const CreateDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      CreateDirectorySuccessCallback success_callback,
+      ErrorCallback error_callback,
       const base::File::Error error);
 
   // Called when GetFileInfo() succeeds. GetFileInfo() is invoked to
@@ -295,8 +288,8 @@
   // caller about the file error and process the next pending request.
   void OnDidGetFileInfoToReadDirectory(
       uint32_t dir_id,
-      const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      ReadDirectorySuccessCallback success_callback,
+      ErrorCallback error_callback,
       const base::File::Info& file_info);
 
   // Called when GetFileInfo() succeeds. GetFileInfo() is invoked to
@@ -312,7 +305,7 @@
   // Called when GetFileInfo() for destination path succeeded for a
   // CopyFileFromLocal operation.
   void OnDidGetDestFileInfoToCopyFileFromLocal(
-      const ErrorCallback& error_callback,
+      ErrorCallback error_callback,
       const base::File::Info& file_info);
 
   // Called when GetFileInfo() for destination path failed to copy file from
@@ -320,14 +313,14 @@
   void OnGetDestFileInfoErrorToCopyFileFromLocal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
-      const CopyFileFromLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      CopyFileFromLocalSuccessCallback success_callback,
+      ErrorCallback error_callback,
       const base::File::Error error);
 
   // Called when CreateSignleDirectory() succeeds.
   void OnDidCreateSingleDirectory(
       const base::FilePath& directory_path,
-      const CreateDirectorySuccessCallback& success_callback);
+      CreateDirectorySuccessCallback success_callback);
 
   // Called when parent directory |created_directory| is created as part of
   // CreateDirectory.
@@ -335,14 +328,14 @@
       const base::FilePath& created_directory,
       const std::vector<base::FilePath>& components,
       const bool exclusive,
-      const CreateDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
+      CreateDirectorySuccessCallback success_callback,
+      ErrorCallback error_callback);
 
   // Called when it failed to create a parent directory. For creating parent
   // directories, all errors should be reported as FILE_ERROR_FAILED. This
   // method wraps error callbacks of creating parent directories.
   void OnCreateParentDirectoryErrorToCreateDirectory(
-      const ErrorCallback& callback,
+      ErrorCallback callback,
       const base::File::Error error);
 
   // Called when ReadDirectory() succeeds.
@@ -353,7 +346,7 @@
   // |file_list| contains the directory file entries with their file ids.
   // |has_more| is true if there are more file entries to read.
   void OnDidReadDirectory(uint32_t dir_id,
-                          const ReadDirectorySuccessCallback& success_callback,
+                          ReadDirectorySuccessCallback success_callback,
                           const MTPDeviceTaskHelper::MTPEntries& mtp_entries,
                           bool has_more);
 
@@ -379,8 +372,9 @@
   //
   // |success_callback| is invoked to notify the caller about the read bytes.
   // |bytes_read| is the number of bytes read.
-  void OnDidReadBytes(const ReadBytesSuccessCallback& success_callback,
-                      const base::File::Info& file_info, int bytes_read);
+  void OnDidReadBytes(ReadBytesSuccessCallback success_callback,
+                      const base::File::Info& file_info,
+                      int bytes_read);
 
   // Called when FillFileCache() succeeds.
   void OnDidFillFileCache(const base::FilePath& path,
@@ -395,58 +389,57 @@
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CopyFileProgressCallback& progress_callback,
-      const CopyFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      CopyFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback,
       const base::FilePath& temporary_file_path);
 
   // Called when CreateSnapshotFile() succeeds for CopyFileLocal.
   void OnDidCreateSnapshotFileOfCopyFileLocal(
       const base::FilePath& device_file_path,
       const CopyFileProgressCallback& progress_callback,
-      const CopyFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      CopyFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback,
       const base::File::Info& file_info,
       const base::FilePath& temporary_file_path);
 
   // Called when CopyFileFromLocal() succeeds for CopyFileLocal.
   void OnDidCopyFileFromLocalOfCopyFileLocal(
-      const CopyFileFromLocalSuccessCallback success_callback,
+      CopyFileFromLocalSuccessCallback success_callback,
       const base::FilePath& temporary_file_path);
 
   // Called when MoveFileLocal() succeeds with rename operation.
   void OnDidMoveFileLocalWithRename(
-      const MoveFileLocalSuccessCallback& success_callback,
+      MoveFileLocalSuccessCallback success_callback,
       const base::FilePath& source_file_path,
       const uint32_t file_id);
 
   // Called when CopyFileFromLocal() succeeds.
-  void OnDidCopyFileFromLocal(
-      const CopyFileFromLocalSuccessCallback& success_callback,
-      const base::FilePath& file_path,
-      const int source_file_descriptor);
+  void OnDidCopyFileFromLocal(CopyFileFromLocalSuccessCallback success_callback,
+                              const base::FilePath& file_path,
+                              const int source_file_descriptor);
 
   // Called when CopyFileLocal() fails.
-  void HandleCopyFileLocalError(const ErrorCallback& error_callback,
+  void HandleCopyFileLocalError(ErrorCallback error_callback,
                                 const base::FilePath& temporary_file_path,
                                 const base::File::Error error);
 
   // Called when CopyFileFromLocal() fails.
-  void HandleCopyFileFromLocalError(const ErrorCallback& error_callback,
+  void HandleCopyFileFromLocalError(ErrorCallback error_callback,
                                     const int source_file_descriptor,
                                     base::File::Error error);
 
   // Called when DeleteObject() succeeds.
   void OnDidDeleteObject(const base::FilePath& object_path,
                          const uint32_t object_id,
-                         const DeleteObjectSuccessCallback success_callback);
+                         DeleteObjectSuccessCallback success_callback);
 
   // Called when DeleteFileOrDirectory() fails.
-  void HandleDeleteFileOrDirectoryError(const ErrorCallback& error_callback,
+  void HandleDeleteFileOrDirectoryError(ErrorCallback error_callback,
                                         base::File::Error error);
 
   // Handles the device file |error| while operating on |file_id|.
   // |error_callback| is invoked to notify the caller about the file error.
-  void HandleDeviceFileError(const ErrorCallback& error_callback,
+  void HandleDeviceFileError(ErrorCallback error_callback,
                              uint32_t file_id,
                              base::File::Error error);
 
diff --git a/chrome/browser/media_galleries/chromeos/mtp_device_task_helper.cc b/chrome/browser/media_galleries/chromeos/mtp_device_task_helper.cc
index da844455..e644dc6 100644
--- a/chrome/browser/media_galleries/chromeos/mtp_device_task_helper.cc
+++ b/chrome/browser/media_galleries/chromeos/mtp_device_task_helper.cc
@@ -107,84 +107,89 @@
 void MTPDeviceTaskHelper::GetFileInfo(
     uint32_t file_id,
     GetFileInfoSuccessCallback success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (device_handle_.empty())
-    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
+    return HandleDeviceError(std::move(error_callback),
+                             base::File::FILE_ERROR_FAILED);
 
   const std::vector<uint32_t> file_ids = {file_id};
   GetMediaTransferProtocolManager()->GetFileInfo(
       device_handle_, file_ids,
       base::BindOnce(&MTPDeviceTaskHelper::OnGetFileInfo,
                      weak_ptr_factory_.GetWeakPtr(),
-                     std::move(success_callback), error_callback));
+                     std::move(success_callback), std::move(error_callback)));
 }
 
 void MTPDeviceTaskHelper::CreateDirectory(
     const uint32_t parent_id,
     const std::string& directory_name,
     const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (device_handle_.empty())
-    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
+    return HandleDeviceError(std::move(error_callback),
+                             base::File::FILE_ERROR_FAILED);
 
   GetMediaTransferProtocolManager()->CreateDirectory(
       device_handle_, parent_id, directory_name,
       base::BindOnce(&MTPDeviceTaskHelper::OnCreateDirectory,
                      weak_ptr_factory_.GetWeakPtr(), success_callback,
-                     error_callback));
+                     std::move(error_callback)));
 }
 
 void MTPDeviceTaskHelper::ReadDirectory(
     const uint32_t directory_id,
     const ReadDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (device_handle_.empty())
-    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
+    return HandleDeviceError(std::move(error_callback),
+                             base::File::FILE_ERROR_FAILED);
 
   GetMediaTransferProtocolManager()->ReadDirectoryEntryIds(
       device_handle_, directory_id,
       base::BindOnce(
           &MTPDeviceTaskHelper::OnReadDirectoryEntryIdsToReadDirectory,
-          weak_ptr_factory_.GetWeakPtr(), success_callback, error_callback));
+          weak_ptr_factory_.GetWeakPtr(), success_callback,
+          std::move(error_callback)));
 }
 
 void MTPDeviceTaskHelper::CheckDirectoryEmpty(
     uint32_t directory_id,
     CheckDirectoryEmptySuccessCallback success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   if (device_handle_.empty())
-    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
+    return HandleDeviceError(std::move(error_callback),
+                             base::File::FILE_ERROR_FAILED);
 
   GetMediaTransferProtocolManager()->ReadDirectoryEntryIds(
       device_handle_, directory_id,
       base::BindOnce(&MTPDeviceTaskHelper::OnCheckedDirectoryEmpty,
                      weak_ptr_factory_.GetWeakPtr(),
-                     std::move(success_callback), error_callback));
+                     std::move(success_callback), std::move(error_callback)));
 }
 
 void MTPDeviceTaskHelper::WriteDataIntoSnapshotFile(
-    const SnapshotRequestInfo& request_info,
+    SnapshotRequestInfo request_info,
     const base::File::Info& snapshot_file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (device_handle_.empty()) {
-    return HandleDeviceError(request_info.error_callback,
+    return HandleDeviceError(std::move(request_info.error_callback),
                              base::File::FILE_ERROR_FAILED);
   }
 
   if (!read_file_worker_)
     read_file_worker_.reset(new MTPReadFileWorker(device_handle_));
-  read_file_worker_->WriteDataIntoSnapshotFile(request_info,
+  read_file_worker_->WriteDataIntoSnapshotFile(std::move(request_info),
                                                snapshot_file_info);
 }
 
 void MTPDeviceTaskHelper::ReadBytes(
-    const MTPDeviceAsyncDelegate::ReadBytesRequest& request) {
+    MTPDeviceAsyncDelegate::ReadBytesRequest request) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (device_handle_.empty()) {
-    return HandleDeviceError(request.error_callback,
+    return HandleDeviceError(std::move(request.error_callback),
                              base::File::FILE_ERROR_FAILED);
   }
 
@@ -192,21 +197,21 @@
   GetMediaTransferProtocolManager()->GetFileInfo(
       device_handle_, file_ids,
       base::BindOnce(&MTPDeviceTaskHelper::OnGetFileInfoToReadBytes,
-                     weak_ptr_factory_.GetWeakPtr(), request));
+                     weak_ptr_factory_.GetWeakPtr(), std::move(request)));
 }
 
 void MTPDeviceTaskHelper::RenameObject(
     const uint32_t object_id,
     const std::string& new_name,
     const RenameObjectSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   GetMediaTransferProtocolManager()->RenameObject(
       device_handle_, object_id, new_name,
       base::BindOnce(&MTPDeviceTaskHelper::OnRenameObject,
                      weak_ptr_factory_.GetWeakPtr(), success_callback,
-                     error_callback));
+                     std::move(error_callback)));
 }
 
 MTPDeviceTaskHelper::MTPEntry::MTPEntry() : file_id(0) {}
@@ -218,27 +223,27 @@
     const uint32_t parent_id,
     const std::string& file_name,
     const CopyFileFromLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   GetMediaTransferProtocolManager()->CopyFileFromLocal(
       device_handle_, source_file_descriptor, parent_id, file_name,
       base::BindOnce(&MTPDeviceTaskHelper::OnCopyFileFromLocal,
                      weak_ptr_factory_.GetWeakPtr(), success_callback,
-                     error_callback));
+                     std::move(error_callback)));
 }
 
 void MTPDeviceTaskHelper::DeleteObject(
     const uint32_t object_id,
     const DeleteObjectSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   GetMediaTransferProtocolManager()->DeleteObject(
       device_handle_, object_id,
       base::BindOnce(&MTPDeviceTaskHelper::OnDeleteObject,
                      weak_ptr_factory_.GetWeakPtr(), success_callback,
-                     error_callback));
+                     std::move(error_callback)));
 }
 
 void MTPDeviceTaskHelper::CloseStorage() const {
@@ -261,12 +266,12 @@
 
 void MTPDeviceTaskHelper::OnGetFileInfo(
     GetFileInfoSuccessCallback success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     std::vector<device::mojom::MtpFileEntryPtr> entries,
     bool error) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (error || entries.size() != 1) {
-    return HandleDeviceError(error_callback,
+    return HandleDeviceError(std::move(error_callback),
                              base::File::FILE_ERROR_NOT_FOUND);
   }
 
@@ -278,13 +283,13 @@
 
 void MTPDeviceTaskHelper::OnCreateDirectory(
     const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const bool error) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (error) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(error_callback, base::File::FILE_ERROR_FAILED));
+        FROM_HERE, base::BindOnce(std::move(error_callback),
+                                  base::File::FILE_ERROR_FAILED));
     return;
   }
 
@@ -293,13 +298,14 @@
 
 void MTPDeviceTaskHelper::OnReadDirectoryEntryIdsToReadDirectory(
     const ReadDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const std::vector<uint32_t>& file_ids,
     bool error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   if (error)
-    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
+    return HandleDeviceError(std::move(error_callback),
+                             base::File::FILE_ERROR_FAILED);
 
   if (file_ids.empty()) {
     content::GetIOThreadTaskRunner({})->PostTask(
@@ -316,13 +322,13 @@
       device_handle_, file_ids_to_read_now,
       base::BindOnce(&MTPDeviceTaskHelper::OnGotDirectoryEntries,
                      weak_ptr_factory_.GetWeakPtr(), success_callback,
-                     error_callback, file_ids_to_read_now,
+                     std::move(error_callback), file_ids_to_read_now,
                      file_ids_to_read_later));
 }
 
 void MTPDeviceTaskHelper::OnGotDirectoryEntries(
     const ReadDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const std::vector<uint32_t>& expected_file_ids,
     const std::vector<uint32_t>& file_ids_to_read,
     std::vector<device::mojom::MtpFileEntryPtr> file_entries,
@@ -330,7 +336,8 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   if (error)
-    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
+    return HandleDeviceError(std::move(error_callback),
+                             base::File::FILE_ERROR_FAILED);
 
   // Use |expected_file_ids| to verify the results are the requested ids.
   std::vector<uint32_t> sorted_expected_file_ids = expected_file_ids;
@@ -340,7 +347,8 @@
         std::lower_bound(sorted_expected_file_ids.begin(),
                          sorted_expected_file_ids.end(), entry->item_id);
     if (it == sorted_expected_file_ids.end()) {
-      return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
+      return HandleDeviceError(std::move(error_callback),
+                               base::File::FILE_ERROR_FAILED);
     }
   }
 
@@ -375,25 +383,26 @@
       device_handle_, file_ids_to_read_now,
       base::BindOnce(&MTPDeviceTaskHelper::OnGotDirectoryEntries,
                      weak_ptr_factory_.GetWeakPtr(), success_callback,
-                     error_callback, file_ids_to_read_now,
+                     std::move(error_callback), file_ids_to_read_now,
                      file_ids_to_read_later));
 }
 
 void MTPDeviceTaskHelper::OnCheckedDirectoryEmpty(
     CheckDirectoryEmptySuccessCallback success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const std::vector<uint32_t>& file_ids,
     bool error) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (error)
-    return HandleDeviceError(error_callback, base::File::FILE_ERROR_FAILED);
+    return HandleDeviceError(std::move(error_callback),
+                             base::File::FILE_ERROR_FAILED);
 
   content::GetIOThreadTaskRunner({})->PostTask(
       FROM_HERE, base::BindOnce(std::move(success_callback), file_ids.empty()));
 }
 
 void MTPDeviceTaskHelper::OnGetFileInfoToReadBytes(
-    const MTPDeviceAsyncDelegate::ReadBytesRequest& request,
+    MTPDeviceAsyncDelegate::ReadBytesRequest request,
     std::vector<device::mojom::MtpFileEntryPtr> entries,
     bool error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -401,24 +410,25 @@
   DCHECK_GE(request.buf_len, 0);
   DCHECK_GE(request.offset, 0);
   if (error || entries.size() != 1) {
-    return HandleDeviceError(request.error_callback,
+    return HandleDeviceError(std::move(request.error_callback),
                              base::File::FILE_ERROR_FAILED);
   }
 
   base::File::Info file_info = FileInfoFromMTPFileEntry(std::move(entries[0]));
   if (file_info.is_directory) {
-    return HandleDeviceError(request.error_callback,
+    return HandleDeviceError(std::move(request.error_callback),
                              base::File::FILE_ERROR_NOT_A_FILE);
   }
   if (file_info.size < 0 ||
       file_info.size > std::numeric_limits<uint32_t>::max() ||
       request.offset > file_info.size) {
-    return HandleDeviceError(request.error_callback,
+    return HandleDeviceError(std::move(request.error_callback),
                              base::File::FILE_ERROR_FAILED);
   }
   if (request.offset == file_info.size) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(request.success_callback, file_info, 0u));
+        FROM_HERE,
+        base::BindOnce(std::move(request.success_callback), file_info, 0u));
     return;
   }
 
@@ -430,17 +440,18 @@
       device_handle_, request.file_id,
       base::checked_cast<uint32_t>(request.offset), bytes_to_read,
       base::BindOnce(&MTPDeviceTaskHelper::OnDidReadBytes,
-                     weak_ptr_factory_.GetWeakPtr(), request, file_info));
+                     weak_ptr_factory_.GetWeakPtr(), std::move(request),
+                     file_info));
 }
 
 void MTPDeviceTaskHelper::OnDidReadBytes(
-    const MTPDeviceAsyncDelegate::ReadBytesRequest& request,
+    MTPDeviceAsyncDelegate::ReadBytesRequest request,
     const base::File::Info& file_info,
     const std::string& data,
     bool error) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (error) {
-    return HandleDeviceError(request.error_callback,
+    return HandleDeviceError(std::move(request.error_callback),
                              base::File::FILE_ERROR_FAILED);
   }
 
@@ -448,19 +459,19 @@
   std::copy(data.begin(), data.end(), request.buf->data());
 
   content::GetIOThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(request.success_callback, file_info, data.length()));
+      FROM_HERE, base::BindOnce(std::move(request.success_callback), file_info,
+                                data.length()));
 }
 
 void MTPDeviceTaskHelper::OnRenameObject(
     const RenameObjectSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const bool error) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (error) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(error_callback, base::File::FILE_ERROR_FAILED));
+        FROM_HERE, base::BindOnce(std::move(error_callback),
+                                  base::File::FILE_ERROR_FAILED));
     return;
   }
 
@@ -469,13 +480,13 @@
 
 void MTPDeviceTaskHelper::OnCopyFileFromLocal(
     const CopyFileFromLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const bool error) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (error) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(error_callback, base::File::FILE_ERROR_FAILED));
+        FROM_HERE, base::BindOnce(std::move(error_callback),
+                                  base::File::FILE_ERROR_FAILED));
     return;
   }
 
@@ -484,23 +495,22 @@
 
 void MTPDeviceTaskHelper::OnDeleteObject(
     const DeleteObjectSuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     const bool error) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (error) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(error_callback, base::File::FILE_ERROR_FAILED));
+        FROM_HERE, base::BindOnce(std::move(error_callback),
+                                  base::File::FILE_ERROR_FAILED));
     return;
   }
 
   content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, success_callback);
 }
 
-void MTPDeviceTaskHelper::HandleDeviceError(
-    const ErrorCallback& error_callback,
-    base::File::Error error) const {
+void MTPDeviceTaskHelper::HandleDeviceError(ErrorCallback error_callback,
+                                            base::File::Error error) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   content::GetIOThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(error_callback, error));
+      FROM_HERE, base::BindOnce(std::move(error_callback), error));
 }
diff --git a/chrome/browser/media_galleries/chromeos/mtp_device_task_helper.h b/chrome/browser/media_galleries/chromeos/mtp_device_task_helper.h
index 2fbd7a7..e5bafa96 100644
--- a/chrome/browser/media_galleries/chromeos/mtp_device_task_helper.h
+++ b/chrome/browser/media_galleries/chromeos/mtp_device_task_helper.h
@@ -87,13 +87,13 @@
   // notify the caller about the file error.
   void GetFileInfo(uint32_t file_id,
                    GetFileInfoSuccessCallback success_callback,
-                   const ErrorCallback& error_callback);
+                   ErrorCallback error_callback);
 
   // Forwards CreateDirectory request to the MediaTransferProtocolManager.
   void CreateDirectory(const uint32_t parent_id,
                        const std::string& directory_name,
                        const CreateDirectorySuccessCallback& success_callback,
-                       const ErrorCallback& error_callback);
+                       ErrorCallback error_callback);
 
   // Dispatches the read directory request to the MediaTransferProtocolManager.
   //
@@ -110,7 +110,7 @@
   // notify the caller about the file error.
   void ReadDirectory(const uint32_t directory_id,
                      const ReadDirectorySuccessCallback& success_callback,
-                     const ErrorCallback& error_callback);
+                     ErrorCallback error_callback);
 
   // Dispatches a read directory request to the MediaTransferProtocolManager to
   // check if |directory_id| is empty.
@@ -122,29 +122,28 @@
   // notify the caller about the file error.
   void CheckDirectoryEmpty(uint32_t directory_id,
                            CheckDirectoryEmptySuccessCallback success_callback,
-                           const ErrorCallback& error_callback);
+                           ErrorCallback error_callback);
 
   // Forwards the WriteDataIntoSnapshotFile request to the MTPReadFileWorker
   // object.
   //
   // |request_info| specifies the snapshot file request params.
   // |snapshot_file_info| specifies the metadata of the snapshot file.
-  void WriteDataIntoSnapshotFile(
-      const SnapshotRequestInfo& request_info,
-      const base::File::Info& snapshot_file_info);
+  void WriteDataIntoSnapshotFile(SnapshotRequestInfo request_info,
+                                 const base::File::Info& snapshot_file_info);
 
   // Dispatches the read bytes request to the MediaTransferProtocolManager.
   //
   // |request| contains details about the byte request including the file path,
   // byte range, and the callbacks. The callbacks specified within |request| are
   // called on the IO thread to notify the caller about success or failure.
-  void ReadBytes(const MTPDeviceAsyncDelegate::ReadBytesRequest& request);
+  void ReadBytes(MTPDeviceAsyncDelegate::ReadBytesRequest request);
 
   // Forwards RenameObject request to the MediaTransferProtocolManager.
   void RenameObject(const uint32_t object_id,
                     const std::string& new_name,
                     const RenameObjectSuccessCallback& success_callback,
-                    const ErrorCallback& error_callback);
+                    ErrorCallback error_callback);
 
   // Forwards CopyFileFromLocal request to the MediaTransferProtocolManager.
   void CopyFileFromLocal(
@@ -153,12 +152,12 @@
       const uint32_t parent_id,
       const std::string& file_name,
       const CopyFileFromLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
+      ErrorCallback error_callback);
 
   // Forwards DeleteObject request to the MediaTransferProtocolManager.
   void DeleteObject(const uint32_t object_id,
                     const DeleteObjectSuccessCallback& success_callback,
-                    const ErrorCallback& error_callback);
+                    ErrorCallback error_callback);
 
   // Dispatches the CloseStorage request to the MediaTransferProtocolManager.
   void CloseStorage() const;
@@ -185,13 +184,13 @@
   // error has occurred. In this case, |error_callback| is invoked on the IO
   // thread to notify the caller.
   void OnGetFileInfo(GetFileInfoSuccessCallback success_callback,
-                     const ErrorCallback& error_callback,
+                     ErrorCallback error_callback,
                      std::vector<device::mojom::MtpFileEntryPtr> entries,
                      bool error) const;
 
   // Called when CreateDirectory completes.
   void OnCreateDirectory(const CreateDirectorySuccessCallback& success_callback,
-                         const ErrorCallback& error_callback,
+                         ErrorCallback error_callback,
                          const bool error) const;
 
   // Query callback for ReadDirectoryEntryIds().
@@ -205,7 +204,7 @@
   // invoked on the IO thread to notify the caller.
   void OnReadDirectoryEntryIdsToReadDirectory(
       const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      ErrorCallback error_callback,
       const std::vector<uint32_t>& file_ids,
       bool error);
 
@@ -220,7 +219,7 @@
   // |error| indicates if the GetFileInfo() call succeeded or failed.
   void OnGotDirectoryEntries(
       const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      ErrorCallback error_callback,
       const std::vector<uint32_t>& expected_file_ids,
       const std::vector<uint32_t>& file_ids_to_read,
       std::vector<device::mojom::MtpFileEntryPtr> file_entries,
@@ -236,13 +235,13 @@
   // and |error_callback| is invoked on the IO thread to notify the caller.
   void OnCheckedDirectoryEmpty(
       CheckDirectoryEmptySuccessCallback success_callback,
-      const ErrorCallback& error_callback,
+      ErrorCallback error_callback,
       const std::vector<uint32_t>& file_ids,
       bool error) const;
 
   // Intermediate step to finish a ReadBytes request.
   void OnGetFileInfoToReadBytes(
-      const MTPDeviceAsyncDelegate::ReadBytesRequest& request,
+      MTPDeviceAsyncDelegate::ReadBytesRequest request,
       std::vector<device::mojom::MtpFileEntryPtr> entries,
       bool error);
 
@@ -255,33 +254,32 @@
   // If there is an error, |error| is set to true, the buffer within |request|
   // is untouched, and the error callback within |request| is invoked on the
   // IO thread to notify the caller.
-  void OnDidReadBytes(
-      const MTPDeviceAsyncDelegate::ReadBytesRequest& request,
-      const base::File::Info& file_info,
-      const std::string& data,
-      bool error) const;
+  void OnDidReadBytes(MTPDeviceAsyncDelegate::ReadBytesRequest request,
+                      const base::File::Info& file_info,
+                      const std::string& data,
+                      bool error) const;
 
   // Called when RenameObject completes.
   void OnRenameObject(const RenameObjectSuccessCallback& success_callback,
-                      const ErrorCallback& error_callback,
+                      ErrorCallback error_callback,
                       const bool error) const;
 
   // Called when CopyFileFromLocal completes.
   void OnCopyFileFromLocal(
       const CopyFileFromLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback,
+      ErrorCallback error_callback,
       const bool error) const;
 
   // Called when DeleteObject completes.
   void OnDeleteObject(const DeleteObjectSuccessCallback& success_callback,
-                      const ErrorCallback& error_callback,
+                      ErrorCallback error_callback,
                       const bool error) const;
 
   // Called when the device is uninitialized.
   //
   // Runs |error_callback| on the IO thread to notify the caller about the
   // device |error|.
-  void HandleDeviceError(const ErrorCallback& error_callback,
+  void HandleDeviceError(ErrorCallback error_callback,
                          base::File::Error error) const;
 
   // Handle to communicate with the MTP device.
diff --git a/chrome/browser/media_galleries/chromeos/mtp_read_file_worker.cc b/chrome/browser/media_galleries/chromeos/mtp_read_file_worker.cc
index f368841..43e71d7 100644
--- a/chrome/browser/media_galleries/chromeos/mtp_read_file_worker.cc
+++ b/chrome/browser/media_galleries/chromeos/mtp_read_file_worker.cc
@@ -46,11 +46,11 @@
 }
 
 void MTPReadFileWorker::WriteDataIntoSnapshotFile(
-    const SnapshotRequestInfo& request_info,
+    SnapshotRequestInfo request_info,
     const base::File::Info& snapshot_file_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  ReadDataChunkFromDeviceFile(
-      std::make_unique<SnapshotFileDetails>(request_info, snapshot_file_info));
+  ReadDataChunkFromDeviceFile(std::make_unique<SnapshotFileDetails>(
+      std::move(request_info), snapshot_file_info));
 }
 
 void MTPReadFileWorker::ReadDataChunkFromDeviceFile(
diff --git a/chrome/browser/media_galleries/chromeos/mtp_read_file_worker.h b/chrome/browser/media_galleries/chromeos/mtp_read_file_worker.h
index 23fa1a9..8814bb3 100644
--- a/chrome/browser/media_galleries/chromeos/mtp_read_file_worker.h
+++ b/chrome/browser/media_galleries/chromeos/mtp_read_file_worker.h
@@ -30,9 +30,8 @@
   //
   // |request_info| specifies the snapshot file request params.
   // |snapshot_file_info| specifies the metadata of the snapshot file.
-  void WriteDataIntoSnapshotFile(
-      const SnapshotRequestInfo& request_info,
-      const base::File::Info& snapshot_file_info);
+  void WriteDataIntoSnapshotFile(SnapshotRequestInfo request_info,
+                                 const base::File::Info& snapshot_file_info);
 
  private:
   // Called when WriteDataIntoSnapshotFile() completes.
diff --git a/chrome/browser/media_galleries/chromeos/snapshot_file_details.cc b/chrome/browser/media_galleries/chromeos/snapshot_file_details.cc
index 347f332..9665b152 100644
--- a/chrome/browser/media_galleries/chromeos/snapshot_file_details.cc
+++ b/chrome/browser/media_galleries/chromeos/snapshot_file_details.cc
@@ -15,35 +15,29 @@
 SnapshotRequestInfo::SnapshotRequestInfo(
     uint32_t file_id,
     const base::FilePath& snapshot_file_path,
-    const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback&
-        success_callback,
-    const MTPDeviceAsyncDelegate::ErrorCallback& error_callback)
+    MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback success_callback,
+    MTPDeviceAsyncDelegate::ErrorCallback error_callback)
     : file_id(file_id),
       snapshot_file_path(snapshot_file_path),
-      success_callback(success_callback),
-      error_callback(error_callback) {}
+      success_callback(std::move(success_callback)),
+      error_callback(std::move(error_callback)) {}
 
-SnapshotRequestInfo::SnapshotRequestInfo(const SnapshotRequestInfo& other) =
-    default;
+SnapshotRequestInfo::SnapshotRequestInfo(SnapshotRequestInfo&& other) = default;
 
-SnapshotRequestInfo::~SnapshotRequestInfo() {
-}
+SnapshotRequestInfo::~SnapshotRequestInfo() = default;
 
 ////////////////////////////////////////////////////////////////////////////////
 //                             SnapshotFileDetails                            //
 ////////////////////////////////////////////////////////////////////////////////
 
-SnapshotFileDetails::SnapshotFileDetails(
-    const SnapshotRequestInfo& request_info,
-    const base::File::Info& file_info)
-    : request_info_(request_info),
+SnapshotFileDetails::SnapshotFileDetails(SnapshotRequestInfo request_info,
+                                         const base::File::Info& file_info)
+    : request_info_(std::move(request_info)),
       file_info_(file_info),
       bytes_written_(0),
-      error_occurred_(false) {
-}
+      error_occurred_(false) {}
 
-SnapshotFileDetails::~SnapshotFileDetails() {
-}
+SnapshotFileDetails::~SnapshotFileDetails() = default;
 
 void SnapshotFileDetails::set_error_occurred(bool error) {
   error_occurred_ = error;
diff --git a/chrome/browser/media_galleries/chromeos/snapshot_file_details.h b/chrome/browser/media_galleries/chromeos/snapshot_file_details.h
index d20b9f8..f5b31d7 100644
--- a/chrome/browser/media_galleries/chromeos/snapshot_file_details.h
+++ b/chrome/browser/media_galleries/chromeos/snapshot_file_details.h
@@ -17,13 +17,14 @@
 
 // Used to represent snapshot file request params.
 struct SnapshotRequestInfo {
-  SnapshotRequestInfo(
-      uint32_t file_id,
-      const base::FilePath& snapshot_file_path,
-      const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback&
-          success_callback,
-      const MTPDeviceAsyncDelegate::ErrorCallback& error_callback);
-  SnapshotRequestInfo(const SnapshotRequestInfo& other);
+  SnapshotRequestInfo(uint32_t file_id,
+                      const base::FilePath& snapshot_file_path,
+                      MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback
+                          success_callback,
+                      MTPDeviceAsyncDelegate::ErrorCallback error_callback);
+  SnapshotRequestInfo(SnapshotRequestInfo&& other);
+  SnapshotRequestInfo(const SnapshotRequestInfo& other) = delete;
+  SnapshotRequestInfo& operator=(const SnapshotRequestInfo& other) = delete;
   ~SnapshotRequestInfo();
 
   // MTP device file id.
@@ -33,11 +34,10 @@
   const base::FilePath snapshot_file_path;
 
   // A callback to be called when CreateSnapshotFile() succeeds.
-  const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback
-      success_callback;
+  MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback success_callback;
 
   // A callback to be called when CreateSnapshotFile() fails.
-  const MTPDeviceAsyncDelegate::ErrorCallback error_callback;
+  MTPDeviceAsyncDelegate::ErrorCallback error_callback;
 };
 
 // SnapshotFileDetails tracks the current state of the snapshot file (e.g how
@@ -45,7 +45,7 @@
 // metadata information, etc).
 class SnapshotFileDetails {
  public:
-  SnapshotFileDetails(const SnapshotRequestInfo& request_info,
+  SnapshotFileDetails(SnapshotRequestInfo request_info,
                       const base::File::Info& file_info);
 
   ~SnapshotFileDetails();
@@ -62,13 +62,12 @@
     return file_info_;
   }
 
-  const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback
-      success_callback() const {
-    return request_info_.success_callback;
+  MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback success_callback() {
+    return std::move(request_info_.success_callback);
   }
 
-  const MTPDeviceAsyncDelegate::ErrorCallback error_callback() const {
-    return request_info_.error_callback;
+  MTPDeviceAsyncDelegate::ErrorCallback error_callback() {
+    return std::move(request_info_.error_callback);
   }
 
   bool error_occurred() const {
@@ -94,7 +93,7 @@
 
  private:
   // Snapshot file request params.
-  const SnapshotRequestInfo request_info_;
+  SnapshotRequestInfo request_info_;
 
   // Metadata of the snapshot file (such as name, size, type, etc).
   const base::File::Info file_info_;
diff --git a/chrome/browser/media_galleries/fileapi/device_media_async_file_util.cc b/chrome/browser/media_galleries/fileapi/device_media_async_file_util.cc
index 732583f..14ecc30f 100644
--- a/chrome/browser/media_galleries/fileapi/device_media_async_file_util.cc
+++ b/chrome/browser/media_galleries/fileapi/device_media_async_file_util.cc
@@ -222,9 +222,9 @@
   delegate->CreateSnapshotFile(
       url.path(),  // device file path
       snapshot_file_path,
-      base::Bind(&OnDidCreateSnapshotFile, copyable_callback,
-                 base::RetainedRef(context->task_runner()),
-                 validate_media_files),
+      base::BindRepeating(&OnDidCreateSnapshotFile, copyable_callback,
+                          base::RetainedRef(context->task_runner()),
+                          validate_media_files),
       base::BindRepeating(&OnCreateSnapshotFileError, copyable_callback));
 }
 
diff --git a/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.cc b/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.cc
index 6a6d964..091fbdd 100644
--- a/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.cc
+++ b/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.cc
@@ -11,16 +11,16 @@
     net::IOBuffer* buf,
     int64_t offset,
     int buf_len,
-    const ReadBytesSuccessCallback& success_callback,
-    const ErrorCallback& error_callback)
+    ReadBytesSuccessCallback success_callback,
+    ErrorCallback error_callback)
     : file_id(file_id),
       buf(buf),
       offset(offset),
       buf_len(buf_len),
-      success_callback(success_callback),
-      error_callback(error_callback) {}
+      success_callback(std::move(success_callback)),
+      error_callback(std::move(error_callback)) {}
 
 MTPDeviceAsyncDelegate::ReadBytesRequest::ReadBytesRequest(
-    const ReadBytesRequest& other) = default;
+    ReadBytesRequest&& other) = default;
 
-MTPDeviceAsyncDelegate::ReadBytesRequest::~ReadBytesRequest() {}
+MTPDeviceAsyncDelegate::ReadBytesRequest::~ReadBytesRequest() = default;
diff --git a/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h b/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h
index 97b5033e6..50bc231 100644
--- a/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h
+++ b/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h
@@ -34,35 +34,35 @@
       base::OnceCallback<void(const base::File::Info& file_info)>;
 
   // A callback to be called when CreateDirectory method call succeeds.
-  typedef base::RepeatingClosure CreateDirectorySuccessCallback;
+  using CreateDirectorySuccessCallback = base::OnceClosure;
 
   // A callback to be called when ReadDirectory method call succeeds.
-  typedef base::RepeatingCallback<
-      void(storage::AsyncFileUtil::EntryList file_list, bool has_more)>
-      ReadDirectorySuccessCallback;
+  using ReadDirectorySuccessCallback =
+      base::OnceCallback<void(storage::AsyncFileUtil::EntryList file_list,
+                              bool has_more)>;
 
   // A callback to be called when GetFileInfo/ReadDirectory/CreateSnapshot
   // method call fails.
-  typedef base::RepeatingCallback<void(base::File::Error error)> ErrorCallback;
+  using ErrorCallback = base::OnceCallback<void(base::File::Error error)>;
 
   // A callback to be called when CreateSnapshotFile method call succeeds.
-  typedef base::Callback<
-      void(const base::File::Info& file_info,
-           const base::FilePath& local_path)> CreateSnapshotFileSuccessCallback;
+  using CreateSnapshotFileSuccessCallback =
+      base::OnceCallback<void(const base::File::Info& file_info,
+                              const base::FilePath& local_path)>;
 
   // A callback to be called when ReadBytes method call succeeds.
-  typedef base::Callback<
-      void(const base::File::Info& file_info,
-           int bytes_read)> ReadBytesSuccessCallback;
+  using ReadBytesSuccessCallback =
+      base::OnceCallback<void(const base::File::Info& file_info,
+                              int bytes_read)>;
 
   struct ReadBytesRequest {
     ReadBytesRequest(uint32_t file_id,
                      net::IOBuffer* buf,
                      int64_t offset,
                      int buf_len,
-                     const ReadBytesSuccessCallback& success_callback,
-                     const ErrorCallback& error_callback);
-    ReadBytesRequest(const ReadBytesRequest& other);
+                     ReadBytesSuccessCallback success_callback,
+                     ErrorCallback error_callback);
+    ReadBytesRequest(ReadBytesRequest&& other);
     ~ReadBytesRequest();
 
     uint32_t file_id;
@@ -79,19 +79,19 @@
   typedef base::Callback<base::FilePath()> CreateTemporaryFileCallback;
 
   // A callback to be called when CopyFileLocal method call succeeds.
-  typedef base::Closure CopyFileLocalSuccessCallback;
+  using CopyFileLocalSuccessCallback = base::OnceClosure;
 
   // A callback to be called when MoveFileLocal method call succeeds.
-  typedef base::Closure MoveFileLocalSuccessCallback;
+  using MoveFileLocalSuccessCallback = base::OnceClosure;
 
   // A callback to be called when CopyFileFromLocal method call succeeds.
-  typedef base::Closure CopyFileFromLocalSuccessCallback;
+  using CopyFileFromLocalSuccessCallback = base::OnceClosure;
 
   // A callback to be called when DeleteFile method call succeeds.
-  typedef base::Closure DeleteFileSuccessCallback;
+  using DeleteFileSuccessCallback = base::OnceClosure;
 
   // A callback to be called when DeleteDirectory method call succeeds.
-  typedef base::Closure DeleteDirectorySuccessCallback;
+  using DeleteDirectorySuccessCallback = base::OnceClosure;
 
   typedef storage::AsyncFileUtil::CopyFileProgressCallback
       CopyFileProgressCallback;
@@ -100,33 +100,31 @@
   // callback asynchronously when complete.
   virtual void GetFileInfo(const base::FilePath& file_path,
                            GetFileInfoSuccessCallback success_callback,
-                           const ErrorCallback& error_callback) = 0;
+                           ErrorCallback error_callback) = 0;
 
   // Creates a directory to |directory_path|. When |exclusive| is true, this
   // returns base::File::FILE_ERROR_EXISTS if a directory already exists for
   // |directory_path|. When |recursive| is true, the directory is created
   // recursively to |directory_path|.
-  virtual void CreateDirectory(
-      const base::FilePath& directory_path,
-      const bool exclusive,
-      const bool recursive,
-      const CreateDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback) = 0;
+  virtual void CreateDirectory(const base::FilePath& directory_path,
+                               const bool exclusive,
+                               const bool recursive,
+                               CreateDirectorySuccessCallback success_callback,
+                               ErrorCallback error_callback) = 0;
 
   // Enumerates the |root| directory contents and invokes the appropriate
   // callback asynchronously when complete.
-  virtual void ReadDirectory(
-      const base::FilePath& root,
-      const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback) = 0;
+  virtual void ReadDirectory(const base::FilePath& root,
+                             ReadDirectorySuccessCallback success_callback,
+                             ErrorCallback error_callback) = 0;
 
   // Copy the contents of |device_file_path| to |local_path|. Invokes the
   // appropriate callback asynchronously when complete.
   virtual void CreateSnapshotFile(
       const base::FilePath& device_file_path,
       const base::FilePath& local_path,
-      const CreateSnapshotFileSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) = 0;
+      CreateSnapshotFileSuccessCallback success_callback,
+      ErrorCallback error_callback) = 0;
 
   // Platform-specific implementations that are streaming don't create a local
   // snapshot file. Blobs are instead FileSystemURL backed and read in a stream.
@@ -139,8 +137,8 @@
                          const scoped_refptr<net::IOBuffer>& buf,
                          int64_t offset,
                          int buf_len,
-                         const ReadBytesSuccessCallback& success_callback,
-                         const ErrorCallback& error_callback) = 0;
+                         ReadBytesSuccessCallback success_callback,
+                         ErrorCallback error_callback) = 0;
 
   // Returns true if storage is opened for read only.
   virtual bool IsReadOnly() const = 0;
@@ -152,8 +150,8 @@
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
       const CopyFileProgressCallback& progress_callback,
-      const CopyFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) = 0;
+      CopyFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) = 0;
 
   // Moves a file |source_file_path| to |device_file_path|.
   // |create_temporary_file_callback| can be used to create a temporary file.
@@ -161,26 +159,25 @@
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
-      const MoveFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) = 0;
+      MoveFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) = 0;
 
   // Copies a file from |source_file_path| to |device_file_path|.
   virtual void CopyFileFromLocal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
-      const CopyFileFromLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) = 0;
+      CopyFileFromLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) = 0;
 
   // Deletes a file at |file_path|.
   virtual void DeleteFile(const base::FilePath& file_path,
-                          const DeleteFileSuccessCallback& success_callback,
-                          const ErrorCallback& error_callback) = 0;
+                          DeleteFileSuccessCallback success_callback,
+                          ErrorCallback error_callback) = 0;
 
   // Deletes a directory at |file_path|. The directory must be empty.
-  virtual void DeleteDirectory(
-      const base::FilePath& file_path,
-      const DeleteDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback) = 0;
+  virtual void DeleteDirectory(const base::FilePath& file_path,
+                               DeleteDirectorySuccessCallback success_callback,
+                               ErrorCallback error_callback) = 0;
 
   // Adds watcher to |file_path| as |origin|.
   virtual void AddWatcher(
diff --git a/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.cc b/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.cc
index 7604e4f1..e47c86a 100644
--- a/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.cc
+++ b/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.cc
@@ -64,17 +64,17 @@
       header_buf_len = net::kMaxBytesToSniff;
     }
 
-    ReadBytes(
-        url_, header_buf.get(), 0, header_buf_len,
-        base::Bind(&MTPFileStreamReader::FinishValidateMediaHeader,
-                   weak_factory_.GetWeakPtr(), base::RetainedRef(header_buf),
-                   base::RetainedRef(buf), buf_len));
+    ReadBytes(url_, header_buf.get(), 0, header_buf_len,
+              base::BindOnce(&MTPFileStreamReader::FinishValidateMediaHeader,
+                             weak_factory_.GetWeakPtr(),
+                             base::RetainedRef(header_buf),
+                             base::RetainedRef(buf), buf_len));
     return net::ERR_IO_PENDING;
   }
 
-  ReadBytes(
-      url_, buf, current_offset_, buf_len,
-      base::Bind(&MTPFileStreamReader::FinishRead, weak_factory_.GetWeakPtr()));
+  ReadBytes(url_, buf, current_offset_, buf_len,
+            base::BindOnce(&MTPFileStreamReader::FinishRead,
+                           weak_factory_.GetWeakPtr()));
 
   return net::ERR_IO_PENDING;
 }
@@ -123,9 +123,9 @@
 
   // Header buffer isn't the same as the original read buffer. Make a separate
   // request for that.
-  ReadBytes(
-      url_, buf, current_offset_, buf_len,
-      base::Bind(&MTPFileStreamReader::FinishRead, weak_factory_.GetWeakPtr()));
+  ReadBytes(url_, buf, current_offset_, buf_len,
+            base::BindOnce(&MTPFileStreamReader::FinishRead,
+                           weak_factory_.GetWeakPtr()));
 }
 
 void MTPFileStreamReader::FinishRead(const base::File::Info& file_info,
@@ -169,7 +169,7 @@
     const scoped_refptr<net::IOBuffer>& buf,
     int64_t offset,
     int buf_len,
-    const MTPDeviceAsyncDelegate::ReadBytesSuccessCallback& success_callback) {
+    MTPDeviceAsyncDelegate::ReadBytesSuccessCallback success_callback) {
   MTPDeviceAsyncDelegate* delegate =
       MTPDeviceMapService::GetInstance()->GetMTPDeviceAsyncDelegate(url);
   if (!delegate) {
@@ -178,7 +178,7 @@
   }
 
   delegate->ReadBytes(
-      url.path(), buf, offset, buf_len, success_callback,
+      url.path(), buf, offset, buf_len, std::move(success_callback),
       base::BindRepeating(
           &MTPFileStreamReader::CallReadCallbackwithPlatformFileError,
           weak_factory_.GetWeakPtr()));
diff --git a/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h b/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h
index 333078e1..d7d137c 100644
--- a/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h
+++ b/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h
@@ -57,7 +57,7 @@
       const scoped_refptr<net::IOBuffer>& buf,
       int64_t offset,
       int buf_len,
-      const MTPDeviceAsyncDelegate::ReadBytesSuccessCallback& success_callback);
+      MTPDeviceAsyncDelegate::ReadBytesSuccessCallback success_callback);
 
   scoped_refptr<storage::FileSystemContext> file_system_context_;
   storage::FileSystemURL url_;
diff --git a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h
index 061d088..5e973f6 100644
--- a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h
+++ b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h
@@ -37,57 +37,55 @@
   // similarly-named methods on the UI thread.
   void GetFileInfo(const base::FilePath& file_path,
                    GetFileInfoSuccessCallback success_callback,
-                   const ErrorCallback& error_callback) override;
+                   ErrorCallback error_callback) override;
 
   void CreateDirectory(const base::FilePath& directory_path,
                        const bool exclusive,
                        const bool recursive,
-                       const CreateDirectorySuccessCallback& success_callback,
-                       const ErrorCallback& error_callback) override;
+                       CreateDirectorySuccessCallback success_callback,
+                       ErrorCallback error_callback) override;
 
   // Note: passed absolute paths, but expects relative paths in reply.
   void ReadDirectory(const base::FilePath& root,
-                     const ReadDirectorySuccessCallback& success_callback,
-                     const ErrorCallback& error_callback) override;
+                     ReadDirectorySuccessCallback success_callback,
+                     ErrorCallback error_callback) override;
 
   // Note: passed absolute paths.
-  void CreateSnapshotFile(
-      const base::FilePath& device_file_path,
-      const base::FilePath& local_path,
-      const CreateSnapshotFileSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+  void CreateSnapshotFile(const base::FilePath& device_file_path,
+                          const base::FilePath& local_path,
+                          CreateSnapshotFileSuccessCallback success_callback,
+                          ErrorCallback error_callback) override;
   bool IsStreaming() override;
   void ReadBytes(const base::FilePath& device_file_path,
                  const scoped_refptr<net::IOBuffer>& buf,
                  int64_t offset,
                  int buf_len,
-                 const ReadBytesSuccessCallback& success_callback,
-                 const ErrorCallback& error_callback) override;
+                 ReadBytesSuccessCallback success_callback,
+                 ErrorCallback error_callback) override;
   bool IsReadOnly() const override;
   void CopyFileLocal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
       const CopyFileProgressCallback& progress_callback,
-      const CopyFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+      CopyFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) override;
   void MoveFileLocal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
-      const MoveFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
-  void CopyFileFromLocal(
-      const base::FilePath& source_file_path,
-      const base::FilePath& device_file_path,
-      const CopyFileFromLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+      MoveFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) override;
+  void CopyFileFromLocal(const base::FilePath& source_file_path,
+                         const base::FilePath& device_file_path,
+                         CopyFileFromLocalSuccessCallback success_callback,
+                         ErrorCallback error_callback) override;
   void DeleteFile(const base::FilePath& file_path,
-                  const DeleteFileSuccessCallback& success_callback,
-                  const ErrorCallback& error_callback) override;
+                  DeleteFileSuccessCallback success_callback,
+                  ErrorCallback error_callback) override;
   void DeleteDirectory(const base::FilePath& file_path,
-                       const DeleteDirectorySuccessCallback& success_callback,
-                       const ErrorCallback& error_callback) override;
+                       DeleteDirectorySuccessCallback success_callback,
+                       ErrorCallback error_callback) override;
   void AddWatcher(const GURL& origin,
                   const base::FilePath& file_path,
                   const bool recursive,
@@ -124,17 +122,15 @@
                        base::File::Error* error);
 
   // Delegate for ReadDirectory, called on the UI thread.
-  void ReadDirectoryImpl(
-      const base::FilePath& root,
-      const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
+  void ReadDirectoryImpl(const base::FilePath& root,
+                         ReadDirectorySuccessCallback success_callback,
+                         ErrorCallback error_callback);
 
   // Delegate for CreateSnapshotFile, called on the UI thread.
-  void DownloadFile(
-      const base::FilePath& device_file_path,
-      const base::FilePath& local_path,
-      const CreateSnapshotFileSuccessCallback& success_callback,
-      const ErrorCallback& error_callback);
+  void DownloadFile(const base::FilePath& device_file_path,
+                    const base::FilePath& local_path,
+                    CreateSnapshotFileSuccessCallback success_callback,
+                    ErrorCallback error_callback);
 
   // Public for closures; should not be called except by
   // CancelTasksAndDeleteDelegate.
@@ -168,7 +164,6 @@
                     const base::FilePath& snapshot_filename,
                     CreateSnapshotFileSuccessCallback success_cb,
                     ErrorCallback error_cb);
-    ReadFileRequest(const ReadFileRequest& other);
     ~ReadFileRequest();
 
     std::string request_file;
@@ -183,7 +178,6 @@
     ReadDirectoryRequest(const base::FilePath& dir,
                          ReadDirectorySuccessCallback success_cb,
                          ErrorCallback error_cb);
-    ReadDirectoryRequest(const ReadDirectoryRequest& other);
     ~ReadDirectoryRequest();
 
     base::FilePath directory;
diff --git a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.mm b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.mm
index b133424..5307755 100644
--- a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.mm
+++ b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.mm
@@ -152,11 +152,11 @@
 void ForwardGetFileInfo(base::File::Info* info,
                         base::File::Error* error,
                         GetFileInfoSuccessCallback success_callback,
-                        const ErrorCallback& error_callback) {
+                        ErrorCallback error_callback) {
   if (*error == base::File::FILE_OK)
     std::move(success_callback).Run(*info);
   else
-    error_callback.Run(*error);
+    std::move(error_callback).Run(*error);
 }
 
 }  // namespace
@@ -164,7 +164,7 @@
 void MTPDeviceDelegateImplMac::GetFileInfo(
     const base::FilePath& file_path,
     GetFileInfoSuccessCallback success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   base::File::Info* info = new base::File::Info;
   base::File::Error* error = new base::File::Error;
   // Note: ownership of these objects passed into the reply callback.
@@ -173,37 +173,39 @@
       base::Bind(&MTPDeviceDelegateImplMac::GetFileInfoImpl,
                  base::Unretained(this), file_path, info, error),
       base::BindOnce(&ForwardGetFileInfo, base::Owned(info), base::Owned(error),
-                     std::move(success_callback), error_callback));
+                     std::move(success_callback), std::move(error_callback)));
 }
 
 void MTPDeviceDelegateImplMac::CreateDirectory(
     const base::FilePath& directory_path,
     const bool exclusive,
     const bool recursive,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
 void MTPDeviceDelegateImplMac::ReadDirectory(
-      const base::FilePath& root,
-      const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback) {
+    const base::FilePath& root,
+    ReadDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&MTPDeviceDelegateImplMac::ReadDirectoryImpl,
-                                base::Unretained(this), root, success_callback,
-                                error_callback));
+      FROM_HERE,
+      base::BindOnce(&MTPDeviceDelegateImplMac::ReadDirectoryImpl,
+                     base::Unretained(this), root, std::move(success_callback),
+                     std::move(error_callback)));
 }
 
 void MTPDeviceDelegateImplMac::CreateSnapshotFile(
-      const base::FilePath& device_file_path,
-      const base::FilePath& local_path,
-      const CreateSnapshotFileSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) {
+    const base::FilePath& device_file_path,
+    const base::FilePath& local_path,
+    CreateSnapshotFileSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&MTPDeviceDelegateImplMac::DownloadFile,
-                                base::Unretained(this), device_file_path,
-                                local_path, success_callback, error_callback));
+      FROM_HERE,
+      base::BindOnce(&MTPDeviceDelegateImplMac::DownloadFile,
+                     base::Unretained(this), device_file_path, local_path,
+                     std::move(success_callback), std::move(error_callback)));
 }
 
 bool MTPDeviceDelegateImplMac::IsStreaming() {
@@ -215,8 +217,8 @@
     const scoped_refptr<net::IOBuffer>& buf,
     int64_t offset,
     int buf_len,
-    const ReadBytesSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ReadBytesSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
@@ -229,8 +231,8 @@
     const base::FilePath& device_file_path,
     const CreateTemporaryFileCallback& create_temporary_file_callback,
     const CopyFileProgressCallback& progress_callback,
-    const CopyFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CopyFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
@@ -238,30 +240,30 @@
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
     const CreateTemporaryFileCallback& create_temporary_file_callback,
-    const MoveFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    MoveFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
 void MTPDeviceDelegateImplMac::CopyFileFromLocal(
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
-    const CopyFileFromLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CopyFileFromLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
 void MTPDeviceDelegateImplMac::DeleteFile(
     const base::FilePath& file_path,
-    const DeleteFileSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    DeleteFileSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
 void MTPDeviceDelegateImplMac::DeleteDirectory(
     const base::FilePath& file_path,
-    const DeleteDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    DeleteDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
@@ -305,13 +307,13 @@
 }
 
 void MTPDeviceDelegateImplMac::ReadDirectoryImpl(
-      const base::FilePath& root,
-      const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback) {
+    const base::FilePath& root,
+    ReadDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  read_dir_transactions_.push_back(ReadDirectoryRequest(
-      root, success_callback, error_callback));
+  read_dir_transactions_.emplace_back(root, std::move(success_callback),
+                                      std::move(error_callback));
 
   if (received_all_files_) {
     NotifyReadDir();
@@ -337,16 +339,16 @@
       ++iter;
       continue;
     }
-    iter->error_callback.Run(base::File::FILE_ERROR_ABORT);
+    std::move(iter->error_callback).Run(base::File::FILE_ERROR_ABORT);
     iter = read_dir_transactions_.erase(iter);
   }
 }
 
 void MTPDeviceDelegateImplMac::DownloadFile(
-      const base::FilePath& device_file_path,
-      const base::FilePath& local_path,
-      const CreateSnapshotFileSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) {
+    const base::FilePath& device_file_path,
+    const base::FilePath& local_path,
+    CreateSnapshotFileSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   base::File::Error error;
@@ -354,16 +356,16 @@
   GetFileInfoImpl(device_file_path, &info, &error);
   if (error != base::File::FILE_OK) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(error_callback, error));
+        FROM_HERE, base::BindOnce(std::move(error_callback), error));
     return;
   }
 
   base::FilePath relative_path;
   root_path_.AppendRelativePath(device_file_path, &relative_path);
 
-  read_file_transactions_.push_back(
-      ReadFileRequest(relative_path.value(), local_path,
-                      success_callback, error_callback));
+  read_file_transactions_.emplace_back(relative_path.value(), local_path,
+                                       std::move(success_callback),
+                                       std::move(error_callback));
 
   camera_interface_->DownloadFile(relative_path.value(), local_path);
 }
@@ -391,16 +393,16 @@
   for (ReadFileTransactionList::iterator iter = read_file_transactions_.begin();
        iter != read_file_transactions_.end(); ++iter) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(iter->error_callback, base::File::FILE_ERROR_ABORT));
+        FROM_HERE, base::BindOnce(std::move(iter->error_callback),
+                                  base::File::FILE_ERROR_ABORT));
   }
   read_file_transactions_.clear();
 
   for (ReadDirTransactionList::iterator iter = read_dir_transactions_.begin();
        iter != read_dir_transactions_.end(); ++iter) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(iter->error_callback, base::File::FILE_ERROR_ABORT));
+        FROM_HERE, base::BindOnce(std::move(iter->error_callback),
+                                  base::File::FILE_ERROR_ABORT));
   }
   read_dir_transactions_.clear();
 }
@@ -471,10 +473,11 @@
 
     if (found_path) {
       content::GetIOThreadTaskRunner({})->PostTask(
-          FROM_HERE, base::BindOnce(iter->success_callback, entry_list, false));
+          FROM_HERE,
+          base::BindOnce(std::move(iter->success_callback), entry_list, false));
     } else {
       content::GetIOThreadTaskRunner({})->PostTask(
-          FROM_HERE, base::BindOnce(iter->error_callback,
+          FROM_HERE, base::BindOnce(std::move(iter->error_callback),
                                     base::File::FILE_ERROR_NOT_FOUND));
     }
   }
@@ -502,7 +505,7 @@
 
   if (error != base::File::FILE_OK) {
     content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(iter->error_callback, error));
+        FROM_HERE, base::BindOnce(std::move(iter->error_callback), error));
     read_file_transactions_.erase(iter);
     return;
   }
@@ -519,8 +522,8 @@
 
   base::File::Info info = file_info_[item_filename.value()];
   content::GetIOThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(iter->success_callback, info, iter->snapshot_file));
+      FROM_HERE, base::BindOnce(std::move(iter->success_callback), info,
+                                iter->snapshot_file));
   read_file_transactions_.erase(iter);
 }
 
@@ -531,26 +534,19 @@
     ErrorCallback error_cb)
     : request_file(file),
       snapshot_file(snapshot_filename),
-      success_callback(success_cb),
-      error_callback(error_cb) {}
+      success_callback(std::move(success_cb)),
+      error_callback(std::move(error_cb)) {}
 
-MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() {}
-
-MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest(
-    const ReadFileRequest& other) = default;
-
-MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() {}
+MTPDeviceDelegateImplMac::ReadFileRequest::ReadFileRequest() = default;
+MTPDeviceDelegateImplMac::ReadFileRequest::~ReadFileRequest() = default;
 
 MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
     const base::FilePath& dir,
     ReadDirectorySuccessCallback success_cb,
     ErrorCallback error_cb)
     : directory(dir),
-      success_callback(success_cb),
-      error_callback(error_cb) {}
-
-MTPDeviceDelegateImplMac::ReadDirectoryRequest::ReadDirectoryRequest(
-    const ReadDirectoryRequest& other) = default;
+      success_callback(std::move(success_cb)),
+      error_callback(std::move(error_cb)) {}
 
 MTPDeviceDelegateImplMac::ReadDirectoryRequest::~ReadDirectoryRequest() {}
 
diff --git a/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.cc b/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.cc
index 0ebf5fe..b13fc6c 100644
--- a/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.cc
+++ b/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.cc
@@ -271,13 +271,14 @@
 // Returns the total number of bytes written to the snapshot file for non-empty
 // files, or 0 on failure. For empty files, just return 0.
 DWORD WriteDataChunkIntoSnapshotFileOnBlockingPoolThread(
-    const SnapshotFileDetails& file_details) {
-  if (file_details.file_info().size == 0)
+    const base::File::Info& file_info,
+    Microsoft::WRL::ComPtr<IStream> device_file_stream,
+    const base::FilePath& snapshot_file_path,
+    DWORD optimal_transfer_size) {
+  if (file_info.size == 0)
     return 0;
   return media_transfer_protocol::CopyDataChunkToLocalFile(
-      file_details.device_file_stream(),
-      file_details.request_info().snapshot_file_path,
-      file_details.optimal_transfer_size());
+      device_file_stream.Get(), snapshot_file_path, optimal_transfer_size);
 }
 
 void DeletePortableDeviceOnBlockingPoolThread(
@@ -374,7 +375,7 @@
 void MTPDeviceDelegateImplWin::GetFileInfo(
     const base::FilePath& file_path,
     GetFileInfoSuccessCallback success_callback,
-    const ErrorCallback& error_callback) {
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!file_path.empty());
   base::File::Info* file_info = new base::File::Info;
@@ -384,7 +385,7 @@
                      file_path, base::Unretained(file_info)),
       base::BindOnce(&MTPDeviceDelegateImplWin::OnGetFileInfo,
                      weak_ptr_factory_.GetWeakPtr(),
-                     std::move(success_callback), error_callback,
+                     std::move(success_callback), std::move(error_callback),
                      base::Owned(file_info))));
 }
 
@@ -392,15 +393,15 @@
     const base::FilePath& directory_path,
     const bool exclusive,
     const bool recursive,
-    const CreateDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
 void MTPDeviceDelegateImplWin::ReadDirectory(
     const base::FilePath& root,
-    const ReadDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ReadDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!root.empty());
   storage::AsyncFileUtil::EntryList* entries =
@@ -410,21 +411,23 @@
       base::BindOnce(&ReadDirectoryOnBlockingPoolThread, storage_device_info_,
                      root, base::Unretained(entries)),
       base::BindOnce(&MTPDeviceDelegateImplWin::OnDidReadDirectory,
-                     weak_ptr_factory_.GetWeakPtr(), success_callback,
-                     error_callback, base::Owned(entries))));
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(success_callback), std::move(error_callback),
+                     base::Owned(entries))));
 }
 
 void MTPDeviceDelegateImplWin::CreateSnapshotFile(
     const base::FilePath& device_file_path,
     const base::FilePath& snapshot_file_path,
-    const CreateSnapshotFileSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CreateSnapshotFileSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!device_file_path.empty());
   DCHECK(!snapshot_file_path.empty());
-  std::unique_ptr<SnapshotFileDetails> file_details(new SnapshotFileDetails(
-      SnapshotRequestInfo(device_file_path, snapshot_file_path,
-                          success_callback, error_callback)));
+  std::unique_ptr<SnapshotFileDetails> file_details(
+      new SnapshotFileDetails(SnapshotRequestInfo(
+          device_file_path, snapshot_file_path, std::move(success_callback),
+          std::move(error_callback))));
   // Passing a raw SnapshotFileDetails* to the blocking pool is safe, because
   // it is owned by |file_details| in the reply callback.
   EnsureInitAndRunTask(PendingTaskInfo(
@@ -444,8 +447,8 @@
     const scoped_refptr<net::IOBuffer>& buf,
     int64_t offset,
     int buf_len,
-    const ReadBytesSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    ReadBytesSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
@@ -458,8 +461,8 @@
     const base::FilePath& device_file_path,
     const CreateTemporaryFileCallback& create_temporary_file_callback,
     const CopyFileProgressCallback& progress_callback,
-    const CopyFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CopyFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
@@ -467,30 +470,30 @@
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
     const CreateTemporaryFileCallback& create_temporary_file_callback,
-    const MoveFileLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    MoveFileLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
 void MTPDeviceDelegateImplWin::CopyFileFromLocal(
     const base::FilePath& source_file_path,
     const base::FilePath& device_file_path,
-    const CopyFileFromLocalSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    CopyFileFromLocalSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
 void MTPDeviceDelegateImplWin::DeleteFile(
     const base::FilePath& file_path,
-    const DeleteFileSuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    DeleteFileSuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
 void MTPDeviceDelegateImplWin::DeleteDirectory(
     const base::FilePath& file_path,
-    const DeleteDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback) {
+    DeleteDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback) {
   NOTREACHED();
 }
 
@@ -555,8 +558,13 @@
   DCHECK(current_snapshot_details_.get());
   base::PostTaskAndReplyWithResult(
       media_task_runner_.get(), FROM_HERE,
-      base::BindOnce(&WriteDataChunkIntoSnapshotFileOnBlockingPoolThread,
-                     *current_snapshot_details_),
+      base::BindOnce(
+          &WriteDataChunkIntoSnapshotFileOnBlockingPoolThread,
+          current_snapshot_details_->file_info(),
+          Microsoft::WRL::ComPtr<IStream>(
+              current_snapshot_details_->device_file_stream()),
+          current_snapshot_details_->request_info().snapshot_file_path,
+          current_snapshot_details_->optimal_transfer_size()),
       base::BindOnce(
           &MTPDeviceDelegateImplWin::OnWroteDataChunkIntoSnapshotFile,
           weak_ptr_factory_.GetWeakPtr(),
@@ -584,7 +592,7 @@
 
 void MTPDeviceDelegateImplWin::OnGetFileInfo(
     GetFileInfoSuccessCallback success_callback,
-    const ErrorCallback& error_callback,
+    ErrorCallback error_callback,
     base::File::Info* file_info,
     base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@@ -592,22 +600,22 @@
   if (error == base::File::FILE_OK)
     std::move(success_callback).Run(*file_info);
   else
-    error_callback.Run(error);
+    std::move(error_callback).Run(error);
   task_in_progress_ = false;
   ProcessNextPendingRequest();
 }
 
 void MTPDeviceDelegateImplWin::OnDidReadDirectory(
-    const ReadDirectorySuccessCallback& success_callback,
-    const ErrorCallback& error_callback,
+    ReadDirectorySuccessCallback success_callback,
+    ErrorCallback error_callback,
     storage::AsyncFileUtil::EntryList* file_list,
     base::File::Error error) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(file_list);
   if (error == base::File::FILE_OK)
-    success_callback.Run(*file_list, false /*no more entries*/);
+    std::move(success_callback).Run(*file_list, false /*no more entries*/);
   else
-    error_callback.Run(error);
+    std::move(error_callback).Run(error);
   task_in_progress_ = false;
   ProcessNextPendingRequest();
 }
@@ -621,7 +629,7 @@
   DCHECK(!file_details->request_info().snapshot_file_path.empty());
   DCHECK(!current_snapshot_details_.get());
   if (error != base::File::FILE_OK) {
-    file_details->request_info().error_callback.Run(error);
+    std::move(file_details->error_callback()).Run(error);
     task_in_progress_ = false;
     ProcessNextPendingRequest();
     return;
@@ -664,12 +672,12 @@
     return;
   }
   if (succeeded) {
-    current_snapshot_details_->request_info().success_callback.Run(
-        current_snapshot_details_->file_info(),
-        current_snapshot_details_->request_info().snapshot_file_path);
+    std::move(current_snapshot_details_->success_callback())
+        .Run(current_snapshot_details_->file_info(),
+             current_snapshot_details_->request_info().snapshot_file_path);
   } else {
-    current_snapshot_details_->request_info().error_callback.Run(
-        base::File::FILE_ERROR_FAILED);
+    std::move(current_snapshot_details_->error_callback())
+        .Run(base::File::FILE_ERROR_FAILED);
   }
   task_in_progress_ = false;
   current_snapshot_details_.reset();
diff --git a/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h b/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h
index b7a087b..7f86856a 100644
--- a/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h
+++ b/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h
@@ -96,54 +96,50 @@
   // MTPDeviceAsyncDelegate:
   void GetFileInfo(const base::FilePath& file_path,
                    GetFileInfoSuccessCallback success_callback,
-                   const ErrorCallback& error_callback) override;
-  void CreateDirectory(
-      const base::FilePath& directory_path,
-      const bool exclusive,
-      const bool recursive,
-      const CreateDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
-  void ReadDirectory(
-      const base::FilePath& root,
-      const ReadDirectorySuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
-  void CreateSnapshotFile(
-      const base::FilePath& device_file_path,
-      const base::FilePath& local_path,
-      const CreateSnapshotFileSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+                   ErrorCallback error_callback) override;
+  void CreateDirectory(const base::FilePath& directory_path,
+                       const bool exclusive,
+                       const bool recursive,
+                       CreateDirectorySuccessCallback success_callback,
+                       ErrorCallback error_callback) override;
+  void ReadDirectory(const base::FilePath& root,
+                     ReadDirectorySuccessCallback success_callback,
+                     ErrorCallback error_callback) override;
+  void CreateSnapshotFile(const base::FilePath& device_file_path,
+                          const base::FilePath& local_path,
+                          CreateSnapshotFileSuccessCallback success_callback,
+                          ErrorCallback error_callback) override;
   bool IsStreaming() override;
   void ReadBytes(const base::FilePath& device_file_path,
                  const scoped_refptr<net::IOBuffer>& buf,
                  int64_t offset,
                  int buf_len,
-                 const ReadBytesSuccessCallback& success_callback,
-                 const ErrorCallback& error_callback) override;
+                 ReadBytesSuccessCallback success_callback,
+                 ErrorCallback error_callback) override;
   bool IsReadOnly() const override;
   void CopyFileLocal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
       const CopyFileProgressCallback& progress_callback,
-      const CopyFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+      CopyFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) override;
   void MoveFileLocal(
       const base::FilePath& source_file_path,
       const base::FilePath& device_file_path,
       const CreateTemporaryFileCallback& create_temporary_file_callback,
-      const MoveFileLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
-  void CopyFileFromLocal(
-      const base::FilePath& source_file_path,
-      const base::FilePath& device_file_path,
-      const CopyFileFromLocalSuccessCallback& success_callback,
-      const ErrorCallback& error_callback) override;
+      MoveFileLocalSuccessCallback success_callback,
+      ErrorCallback error_callback) override;
+  void CopyFileFromLocal(const base::FilePath& source_file_path,
+                         const base::FilePath& device_file_path,
+                         CopyFileFromLocalSuccessCallback success_callback,
+                         ErrorCallback error_callback) override;
   void DeleteFile(const base::FilePath& file_path,
-                  const DeleteFileSuccessCallback& success_callback,
-                  const ErrorCallback& error_callback) override;
+                  DeleteFileSuccessCallback success_callback,
+                  ErrorCallback error_callback) override;
   void DeleteDirectory(const base::FilePath& file_path,
-                       const DeleteDirectorySuccessCallback& success_callback,
-                       const ErrorCallback& error_callback) override;
+                       DeleteDirectorySuccessCallback success_callback,
+                       ErrorCallback error_callback) override;
   void AddWatcher(const GURL& origin,
                   const base::FilePath& file_path,
                   const bool recursive,
@@ -190,7 +186,7 @@
   // If the GetFileInfo() fails, |file_info| is not set and |error_callback| is
   // invoked to notify the caller about the platform file |error|.
   void OnGetFileInfo(GetFileInfoSuccessCallback success_callback,
-                     const ErrorCallback& error_callback,
+                     ErrorCallback error_callback,
                      base::File::Info* file_info,
                      base::File::Error error);
 
@@ -202,8 +198,8 @@
   //
   // If the ReadDirectory() fails, |file_list| is not set and |error_callback|
   // is invoked to notify the caller about the platform file |error|.
-  void OnDidReadDirectory(const ReadDirectorySuccessCallback& success_callback,
-                          const ErrorCallback& error_callback,
+  void OnDidReadDirectory(ReadDirectorySuccessCallback success_callback,
+                          ErrorCallback error_callback,
                           storage::AsyncFileUtil::EntryList* file_list,
                           base::File::Error error);
 
diff --git a/chrome/browser/media_galleries/win/snapshot_file_details.cc b/chrome/browser/media_galleries/win/snapshot_file_details.cc
index 5ad3678..cd39768 100644
--- a/chrome/browser/media_galleries/win/snapshot_file_details.cc
+++ b/chrome/browser/media_galleries/win/snapshot_file_details.cc
@@ -15,34 +15,25 @@
 SnapshotRequestInfo::SnapshotRequestInfo(
     const base::FilePath& device_file_path,
     const base::FilePath& snapshot_file_path,
-    const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback&
-        success_callback,
-    const MTPDeviceAsyncDelegate::ErrorCallback& error_callback)
+    MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback success_callback,
+    MTPDeviceAsyncDelegate::ErrorCallback error_callback)
     : device_file_path(device_file_path),
       snapshot_file_path(snapshot_file_path),
-      success_callback(success_callback),
-      error_callback(error_callback) {
-}
+      success_callback(std::move(success_callback)),
+      error_callback(std::move(error_callback)) {}
 
-SnapshotRequestInfo::SnapshotRequestInfo(const SnapshotRequestInfo& other) =
-    default;
+SnapshotRequestInfo::SnapshotRequestInfo(SnapshotRequestInfo&& other) = default;
 
-SnapshotRequestInfo::~SnapshotRequestInfo() {
-}
+SnapshotRequestInfo::~SnapshotRequestInfo() = default;
 
 ///////////////////////////////////////////////////////////////////////////////
 //                       SnapshotFileDetails                                 //
 ///////////////////////////////////////////////////////////////////////////////
 
-SnapshotFileDetails::SnapshotFileDetails(
-    const SnapshotRequestInfo& request_info)
-    : request_info_(request_info),
+SnapshotFileDetails::SnapshotFileDetails(SnapshotRequestInfo request_info)
+    : request_info_(std::move(request_info)),
       optimal_transfer_size_(0),
-      bytes_written_(0) {
-}
-
-SnapshotFileDetails::SnapshotFileDetails(const SnapshotFileDetails& other) =
-    default;
+      bytes_written_(0) {}
 
 SnapshotFileDetails::~SnapshotFileDetails() {
   file_stream_.Reset();
diff --git a/chrome/browser/media_galleries/win/snapshot_file_details.h b/chrome/browser/media_galleries/win/snapshot_file_details.h
index 136124f..eb26ddfc 100644
--- a/chrome/browser/media_galleries/win/snapshot_file_details.h
+++ b/chrome/browser/media_galleries/win/snapshot_file_details.h
@@ -13,13 +13,14 @@
 
 // Structure used to represent snapshot file request params.
 struct SnapshotRequestInfo {
-  SnapshotRequestInfo(
-      const base::FilePath& device_file_path,
-      const base::FilePath& snapshot_file_path,
-      const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback&
-          success_callback,
-      const MTPDeviceAsyncDelegate::ErrorCallback& error_callback);
-  SnapshotRequestInfo(const SnapshotRequestInfo& other);
+  SnapshotRequestInfo(const base::FilePath& device_file_path,
+                      const base::FilePath& snapshot_file_path,
+                      MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback
+                          success_callback,
+                      MTPDeviceAsyncDelegate::ErrorCallback error_callback);
+  SnapshotRequestInfo(SnapshotRequestInfo&& other);
+  SnapshotRequestInfo(const SnapshotRequestInfo& other) = delete;
+  SnapshotRequestInfo& operator=(const SnapshotRequestInfo& other) = delete;
   ~SnapshotRequestInfo();
 
   // Device file path.
@@ -39,16 +40,21 @@
 // Provides the details for the the creation of snapshot file.
 class SnapshotFileDetails {
  public:
-  explicit SnapshotFileDetails(const SnapshotRequestInfo& request_info);
-  SnapshotFileDetails(const SnapshotFileDetails& other);
+  explicit SnapshotFileDetails(SnapshotRequestInfo request_info);
   ~SnapshotFileDetails();
 
   void set_file_info(const base::File::Info& file_info);
   void set_device_file_stream(IStream* file_stream);
   void set_optimal_transfer_size(DWORD optimal_transfer_size);
 
-  SnapshotRequestInfo request_info() const {
-    return request_info_;
+  const SnapshotRequestInfo& request_info() const { return request_info_; }
+
+  MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback success_callback() {
+    return std::move(request_info_.success_callback);
+  }
+
+  MTPDeviceAsyncDelegate::ErrorCallback error_callback() {
+    return std::move(request_info_.error_callback);
   }
 
   base::File::Info file_info() const {
diff --git a/chrome/browser/notifications/chime/android/features.cc b/chrome/browser/notifications/chime/android/features.cc
index afb0c10..66a12c368 100644
--- a/chrome/browser/notifications/chime/android/features.cc
+++ b/chrome/browser/notifications/chime/android/features.cc
@@ -11,4 +11,9 @@
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
 }  // namespace features
+
+namespace switches {
+const char kDebugChimeNotification[] = "debug-chime-notification";
+}  // namespace switches
+
 }  // namespace notifications
diff --git a/chrome/browser/notifications/chime/android/features.h b/chrome/browser/notifications/chime/android/features.h
index a3c61bb..7c052c1 100644
--- a/chrome/browser/notifications/chime/android/features.h
+++ b/chrome/browser/notifications/chime/android/features.h
@@ -14,6 +14,11 @@
 extern const base::Feature kUseChimeAndroidSdk;
 
 }  // namespace features
+
+namespace switches {
+// Debug flag to show Chime notifications.
+extern const char kDebugChimeNotification[];
+}  // namespace switches
 }  // namespace notifications
 
 #endif  // CHROME_BROWSER_NOTIFICATIONS_CHIME_ANDROID_FEATURES_H_
diff --git a/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc b/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
index 9722503..9307551 100644
--- a/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
+++ b/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
@@ -105,8 +105,8 @@
     category_params->set_min_none_weight(0.8);
     category_params->set_min_category_weight(0.0);
     category_params->set_min_normalized_weight_within_top_n(0.1);
-    // TODO(crbug/1177102): Add Floc-Protected params when test model supports
-    // it.
+    output_params->mutable_floc_protected_params()->set_category_name(
+        "FLOC_PROTECTED");
     page_topics_model_metadata.SerializeToString(any_metadata.mutable_value());
     base::FilePath source_root_dir;
     base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir);
diff --git a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
index c18dbc5..ff29d87 100644
--- a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
+++ b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/services/PaintPreviewTabServiceTest.java
@@ -19,7 +19,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModel;
@@ -37,8 +36,8 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class PaintPreviewTabServiceTest {
-    private static final long TIMEOUT_MS = ScalableTimeout.scaleTimeout(5000);
-    private static final long POLLING_INTERVAL_MS = ScalableTimeout.scaleTimeout(500);
+    private static final long TIMEOUT_MS = 5000;
+    private static final long POLLING_INTERVAL_MS = 500;
 
     @Rule
     public final ChromeTabbedActivityTestRule mActivityTestRule =
diff --git a/chrome/browser/performance_hints/performance_hints_observer_unittest.cc b/chrome/browser/performance_hints/performance_hints_observer_unittest.cc
index bd20104e..9586ca5 100644
--- a/chrome/browser/performance_hints/performance_hints_observer_unittest.cc
+++ b/chrome/browser/performance_hints/performance_hints_observer_unittest.cc
@@ -63,7 +63,9 @@
          // Need to enable kOptimizationHints or GetForProfile will return
          // nullptr.
          optimization_guide::features::kOptimizationHints},
-        {});
+
+        {// Need to disable model downloading for these tests.
+         optimization_guide::features::kOptimizationGuideModelDownloading});
   }
   ~PerformanceHintsObserverTest() override = default;
 
diff --git a/chrome/browser/performance_manager/decorators/page_aggregator.cc b/chrome/browser/performance_manager/decorators/page_aggregator.cc
index 5791219..d22b7693 100644
--- a/chrome/browser/performance_manager/decorators/page_aggregator.cc
+++ b/chrome/browser/performance_manager/decorators/page_aggregator.cc
@@ -228,9 +228,9 @@
 }
 
 void PageAggregator::OnPassedToGraph(Graph* graph) {
-  // This observer presumes that it's been added before any nodes exist in the
-  // graph.
-  DCHECK(GraphImpl::FromGraph(graph)->nodes().empty());
+  // This observer presumes that it's been added before any frame nodes exist in
+  // the graph.
+  DCHECK(graph->GetAllFrameNodes().empty());
   graph->AddFrameNodeObserver(this);
   graph->GetNodeDataDescriberRegistry()->RegisterDescriber(this,
                                                            kDescriberName);
diff --git a/chrome/browser/performance_manager/metrics/memory_pressure_metrics.cc b/chrome/browser/performance_manager/metrics/memory_pressure_metrics.cc
index 4cfab0ce..cae9dcc 100644
--- a/chrome/browser/performance_manager/metrics/memory_pressure_metrics.cc
+++ b/chrome/browser/performance_manager/metrics/memory_pressure_metrics.cc
@@ -21,23 +21,20 @@
 void MemoryPressureMetrics::OnPassedToGraph(Graph* graph) {
   graph_ = graph;
 
+  graph_->AddSystemNodeObserver(this);
   base::SystemMemoryInfoKB mem_info = {};
   if (base::GetSystemMemoryInfo(&mem_info))
     system_ram_mb_ = mem_info.total / 1024;
-
-  memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>(
-      FROM_HERE, base::BindRepeating(&MemoryPressureMetrics::OnMemoryPressure,
-                                     base::Unretained(this)));
 }
 
 void MemoryPressureMetrics::OnTakenFromGraph(Graph* graph) {
-  memory_pressure_listener_.reset();
+  graph_->RemoveSystemNodeObserver(this);
   graph_ = nullptr;
 }
 
-void MemoryPressureMetrics::OnMemoryPressure(
-    MemoryPressureLevel pressure_level) {
-  if (pressure_level != MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL)
+void MemoryPressureMetrics::OnBeforeMemoryPressure(
+    MemoryPressureLevel new_level) {
+  if (new_level != MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL)
     return;
 
   int total_rss_mb = 0;
@@ -47,7 +44,7 @@
 
   // Records an estimate of the total amount of RAM used by Chrome.
   // Note: The histogram will cap at 100GB.
-  UMA_HISTOGRAM_COUNTS_100000("Discarding.OnCriticalPressure.TotalRSS_Mb",
+  UMA_HISTOGRAM_COUNTS_100000("Discarding.OnCriticalPressure.TotalRSS_Mb2",
                               total_rss_mb);
 
   if (system_ram_mb_ != kInvalidSysRAMValue) {
@@ -57,7 +54,7 @@
     // Note that the estimate might exceeds 100% in some cases as it overcount
     // the shared memory, round the value down to 100% in this case.
     UMA_HISTOGRAM_PERCENTAGE(
-        "Discarding.OnCriticalPressure.TotalRSS_PercentOfRAM",
+        "Discarding.OnCriticalPressure.TotalRSS_PercentOfRAM2",
         std::min(100, static_cast<int>(footprint_percent_of_total_ram)));
   }
 }
diff --git a/chrome/browser/performance_manager/metrics/memory_pressure_metrics.h b/chrome/browser/performance_manager/metrics/memory_pressure_metrics.h
index b990906..5e8822c 100644
--- a/chrome/browser/performance_manager/metrics/memory_pressure_metrics.h
+++ b/chrome/browser/performance_manager/metrics/memory_pressure_metrics.h
@@ -8,12 +8,14 @@
 #include "base/memory/memory_pressure_listener.h"
 #include "base/memory/weak_ptr.h"
 #include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/system_node.h"
 
 namespace performance_manager {
 namespace metrics {
 
 // Record some memory pressure related metrics.
-class MemoryPressureMetrics : public GraphOwned {
+class MemoryPressureMetrics : public GraphOwned,
+                              public SystemNode::ObserverDefaultImpl {
  public:
   MemoryPressureMetrics();
   ~MemoryPressureMetrics() override;
@@ -32,9 +34,9 @@
   using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
   static constexpr int kInvalidSysRAMValue = 0;
 
-  void OnMemoryPressure(MemoryPressureLevel pressure_level);
+  // SystemNodeObserver:
+  void OnBeforeMemoryPressure(MemoryPressureLevel new_level) override;
 
-  std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
   Graph* graph_ = nullptr;
   int system_ram_mb_ = kInvalidSysRAMValue;
 };
diff --git a/chrome/browser/performance_manager/metrics/memory_pressure_metrics_unittest.cc b/chrome/browser/performance_manager/metrics/memory_pressure_metrics_unittest.cc
index 2381b46..212950a 100644
--- a/chrome/browser/performance_manager/metrics/memory_pressure_metrics_unittest.cc
+++ b/chrome/browser/performance_manager/metrics/memory_pressure_metrics_unittest.cc
@@ -7,10 +7,11 @@
 
 #include "base/memory/memory_pressure_listener.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/util/memory_pressure/fake_memory_pressure_monitor.h"
 #include "build/build_config.h"
 #include "components/performance_manager/graph/process_node_impl.h"
+#include "components/performance_manager/graph/system_node_impl.h"
 #include "components/performance_manager/test_support/graph_test_harness.h"
+#include "components/performance_manager/test_support/mock_graphs.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -33,34 +34,20 @@
         std::make_unique<MemoryPressureMetrics>();
     metrics_ = metrics.get();
     graph()->PassToGraph(std::move(metrics));
-
-    process_node_ = CreateNode<performance_manager::ProcessNodeImpl>();
     histogram_tester_ = std::make_unique<base::HistogramTester>();
   }
 
   void TearDown() override {
-    process_node_.reset();
     histogram_tester_.reset();
     Super::TearDown();
   }
 
  protected:
-  void SimulateMemoryPressure() {
-    mem_pressure_monitor_.SetAndNotifyMemoryPressure(
-        base::MemoryPressureListener::MemoryPressureLevel::
-            MEMORY_PRESSURE_LEVEL_CRITICAL);
-    task_env().RunUntilIdle();
-  }
-
-  ProcessNodeImpl* process_node() { return process_node_.get(); }
   base::HistogramTester* histogram_tester() { return histogram_tester_.get(); }
   MemoryPressureMetrics* metrics() { return metrics_; }
 
  private:
   MemoryPressureMetrics* metrics_;
-  util::test::FakeMemoryPressureMonitor mem_pressure_monitor_;
-  performance_manager::TestNodeWrapper<performance_manager::ProcessNodeImpl>
-      process_node_;
   std::unique_ptr<base::HistogramTester> histogram_tester_;
 };
 
@@ -72,18 +59,21 @@
 #endif
 
 TEST_F(MemoryPressureMetricsTest, MAYBE_TestHistograms) {
+  MockSinglePageInSingleProcessGraph mock_graph(graph());
   const int kFakeSystemRamMb = 4096;
   // Pretends that we have one process using half of the RAM.
-  process_node()->set_resident_set_kb(kFakeSystemRamMb * 1024 / 2);
+  mock_graph.process->set_resident_set_kb(kFakeSystemRamMb * 1024 / 2);
   metrics()->set_system_ram_mb_for_testing(kFakeSystemRamMb);
 
-  SimulateMemoryPressure();
+  mock_graph.system->OnMemoryPressureForTesting(
+      base::MemoryPressureListener::MemoryPressureLevel::
+          MEMORY_PRESSURE_LEVEL_CRITICAL);
 
   histogram_tester()->ExpectBucketCount(
-      "Discarding.OnCriticalPressure.TotalRSS_Mb", kFakeSystemRamMb / 2, 1);
+      "Discarding.OnCriticalPressure.TotalRSS_Mb2", kFakeSystemRamMb / 2, 1);
 
   histogram_tester()->ExpectBucketCount(
-      "Discarding.OnCriticalPressure.TotalRSS_PercentOfRAM", 50, 1);
+      "Discarding.OnCriticalPressure.TotalRSS_PercentOfRAM2", 50, 1);
 }
 
 }  // namespace metrics
diff --git a/chrome/browser/performance_manager/policies/background_tab_loading_policy.cc b/chrome/browser/performance_manager/policies/background_tab_loading_policy.cc
index 745c788..9bdde842 100644
--- a/chrome/browser/performance_manager/policies/background_tab_loading_policy.cc
+++ b/chrome/browser/performance_manager/policies/background_tab_loading_policy.cc
@@ -71,11 +71,7 @@
 }
 
 BackgroundTabLoadingPolicy::BackgroundTabLoadingPolicy()
-    : memory_pressure_listener_(
-          FROM_HERE,
-          base::BindRepeating(&BackgroundTabLoadingPolicy::OnMemoryPressure,
-                              base::Unretained(this))),
-      page_loader_(std::make_unique<mechanism::PageLoader>()) {
+    : page_loader_(std::make_unique<mechanism::PageLoader>()) {
   DCHECK(!g_background_tab_loading_policy);
   g_background_tab_loading_policy = this;
   max_simultaneous_tab_loads_ = CalculateMaxSimultaneousTabLoads(
@@ -90,12 +86,14 @@
 
 void BackgroundTabLoadingPolicy::OnPassedToGraph(Graph* graph) {
   graph->AddPageNodeObserver(this);
+  graph->AddSystemNodeObserver(this);
   graph->GetNodeDataDescriberRegistry()->RegisterDescriber(this,
                                                            kDescriberName);
 }
 
 void BackgroundTabLoadingPolicy::OnTakenFromGraph(Graph* graph) {
   graph->GetNodeDataDescriberRegistry()->UnregisterDescriber(this);
+  graph->RemoveSystemNodeObserver(this);
   graph->RemovePageNodeObserver(this);
 }
 
@@ -286,8 +284,8 @@
 }
 
 void BackgroundTabLoadingPolicy::OnMemoryPressure(
-    base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
-  switch (memory_pressure_level) {
+    base::MemoryPressureListener::MemoryPressureLevel new_level) {
+  switch (new_level) {
     case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
       break;
     case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
diff --git a/chrome/browser/performance_manager/policies/background_tab_loading_policy.h b/chrome/browser/performance_manager/policies/background_tab_loading_policy.h
index e948b20..360c22a 100644
--- a/chrome/browser/performance_manager/policies/background_tab_loading_policy.h
+++ b/chrome/browser/performance_manager/policies/background_tab_loading_policy.h
@@ -11,6 +11,7 @@
 #include "components/performance_manager/public/graph/graph.h"
 #include "components/performance_manager/public/graph/node_data_describer.h"
 #include "components/performance_manager/public/graph/page_node.h"
+#include "components/performance_manager/public/graph/system_node.h"
 #include "url/gurl.h"
 
 namespace performance_manager {
@@ -30,7 +31,8 @@
 // background tab loading at all times.
 class BackgroundTabLoadingPolicy : public GraphOwned,
                                    public NodeDataDescriberDefaultImpl,
-                                   public PageNode::ObserverDefaultImpl {
+                                   public PageNode::ObserverDefaultImpl,
+                                   public SystemNode::ObserverDefaultImpl {
  public:
   BackgroundTabLoadingPolicy();
   ~BackgroundTabLoadingPolicy() override;
@@ -87,6 +89,10 @@
   base::Value DescribePageNodeData(const PageNode* node) const override;
   base::Value DescribeSystemNodeData(const SystemNode* node) const override;
 
+  // SystemNodeObserver:
+  void OnMemoryPressure(
+      base::MemoryPressureListener::MemoryPressureLevel new_level) override;
+
   // Determines whether or not the given PageNode should be loaded. If this
   // returns false, then the policy no longer attempts to load |page_node| and
   // removes it from the policy's internal state. This is called immediately
@@ -100,10 +106,6 @@
   // Stops loading tabs by clearing |page_nodes_to_load_|.
   void StopLoadingTabs();
 
-  // React to memory pressure by stopping to load any more tabs.
-  void OnMemoryPressure(
-      base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
-
   // Calculates a |score| for the given tab.
   void ScoreTab(PageNodeToLoadData* page_node_to_load_data);
 
@@ -145,10 +147,6 @@
   void ErasePageNodeToLoadData(const PageNode* page_node);
   PageNodeToLoadData* FindPageNodeToLoadData(const PageNode* page_node);
 
-  // Listens for system under memory pressure notifications and stops loading
-  // of tabs when we start running out of memory.
-  base::MemoryPressureListener memory_pressure_listener_;
-
   // The mechanism used to load the pages.
   std::unique_ptr<performance_manager::mechanism::PageLoader> page_loader_;
 
@@ -215,7 +213,6 @@
   FRIEND_TEST_ALL_PREFIXES(BackgroundTabLoadingPolicyTest,
                            ShouldLoad_FreeMemory);
   FRIEND_TEST_ALL_PREFIXES(BackgroundTabLoadingPolicyTest, ShouldLoad_OldTab);
-  FRIEND_TEST_ALL_PREFIXES(BackgroundTabLoadingPolicyTest, OnMemoryPressure);
   FRIEND_TEST_ALL_PREFIXES(
       ::performance_manager::BackgroundTabLoadingBrowserTest,
       RestoredTabsAreLoadedGradually);
diff --git a/chrome/browser/performance_manager/policies/background_tab_loading_policy_unittest.cc b/chrome/browser/performance_manager/policies/background_tab_loading_policy_unittest.cc
index cc62d324..47ad400f 100644
--- a/chrome/browser/performance_manager/policies/background_tab_loading_policy_unittest.cc
+++ b/chrome/browser/performance_manager/policies/background_tab_loading_policy_unittest.cc
@@ -4,9 +4,11 @@
 
 #include "chrome/browser/performance_manager/policies/background_tab_loading_policy.h"
 
+#include <memory>
 #include <vector>
 
 #include "chrome/browser/performance_manager/mechanisms/page_loader.h"
+#include "components/performance_manager/graph/graph_impl.h"
 #include "components/performance_manager/graph/page_node_impl.h"
 #include "components/performance_manager/public/decorators/tab_properties_decorator.h"
 #include "components/performance_manager/test_support/graph_test_harness.h"
@@ -44,6 +46,9 @@
   void SetUp() override {
     Super::SetUp();
 
+    system_node_ = std::make_unique<TestNodeWrapper<SystemNodeImpl>>(
+        TestNodeWrapper<SystemNodeImpl>::Create(graph()));
+
     // Create the policy.
     auto policy = std::make_unique<BackgroundTabLoadingPolicy>();
     policy_ = policy.get();
@@ -61,6 +66,7 @@
 
   void TearDown() override {
     graph()->TakeFromGraph(policy_);
+    system_node_->reset();
     Super::TearDown();
   }
 
@@ -68,7 +74,12 @@
   BackgroundTabLoadingPolicy* policy() { return policy_; }
   MockPageLoader* loader() { return mock_loader_; }
 
+  SystemNodeImpl* system_node() { return system_node_.get()->get(); }
+
  private:
+  std::unique_ptr<
+      performance_manager::TestNodeWrapper<performance_manager::SystemNodeImpl>>
+      system_node_;
   BackgroundTabLoadingPolicy* policy_;
   MockPageLoader* mock_loader_;
 };
@@ -315,7 +326,7 @@
   testing::Mock::VerifyAndClear(loader());
 
   // Simulate memory pressure and expect the tab loader to disable loading.
-  policy()->OnMemoryPressure(
+  system_node()->OnMemoryPressureForTesting(
       base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
 
   PageNodeImpl* page_node_impl = page_nodes[0].get();
diff --git a/chrome/browser/performance_manager/policies/urgent_page_discarding_policy.cc b/chrome/browser/performance_manager/policies/urgent_page_discarding_policy.cc
index 1172a19..3118c90 100644
--- a/chrome/browser/performance_manager/policies/urgent_page_discarding_policy.cc
+++ b/chrome/browser/performance_manager/policies/urgent_page_discarding_policy.cc
@@ -19,55 +19,49 @@
 void UrgentPageDiscardingPolicy::OnPassedToGraph(Graph* graph) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   graph_ = graph;
-  RegisterMemoryPressureListener();
+  DCHECK(!handling_memory_pressure_notification_);
+  graph_->AddSystemNodeObserver(this);
+  DCHECK(PageDiscardingHelper::GetFromGraph(graph_))
+      << "A PageDiscardingHelper instance should be registered against the "
+         "graph in order to use this policy.";
 }
 
 void UrgentPageDiscardingPolicy::OnTakenFromGraph(Graph* graph) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  UnregisterMemoryPressureListener();
+  graph_->RemoveSystemNodeObserver(this);
   graph_ = nullptr;
 }
 
 void UrgentPageDiscardingPolicy::OnMemoryPressure(
-    base::MemoryPressureListener::MemoryPressureLevel level) {
+    base::MemoryPressureListener::MemoryPressureLevel new_level) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (level != base::MemoryPressureListener::MemoryPressureLevel::
-                   MEMORY_PRESSURE_LEVEL_CRITICAL) {
+  // The Memory Pressure Monitor will send notifications at regular interval,
+  // |handling_memory_pressure_notification_| prevents this class from trying to
+  // reply to multiple notifications at the same time.
+  if (handling_memory_pressure_notification_ ||
+      new_level != base::MemoryPressureListener::MemoryPressureLevel::
+                       MEMORY_PRESSURE_LEVEL_CRITICAL) {
     return;
   }
 
-  // The Memory Pressure Monitor will send notifications at regular interval,
-  // it's important to unregister the pressure listener to ensure that we don't
-  // reply to multiple notifications at the same time.
-  UnregisterMemoryPressureListener();
+  handling_memory_pressure_notification_ = true;
 
   PageDiscardingHelper::GetFromGraph(graph_)->UrgentlyDiscardAPage(
       features::UrgentDiscardingParams::GetParams().discard_strategy(),
       base::BindOnce(
           [](UrgentPageDiscardingPolicy* policy, bool success_unused) {
-            policy->RegisterMemoryPressureListener();
+            DCHECK(policy->handling_memory_pressure_notification_);
+            policy->handling_memory_pressure_notification_ = false;
           },
+          // |PageDiscardingHelper| and this class are both GraphOwned objects,
+          // their lifetime is tied to the Graph's lifetime and both objects
+          // will be released sequentially while it's being torn down. This
+          // ensures that the reply callback passed to |UrgentlyDiscardAPage|
+          // won't ever run after the destruction of this class and so it's safe
+          // to use Unretained.
           base::Unretained(this)));
 }
 
-void UrgentPageDiscardingPolicy::RegisterMemoryPressureListener() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!memory_pressure_listener_);
-  DCHECK(PageDiscardingHelper::GetFromGraph(graph_))
-      << "A PageDiscardingHelper instance should be registered against the "
-         "graph in order to use this policy.";
-
-  memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>(
-      FROM_HERE,
-      base::BindRepeating(&UrgentPageDiscardingPolicy::OnMemoryPressure,
-                          base::Unretained(this)));
-}
-
-void UrgentPageDiscardingPolicy::UnregisterMemoryPressureListener() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  memory_pressure_listener_.reset();
-}
-
 }  // namespace policies
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/policies/urgent_page_discarding_policy.h b/chrome/browser/performance_manager/policies/urgent_page_discarding_policy.h
index cae15044..96d192e 100644
--- a/chrome/browser/performance_manager/policies/urgent_page_discarding_policy.h
+++ b/chrome/browser/performance_manager/policies/urgent_page_discarding_policy.h
@@ -9,6 +9,7 @@
 #include "base/memory/memory_pressure_listener.h"
 #include "base/sequence_checker.h"
 #include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/system_node.h"
 
 namespace performance_manager {
 
@@ -17,7 +18,8 @@
 // Urgently discard a tab when receiving a memory pressure signal. The discard
 // strategy used by this policy is based on a feature flag, see
 // UrgentDiscardingParams for more details.
-class UrgentPageDiscardingPolicy : public GraphOwned {
+class UrgentPageDiscardingPolicy : public GraphOwned,
+                                   public SystemNode::ObserverDefaultImpl {
  public:
   UrgentPageDiscardingPolicy();
   ~UrgentPageDiscardingPolicy() override;
@@ -30,21 +32,18 @@
   void OnTakenFromGraph(Graph* graph) override;
 
  private:
+  // SystemNodeObserver:
   void OnMemoryPressure(
-      base::MemoryPressureListener::MemoryPressureLevel level);
-
-  // Register to start listening to memory pressure. Called on startup or after
-  // handling a pressure event.
-  void RegisterMemoryPressureListener();
-
-  // Unregister to stop listening to memory pressure. Called on shutdown or
-  // when handling a pressure event.
-  void UnregisterMemoryPressureListener();
+      base::MemoryPressureListener::MemoryPressureLevel new_level) override;
 
   // Callback called when a discard attempt has completed.
   void PostDiscardAttemptCallback(bool success);
 
-  std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
+  // True while we are in the process of discarding tab(s) in response to a
+  // memory pressure notification. It becomes false once we're done responding
+  // to this notification.
+  bool handling_memory_pressure_notification_ = false;
+
   Graph* graph_ = nullptr;
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/chrome/browser/performance_manager/policies/urgent_page_discarding_policy_unittest.cc b/chrome/browser/performance_manager/policies/urgent_page_discarding_policy_unittest.cc
index 305a8bc..7b49b7d1 100644
--- a/chrome/browser/performance_manager/policies/urgent_page_discarding_policy_unittest.cc
+++ b/chrome/browser/performance_manager/policies/urgent_page_discarding_policy_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/time/time.h"
 #include "base/util/memory_pressure/fake_memory_pressure_monitor.h"
 #include "chrome/browser/performance_manager/decorators/page_aggregator.h"
+#include "chrome/browser/performance_manager/policies/page_discarding_helper.h"
 #include "chrome/browser/performance_manager/policies/policy_features.h"
 #include "chrome/browser/performance_manager/test_support/page_discarding_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -41,46 +42,35 @@
     testing::GraphTestHarnessWithMockDiscarder::TearDown();
   }
 
- protected:
-  void SimulateMemoryPressure(size_t pressure_event_counts = 1) {
-    for (size_t i = 0; i < pressure_event_counts; ++i) {
-      mem_pressure_monitor_.SetAndNotifyMemoryPressure(
-          base::MemoryPressureListener::MemoryPressureLevel::
-              MEMORY_PRESSURE_LEVEL_CRITICAL);
-      task_env().RunUntilIdle();
-    }
-    mem_pressure_monitor_.SetAndNotifyMemoryPressure(
-        base::MemoryPressureListener::MemoryPressureLevel::
-            MEMORY_PRESSURE_LEVEL_MODERATE);
-    task_env().RunUntilIdle();
-  }
-
-  util::test::FakeMemoryPressureMonitor* mem_pressure_monitor() {
-    return &mem_pressure_monitor_;
-  }
-
  private:
-  util::test::FakeMemoryPressureMonitor mem_pressure_monitor_;
   UrgentPageDiscardingPolicy* policy_;
 };
 
 TEST_F(UrgentPageDiscardingPolicyTest, DiscardOnCriticalPressure) {
   EXPECT_CALL(*discarder(), DiscardPageNodeImpl(page_node()))
       .WillOnce(::testing::Return(true));
-
-  mem_pressure_monitor()->SetAndNotifyMemoryPressure(
+  system_node()->OnMemoryPressureForTesting(
       base::MemoryPressureListener::MemoryPressureLevel::
           MEMORY_PRESSURE_LEVEL_CRITICAL);
-  task_env().RunUntilIdle();
+  ::testing::Mock::VerifyAndClearExpectations(discarder());
+
+  // Send a second memory pressure notification without switching back to the
+  // no pressure state. This happens when a single discard isn't sufficient to
+  // exit memory pressure.
+  EXPECT_CALL(*discarder(), DiscardPageNodeImpl(page_node()))
+      .WillOnce(::testing::Return(true));
+  PageDiscardingHelper::RemovesDiscardAttemptMarkerForTesting(page_node());
+  system_node()->OnMemoryPressureForTesting(
+      base::MemoryPressureListener::MemoryPressureLevel::
+          MEMORY_PRESSURE_LEVEL_CRITICAL);
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 }
 
 TEST_F(UrgentPageDiscardingPolicyTest, NoDiscardOnModeratePressure) {
   // No tab should be discarded on moderate pressure.
-  mem_pressure_monitor()->SetAndNotifyMemoryPressure(
+  system_node()->OnMemoryPressureForTesting(
       base::MemoryPressureListener::MemoryPressureLevel::
           MEMORY_PRESSURE_LEVEL_MODERATE);
-  task_env().RunUntilIdle();
   ::testing::Mock::VerifyAndClearExpectations(discarder());
 }
 
diff --git a/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos.cc b/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos.cc
index b2d1321..8655f77 100644
--- a/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos.cc
+++ b/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos.cc
@@ -70,19 +70,19 @@
   graph_ = graph;
   graph->AddProcessNodeObserver(this);
 
-  // Only create a memory pressure listener if the feature to swap on moderate
-  // pressure is enabled.
+  // Only handle the memory pressure notifications if the feature to swap on
+  // moderate pressure is enabled.
   if (config_.swap_on_moderate_pressure) {
-    memory_pressure_listener_.emplace(
-        FROM_HERE, base::BindRepeating(&UserspaceSwapPolicy::OnMemoryPressure,
-                                       weak_factory_.GetWeakPtr()));
+    graph_->AddSystemNodeObserver(this);
   }
 }
 
 void UserspaceSwapPolicy::OnTakenFromGraph(Graph* graph) {
   DCHECK_EQ(graph_, graph);
 
-  memory_pressure_listener_.reset();
+  if (config_.swap_on_moderate_pressure) {
+    graph_->RemoveSystemNodeObserver(this);
+  }
 
   graph->RemoveProcessNodeObserver(this);
   graph_ = nullptr;
@@ -144,9 +144,10 @@
 }
 
 void UserspaceSwapPolicy::OnMemoryPressure(
-    base::MemoryPressureListener::MemoryPressureLevel level) {
-  if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE)
+    base::MemoryPressureListener::MemoryPressureLevel new_level) {
+  if (new_level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
     return;
+  }
 
   auto now_ticks = base::TimeTicks::Now();
   // Try not to walk the graph too frequently because we can receive moderate
diff --git a/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos.h b/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos.h
index 71a5f5d..c10fe1d 100644
--- a/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos.h
+++ b/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/resource_coordinator/lifecycle_unit_observer.h"
 #include "components/performance_manager/public/graph/graph.h"
 #include "components/performance_manager/public/graph/process_node.h"
+#include "components/performance_manager/public/graph/system_node.h"
 
 namespace chromeos {
 namespace memory {
@@ -29,7 +30,8 @@
 // UserspaceSwapPolicy is a policy which will trigger a renderer to swap itself
 // via userspace.
 class UserspaceSwapPolicy : public GraphOwned,
-                            public ProcessNode::ObserverDefaultImpl {
+                            public ProcessNode::ObserverDefaultImpl,
+                            public SystemNode::ObserverDefaultImpl {
  public:
   UserspaceSwapPolicy();
   ~UserspaceSwapPolicy() override;
@@ -43,9 +45,9 @@
   void OnProcessNodeAdded(const ProcessNode* process_node) override;
   void OnProcessLifetimeChange(const ProcessNode* process_node) override;
 
-  // MemoryPressureListener impl:
+  // SystemNodeObserver:
   void OnMemoryPressure(
-      base::MemoryPressureListener::MemoryPressureLevel level);
+      base::MemoryPressureListener::MemoryPressureLevel new_level) override;
 
   // Returns true if running on a platform that supports the kernel features
   // necessary for userspace swapping, most important would be userfaultfd(2).
@@ -99,8 +101,6 @@
 
   Graph* graph_ = nullptr;
 
-  base::Optional<base::MemoryPressureListener> memory_pressure_listener_;
-
  private:
   // A helper method which sets the last trim time to the specified time.
   void SetLastSwapTime(const ProcessNode* process_node, base::TimeTicks time);
diff --git a/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos_unittest.cc b/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos_unittest.cc
index e7e0ae61..1b1562c 100644
--- a/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos_unittest.cc
+++ b/chrome/browser/performance_manager/policies/userspace_swap_policy_chromeos_unittest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/performance_manager/policies/policy_features.h"
 #include "chrome/common/performance_manager/mojom/tcmalloc.mojom.h"
 #include "chromeos/memory/userspace_swap/userspace_swap.h"
+#include "components/performance_manager/graph/graph_impl.h"
 #include "components/performance_manager/graph/graph_impl_operations.h"
 #include "components/performance_manager/graph/page_node_impl.h"
 #include "components/performance_manager/graph/process_node_impl.h"
@@ -65,10 +66,6 @@
     return UserspaceSwapPolicy::SwapNodesOnGraph();
   }
 
-  base::MemoryPressureListener& listener() {
-    return memory_pressure_listener_.value();
-  }
-
   base::TimeTicks get_last_graph_walk() { return last_graph_walk_; }
   void set_last_graph_walk(base::TimeTicks t) { last_graph_walk_ = t; }
 
@@ -114,6 +111,8 @@
     page_node_ = CreateNode<PageNodeImpl>();
     frame_node_ =
         graph()->CreateFrameNodeAutoId(process_node().get(), page_node().get());
+    system_node_ = std::make_unique<TestNodeWrapper<SystemNodeImpl>>(
+        TestNodeWrapper<SystemNodeImpl>::Create(graph()));
   }
 
   void AttachProcess() {
@@ -128,6 +127,7 @@
     frame_node_.reset();
     page_node_.reset();
     process_node_.reset();
+    system_node_.reset();
     graph_.TearDown();
   }
 
@@ -153,6 +153,9 @@
   TestNodeWrapper<ProcessNodeImpl>& process_node() { return process_node_; }
   TestNodeWrapper<PageNodeImpl>& page_node() { return page_node_; }
   TestNodeWrapper<FrameNodeImpl>& frame_node() { return frame_node_; }
+  TestNodeWrapper<SystemNodeImpl>& system_node() {
+    return *(system_node_.get());
+  }
 
   void FastForwardBy(base::TimeDelta delta) {
     browser_env()->FastForwardBy(delta);
@@ -168,6 +171,7 @@
   TestNodeWrapper<ProcessNodeImpl> process_node_;
   TestNodeWrapper<PageNodeImpl> page_node_;
   TestNodeWrapper<FrameNodeImpl> frame_node_;
+  std::unique_ptr<TestNodeWrapper<SystemNodeImpl>> system_node_;
 
   DISALLOW_COPY_AND_ASSIGN(UserspaceSwapPolicyTest);
 };
@@ -211,7 +215,7 @@
 
   // Triger memory pressure and we should observe the walk since we've never
   // walked before.
-  policy()->listener().SimulatePressureNotification(
+  system_node()->OnMemoryPressureForTesting(
       base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
   auto initial_walk_time = base::TimeTicks::Now();
   FastForwardBy(base::TimeDelta::FromSeconds(1));
@@ -221,7 +225,7 @@
   // don't walk again even when we receive another moderate pressure
   // notification.
   FastForwardBy(base::TimeDelta::FromSeconds(1));
-  policy()->listener().SimulatePressureNotification(
+  system_node()->OnMemoryPressureForTesting(
       base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
   // Since it's been less than the graph walk frequency we don't expect to walk.
   ASSERT_EQ(initial_walk_time, policy()->get_last_graph_walk());
@@ -229,7 +233,7 @@
   // Finally we will advance by a graph walk frequency and confirm we walk
   // again.
   FastForwardBy(policy()->config().graph_walk_frequency);
-  policy()->listener().SimulatePressureNotification(
+  system_node()->OnMemoryPressureForTesting(
       base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
 
   FastForwardBy(base::TimeDelta::FromSeconds(1));
@@ -254,7 +258,7 @@
   EXPECT_CALL(*policy(), SwapProcessNode(process_node().get())).Times(0);
 
   // Trigger moderate memory pressure to start the graph walk.
-  policy()->listener().SimulatePressureNotification(
+  system_node()->OnMemoryPressureForTesting(
       base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
   FastForwardBy(base::TimeDelta::FromSeconds(1));
 }
@@ -274,7 +278,7 @@
   EXPECT_CALL(*policy(), SwapProcessNode(process_node().get())).Times(1);
 
   // Trigger moderate memory pressure to start the graph walk.
-  policy()->listener().SimulatePressureNotification(
+  system_node()->OnMemoryPressureForTesting(
       base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
   FastForwardBy(base::TimeDelta::FromSeconds(1));
 }
@@ -382,7 +386,7 @@
   // swap.
   for (int i = 0; i < 3; ++i) {
     FastForwardBy(policy()->config().graph_walk_frequency);
-    policy()->listener().SimulatePressureNotification(
+    system_node()->OnMemoryPressureForTesting(
         base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
   }
 }
diff --git a/chrome/browser/performance_manager/test_support/page_discarding_utils.cc b/chrome/browser/performance_manager/test_support/page_discarding_utils.cc
index 4e114fa..ff0767f5 100644
--- a/chrome/browser/performance_manager/test_support/page_discarding_utils.cc
+++ b/chrome/browser/performance_manager/test_support/page_discarding_utils.cc
@@ -10,6 +10,7 @@
 #include "components/performance_manager/decorators/freezing_vote_decorator.h"
 #include "components/performance_manager/freezing/freezing_vote_aggregator.h"
 #include "components/performance_manager/graph/frame_node_impl.h"
+#include "components/performance_manager/graph/graph_impl.h"
 #include "components/performance_manager/graph/page_node_impl.h"
 #include "components/performance_manager/public/decorators/page_live_state_decorator.h"
 #include "components/performance_manager/test_support/graph_test_harness.h"
@@ -26,7 +27,8 @@
   std::move(post_discard_cb).Run(DiscardPageNodeImpl(page_node));
 }
 
-GraphTestHarnessWithMockDiscarder::GraphTestHarnessWithMockDiscarder() {
+GraphTestHarnessWithMockDiscarder::GraphTestHarnessWithMockDiscarder()
+    : system_node_(TestNodeWrapper<SystemNodeImpl>::Create(graph())) {
   // Some tests depends on the existence of the PageAggregator.
   graph()->PassToGraph(std::make_unique<PageAggregator>());
 }
diff --git a/chrome/browser/performance_manager/test_support/page_discarding_utils.h b/chrome/browser/performance_manager/test_support/page_discarding_utils.h
index 20aaf8005..84041628f 100644
--- a/chrome/browser/performance_manager/test_support/page_discarding_utils.h
+++ b/chrome/browser/performance_manager/test_support/page_discarding_utils.h
@@ -7,6 +7,7 @@
 
 #include "chrome/browser/performance_manager/mechanisms/page_discarder.h"
 #include "chrome/browser/performance_manager/policies/page_discarding_helper.h"
+#include "components/performance_manager/graph/graph_impl.h"
 #include "components/performance_manager/test_support/graph_test_harness.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -54,6 +55,7 @@
   PageNodeImpl* page_node() { return page_node_.get(); }
   ProcessNodeImpl* process_node() { return process_node_.get(); }
   FrameNodeImpl* frame_node() { return main_frame_node_.get(); }
+  SystemNodeImpl* system_node() { return system_node_.get(); }
   void ResetFrameNode() { main_frame_node_.reset(); }
   testing::MockPageDiscarder* discarder() { return mock_discarder_; }
 
@@ -65,6 +67,8 @@
       process_node_;
   performance_manager::TestNodeWrapper<performance_manager::FrameNodeImpl>
       main_frame_node_;
+  performance_manager::TestNodeWrapper<performance_manager::SystemNodeImpl>
+      system_node_;
 };
 
 // Make sure that |page_node| is discardable.
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 24b0687a..8be5408 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -546,6 +546,7 @@
   { key::kWebUsbBlockedForUrls,
     prefs::kManagedWebUsbBlockedForUrls,
     base::Value::Type::LIST },
+#if !defined(OS_ANDROID)
   { key::kDefaultSerialGuardSetting,
     prefs::kManagedDefaultSerialGuardSetting,
     base::Value::Type::INTEGER },
@@ -555,6 +556,10 @@
   { key::kSerialBlockedForUrls,
     prefs::kManagedSerialBlockedForUrls,
     base::Value::Type::LIST },
+  { key::kSerialAllowAllPortsForUrls,
+    prefs::kManagedSerialAllowAllPortsForUrls,
+    base::Value::Type::LIST },
+#endif  // !defined(OS_ANDROID)
   { key::kDefaultFileSystemReadGuardSetting,
     prefs::kManagedDefaultFileSystemReadGuardSetting,
     base::Value::Type::INTEGER },
@@ -1431,6 +1436,14 @@
   handlers->AddHandler(std::make_unique<HomepageLocationPolicyHandler>());
   handlers->AddHandler(std::make_unique<ProxyPolicyHandler>());
   handlers->AddHandler(std::make_unique<SecureDnsPolicyHandler>());
+#if !defined(OS_ANDROID)
+  handlers->AddHandler(std::make_unique<SimpleSchemaValidatingPolicyHandler>(
+      key::kSerialAllowUsbDevicesForUrls,
+      prefs::kManagedSerialAllowUsbDevicesForUrls, chrome_schema,
+      SCHEMA_ALLOW_UNKNOWN,
+      SimpleSchemaValidatingPolicyHandler::RECOMMENDED_PROHIBITED,
+      SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED));
+#endif  // !defined(OS_ANDROID)
   handlers->AddHandler(std::make_unique<SimpleSchemaValidatingPolicyHandler>(
       key::kCertificateTransparencyEnforcementDisabledForUrls,
       certificate_transparency::prefs::kCTExcludedHosts, chrome_schema,
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index c03e8b8..ea09ada 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -235,6 +235,7 @@
 #include "chrome/browser/search/promos/promo_service.h"
 #include "chrome/browser/search/search_suggest/search_suggest_service.h"
 #include "chrome/browser/search/task_module/task_module_service.h"
+#include "chrome/browser/serial/serial_policy_allowed_ports.h"
 #include "chrome/browser/signin/signin_promo.h"
 #include "chrome/browser/ui/startup/startup_browser_creator.h"
 #include "chrome/browser/ui/webui/history/foreign_session_handler.h"
@@ -935,6 +936,9 @@
       registry);
   security_interstitials::InsecureFormBlockingPage::RegisterProfilePrefs(
       registry);
+#if !defined(OS_ANDROID)
+  SerialPolicyAllowedPorts::RegisterProfilePrefs(registry);
+#endif
   SessionStartupPref::RegisterProfilePrefs(registry);
   SharingSyncPreference::RegisterProfilePrefs(registry);
   site_engagement::SiteEngagementService::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 69daa4a4..cb44a03 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -200,6 +200,11 @@
 #include "chrome/browser/lacros/cert_db_initializer_factory.h"
 #endif
 
+#if defined(OS_MAC)
+#include "chrome/browser/ui/cocoa/screentime/history_bridge_factory.h"
+#include "chrome/browser/ui/cocoa/screentime/screentime_features.h"
+#endif
+
 namespace chrome {
 
 void AddProfilesExtraParts(ChromeBrowserMainParts* main_parts) {
@@ -394,6 +399,10 @@
 #if BUILDFLAG(FULL_SAFE_BROWSING)
   safe_browsing::AdvancedProtectionStatusManagerFactory::GetInstance();
 #endif
+#if defined(OS_MAC)
+  if (screentime::IsScreenTimeEnabled())
+    screentime::HistoryBridgeFactory::GetInstance();
+#endif
   SCTReportingServiceFactory::GetInstance();
 #if defined(OS_ANDROID)
   SearchPermissionsService::Factory::GetInstance();
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index e571c77..c97e91c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -54,7 +54,6 @@
   "background/injected_script_loader.js",
   "background/keyboard_handler.js",
   "background/keymaps/key_map.js",
-  "background/live_regions.js",
   "background/locale_output_helper.js",
   "background/logging/event_stream_logger.js",
   "background/logging/log.js",
@@ -117,6 +116,7 @@
 # ES6 modules.
 chromevox_es6_modules = [
   "background/background.js",
+  "background/live_regions.js",
   "background/es6_loader.js",
   "background/find_handler.js",
   "background/media_automation_handler.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index 465a9f8..ee30d41 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import {FindHandler} from './find_handler.js';
+import {LiveRegions} from './live_regions.js';
 import {MediaAutomationHandler} from './media_automation_handler.js';
 import {NextEarcons} from './next_earcons.js';
 import {RangeAutomationHandler} from './range_automation_handler.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions.js
index ecabe07..70629c11 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions.js
@@ -6,11 +6,6 @@
  * @fileoverview Implements support for live regions in ChromeVox Next.
  */
 
-goog.provide('LiveRegions');
-
-goog.require('ChromeVoxState');
-
-goog.scope(function() {
 const AutomationNode = chrome.automation.AutomationNode;
 const RoleType = chrome.automation.RoleType;
 const StateType = chrome.automation.StateType;
@@ -21,7 +16,7 @@
 /**
  * ChromeVox2 live region handler.
  */
-LiveRegions = class {
+export class LiveRegions {
   /**
    * @param {!ChromeVoxState} chromeVoxState The ChromeVox state object,
    *     keeping track of the current mode and current range.
@@ -245,7 +240,7 @@
 
     return true;
   }
-};
+}
 
 /**
  * Live region events received in fewer than this many milliseconds will
@@ -269,4 +264,3 @@
  * @private
  */
 LiveRegions.announceLiveRegionsFromBackgroundTabs_ = false;
-});  // goog.scope
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
index e64436f..2e53639 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
@@ -11,6 +11,17 @@
  * Test fixture for Live Regions.
  */
 ChromeVoxLiveRegionsTest = class extends ChromeVoxNextE2ETest {
+  setUp() {
+    window.TreeChangeType = chrome.automation.TreeChangeType;
+    const runTest = this.deferRunTest(WhenTestDone.EXPECT);
+    (async function() {
+      const module = await import('/chromevox/background/live_regions.js');
+      window.LiveRegions = module.LiveRegions;
+
+      runTest();
+    })();
+  }
+
   /**
    * Simulates work done when users interact using keyboard, braille, or
    * touch.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
index 9160f1a..3168262f 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
@@ -22,7 +22,6 @@
 goog.require('FocusAutomationHandler');
 goog.require('GestureCommandHandler');
 goog.require('InstanceChecker');
-goog.require('LiveRegions');
 goog.require('LocaleOutputHelper');
 goog.require('MathHandler');
 goog.require('NavBraille');
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
index a3d6ecbd..2c3d1d2 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
@@ -47,7 +47,9 @@
 <template is="dom-if" if="[[showCellularSim_(managedProperties_)]]"
     restamp>
   <div class="cr-row">
-    <network-siminfo class="flex" device-state="[[deviceState_]]">
+    <network-siminfo class="flex"
+        network-state="[[getNetworkState_(managedProperties_)]]"
+        device-state="[[deviceState_]]">
     </network-siminfo>
   </div>
 </template>
diff --git a/chrome/browser/resources/feedback_webui/js/feedback.js b/chrome/browser/resources/feedback_webui/js/feedback.js
index 214e5f81..7946992 100644
--- a/chrome/browser/resources/feedback_webui/js/feedback.js
+++ b/chrome/browser/resources/feedback_webui/js/feedback.js
@@ -9,6 +9,10 @@
 import {FEEDBACK_LANDING_PAGE, FEEDBACK_LANDING_PAGE_TECHSTOP, FEEDBACK_LEGAL_HELP_URL, FEEDBACK_PRIVACY_POLICY_URL, FEEDBACK_TERM_OF_SERVICE_URL, openUrlInAppWindow} from './feedback_util.js';
 import {takeScreenshot} from './take_screenshot.js';
 
+
+/** @type {string} */
+const dialogArgs = chrome.getVariableValue('dialogArguments');
+
 /**
  * The object will be manipulated by feedbackHelper
  *
@@ -40,35 +44,6 @@
     this.systemInformationLoaded = false;
   }
 
-  getFeedbackInfo() {
-    // If the getFeedbackInfo is implemented by a message handler, then the
-    // data returned from it will be used. Otherwise, the default data is used.
-    // Currently, if a user visits chrome://feedback directly, the message
-    // handler will not be available. In this case, we should still allow the
-    // user submit a feedback.
-    return new Promise(
-        resolve => sendWithPromise('getFeedbackInfo')
-                       .then(info => resolve(info))
-                       .catch(() => {
-                         resolve({
-                           assistantDebugInfoAllowed: false,
-                           attachedFile: undefined,
-                           attachedFileBlobUuid: undefined,
-                           categoryTag: undefined,
-                           description: undefined,
-                           descriptionPlaceholder: undefined,
-                           email: undefined,
-                           flow: 'regular',
-                           fromAssistant: false,
-                           includeBluetoothLogs: false,
-                           pageUrl: undefined,
-                           screenshot: {},
-                           systemInformation: [],
-                           useSystemWindowFrame: false,
-                         });
-                       }));
-  }
-
   getSystemInformation() {
     return new Promise(
         resolve => chrome.feedbackPrivate.getSystemInformation(resolve));
@@ -694,21 +669,25 @@
   };
 
   window.addEventListener('DOMContentLoaded', function() {
-    feedbackHelper.getFeedbackInfo().then(function(data) {
-      feedbackInfo = data;
-      applyData(feedbackInfo);
+    if (dialogArgs) {
+      feedbackInfo = /** @type {chrome.feedbackPrivate.FeedbackInfo} */ (
+          JSON.parse(dialogArgs));
+    }
+    applyData(feedbackInfo);
 
-      // Setup our event handlers.
-      $('attach-file').addEventListener('change', onFileSelected);
-      $('attach-file').addEventListener('click', onOpenFileDialog);
-      $('send-report-button').onclick = sendReport;
-      $('cancel-button').onclick = cancel;
-      $('remove-attached-file').onclick = clearAttachedFile;
-      // <if expr="chromeos">
-      $('performance-info-checkbox')
-          .addEventListener('change', performanceFeedbackChanged);
-      // </if>
-    });
+    window.feedbackInfo = feedbackInfo;
+    window.feedbackHelper = feedbackHelper;
+
+    // Setup our event handlers.
+    $('attach-file').addEventListener('change', onFileSelected);
+    $('attach-file').addEventListener('click', onOpenFileDialog);
+    $('send-report-button').onclick = sendReport;
+    $('cancel-button').onclick = cancel;
+    $('remove-attached-file').onclick = clearAttachedFile;
+    // <if expr="chromeos">
+    $('performance-info-checkbox')
+        .addEventListener('change', performanceFeedbackChanged);
+    // </if>
   });
 }
 
diff --git a/chrome/browser/resources/settings/about_page/about_page.html b/chrome/browser/resources/settings/about_page/about_page.html
index e8299dc..6a233b35 100644
--- a/chrome/browser/resources/settings/about_page/about_page.html
+++ b/chrome/browser/resources/settings/about_page/about_page.html
@@ -50,9 +50,6 @@
         <h1 class="product-title">$i18n{aboutProductTitle}</h1>
       </div>
       <div class="cr-row two-line">
-        <!-- TODO(dpapad): Investigate why vulcanize does not handle well
-          a new line after "getThrobberSrcIfUpdating_(", causes incorrect
-          src URL -->
         <!-- Set the icon from the iconset (when it's obsolete/EOL and
           when update is done) or set the src (when it's updating). -->
 <if expr="not chromeos">
@@ -61,7 +58,8 @@
           <iron-icon
               icon$="[[getUpdateStatusIcon_(
                   obsoleteSystemInfo_, currentUpdateStatusEvent_)]]"
-              src="[[getThrobberSrcIfUpdating_(obsoleteSystemInfo_, currentUpdateStatusEvent_)]]">
+              src="[[getThrobberSrcIfUpdating_(
+                  obsoleteSystemInfo_, currentUpdateStatusEvent_)]]">
           </iron-icon>
         </div>
 </if>
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.html b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.html
index 78c9554..200f064 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.html
@@ -266,6 +266,7 @@
           restamp>
         <div class="settings-box single-column stretch">
           <network-siminfo id="cellularSimInfo"
+              network-state="[[getNetworkState_(managedProperties_)]]"
               device-state="[[deviceState_]]">
           </network-siminfo>
         </div>
@@ -305,7 +306,9 @@
             <template is="dom-if"
                 if="[[showCellularSimUpdatedUi_(managedProperties_)]]" restamp>
               <div class="single-column stretch">
-                <network-siminfo device-state="[[deviceState_]]">
+                <network-siminfo
+                    network-state="[[getNetworkState_(managedProperties_)]]"
+                    device-state="[[deviceState_]]">
                 </network-siminfo>
               </div>
             </template>
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
index fa4e70a..bd23e738 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.html
@@ -59,7 +59,9 @@
         </div>
 
         <template is="dom-if" if="[[showSimInfo_(deviceState)]]" restamp>
-          <network-siminfo device-state="[[deviceState]]" on-click="doNothing_">
+          <network-siminfo
+              network-state="[[activeNetworkState]]"
+              device-state="[[deviceState]]">
           </network-siminfo>
         </template>
 
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
index 7e27c49d..3d12a41 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
@@ -148,10 +148,12 @@
             <template is="dom-if"
                 if="[[prefs.nearby_sharing.onboarding_complete.value]]"
                 restamp>
-              <settings-toggle-button id="nearbySharingToggleButton"
-                  pref="{{prefs.nearby_sharing.enabled}}"
+              <cr-toggle id="nearbySharingToggleButton"
+                  class="margin-matches-padding"
+                  aria-label="$i18n{nearbyShareTitle}"
+                  checked="{{prefs.nearby_sharing.enabled.value}}"
                   deep-link-focus-id$="[[Setting.kNearbyShareOnOff]]">
-              </settings-toggle-button>
+              </cr-toggle>
             </template>
             <template is="dom-if"
                 if="[[!prefs.nearby_sharing.onboarding_complete.value]]"
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.html b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.html
index c7ae387..20e496b5 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.html
@@ -57,23 +57,16 @@
         padding-inline-end: var(--cr-section-padding);
       }
     </style>
-    <div id="toggleRow" class="cr-row first" on-click="onEnableTap_" actionable>
-      <div id="onOff" class="flex cr-padded-text"
-          on$="[[prefs.nearby_sharing.enabled.value]]" aria-hidden="true">
-        [[getOnOffString_(prefs.nearby_sharing.enabled.value,
-            '$i18nPolymer{deviceOn}', '$i18nPolymer{deviceOff}')]]
-      </div>
-
-      <!-- Use a template to work around visual glitch where the toggle flips
-          on when the page first loads. -->
-      <template is="dom-if" if="[[prefs]]" restamp>
-        <settings-toggle-button id="featureToggleButton"
-            aria-describedby="onOff"
-            pref="{{prefs.nearby_sharing.enabled}}"
-            deep-link-focus-id$="[[Setting.kNearbyShareOnOff]]">
-        </settings-toggle-button>
-      </template>
-    </div>
+    <!-- Use a template to work around visual glitch where the toggle flips
+        on when the page first loads. -->
+    <template is="dom-if" if="[[prefs]]" restamp>
+      <settings-toggle-button id="featureToggleButton"
+          label="[[getOnOffString_(prefs.nearby_sharing.enabled.value,
+              '$i18nPolymer{deviceOn}', '$i18nPolymer{deviceOff}')]]"
+          pref="{{prefs.nearby_sharing.enabled}}"
+          deep-link-focus-id$="[[Setting.kNearbyShareOnOff]]">
+      </settings-toggle-button>
+    </template>
     <div class="settings-box two-line">
       <div class="start"
           aria-label="[[getAccountRowLabel(profileName_, profileLabel_)]]">
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.js b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.js
index c8a7977..d9325c0 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_subpage.js
@@ -121,17 +121,6 @@
     nearby_share.getContactManager().downloadContacts();
   },
 
-  /**
-   * @param {!Event} event
-   * @private
-   */
-  onEnableTap_(event) {
-    this.setPrefValue(
-        'nearby_sharing.enabled',
-        !this.getPref('nearby_sharing.enabled').value);
-    event.stopPropagation();
-  },
-
   /** @private */
   onDeviceNameTap_() {
     if (this.showDeviceNameDialog_) {
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
index a5c8652..f33aeec 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
@@ -97,9 +97,6 @@
             <h1 class="product-title">$i18n{aboutOsProductTitle}</h1>
           </div>
           <div class="settings-box two-line">
-            <!-- TODO(dpapad): Investigate why vulcanize does not handle well
-              a new line after "getThrobberSrcIfUpdating_(", causes incorrect
-              src URL -->
             <!-- Set the icon from the iconset (when it's obsolete/EOL and
               when update is done) or set the src (when it's updating). -->
             <div class="icon-container"
@@ -108,7 +105,8 @@
               <iron-icon
                   icon$="[[getUpdateStatusIcon_(
                       hasEndOfLife_, currentUpdateStatusEvent_)]]"
-                  src="[[getThrobberSrcIfUpdating_(hasEndOfLife_, currentUpdateStatusEvent_)]]">
+                  src="[[getThrobberSrcIfUpdating_(
+                      hasEndOfLife_, currentUpdateStatusEvent_)]]">
               </iron-icon>
             </div>
             <div class="start padded">
diff --git a/chrome/browser/resources/settings/controls/settings_idle_load.js b/chrome/browser/resources/settings/controls/settings_idle_load.js
index af8f5db2..4cbb765 100644
--- a/chrome/browser/resources/settings/controls/settings_idle_load.js
+++ b/chrome/browser/resources/settings/controls/settings_idle_load.js
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(dpapad): Complete Polymer 3 migration of this file.
-
 /**
  * @fileoverview
  * settings-idle-load is a simple variant of dom-if designed for lazy
diff --git a/chrome/browser/resources/webui_js_error/BUILD.gn b/chrome/browser/resources/webui_js_error/BUILD.gn
index c927802..0b2172a 100644
--- a/chrome/browser/resources/webui_js_error/BUILD.gn
+++ b/chrome/browser/resources/webui_js_error/BUILD.gn
@@ -16,7 +16,7 @@
 if (optimize_webui) {
   build_manifest = "build_manifest.json"
   optimize_webui("build") {
-    host = "webui_js_error"
+    host = "webuijserror"
     input = rebase_path("$target_gen_dir/$preprocess_folder", root_build_dir)
     deps = [
       ":preprocess",
diff --git a/chrome/browser/serial/serial_chooser_context.cc b/chrome/browser/serial/serial_chooser_context.cc
index aba5316c..f9106775 100644
--- a/chrome/browser/serial/serial_chooser_context.cc
+++ b/chrome/browser/serial/serial_chooser_context.cc
@@ -96,7 +96,8 @@
     : ChooserContextBase(ContentSettingsType::SERIAL_GUARD,
                          ContentSettingsType::SERIAL_CHOOSER_DATA,
                          HostContentSettingsMapFactory::GetForProfile(profile)),
-      is_incognito_(profile->IsOffTheRecord()) {}
+      is_incognito_(profile->IsOffTheRecord()),
+      policy_(profile->GetPrefs()) {}
 
 SerialChooserContext::~SerialChooserContext() = default;
 
@@ -222,6 +223,10 @@
     return false;
   }
 
+  if (policy_.HasPortPermission(origin, port)) {
+    return true;
+  }
+
   if (!CanRequestObjectPermission(origin)) {
     return false;
   }
diff --git a/chrome/browser/serial/serial_chooser_context.h b/chrome/browser/serial/serial_chooser_context.h
index 490fa913..5427514 100644
--- a/chrome/browser/serial/serial_chooser_context.h
+++ b/chrome/browser/serial/serial_chooser_context.h
@@ -14,6 +14,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/unguessable_token.h"
+#include "chrome/browser/serial/serial_policy_allowed_ports.h"
 #include "components/permissions/chooser_context_base.h"
 #include "content/public/browser/serial_delegate.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -82,6 +83,8 @@
 
   const bool is_incognito_;
 
+  SerialPolicyAllowedPorts policy_;
+
   // Tracks the set of ports to which an origin has access to.
   std::map<url::Origin, std::set<base::UnguessableToken>> ephemeral_ports_;
 
diff --git a/chrome/browser/serial/serial_chooser_context_unittest.cc b/chrome/browser/serial/serial_chooser_context_unittest.cc
index 275d8ee..fc3d5f9 100644
--- a/chrome/browser/serial/serial_chooser_context_unittest.cc
+++ b/chrome/browser/serial/serial_chooser_context_unittest.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/serial/serial_blocklist.h"
 #include "chrome/browser/serial/serial_chooser_context_factory.h"
 #include "chrome/browser/serial/serial_chooser_histograms.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/pref_names.h"
@@ -60,6 +61,14 @@
   return port;
 }
 
+std::unique_ptr<base::Value> ReadJson(base::StringPiece json) {
+  base::JSONReader::ValueWithError result =
+      base::JSONReader::ReadAndReturnValueWithError(json);
+  EXPECT_TRUE(result.value) << result.error_message;
+  return result.value ? base::Value::ToUniquePtrValue(std::move(*result.value))
+                      : nullptr;
+}
+
 class SerialChooserContextTest : public testing::Test {
  public:
   SerialChooserContextTest() {
@@ -407,9 +416,7 @@
   prefs->SetManagedPref(prefs::kManagedDefaultSerialGuardSetting,
                         std::make_unique<base::Value>(CONTENT_SETTING_BLOCK));
   prefs->SetManagedPref(prefs::kManagedSerialAskForUrls,
-                        base::JSONReader::ReadDeprecated(R"(
-    [ "https://foo.origin" ]
-  )"));
+                        ReadJson(R"([ "https://foo.origin" ])"));
 
   EXPECT_TRUE(context()->CanRequestObjectPermission(kFooOrigin));
   EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *port));
@@ -438,9 +445,7 @@
 
   auto* prefs = profile()->GetTestingPrefService();
   prefs->SetManagedPref(prefs::kManagedSerialBlockedForUrls,
-                        base::JSONReader::ReadDeprecated(R"(
-    [ "https://foo.origin" ]
-  )"));
+                        ReadJson(R"([ "https://foo.origin" ])"));
 
   EXPECT_FALSE(context()->CanRequestObjectPermission(kFooOrigin));
   EXPECT_FALSE(context()->HasPortPermission(kFooOrigin, *port));
@@ -458,6 +463,92 @@
   EXPECT_EQ(1u, all_origin_objects.size());
 }
 
+TEST_F(SerialChooserContextTest, PolicyAllowForUrls) {
+  const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
+  const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
+
+  auto* prefs = profile()->GetTestingPrefService();
+  prefs->SetManagedPref(prefs::kManagedSerialAllowAllPortsForUrls,
+                        ReadJson(R"([ "https://foo.origin" ])"));
+  prefs->SetManagedPref(prefs::kManagedSerialAllowUsbDevicesForUrls,
+                        ReadJson(R"([
+               {
+                 "devices": [{ "vendor_id": 1234, "product_id": 5678 }],
+                 "urls": [ "https://bar.origin" ]
+               }
+             ])"));
+
+  auto platform_port = device::mojom::SerialPortInfo::New();
+  platform_port->token = base::UnguessableToken::Create();
+
+  auto usb_port1 = device::mojom::SerialPortInfo::New();
+  usb_port1->token = base::UnguessableToken::Create();
+  usb_port1->has_vendor_id = true;
+  usb_port1->vendor_id = 1234;
+  usb_port1->has_product_id = true;
+  usb_port1->product_id = 5678;
+
+  auto usb_port2 = device::mojom::SerialPortInfo::New();
+  usb_port2->token = base::UnguessableToken::Create();
+  usb_port2->has_vendor_id = true;
+  usb_port2->vendor_id = 1234;
+  usb_port2->has_product_id = true;
+  usb_port2->product_id = 8765;
+
+  EXPECT_TRUE(context()->CanRequestObjectPermission(kFooOrigin));
+  EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *platform_port));
+  EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *usb_port1));
+  EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *usb_port2));
+
+  EXPECT_TRUE(context()->CanRequestObjectPermission(kBarOrigin));
+  EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *platform_port));
+  EXPECT_TRUE(context()->HasPortPermission(kBarOrigin, *usb_port1));
+  EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *usb_port2));
+
+  // TODO(crbug.com/1001242): Add tests for GetGrantedObjects() and
+  // GetAllGrantedObjects() once those have been updated to include device
+  // permissions granted by policy.
+}
+
+TEST_F(SerialChooserContextTest, PolicyAllowOverridesGuard) {
+  const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
+  const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
+
+  auto* prefs = profile()->GetTestingPrefService();
+  prefs->SetManagedPref(prefs::kManagedDefaultSerialGuardSetting,
+                        std::make_unique<base::Value>(CONTENT_SETTING_BLOCK));
+  prefs->SetManagedPref(prefs::kManagedSerialAllowAllPortsForUrls,
+                        ReadJson(R"([ "https://foo.origin" ])"));
+
+  auto port = device::mojom::SerialPortInfo::New();
+  port->token = base::UnguessableToken::Create();
+
+  EXPECT_FALSE(context()->CanRequestObjectPermission(kFooOrigin));
+  EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *port));
+  EXPECT_FALSE(context()->CanRequestObjectPermission(kBarOrigin));
+  EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *port));
+}
+
+TEST_F(SerialChooserContextTest, PolicyAllowOverridesBlocked) {
+  const auto kFooOrigin = url::Origin::Create(GURL("https://foo.origin"));
+  const auto kBarOrigin = url::Origin::Create(GURL("https://bar.origin"));
+
+  auto* prefs = profile()->GetTestingPrefService();
+  prefs->SetManagedPref(
+      prefs::kManagedSerialBlockedForUrls,
+      ReadJson(R"([ "https://foo.origin", "https://bar.origin" ])"));
+  prefs->SetManagedPref(prefs::kManagedSerialAllowAllPortsForUrls,
+                        ReadJson(R"([ "https://foo.origin" ])"));
+
+  auto port = device::mojom::SerialPortInfo::New();
+  port->token = base::UnguessableToken::Create();
+
+  EXPECT_FALSE(context()->CanRequestObjectPermission(kFooOrigin));
+  EXPECT_TRUE(context()->HasPortPermission(kFooOrigin, *port));
+  EXPECT_FALSE(context()->CanRequestObjectPermission(kBarOrigin));
+  EXPECT_FALSE(context()->HasPortPermission(kBarOrigin, *port));
+}
+
 TEST_F(SerialChooserContextTest, Blocklist) {
   const auto origin = url::Origin::Create(GURL("https://google.com"));
 
@@ -487,3 +578,29 @@
       all_origin_objects = context()->GetAllGrantedObjects();
   EXPECT_EQ(1u, all_origin_objects.size());
 }
+
+TEST_F(SerialChooserContextTest, BlocklistOverridesPolicy) {
+  const auto origin = url::Origin::Create(GURL("https://google.com"));
+
+  auto* prefs = profile()->GetTestingPrefService();
+  prefs->SetManagedPref(prefs::kManagedSerialAllowUsbDevicesForUrls,
+                        ReadJson(R"([
+               {
+                 "devices": [{ "vendor_id": 6353, "product_id": 22768 }],
+                 "urls": [ "https://google.com" ]
+               }
+             ])"));
+
+  auto port = device::mojom::SerialPortInfo::New();
+  port->token = base::UnguessableToken::Create();
+  port->has_vendor_id = true;
+  port->vendor_id = 0x18D1;
+  port->has_product_id = true;
+  port->product_id = 0x58F0;
+  EXPECT_TRUE(context()->HasPortPermission(origin, *port));
+
+  // Adding a USB device to the blocklist overrides permissions granted by
+  // policy.
+  SetDynamicBlocklist("usb:18D1:58F0");
+  EXPECT_FALSE(context()->HasPortPermission(origin, *port));
+}
diff --git a/chrome/browser/serial/serial_policy_allowed_ports.cc b/chrome/browser/serial/serial_policy_allowed_ports.cc
new file mode 100644
index 0000000..5b66bea
--- /dev/null
+++ b/chrome/browser/serial/serial_policy_allowed_ports.cc
@@ -0,0 +1,154 @@
+// 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/serial/serial_policy_allowed_ports.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/values.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "services/device/public/mojom/serial.mojom.h"
+#include "url/gurl.h"
+
+namespace {
+
+constexpr char kPrefDevicesKey[] = "devices";
+constexpr char kPrefUrlsKey[] = "urls";
+constexpr char kPrefVendorIdKey[] = "vendor_id";
+constexpr char kPrefProductIdKey[] = "product_id";
+
+}  // namespace
+
+SerialPolicyAllowedPorts::SerialPolicyAllowedPorts(PrefService* pref_service) {
+  pref_change_registrar_.Init(pref_service);
+
+  // The lifetime of |pref_change_registrar_| is managed by this class so it is
+  // safe to use base::Unretained() here.
+  pref_change_registrar_.Add(
+      prefs::kManagedSerialAllowAllPortsForUrls,
+      base::BindRepeating(
+          &SerialPolicyAllowedPorts::LoadAllowAllPortsForUrlsPolicy,
+          base::Unretained(this)));
+  pref_change_registrar_.Add(
+      prefs::kManagedSerialAllowUsbDevicesForUrls,
+      base::BindRepeating(
+          &SerialPolicyAllowedPorts::LoadAllowUsbDevicesForUrlsPolicy,
+          base::Unretained(this)));
+
+  LoadAllowAllPortsForUrlsPolicy();
+  LoadAllowUsbDevicesForUrlsPolicy();
+}
+
+SerialPolicyAllowedPorts::~SerialPolicyAllowedPorts() = default;
+
+// static
+void SerialPolicyAllowedPorts::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterListPref(prefs::kManagedSerialAllowAllPortsForUrls);
+  registry->RegisterListPref(prefs::kManagedSerialAllowUsbDevicesForUrls);
+}
+
+bool SerialPolicyAllowedPorts::HasPortPermission(
+    const url::Origin& origin,
+    const device::mojom::SerialPortInfo& port_info) {
+  if (base::Contains(all_ports_policy_, origin)) {
+    return true;
+  }
+
+  if (port_info.has_vendor_id) {
+    auto it = usb_vendor_policy_.find(port_info.vendor_id);
+    if (it != usb_vendor_policy_.end() && base::Contains(it->second, origin)) {
+      return true;
+    }
+  }
+
+  if (port_info.has_vendor_id && port_info.has_product_id) {
+    auto it = usb_device_policy_.find(
+        std::make_pair(port_info.vendor_id, port_info.product_id));
+    if (it != usb_device_policy_.end() && base::Contains(it->second, origin)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void SerialPolicyAllowedPorts::LoadAllowAllPortsForUrlsPolicy() {
+  all_ports_policy_.clear();
+
+  const base::Value* pref_value = pref_change_registrar_.prefs()->Get(
+      prefs::kManagedSerialAllowAllPortsForUrls);
+  if (!pref_value) {
+    return;
+  }
+
+  // The pref value has already been validated by the policy handler, so it is
+  // safe to assume that |pref_value| follows the policy template.
+  std::vector<url::Origin> urls;
+  for (const auto& url_value : pref_value->GetList()) {
+    GURL url(url_value.GetString());
+    if (!url.is_valid()) {
+      continue;
+    }
+
+    urls.push_back(url::Origin::Create(url));
+  }
+
+  all_ports_policy_.insert(urls.begin(), urls.end());
+}
+
+void SerialPolicyAllowedPorts::LoadAllowUsbDevicesForUrlsPolicy() {
+  usb_device_policy_.clear();
+  usb_vendor_policy_.clear();
+
+  const base::Value* pref_value = pref_change_registrar_.prefs()->Get(
+      prefs::kManagedSerialAllowUsbDevicesForUrls);
+  if (!pref_value) {
+    return;
+  }
+
+  // The pref value has already been validated by the policy handler, so it is
+  // safe to assume that |pref_value| follows the policy template.
+  for (const auto& item : pref_value->GetList()) {
+    const base::Value* urls_value = item.FindKey(kPrefUrlsKey);
+    DCHECK(urls_value);
+
+    std::vector<url::Origin> urls;
+    for (const auto& url_value : urls_value->GetList()) {
+      GURL url(url_value.GetString());
+      if (!url.is_valid()) {
+        continue;
+      }
+
+      urls.push_back(url::Origin::Create(url));
+    }
+
+    if (urls.empty()) {
+      continue;
+    }
+
+    const base::Value* devices_value = item.FindKey(kPrefDevicesKey);
+    DCHECK(devices_value);
+    for (const auto& port_value : devices_value->GetList()) {
+      const base::Value* vendor_id_value = port_value.FindKey(kPrefVendorIdKey);
+      DCHECK(vendor_id_value);
+
+      const base::Value* product_id_value =
+          port_value.FindKey(kPrefProductIdKey);
+      // "product_id" is optional and the policy matches all devices with the
+      // given vendor ID if it is not specified.
+      if (product_id_value) {
+        usb_device_policy_[{vendor_id_value->GetInt(),
+                            product_id_value->GetInt()}]
+            .insert(urls.begin(), urls.end());
+      } else {
+        usb_vendor_policy_[vendor_id_value->GetInt()].insert(urls.begin(),
+                                                             urls.end());
+      }
+    }
+  }
+}
diff --git a/chrome/browser/serial/serial_policy_allowed_ports.h b/chrome/browser/serial/serial_policy_allowed_ports.h
new file mode 100644
index 0000000..46bbe8b
--- /dev/null
+++ b/chrome/browser/serial/serial_policy_allowed_ports.h
@@ -0,0 +1,70 @@
+// 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_SERIAL_SERIAL_POLICY_ALLOWED_PORTS_H_
+#define CHROME_BROWSER_SERIAL_SERIAL_POLICY_ALLOWED_PORTS_H_
+
+#include <map>
+#include <set>
+
+#include "components/prefs/pref_change_registrar.h"
+#include "url/origin.h"
+
+namespace device {
+namespace mojom {
+class SerialPortInfo;
+}  // namespace mojom
+}  // namespace device
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class PrefService;
+
+// This class is used to maintain and interpret the SerialAllowForUrls and
+// SerialAllowUsbDevicesForUrls policies.
+//
+// A PrefChangeRegistrar is used to observe changes to the preference values so
+// that the policy can be updated in real-time.
+class SerialPolicyAllowedPorts {
+ public:
+  explicit SerialPolicyAllowedPorts(PrefService* pref_service);
+  SerialPolicyAllowedPorts(SerialPolicyAllowedPorts& other) = delete;
+  SerialPolicyAllowedPorts& operator=(SerialPolicyAllowedPorts& other) = delete;
+  ~SerialPolicyAllowedPorts();
+
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+  // Checks if |origin| is allowed to use the port with |port_info|.
+  bool HasPortPermission(const url::Origin& origin,
+                         const device::mojom::SerialPortInfo& port_info);
+
+  const std::map<std::pair<int, int>, std::set<url::Origin>>&
+  usb_device_policy() const {
+    return usb_device_policy_;
+  }
+  const std::map<int, std::set<url::Origin>>& usb_vendor_policy() const {
+    return usb_vendor_policy_;
+  }
+  const std::set<url::Origin>& all_ports_policy() const {
+    return all_ports_policy_;
+  }
+
+ private:
+  void LoadAllowAllPortsForUrlsPolicy();
+  void LoadAllowUsbDevicesForUrlsPolicy();
+
+  PrefChangeRegistrar pref_change_registrar_;
+
+  // Stores the current policy configuration for specific USB devices
+  // identified by vendor and product IDs (usb_device_policy_), all USB
+  // devices from a particular vendor ID (usb_vendor_policy_) and origins
+  // which are allowed to access all ports.
+  std::map<std::pair<int, int>, std::set<url::Origin>> usb_device_policy_;
+  std::map<int, std::set<url::Origin>> usb_vendor_policy_;
+  std::set<url::Origin> all_ports_policy_;
+};
+
+#endif  // CHROME_BROWSER_SERIAL_SERIAL_POLICY_ALLOWED_PORTS_H_
diff --git a/chrome/browser/serial/serial_policy_allowed_ports_unittest.cc b/chrome/browser/serial/serial_policy_allowed_ports_unittest.cc
new file mode 100644
index 0000000..78520e6
--- /dev/null
+++ b/chrome/browser/serial/serial_policy_allowed_ports_unittest.cc
@@ -0,0 +1,339 @@
+// 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/serial/serial_policy_allowed_ports.h"
+
+#include "base/json/json_reader.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "services/device/public/mojom/serial.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace {
+
+using ::testing::UnorderedElementsAre;
+
+base::Value ReadJson(base::StringPiece json) {
+  base::JSONReader::ValueWithError result =
+      base::JSONReader::ReadAndReturnValueWithError(json);
+  EXPECT_TRUE(result.value) << result.error_message;
+  return result.value ? std::move(*result.value) : base::Value();
+}
+
+device::mojom::SerialPortInfoPtr CreateUsbDevice(uint16_t vendor_id,
+                                                 uint16_t product_id) {
+  auto port = device::mojom::SerialPortInfo::New();
+  port->token = base::UnguessableToken::Create();
+  port->has_vendor_id = true;
+  port->vendor_id = vendor_id;
+  port->has_product_id = true;
+  port->product_id = product_id;
+  return port;
+}
+
+device::mojom::SerialPortInfoPtr CreatePlatformPort() {
+  auto port = device::mojom::SerialPortInfo::New();
+  port->token = base::UnguessableToken::Create();
+  return port;
+}
+
+}  // namespace
+
+class SerialPolicyAllowedPortsTest : public testing::Test {
+ public:
+  SerialPolicyAllowedPortsTest() = default;
+  ~SerialPolicyAllowedPortsTest() override = default;
+
+  void SetAllowAllPortsForUrlsPrefValue(const base::Value& value) {
+    profile_.GetPrefs()->Set(prefs::kManagedSerialAllowAllPortsForUrls, value);
+  }
+
+  void SetAllowUsbDevicesForUrlsPrefValue(const base::Value& value) {
+    profile_.GetPrefs()->Set(prefs::kManagedSerialAllowUsbDevicesForUrls,
+                             value);
+  }
+
+  void InitializePolicy() {
+    EXPECT_FALSE(policy_);
+    policy_ = std::make_unique<SerialPolicyAllowedPorts>(profile_.GetPrefs());
+  }
+
+ protected:
+  SerialPolicyAllowedPorts* policy() { return policy_.get(); }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile profile_;
+  std::unique_ptr<SerialPolicyAllowedPorts> policy_;
+};
+
+TEST_F(SerialPolicyAllowedPortsTest, InitializeWithMissingPrefValue) {
+  InitializePolicy();
+  EXPECT_TRUE(policy()->usb_device_policy().empty());
+  EXPECT_TRUE(policy()->usb_vendor_policy().empty());
+  EXPECT_TRUE(policy()->all_ports_policy().empty());
+}
+
+TEST_F(SerialPolicyAllowedPortsTest, InitializeWithEmptyPrefValues) {
+  SetAllowAllPortsForUrlsPrefValue(base::Value(base::Value::Type::LIST));
+  SetAllowUsbDevicesForUrlsPrefValue(base::Value(base::Value::Type::LIST));
+
+  InitializePolicy();
+  EXPECT_TRUE(policy()->usb_device_policy().empty());
+  EXPECT_TRUE(policy()->usb_vendor_policy().empty());
+  EXPECT_TRUE(policy()->all_ports_policy().empty());
+}
+
+TEST_F(SerialPolicyAllowedPortsTest, InitializeWithPrefValues) {
+  constexpr char kAllPortsPolicySetting[] = R"(["https://www.youtube.com"])";
+  constexpr char kUsbDevicesPolicySetting[] = R"(
+    [
+      {
+        "devices": [
+          { "vendor_id": 1234, "product_id": 5678 },
+          { "vendor_id": 4321 }
+        ],
+        "urls": [
+          "https://google.com",
+          "https://crbug.com"
+        ]
+      }
+    ])";
+  const auto kYoutubeOrigin =
+      url::Origin::Create(GURL("https://www.youtube.com"));
+  const auto kGoogleOrigin = url::Origin::Create(GURL("https://google.com"));
+  const auto kCrbugOrigin = url::Origin::Create(GURL("https://crbug.com"));
+
+  SetAllowAllPortsForUrlsPrefValue(ReadJson(kAllPortsPolicySetting));
+  SetAllowUsbDevicesForUrlsPrefValue(ReadJson(kUsbDevicesPolicySetting));
+  InitializePolicy();
+
+  EXPECT_EQ(1u, policy()->usb_device_policy().size());
+  EXPECT_EQ(1u, policy()->usb_vendor_policy().size());
+  EXPECT_EQ(1u, policy()->all_ports_policy().size());
+
+  EXPECT_THAT(policy()->all_ports_policy(),
+              UnorderedElementsAre(kYoutubeOrigin));
+
+  const auto device_key = std::make_pair(1234, 5678);
+  ASSERT_TRUE(base::Contains(policy()->usb_device_policy(), device_key));
+  EXPECT_THAT(policy()->usb_device_policy().at(device_key),
+              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));
+
+  ASSERT_TRUE(base::Contains(policy()->usb_vendor_policy(), 4321));
+  EXPECT_THAT(policy()->usb_vendor_policy().at(4321),
+              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));
+
+  auto vendor1_device = CreateUsbDevice(1234, 5678);
+  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor1_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor1_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor1_device));
+
+  auto vendor1_alternate_device = CreateUsbDevice(1234, 1234);
+  EXPECT_TRUE(
+      policy()->HasPortPermission(kYoutubeOrigin, *vendor1_alternate_device));
+  EXPECT_FALSE(
+      policy()->HasPortPermission(kGoogleOrigin, *vendor1_alternate_device));
+  EXPECT_FALSE(
+      policy()->HasPortPermission(kCrbugOrigin, *vendor1_alternate_device));
+
+  auto vendor2_device = CreateUsbDevice(4321, 1234);
+  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor2_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor2_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor2_device));
+
+  auto platform_port = CreatePlatformPort();
+  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *platform_port));
+  EXPECT_FALSE(policy()->HasPortPermission(kGoogleOrigin, *platform_port));
+  EXPECT_FALSE(policy()->HasPortPermission(kCrbugOrigin, *platform_port));
+}
+
+TEST_F(SerialPolicyAllowedPortsTest,
+       InitializeWithMissingPrefValuesThenUpdate) {
+  InitializePolicy();
+
+  constexpr char kAllPortsPolicySetting[] = R"(["https://www.youtube.com"])";
+  constexpr char kUsbDevicesPolicySetting[] = R"(
+    [
+      {
+        "devices": [
+          { "vendor_id": 1234, "product_id": 5678 },
+          { "vendor_id": 4321 }
+        ],
+        "urls": [
+          "https://google.com",
+          "https://crbug.com"
+        ]
+      }
+    ])";
+  const auto kYoutubeOrigin =
+      url::Origin::Create(GURL("https://www.youtube.com"));
+  const auto kGoogleOrigin = url::Origin::Create(GURL("https://google.com"));
+  const auto kCrbugOrigin = url::Origin::Create(GURL("https://crbug.com"));
+
+  SetAllowAllPortsForUrlsPrefValue(ReadJson(kAllPortsPolicySetting));
+  SetAllowUsbDevicesForUrlsPrefValue(ReadJson(kUsbDevicesPolicySetting));
+
+  EXPECT_EQ(1u, policy()->usb_device_policy().size());
+  EXPECT_EQ(1u, policy()->usb_vendor_policy().size());
+  EXPECT_EQ(1u, policy()->all_ports_policy().size());
+
+  EXPECT_THAT(policy()->all_ports_policy(),
+              UnorderedElementsAre(kYoutubeOrigin));
+
+  const auto device_key = std::make_pair(1234, 5678);
+  ASSERT_TRUE(base::Contains(policy()->usb_device_policy(), device_key));
+  EXPECT_THAT(policy()->usb_device_policy().at(device_key),
+              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));
+
+  ASSERT_TRUE(base::Contains(policy()->usb_vendor_policy(), 4321));
+  EXPECT_THAT(policy()->usb_vendor_policy().at(4321),
+              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));
+
+  auto vendor1_device = CreateUsbDevice(1234, 5678);
+  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor1_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor1_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor1_device));
+
+  auto vendor1_alternate_device = CreateUsbDevice(1234, 1234);
+  EXPECT_TRUE(
+      policy()->HasPortPermission(kYoutubeOrigin, *vendor1_alternate_device));
+  EXPECT_FALSE(
+      policy()->HasPortPermission(kGoogleOrigin, *vendor1_alternate_device));
+  EXPECT_FALSE(
+      policy()->HasPortPermission(kCrbugOrigin, *vendor1_alternate_device));
+
+  auto vendor2_device = CreateUsbDevice(4321, 1234);
+  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor2_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor2_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor2_device));
+
+  auto platform_port = CreatePlatformPort();
+  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *platform_port));
+  EXPECT_FALSE(policy()->HasPortPermission(kGoogleOrigin, *platform_port));
+  EXPECT_FALSE(policy()->HasPortPermission(kCrbugOrigin, *platform_port));
+}
+
+TEST_F(SerialPolicyAllowedPortsTest, InitializeWithPrefValuesThenRemovePolicy) {
+  constexpr char kAllPortsPolicySetting[] = R"(["https://www.youtube.com"])";
+  constexpr char kUsbDevicesPolicySetting[] = R"(
+    [
+      {
+        "devices": [
+          { "vendor_id": 1234, "product_id": 5678 },
+          { "vendor_id": 4321 }
+        ],
+        "urls": [
+          "https://google.com",
+          "https://crbug.com"
+        ]
+      }
+    ])";
+
+  SetAllowAllPortsForUrlsPrefValue(ReadJson(kAllPortsPolicySetting));
+  SetAllowUsbDevicesForUrlsPrefValue(ReadJson(kUsbDevicesPolicySetting));
+  InitializePolicy();
+
+  SetAllowAllPortsForUrlsPrefValue(base::Value(base::Value::Type::LIST));
+  SetAllowUsbDevicesForUrlsPrefValue(base::Value(base::Value::Type::LIST));
+  EXPECT_TRUE(policy()->usb_device_policy().empty());
+  EXPECT_TRUE(policy()->usb_vendor_policy().empty());
+  EXPECT_TRUE(policy()->all_ports_policy().empty());
+}
+
+TEST_F(SerialPolicyAllowedPortsTest, MultipleItemsWithOverlap) {
+  constexpr char kUsbDevicesPolicySetting[] = R"(
+    [
+      {
+        "devices": [
+          { "vendor_id": 1234, "product_id": 5678 },
+          { "vendor_id": 4321 }
+        ],
+        "urls": [
+          "https://google.com",
+          "https://crbug.com"
+        ]
+      },
+      {
+        "devices": [
+          { "vendor_id": 1234 },
+          { "vendor_id": 4321, "product_id": 8765 }
+        ],
+        "urls": [
+          "https://crbug.com",
+          "https://www.youtube.com"
+        ]
+      }
+    ])";
+  const auto kGoogleOrigin = url::Origin::Create(GURL("https://google.com"));
+  const auto kCrbugOrigin = url::Origin::Create(GURL("https://crbug.com"));
+  const auto kYoutubeOrigin =
+      url::Origin::Create(GURL("https://www.youtube.com"));
+
+  SetAllowUsbDevicesForUrlsPrefValue(ReadJson(kUsbDevicesPolicySetting));
+  InitializePolicy();
+
+  EXPECT_EQ(2u, policy()->usb_device_policy().size());
+  EXPECT_EQ(2u, policy()->usb_vendor_policy().size());
+  EXPECT_EQ(0u, policy()->all_ports_policy().size());
+
+  const auto device1_key = std::make_pair(1234, 5678);
+  ASSERT_TRUE(base::Contains(policy()->usb_device_policy(), device1_key));
+  EXPECT_THAT(policy()->usb_device_policy().at(device1_key),
+              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));
+
+  const auto device2_key = std::make_pair(4321, 8765);
+  ASSERT_TRUE(base::Contains(policy()->usb_device_policy(), device2_key));
+  EXPECT_THAT(policy()->usb_device_policy().at(device2_key),
+              UnorderedElementsAre(kCrbugOrigin, kYoutubeOrigin));
+
+  ASSERT_TRUE(base::Contains(policy()->usb_vendor_policy(), 1234));
+  EXPECT_THAT(policy()->usb_vendor_policy().at(1234),
+              UnorderedElementsAre(kCrbugOrigin, kYoutubeOrigin));
+
+  ASSERT_TRUE(base::Contains(policy()->usb_vendor_policy(), 4321));
+  EXPECT_THAT(policy()->usb_vendor_policy().at(4321),
+              UnorderedElementsAre(kGoogleOrigin, kCrbugOrigin));
+
+  auto vendor1_device1 = CreateUsbDevice(1234, 5678);
+  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor1_device1));
+  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor1_device1));
+  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor1_device1));
+
+  auto vendor1_alternate_device = CreateUsbDevice(1234, 1234);
+  EXPECT_FALSE(
+      policy()->HasPortPermission(kGoogleOrigin, *vendor1_alternate_device));
+  EXPECT_TRUE(
+      policy()->HasPortPermission(kCrbugOrigin, *vendor1_alternate_device));
+  EXPECT_TRUE(
+      policy()->HasPortPermission(kYoutubeOrigin, *vendor1_alternate_device));
+
+  auto vendor2_device = CreateUsbDevice(4321, 8765);
+  EXPECT_TRUE(policy()->HasPortPermission(kGoogleOrigin, *vendor2_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kCrbugOrigin, *vendor2_device));
+  EXPECT_TRUE(policy()->HasPortPermission(kYoutubeOrigin, *vendor2_device));
+
+  auto vendor2_alternate_device = CreateUsbDevice(4321, 1234);
+  EXPECT_TRUE(
+      policy()->HasPortPermission(kGoogleOrigin, *vendor2_alternate_device));
+  EXPECT_TRUE(
+      policy()->HasPortPermission(kCrbugOrigin, *vendor2_alternate_device));
+  EXPECT_FALSE(
+      policy()->HasPortPermission(kYoutubeOrigin, *vendor2_alternate_device));
+
+  auto vendor3_device = CreateUsbDevice(9012, 3456);
+  EXPECT_FALSE(policy()->HasPortPermission(kGoogleOrigin, *vendor3_device));
+  EXPECT_FALSE(policy()->HasPortPermission(kCrbugOrigin, *vendor3_device));
+  EXPECT_FALSE(policy()->HasPortPermission(kYoutubeOrigin, *vendor3_device));
+
+  auto platform_port = CreatePlatformPort();
+  EXPECT_FALSE(policy()->HasPortPermission(kGoogleOrigin, *platform_port));
+  EXPECT_FALSE(policy()->HasPortPermission(kCrbugOrigin, *platform_port));
+  EXPECT_FALSE(policy()->HasPortPermission(kYoutubeOrigin, *platform_port));
+}
diff --git a/chrome/browser/ssl/sct_reporting_service_browsertest.cc b/chrome/browser/ssl/sct_reporting_service_browsertest.cc
index bbeb9ed..26546f3 100644
--- a/chrome/browser/ssl/sct_reporting_service_browsertest.cc
+++ b/chrome/browser/ssl/sct_reporting_service_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/synchronization/lock.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/net/system_network_context_manager.h"
@@ -171,19 +172,28 @@
   net::EmbeddedTestServer* report_server() { return &report_server_; }
 
   void WaitForRequests(size_t num_requests) {
-    if (requests_seen_ >= num_requests)
-      return;
-
-    requests_expected_ = num_requests;
-
-    base::RunLoop run_loop;
-    quit_closure_ = run_loop.QuitClosure();
-    run_loop.Run();
+    // Each loop iteration will account for one request being processed. (This
+    // simplifies the request handler code below, and reduces the state that
+    // must be tracked and handled under locks.)
+    while (true) {
+      base::RunLoop run_loop;
+      {
+        base::AutoLock auto_lock(requests_lock_);
+        if (requests_seen_ >= num_requests)
+          return;
+        requests_closure_ = run_loop.QuitClosure();
+      }
+      run_loop.Run();
+    }
   }
 
-  size_t requests_seen() { return requests_seen_; }
+  size_t requests_seen() {
+    base::AutoLock auto_lock(requests_lock_);
+    return requests_seen_;
+  }
 
   sct_auditing::SCTClientReport GetLastSeenReport() {
+    base::AutoLock auto_lock(requests_lock_);
     sct_auditing::SCTClientReport auditing_report;
     if (last_seen_request_.has_content)
       auditing_report.ParseFromString(last_seen_request_.content);
@@ -212,11 +222,11 @@
  private:
   std::unique_ptr<net::test_server::HttpResponse> HandleReportRequest(
       const net::test_server::HttpRequest& request) {
+    base::AutoLock auto_lock(requests_lock_);
     last_seen_request_ = request;
     ++requests_seen_;
-    if (!quit_closure_.is_null() && requests_seen_ >= requests_expected_) {
-      std::move(quit_closure_).Run();
-    }
+    if (requests_closure_)
+      std::move(requests_closure_).Run();
 
     auto http_response =
         std::make_unique<net::test_server::BasicHttpResponse>();
@@ -228,10 +238,12 @@
   net::EmbeddedTestServer report_server_;
   base::test::ScopedFeatureList scoped_feature_list_;
 
+  // `requests_lock_` is used to force sequential access to these variables to
+  // avoid races that can cause test flakes.
+  base::Lock requests_lock_;
   net::test_server::HttpRequest last_seen_request_;
   size_t requests_seen_ = 0;
-  size_t requests_expected_ = 0;
-  base::OnceClosure quit_closure_;
+  base::OnceClosure requests_closure_;
 };
 
 // Tests that reports should not be sent when extended reporting is not opted
diff --git a/chrome/browser/themes/theme_syncable_service.cc b/chrome/browser/themes/theme_syncable_service.cc
index 71b02f3..21cd36128 100644
--- a/chrome/browser/themes/theme_syncable_service.cc
+++ b/chrome/browser/themes/theme_syncable_service.cc
@@ -67,7 +67,7 @@
     ThemeSyncableService::Observer* observer) {
   observer_list_.AddObserver(observer);
   if (sync_processor_)
-    observer->OnThemeSyncStarted();
+    observer->OnThemeSyncStarted(startup_state_);
 }
 
 void ThemeSyncableService::RemoveObserver(
@@ -75,7 +75,9 @@
   observer_list_.RemoveObserver(observer);
 }
 
-void ThemeSyncableService::NotifyOnSyncStartedForTesting() {
+void ThemeSyncableService::NotifyOnSyncStartedForTesting(
+    ThemeSyncState startup_state) {
+  startup_state_ = startup_state;
   NotifyOnSyncStarted();
 }
 
@@ -121,7 +123,7 @@
     if (sync_data->GetSpecifics().has_theme()) {
       if (!HasNonDefaultTheme(current_specifics) ||
           HasNonDefaultTheme(sync_data->GetSpecifics().theme())) {
-        MaybeSetTheme(current_specifics, *sync_data);
+        startup_state_ = MaybeSetTheme(current_specifics, *sync_data);
         NotifyOnSyncStarted();
         return base::nullopt;
       }
@@ -131,6 +133,7 @@
   // No theme specifics are found. Create one according to current theme.
   base::Optional<syncer::ModelError> error =
       ProcessNewTheme(syncer::SyncChange::ACTION_ADD, current_specifics);
+  startup_state_ = ThemeSyncState::kApplied;
   NotifyOnSyncStarted();
   return error;
 }
@@ -210,23 +213,23 @@
   return syncer::ModelError(FROM_HERE, "Didn't find valid theme specifics");
 }
 
-void ThemeSyncableService::MaybeSetTheme(
+ThemeSyncableService::ThemeSyncState ThemeSyncableService::MaybeSetTheme(
     const sync_pb::ThemeSpecifics& current_specs,
     const syncer::SyncData& sync_data) {
   const sync_pb::ThemeSpecifics& sync_theme = sync_data.GetSpecifics().theme();
   use_system_theme_by_default_ = sync_theme.use_system_theme_by_default();
   DVLOG(1) << "Set current theme from specifics: " << sync_data.ToString();
-  if (!AreThemeSpecificsEqual(
-          current_specs,
-          sync_theme,
+  if (AreThemeSpecificsEqual(
+          current_specs, sync_theme,
           theme_service_->IsSystemThemeDistinctFromDefaultTheme())) {
-    SetCurrentThemeFromThemeSpecifics(sync_theme);
-  } else {
     DVLOG(1) << "Skip setting theme because specs are equal";
+    return ThemeSyncState::kApplied;
   }
+  return SetCurrentThemeFromThemeSpecifics(sync_theme);
 }
 
-void ThemeSyncableService::SetCurrentThemeFromThemeSpecifics(
+ThemeSyncableService::ThemeSyncState
+ThemeSyncableService::SetCurrentThemeFromThemeSpecifics(
     const sync_pb::ThemeSpecifics& theme_specifics) {
   if (theme_specifics.use_custom_theme()) {
     // TODO(akalin): Figure out what to do about third-party themes
@@ -246,7 +249,7 @@
     if (extension) {
       if (!extension->is_theme()) {
         DVLOG(1) << "Extension " << id << " is not a theme; aborting";
-        return;
+        return ThemeSyncState::kFailed;
       }
       int disabled_reasons =
           extensions::ExtensionPrefs::Get(profile_)->GetDisableReasons(id);
@@ -254,34 +257,44 @@
           disabled_reasons != extensions::disable_reason::DISABLE_USER_ACTION) {
         DVLOG(1) << "Theme " << id << " is disabled with reason "
                  << disabled_reasons << "; aborting";
-        return;
+        return ThemeSyncState::kFailed;
       }
       // An enabled theme extension with the given id was found, so
       // just set the current theme to it.
       theme_service_->SetTheme(extension);
-    } else {
-      // No extension with this id exists -- we must install it; we do
-      // so by adding it as a pending extension and then triggering an
-      // auto-update cycle.
-      const bool kRemoteInstall = false;
-      if (!extension_service->pending_extension_manager()->AddFromSync(
-              id, update_url, base::Version(), &IsTheme, kRemoteInstall)) {
-        LOG(WARNING) << "Could not add pending extension for " << id;
-        return;
-      }
-      extension_service->CheckForUpdatesSoon();
+      return ThemeSyncState::kApplied;
     }
-  } else if (theme_specifics.has_autogenerated_theme()) {
+
+    // No extension with this id exists -- we must install it; we do
+    // so by adding it as a pending extension and then triggering an
+    // auto-update cycle.
+    const bool kRemoteInstall = false;
+    if (!extension_service->pending_extension_manager()->AddFromSync(
+            id, update_url, base::Version(), &IsTheme, kRemoteInstall)) {
+      LOG(WARNING) << "Could not add pending extension for " << id;
+      return ThemeSyncState::kFailed;
+    }
+    extension_service->CheckForUpdatesSoon();
+    // Return that the call triggered an extension theme installation.
+    return ThemeSyncState::kWaitingForExtensionInstallation;
+  }
+
+  if (theme_specifics.has_autogenerated_theme()) {
     DVLOG(1) << "Applying autogenerated theme";
     theme_service_->BuildAutogeneratedThemeFromColor(
         theme_specifics.autogenerated_theme().color());
-  } else if (theme_specifics.use_system_theme_by_default()) {
+    return ThemeSyncState::kApplied;
+  }
+
+  if (theme_specifics.use_system_theme_by_default()) {
     DVLOG(1) << "Switch to use system theme";
     theme_service_->UseSystemTheme();
-  } else {
-    DVLOG(1) << "Switch to use default theme";
-    theme_service_->UseDefaultTheme();
+    return ThemeSyncState::kApplied;
   }
+
+  DVLOG(1) << "Switch to use default theme";
+  theme_service_->UseDefaultTheme();
+  return ThemeSyncState::kApplied;
 }
 
 bool ThemeSyncableService::GetThemeSpecificsFromCurrentTheme(
@@ -395,5 +408,5 @@
 
 void ThemeSyncableService::NotifyOnSyncStarted() {
   for (Observer& observer : observer_list_)
-    observer.OnThemeSyncStarted();
+    observer.OnThemeSyncStarted(startup_state_);
 }
diff --git a/chrome/browser/themes/theme_syncable_service.h b/chrome/browser/themes/theme_syncable_service.h
index ea97ef4b..1d425fcb 100644
--- a/chrome/browser/themes/theme_syncable_service.h
+++ b/chrome/browser/themes/theme_syncable_service.h
@@ -30,11 +30,24 @@
 class ThemeSyncableService : public syncer::SyncableService,
                              public ThemeServiceObserver {
  public:
+  // State of local theme after applying sync changes.
+  enum class ThemeSyncState {
+    // The remote theme has been applied locally or the other way around (or
+    // there was no change to apply).
+    kApplied,
+    // Remote theme failed to apply locally.
+    kFailed,
+    // Remote theme is an extension theme that is not installed locally, yet.
+    // Theme sync triggered the installation that may not be applied yet (as
+    // extension installation is in nature async and also can fail).
+    kWaitingForExtensionInstallation
+  };
+
   class Observer : public base::CheckedObserver {
    public:
     // Called when theme sync gets started. Observers that register after theme
     // sync gets started are called right away when they register.
-    virtual void OnThemeSyncStarted() = 0;
+    virtual void OnThemeSyncStarted(ThemeSyncState state) = 0;
   };
 
   // `profile` may be nullptr in tests (and is the one used by theme_service,
@@ -49,7 +62,7 @@
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
-  void NotifyOnSyncStartedForTesting();
+  void NotifyOnSyncStartedForTesting(ThemeSyncState startup_state);
 
   // syncer::SyncableService implementation.
   void WaitUntilReadyToSync(base::OnceClosure done) override;
@@ -80,10 +93,11 @@
 
   // Set theme from theme specifics in |sync_data| using
   // SetCurrentThemeFromThemeSpecifics() if it's different from |current_specs|.
-  void MaybeSetTheme(const sync_pb::ThemeSpecifics& current_specs,
-                     const syncer::SyncData& sync_data);
-
-  void SetCurrentThemeFromThemeSpecifics(
+  // Returns the state of themes after the operation.
+  ThemeSyncState MaybeSetTheme(const sync_pb::ThemeSpecifics& current_specs,
+                               const syncer::SyncData& sync_data);
+  // Returns the state of themes after the operation.
+  ThemeSyncState SetCurrentThemeFromThemeSpecifics(
       const sync_pb::ThemeSpecifics& theme_specifics);
 
   // If the current theme is syncable, fills in the passed |theme_specifics|
@@ -111,6 +125,9 @@
   // we're not on one.
   bool use_system_theme_by_default_;
 
+  // Captures the state of theme sync after initial data merge.
+  ThemeSyncState startup_state_ = ThemeSyncState::kFailed;
+
   base::ThreadChecker thread_checker_;
 
   FRIEND_TEST_ALL_PREFIXES(ThemeSyncableServiceTest, AreThemeSpecificsEqual);
diff --git a/chrome/browser/themes/theme_syncable_service_unittest.cc b/chrome/browser/themes/theme_syncable_service_unittest.cc
index 6d391c0e..0bcdfc9 100644
--- a/chrome/browser/themes/theme_syncable_service_unittest.cc
+++ b/chrome/browser/themes/theme_syncable_service_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
+#include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/time/time.h"
 #include "base/values.h"
@@ -149,7 +150,7 @@
 
 std::unique_ptr<KeyedService> BuildMockThemeService(
     content::BrowserContext* profile) {
-  return base::WrapUnique(new FakeThemeService);
+  return std::make_unique<FakeThemeService>();
 }
 
 scoped_refptr<extensions::Extension> MakeThemeExtension(
@@ -175,7 +176,8 @@
 
 }  // namespace
 
-class ThemeSyncableServiceTest : public testing::Test {
+class ThemeSyncableServiceTest : public testing::Test,
+                                 public ThemeSyncableService::Observer {
  protected:
   ThemeSyncableServiceTest() : fake_theme_service_(nullptr) {}
 
@@ -186,11 +188,13 @@
     // considered syncable.
     extension_test_util::SetGalleryUpdateURL(GURL(kCustomThemeUrl));
 
-    profile_.reset(new TestingProfile);
+    profile_ = std::make_unique<TestingProfile>();
     fake_theme_service_ = BuildForProfile(profile_.get());
-    theme_sync_service_.reset(new ThemeSyncableService(profile_.get(),
-                                                       fake_theme_service_));
-    fake_change_processor_.reset(new syncer::FakeSyncChangeProcessor);
+    theme_sync_service_ = std::make_unique<ThemeSyncableService>(
+        profile_.get(), fake_theme_service_);
+    theme_sync_service_->AddObserver(this);
+    fake_change_processor_ =
+        std::make_unique<syncer::FakeSyncChangeProcessor>();
     SetUpExtension();
   }
 
@@ -250,6 +254,17 @@
     return list;
   }
 
+  void OnThemeSyncStarted(ThemeSyncableService::ThemeSyncState state) override {
+    state_ = state;
+  }
+
+  bool HasThemeSyncStarted() { return state_ != base::nullopt; }
+
+  bool HasThemeSyncTriggeredExtensionInstallation() {
+    return state_ && *state_ == ThemeSyncableService::ThemeSyncState::
+                                    kWaitingForExtensionInstallation;
+  }
+
   // Needed for setting up extension service.
   content::BrowserTaskEnvironment task_environment_;
 
@@ -263,6 +278,7 @@
   scoped_refptr<extensions::Extension> theme_extension_;
   std::unique_ptr<ThemeSyncableService> theme_sync_service_;
   std::unique_ptr<syncer::FakeSyncChangeProcessor> fake_change_processor_;
+  base::Optional<ThemeSyncableService::ThemeSyncState> state_;
 };
 
 class PolicyInstalledThemeTest : public ThemeSyncableServiceTest {
@@ -361,6 +377,7 @@
                   fake_change_processor_.get())),
           std::unique_ptr<syncer::SyncErrorFactory>(
               new syncer::SyncErrorFactoryMock()));
+  EXPECT_TRUE(HasThemeSyncStarted());
   EXPECT_FALSE(error.has_value()) << error.value().message();
   EXPECT_FALSE(fake_theme_service_->UsingDefaultTheme());
   EXPECT_EQ(fake_theme_service_->theme_extension(), theme_extension_.get());
@@ -380,6 +397,7 @@
                   fake_change_processor_.get())),
           std::unique_ptr<syncer::SyncErrorFactory>(
               new syncer::SyncErrorFactoryMock()));
+  EXPECT_TRUE(HasThemeSyncStarted());
   EXPECT_FALSE(error.has_value()) << error.value().message();
   EXPECT_FALSE(fake_theme_service_->UsingSystemTheme());
   EXPECT_EQ(fake_theme_service_->theme_extension(), theme_extension_.get());
@@ -390,7 +408,7 @@
   theme_specifics.set_use_custom_theme(true);
   theme_specifics.set_custom_theme_id(theme_extension_->id());
   theme_specifics.set_custom_theme_name(kCustomThemeName);
-  theme_specifics.set_custom_theme_name(kCustomThemeUrl);
+  theme_specifics.set_custom_theme_update_url(kCustomThemeUrl);
 
   // Set up theme service to use default theme.
   fake_theme_service_->UseDefaultTheme();
@@ -402,10 +420,41 @@
                   fake_change_processor_.get())),
           std::unique_ptr<syncer::SyncErrorFactory>(
               new syncer::SyncErrorFactoryMock()));
+  EXPECT_TRUE(HasThemeSyncStarted());
+  EXPECT_FALSE(HasThemeSyncTriggeredExtensionInstallation());
   EXPECT_FALSE(error.has_value()) << error.value().message();
   EXPECT_EQ(fake_theme_service_->theme_extension(), theme_extension_.get());
 }
 
+TEST_F(ThemeSyncableServiceTest, SetCurrentThemeCustomTheme_Extension_Install) {
+  sync_pb::ThemeSpecifics theme_specifics;
+  theme_specifics.set_use_custom_theme(true);
+  // Use an arbitrary id (such an extension is not installed, yet).
+  theme_specifics.set_custom_theme_id("fake_extension_id");
+  theme_specifics.set_custom_theme_name(kCustomThemeName);
+  theme_specifics.set_custom_theme_update_url(kCustomThemeUrl);
+
+  // Set up theme service to use default theme.
+  fake_theme_service_->UseDefaultTheme();
+  base::Optional<syncer::ModelError> error =
+      theme_sync_service_->MergeDataAndStartSyncing(
+          syncer::THEMES, MakeThemeDataList(theme_specifics),
+          std::unique_ptr<syncer::SyncChangeProcessor>(
+              new syncer::SyncChangeProcessorWrapperForTest(
+                  fake_change_processor_.get())),
+          std::unique_ptr<syncer::SyncErrorFactory>(
+              new syncer::SyncErrorFactoryMock()));
+  EXPECT_TRUE(HasThemeSyncStarted());
+  EXPECT_FALSE(error.has_value()) << error.value().message();
+  // The theme is not installed yet and thus, the default theme is still used.
+  EXPECT_TRUE(fake_theme_service_->UsingDefaultTheme());
+  EXPECT_TRUE(HasThemeSyncTriggeredExtensionInstallation());
+  EXPECT_TRUE(extensions::ExtensionSystem::Get(profile_.get())
+                  ->extension_service()
+                  ->pending_extension_manager()
+                  ->HasPendingExtensionFromSync());
+}
+
 TEST_F(ThemeSyncableServiceTest, SetCurrentThemeCustomTheme_Autogenerated) {
   sync_pb::ThemeSpecifics theme_specifics;
   theme_specifics.set_use_custom_theme(false);
@@ -422,6 +471,7 @@
                   fake_change_processor_.get())),
           std::unique_ptr<syncer::SyncErrorFactory>(
               new syncer::SyncErrorFactoryMock()));
+  EXPECT_TRUE(HasThemeSyncStarted());
   EXPECT_FALSE(error.has_value()) << error.value().message();
   EXPECT_EQ(fake_theme_service_->GetAutogeneratedThemeColor(),
             SkColorSetRGB(0, 0, 100));
@@ -439,6 +489,7 @@
                   fake_change_processor_.get())),
           std::unique_ptr<syncer::SyncErrorFactory>(
               new syncer::SyncErrorFactoryMock()));
+  EXPECT_TRUE(HasThemeSyncStarted());
   EXPECT_FALSE(error.has_value()) << error.value().message();
   EXPECT_FALSE(fake_theme_service_->is_dirty());
 }
@@ -557,7 +608,7 @@
   theme_specifics.set_use_custom_theme(true);
   theme_specifics.set_custom_theme_id(theme_extension_->id());
   theme_specifics.set_custom_theme_name(kCustomThemeName);
-  theme_specifics.set_custom_theme_name(kCustomThemeUrl);
+  theme_specifics.set_custom_theme_update_url(kCustomThemeUrl);
   sync_pb::EntitySpecifics entity_specifics;
   entity_specifics.mutable_theme()->CopyFrom(theme_specifics);
   syncer::SyncChangeList change_list;
@@ -782,7 +833,7 @@
   theme_specifics.set_use_custom_theme(true);
   theme_specifics.set_custom_theme_id(theme_extension_->id());
   theme_specifics.set_custom_theme_name(kCustomThemeName);
-  theme_specifics.set_custom_theme_name(kCustomThemeUrl);
+  theme_specifics.set_custom_theme_update_url(kCustomThemeUrl);
   theme_specifics.set_use_system_theme_by_default(true);
   base::Optional<syncer::ModelError> error =
       theme_sync_service_->MergeDataAndStartSyncing(
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e52c0f2..87fa30fa 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3153,6 +3153,15 @@
       "cocoa/scoped_menu_bar_lock.mm",
       "cocoa/screentime/fake_webpage_controller.h",
       "cocoa/screentime/fake_webpage_controller.mm",
+      "cocoa/screentime/history_bridge.h",
+      "cocoa/screentime/history_bridge.mm",
+      "cocoa/screentime/history_bridge_factory.h",
+      "cocoa/screentime/history_bridge_factory.mm",
+      "cocoa/screentime/history_deleter.h",
+      "cocoa/screentime/history_deleter_impl.h",
+      "cocoa/screentime/history_deleter_impl.mm",
+      "cocoa/screentime/screentime_features.cc",
+      "cocoa/screentime/screentime_features.h",
       "cocoa/screentime/tab_helper.h",
       "cocoa/screentime/tab_helper.mm",
       "cocoa/screentime/webpage_controller.h",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 44581e7..8a40bcc 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2780,6 +2780,8 @@
       <message name="IDS_NTP_DISCOVER_OFF_BRANDED" desc="Title in the feed header when the feed is turned off and the default search engine is not Google. Please use the branded term for Discover, as listed under Product Names in the Google Glossary Manager (TC ID 1799975766543019278).">
         Discover by Google - off
       </message>
+      <message name="IDS_NTP_FOR_YOU" desc="Title in the feed header for interest-based feed.">For you</message>
+      <message name="IDS_NTP_FOLLOWING" desc="Title in the feed header for user-customized following feed.">Following</message>
       <message name="IDS_NTP_FEED_MENU_IPH" desc="In-product help that points at the menu icon for the news feed on Chrome's new tab page. This string instructs the user to open the menu for settings that let them control the content that appears on the feed.">
         Control your stories and activity here
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_NTP_FOLLOWING.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_NTP_FOLLOWING.png.sha1
new file mode 100644
index 0000000..2b8c670
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_NTP_FOLLOWING.png.sha1
@@ -0,0 +1 @@
+5fc7dcd9c16dde08f56c98710f8e4c25d1c99325
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_NTP_FOR_YOU.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_NTP_FOR_YOU.png.sha1
new file mode 100644
index 0000000..8e366535
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_NTP_FOR_YOU.png.sha1
@@ -0,0 +1 @@
+9823089ad27ce259b55f3942ddd89254f132a8a8
\ No newline at end of file
diff --git a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
index 86aaecd..bcb88b8 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
@@ -630,6 +630,9 @@
 
 void ArcAppListPrefs::SetResizeLockState(const std::string& app_id,
                                          arc::mojom::ArcResizeLockState state) {
+  if (!ash::features::IsArcResizeLockEnabled())
+    return;
+
   if (!IsRegistered(app_id)) {
     VLOG(2) << "Request to set ret resize lock for non-registered app:"
             << app_id << ".";
diff --git a/chrome/browser/ui/apps/app_info_dialog.h b/chrome/browser/ui/apps/app_info_dialog.h
index 7f07bc8..f386e33 100644
--- a/chrome/browser/ui/apps/app_info_dialog.h
+++ b/chrome/browser/ui/apps/app_info_dialog.h
@@ -10,10 +10,6 @@
 #include "base/callback_forward.h"
 #include "build/chromeos_buildflags.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ui/gfx/native_widget_types.h"
-#endif
-
 class Profile;
 
 namespace content {
@@ -24,10 +20,6 @@
 class Extension;
 }
 
-namespace gfx {
-class Rect;
-}
-
 // TODO(tsergeant): Move these methods into a class
 // Returns true if the app info dialog is available on the current platform.
 bool CanPlatformShowAppInfoDialog();
@@ -35,15 +27,6 @@
 // Returns true if the app info dialog is available for an app.
 bool CanShowAppInfoDialog(Profile* profile, const std::string& extension_id);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-// Shows the chrome app information as a frameless window for the given |app|
-// and |profile| at the given |app_info_bounds|.
-void ShowAppInfoInAppList(gfx::NativeWindow parent,
-                          const gfx::Rect& app_info_bounds,
-                          Profile* profile,
-                          const extensions::Extension* app);
-#endif
-
 // Shows the chrome app information in a native dialog box.
 void ShowAppInfoInNativeDialog(content::WebContents* web_contents,
                                Profile* profile,
diff --git a/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc b/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
index 1908384..2036d501 100644
--- a/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_timers_browsertest.cc
@@ -256,13 +256,7 @@
 
   // Start a timer for five minutes.
   tester()->SendTextQuery("Set a timer for 5 minutes");
-  tester()->ExpectAnyOfTheseTextResponses({
-      "Alright, 5 min. Starting… now.",
-      "OK, 5 min. And we're starting… now.",
-      "OK, 5 min. Starting… now.",
-      "Sure, 5 min. And that's starting… now.",
-      "Sure, 5 min. Starting now.",
-  });
+  tester()->ExpectTextResponse("5 min.");
 
   // Tap status area widget (to show notifications in the Message Center).
   TapOnAndWait(FindStatusAreaWidget());
diff --git a/chrome/browser/ui/autofill/payments/offer_notification_infobar_controller_impl_browsertest.cc b/chrome/browser/ui/autofill/payments/offer_notification_infobar_controller_impl_browsertest.cc
new file mode 100644
index 0000000..6ba7dce
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/offer_notification_infobar_controller_impl_browsertest.cc
@@ -0,0 +1,168 @@
+// 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 "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/infobars/infobar_service.h"
+#include "chrome/browser/ui/autofill/payments/offer_notification_infobar_controller_impl.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.h"
+#include "components/infobars/core/infobar.h"
+#include "components/infobars/core/infobar_delegate.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace autofill {
+
+const char kHostName[] = "example.com";
+const char kOfferDetailsURL[] = "http://pay.google.com";
+
+class OfferNotificationInfoBarControllerImplBrowserTest
+    : public AndroidBrowserTest {
+ public:
+  OfferNotificationInfoBarControllerImplBrowserTest() = default;
+  ~OfferNotificationInfoBarControllerImplBrowserTest() override = default;
+
+  void SetUp() override {
+    AndroidBrowserTest::SetUp();
+    card_ = test::GetCreditCard();
+  }
+
+  infobars::InfoBar* GetInfoBar() {
+    InfoBarService* infobar_service = InfoBarService::FromWebContents(
+        chrome_test_utils::GetActiveWebContents(this));
+    for (size_t i = 0; i < infobar_service->infobar_count(); ++i) {
+      infobars::InfoBar* infobar = infobar_service->infobar_at(i);
+      if (infobar->delegate()->GetIdentifier() ==
+          infobars::InfoBarDelegate::
+              AUTOFILL_OFFER_NOTIFICATION_INFOBAR_DELEGATE) {
+        return infobar;
+      }
+    }
+    return nullptr;
+  }
+
+  AutofillOfferNotificationInfoBarDelegateMobile* GetInfoBarDelegate(
+      infobars::InfoBar* infobar) {
+    return static_cast<AutofillOfferNotificationInfoBarDelegateMobile*>(
+        infobar->delegate());
+  }
+
+  void ShowOfferNotificationInfoBar(const std::vector<GURL>& origins) {
+    offer_notification_infobar_controller_->ShowIfNecessary(
+        origins, GURL(kOfferDetailsURL), &card_);
+  }
+
+  void VerifyInfoBarShownCount(int count) {
+    histogram_tester_.ExpectTotalCount(
+        "Autofill.OfferNotificationInfoBarOffer.CardLinkedOffer", count);
+  }
+
+  void VerifyInfoBarResultMetric(
+      AutofillMetrics::OfferNotificationInfoBarResultMetric metric,
+      int count) {
+    histogram_tester_.ExpectBucketCount(
+        "Autofill.OfferNotificationInfoBarResult.CardLinkedOffer", metric,
+        count);
+  }
+
+  content::WebContents* GetWebContents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  GURL GetInitialUrl() {
+    return embedded_test_server()->GetURL(kHostName, "/empty.html");
+  }
+
+  // AndroidBrowserTest
+  void SetUpOnMainThread() override {
+    offer_notification_infobar_controller_ =
+        std::make_unique<OfferNotificationInfoBarControllerImpl>(
+            GetWebContents());
+    host_resolver()->AddRule("*", "127.0.0.1");
+    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+    ASSERT_TRUE(embedded_test_server()->Start());
+    ASSERT_TRUE(content::NavigateToURL(GetWebContents(), GetInitialUrl()));
+  }
+
+ private:
+  std::unique_ptr<OfferNotificationInfoBarControllerImpl>
+      offer_notification_infobar_controller_;
+  // CreditCard that is linked to the offer displayed in the offer notification
+  // infobar.
+  CreditCard card_;
+  base::HistogramTester histogram_tester_;
+};
+
+IN_PROC_BROWSER_TEST_F(OfferNotificationInfoBarControllerImplBrowserTest,
+                       ShowInfobarOnlyOncePerDomain) {
+  ShowOfferNotificationInfoBar({GetInitialUrl().GetOrigin()});
+  // Verify that the infobar was shown and logged.
+  infobars::InfoBar* infobar = GetInfoBar();
+  ASSERT_TRUE(infobar);
+  VerifyInfoBarShownCount(1);
+  // Remove the infobar without any action.
+  infobar->RemoveSelf();
+
+  // Navigate to a different URL within the same domain and try to show the
+  // infobar.
+  GURL secondURL =
+      embedded_test_server()->GetURL(kHostName, "/simple_page.html");
+  ASSERT_TRUE(content::NavigateToURL(GetWebContents(), secondURL));
+  ShowOfferNotificationInfoBar({secondURL.GetOrigin()});
+
+  // Verify that the infobar was not shown again because it has already been
+  // shown for this domain.
+  ASSERT_FALSE(GetInfoBar());
+  VerifyInfoBarShownCount(1);
+
+  // Verify that the previous infobar was closed without user interaction.
+  VerifyInfoBarResultMetric(
+      AutofillMetrics::OfferNotificationInfoBarResultMetric::
+          OFFER_NOTIFICATION_INFOBAR_IGNORED,
+      1);
+}
+
+IN_PROC_BROWSER_TEST_F(OfferNotificationInfoBarControllerImplBrowserTest,
+                       ShowInfobarAndAccept) {
+  ShowOfferNotificationInfoBar({GetInitialUrl().GetOrigin()});
+  // Verify that the infobar was shown.
+  infobars::InfoBar* infobar = GetInfoBar();
+  ASSERT_TRUE(infobar);
+
+  // Accept and close the infobar.
+  GetInfoBarDelegate(infobar)->Accept();
+  infobar->RemoveSelf();
+
+  // Verify histogram counts.
+  VerifyInfoBarShownCount(1);
+  VerifyInfoBarResultMetric(
+      AutofillMetrics::OfferNotificationInfoBarResultMetric::
+          OFFER_NOTIFICATION_INFOBAR_ACKNOWLEDGED,
+      1);
+}
+
+IN_PROC_BROWSER_TEST_F(OfferNotificationInfoBarControllerImplBrowserTest,
+                       ShowInfobarAndClose) {
+  ShowOfferNotificationInfoBar({GetInitialUrl().GetOrigin()});
+  // Verify that the infobar was shown.
+  infobars::InfoBar* infobar = GetInfoBar();
+  ASSERT_TRUE(infobar);
+
+  // Dismiss and close the infobar.
+  GetInfoBarDelegate(infobar)->InfoBarDismissed();
+  infobar->RemoveSelf();
+
+  // Verify histogram counts.
+  VerifyInfoBarShownCount(1);
+  VerifyInfoBarResultMetric(
+      AutofillMetrics::OfferNotificationInfoBarResultMetric::
+          OFFER_NOTIFICATION_INFOBAR_CLOSED,
+      1);
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/cocoa/screentime/README.md b/chrome/browser/ui/cocoa/screentime/README.md
index c918773..bdcebc6 100644
--- a/chrome/browser/ui/cocoa/screentime/README.md
+++ b/chrome/browser/ui/cocoa/screentime/README.md
@@ -1,3 +1,5 @@
+# Screen Time
+
 This directory contains the integration between Chromium and the macOS
 ScreenTime system, which is a digital wellbeing tool allowing users to restrict
 their own use of apps and websites by category.
@@ -15,9 +17,16 @@
 [TabHelper](../../../../../docs/tab_helpers.md) that binds an
 STWebpageController to a WebContents.
 
+There is also a key private class, called `screentime::HistoryBridge`, which
+connects a
+[HistoryService](../../../../../components/history/core/browser/history_service.h)
+to the ScreenTime history deletion controller. HistoryBridge is a profile-keyed
+service, so one exists for each Profile.
+
 ## Testing
 
 So that tests can avoid depending on the real ScreenTime system,
-STWebpageController is wrapped by a C++ class called
-screentime::WebpageController, which has a testing fake called
-screentime::FakeWebpageController.
+`STWebpageController` is wrapped by a C++ class called
+`screentime::WebpageController`, which has a testing fake called
+`screentime::FakeWebpageController`, and `STWebHistory` is wrapped by a C++ class
+called `screentime::HistoryDeleter`.
diff --git a/chrome/browser/ui/cocoa/screentime/history_bridge.h b/chrome/browser/ui/cocoa/screentime/history_bridge.h
new file mode 100644
index 0000000..28ee57e
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/history_bridge.h
@@ -0,0 +1,47 @@
+// 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_COCOA_SCREENTIME_HISTORY_BRIDGE_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_HISTORY_BRIDGE_H_
+
+#include "base/scoped_observation.h"
+#include "components/history/core/browser/history_service.h"
+#include "components/history/core/browser/history_service_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace screentime {
+
+class HistoryDeleter;
+
+// A HistoryBridge connects a HistoryService to a HistoryDeleter, which wraps
+// the system ScreenTime backend. HistoryBridge is responsible for observing
+// deletions of part or all of the history in a HistoryService and deleting the
+// corresponding history from ScreenTime. It passes these to the provided
+// HistoryDeleter, which proxies to the system API (when in production use on
+// macOS 11) or to a test fake.
+class HistoryBridge : public KeyedService,
+                      public history::HistoryServiceObserver {
+ public:
+  HistoryBridge(history::HistoryService* history_service,
+                std::unique_ptr<HistoryDeleter> deleter);
+  HistoryBridge(const HistoryBridge& other) = delete;
+  HistoryBridge& operator=(const HistoryBridge& other) = delete;
+  ~HistoryBridge() override;
+
+  // history::HistoryServiceObserver:
+  void OnURLsDeleted(history::HistoryService* history_service,
+                     const history::DeletionInfo& deletion_info) override;
+  void HistoryServiceBeingDeleted(
+      history::HistoryService* history_service) override;
+
+ private:
+  std::unique_ptr<HistoryDeleter> deleter_;
+  base::ScopedObservation<history::HistoryService,
+                          history::HistoryServiceObserver>
+      history_service_observer_{this};
+};
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_HISTORY_BRIDGE_H_
diff --git a/chrome/browser/ui/cocoa/screentime/history_bridge.mm b/chrome/browser/ui/cocoa/screentime/history_bridge.mm
new file mode 100644
index 0000000..18cb8bf
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/history_bridge.mm
@@ -0,0 +1,56 @@
+// 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/ui/cocoa/screentime/history_bridge.h"
+
+#import <ScreenTime/ScreenTime.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/cocoa/screentime/history_deleter.h"
+
+namespace screentime {
+
+HistoryBridge::HistoryBridge(history::HistoryService* service,
+                             std::unique_ptr<HistoryDeleter> deleter)
+    : deleter_(std::move(deleter)) {
+  history_service_observer_.Observe(service);
+}
+HistoryBridge::~HistoryBridge() = default;
+
+void HistoryBridge::OnURLsDeleted(history::HistoryService* service,
+                                  const history::DeletionInfo& deletion_info) {
+  if (deletion_info.IsAllHistory()) {
+    deleter_->DeleteAllHistory();
+  } else if (deletion_info.time_range().IsValid()) {
+    if (deletion_info.restrict_urls().has_value()) {
+      // Awkward: the ScreenTime API has no way to express "delete history for
+      // this URL within this time range", only "delete all history for this
+      // URL" and "delete all history within this time range". Here, we err on
+      // side of deleting the specific URLs for all time, rather than deleting
+      // all URLs within the given time.
+      for (const auto& url : *deletion_info.restrict_urls())
+        deleter_->DeleteHistoryForURL(url);
+    } else {
+      deleter_->DeleteHistoryDuringInterval(
+          std::make_pair(deletion_info.time_range().begin(),
+                         deletion_info.time_range().end()));
+    }
+  } else {
+    // If the time range isn't valid at all, this is a URL delete, which has no
+    // time bounds.
+    for (const auto& row : deletion_info.deleted_rows())
+      deleter_->DeleteHistoryForURL(row.url());
+  }
+}
+
+void HistoryBridge::HistoryServiceBeingDeleted(
+    history::HistoryService* history_service) {
+  DCHECK(history_service_observer_.IsObservingSource(history_service));
+  history_service_observer_.Reset();
+}
+
+}  // namespace screentime
diff --git a/chrome/browser/ui/cocoa/screentime/history_bridge_factory.h b/chrome/browser/ui/cocoa/screentime/history_bridge_factory.h
new file mode 100644
index 0000000..d42577e
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/history_bridge_factory.h
@@ -0,0 +1,36 @@
+// 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_COCOA_SCREENTIME_HISTORY_BRIDGE_FACTORY_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_HISTORY_BRIDGE_FACTORY_H_
+
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace screentime {
+
+// A BrowserContextKeyedServiceFactory that is responsible for creating a
+// HistoryBridge instance for each loaded Profile. The HistoryBridge instance is
+// created when the Profile is initially created, so there's no explicit
+// creation step.
+class HistoryBridgeFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  HistoryBridgeFactory();
+  ~HistoryBridgeFactory() override;
+
+  HistoryBridgeFactory(const HistoryBridgeFactory&) = delete;
+  HistoryBridgeFactory& operator=(const HistoryBridgeFactory&) = delete;
+
+  static HistoryBridgeFactory* GetInstance();
+  static bool IsEnabled();
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+  bool ServiceIsCreatedWithBrowserContext() const override;
+  bool ServiceIsNULLWhileTesting() const override;
+};
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_HISTORY_BRIDGE_FACTORY_H_
diff --git a/chrome/browser/ui/cocoa/screentime/history_bridge_factory.mm b/chrome/browser/ui/cocoa/screentime/history_bridge_factory.mm
new file mode 100644
index 0000000..962cabe9
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/history_bridge_factory.mm
@@ -0,0 +1,58 @@
+// 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/ui/cocoa/screentime/history_bridge_factory.h"
+
+#include "base/no_destructor.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/cocoa/screentime/history_bridge.h"
+#include "chrome/browser/ui/cocoa/screentime/history_deleter_impl.h"
+#include "chrome/browser/ui/cocoa/screentime/screentime_features.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+namespace screentime {
+
+// static
+HistoryBridgeFactory* HistoryBridgeFactory::GetInstance() {
+  static base::NoDestructor<HistoryBridgeFactory> factory;
+  return factory.get();
+}
+
+HistoryBridgeFactory::HistoryBridgeFactory()
+    : BrowserContextKeyedServiceFactory(
+          "screentime::HistoryBridge",
+          BrowserContextDependencyManager::GetInstance()) {}
+HistoryBridgeFactory::~HistoryBridgeFactory() = default;
+
+// static
+bool HistoryBridgeFactory::IsEnabled() {
+  return base::FeatureList::IsEnabled(kScreenTime);
+}
+
+KeyedService* HistoryBridgeFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  auto* profile = Profile::FromBrowserContext(context);
+  auto* service = HistoryServiceFactory::GetForProfile(
+      profile, ServiceAccessType::IMPLICIT_ACCESS);
+
+  auto deleter = HistoryDeleterImpl::Create();
+
+  return new HistoryBridge(service, std::move(deleter));
+}
+
+bool HistoryBridgeFactory::ServiceIsCreatedWithBrowserContext() const {
+  return true;
+}
+
+bool HistoryBridgeFactory::ServiceIsNULLWhileTesting() const {
+  // Never create a HistoryBridge for a test context. They will always end up
+  // backed by a real HistoryDeleterImpl, which will try to talk to the system
+  // ScreenTime service, which will either make the test very slow or introduce
+  // flake. Tests need to explicitly opt into having real ScreenTime when they
+  // want it.
+  return true;
+}
+
+}  // namespace screentime
diff --git a/chrome/browser/ui/cocoa/screentime/history_bridge_unittest.cc b/chrome/browser/ui/cocoa/screentime/history_bridge_unittest.cc
new file mode 100644
index 0000000..81d1720
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/history_bridge_unittest.cc
@@ -0,0 +1,139 @@
+// 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/ui/cocoa/screentime/history_bridge.h"
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/cocoa/screentime/history_deleter.h"
+#include "components/history/core/browser/history_database_params.h"
+#include "components/history/core/browser/history_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace screentime {
+
+namespace {
+
+class TestHistoryDeleter : public HistoryDeleter {
+ public:
+  ~TestHistoryDeleter() override {}
+
+  bool deleted_all() const { return deleted_all_; }
+  base::Optional<TimeInterval> deleted_interval() const {
+    return deleted_interval_;
+  }
+  const std::set<GURL>& deleted_urls() const { return deleted_urls_; }
+
+  void WaitForDelete() { wait_loop_.Run(); }
+
+  // HistoryDeleter:
+  void DeleteAllHistory() override {
+    deleted_all_ = true;
+    wait_loop_.Quit();
+  }
+  void DeleteHistoryDuringInterval(const TimeInterval& interval) override {
+    deleted_interval_ = interval;
+    wait_loop_.Quit();
+  }
+  void DeleteHistoryForURL(const GURL& url) override {
+    deleted_urls_.insert(url);
+    wait_loop_.Quit();
+  }
+
+ private:
+  bool deleted_all_ = false;
+  base::Optional<TimeInterval> deleted_interval_ = base::nullopt;
+  std::set<GURL> deleted_urls_;
+  base::RunLoop wait_loop_;
+};
+
+}  // namespace
+
+class HistoryBridgeTest : public ::testing::Test {
+ public:
+  HistoryBridgeTest() {
+    service_ = std::make_unique<history::HistoryService>();
+    auto deleter = std::make_unique<TestHistoryDeleter>();
+    deleter_ = deleter.get();
+    bridge_ =
+        std::make_unique<HistoryBridge>(service_.get(), std::move(deleter));
+
+    CHECK(history_dir_.CreateUniqueTempDir());
+    service_->Init(
+        history::HistoryDatabaseParams(history_dir_.GetPath(), 0, 0));
+    service_->SetOnBackendDestroyTask(history_teardown_loop_.QuitClosure());
+  }
+
+  void TearDown() override {
+    service()->Shutdown();
+    history_teardown_loop_.Run();
+  }
+
+  history::HistoryService* service() { return service_.get(); }
+  TestHistoryDeleter* deleter() { return deleter_; }
+
+  void AddPage(const GURL& url, base::Time time = base::Time::Now()) {
+    service()->AddPage(url, time, history::VisitSource::SOURCE_BROWSED);
+  }
+
+  void DeleteHistoryBetween(base::Time start, base::Time end) {
+    base::CancelableTaskTracker tracker;
+    base::RunLoop loop;
+    service()->ExpireHistoryBetween({}, start, end, true, loop.QuitClosure(),
+                                    &tracker);
+    loop.Run();
+  }
+
+  void DeleteAllHistory() { DeleteHistoryBetween(base::Time(), base::Time()); }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  base::ScopedTempDir history_dir_;
+  std::unique_ptr<history::HistoryService> service_;
+  TestHistoryDeleter* deleter_;
+  std::unique_ptr<HistoryBridge> bridge_;
+  base::RunLoop history_teardown_loop_;
+};
+
+TEST_F(HistoryBridgeTest, DeleteAll) {
+  AddPage(GURL("https://www.chromium.org/a"));
+  AddPage(GURL("https://www.chromium.org/b"));
+
+  DeleteAllHistory();
+  deleter()->WaitForDelete();
+  EXPECT_TRUE(deleter()->deleted_all());
+}
+
+TEST_F(HistoryBridgeTest, DeleteURLs) {
+  const GURL kTestUrlA("https://www.chromium.org/a");
+  const base::Time now = base::Time::Now();
+  AddPage(kTestUrlA, now - base::TimeDelta::FromSeconds(2));
+  AddPage(GURL("https://www.chromium.org/b"),
+          now - base::TimeDelta::FromSeconds(1));
+
+  service()->DeleteURLs({kTestUrlA});
+  deleter()->WaitForDelete();
+  EXPECT_FALSE(deleter()->deleted_all());
+  EXPECT_EQ(deleter()->deleted_urls(), std::set<GURL>{kTestUrlA});
+}
+
+TEST_F(HistoryBridgeTest, DeleteTimeInterval) {
+  const base::Time now = base::Time::Now();
+  AddPage(GURL("https://www.chromium.org/a"),
+          now - base::TimeDelta::FromSeconds(2));
+  AddPage(GURL("https://www.chromium.org/b"),
+          now - base::TimeDelta::FromSeconds(1));
+
+  DeleteHistoryBetween(now - base::TimeDelta::FromSeconds(3), now);
+  deleter()->WaitForDelete();
+  EXPECT_FALSE(deleter()->deleted_all());
+  EXPECT_EQ(deleter()->deleted_interval()->first,
+            now - base::TimeDelta::FromSeconds(3));
+  EXPECT_EQ(deleter()->deleted_interval()->second, now);
+}
+
+}  // namespace screentime
diff --git a/chrome/browser/ui/cocoa/screentime/history_deleter.h b/chrome/browser/ui/cocoa/screentime/history_deleter.h
new file mode 100644
index 0000000..86afee9
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/history_deleter.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_COCOA_SCREENTIME_HISTORY_DELETER_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_HISTORY_DELETER_H_
+
+#include <utility>
+
+#include "base/time/time.h"
+
+class GURL;
+
+namespace screentime {
+
+// The HistoryDeleter interface wraps the actual implementation of deleting
+// items from the system ScreenTime history store, so the interface here exactly
+// mirrors the ScreenTime STWebHistory interface:
+//   https://developer.apple.com/documentation/screentime/stwebhistory
+class HistoryDeleter {
+ public:
+  using TimeInterval = std::pair<base::Time, base::Time>;
+
+  virtual ~HistoryDeleter() = default;
+
+  virtual void DeleteAllHistory() = 0;
+  virtual void DeleteHistoryDuringInterval(const TimeInterval& interval) = 0;
+  virtual void DeleteHistoryForURL(const GURL& url) = 0;
+};
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_HISTORY_DELETER_H_
diff --git a/chrome/browser/ui/cocoa/screentime/history_deleter_impl.h b/chrome/browser/ui/cocoa/screentime/history_deleter_impl.h
new file mode 100644
index 0000000..67616e77
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/history_deleter_impl.h
@@ -0,0 +1,39 @@
+// 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_COCOA_SCREENTIME_HISTORY_DELETER_IMPL_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_HISTORY_DELETER_IMPL_H_
+
+#include "base/mac/scoped_nsobject.h"
+#include "chrome/browser/ui/cocoa/screentime/history_deleter.h"
+
+@class STWebHistory;
+
+namespace screentime {
+
+// Implementation of HistoryDeleter that mutates the actual system history
+// store.
+class HistoryDeleterImpl : public HistoryDeleter {
+ public:
+  ~HistoryDeleterImpl() override;
+
+  // The constructor is private so that the actual construction of this object
+  // can be guarded by availability checks inside this class rather than in
+  // callers. This method may return nullptr if called on a system where
+  // ScreenTime is not available!
+  static std::unique_ptr<HistoryDeleterImpl> Create();
+
+  void DeleteAllHistory() override;
+  void DeleteHistoryDuringInterval(const TimeInterval& interval) override;
+  void DeleteHistoryForURL(const GURL& url) override;
+
+ private:
+  HistoryDeleterImpl();
+
+  base::scoped_nsobject<STWebHistory> platform_deleter_;
+};
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_HISTORY_DELETER_IMPL_H_
diff --git a/chrome/browser/ui/cocoa/screentime/history_deleter_impl.mm b/chrome/browser/ui/cocoa/screentime/history_deleter_impl.mm
new file mode 100644
index 0000000..6e591e72
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/history_deleter_impl.mm
@@ -0,0 +1,65 @@
+// 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/ui/cocoa/screentime/history_deleter_impl.h"
+
+#include "base/mac/foundation_util.h"
+#include "base/memory/ptr_util.h"
+#include "base/notreached.h"
+#include "base/strings/sys_string_conversions.h"
+#include "net/base/mac/url_conversions.h"
+
+#import <ScreenTime/ScreenTime.h>
+
+namespace screentime {
+
+HistoryDeleterImpl::~HistoryDeleterImpl() = default;
+
+std::unique_ptr<HistoryDeleterImpl> HistoryDeleterImpl::Create() {
+  if (@available(macOS 11.0, *))
+    return base::WrapUnique(new HistoryDeleterImpl);
+  return nullptr;
+}
+
+void HistoryDeleterImpl::DeleteAllHistory() {
+  if (@available(macOS 11.0, *)) {
+    [platform_deleter_ deleteAllHistory];
+  } else {
+    NOTIMPLEMENTED();
+  }
+}
+
+void HistoryDeleterImpl::DeleteHistoryDuringInterval(
+    const TimeInterval& interval) {
+  if (@available(macOS 11.0, *)) {
+    base::scoped_nsobject<NSDateInterval> nsinterval([[NSDateInterval alloc]
+        initWithStartDate:interval.first.ToNSDate()
+                  endDate:interval.second.ToNSDate()]);
+    [platform_deleter_ deleteHistoryDuringInterval:nsinterval.get()];
+  } else {
+    NOTIMPLEMENTED();
+  }
+}
+
+void HistoryDeleterImpl::DeleteHistoryForURL(const GURL& url) {
+  if (@available(macOS 11.0, *)) {
+    [platform_deleter_ deleteHistoryForURL:net::NSURLWithGURL(url)];
+  } else {
+    NOTIMPLEMENTED();
+  }
+}
+
+HistoryDeleterImpl::HistoryDeleterImpl() {
+  if (@available(macOS 11.0, *)) {
+    NSError* error = nil;
+    NSString* bundle_id = base::SysUTF8ToNSString(base::mac::BaseBundleID());
+    platform_deleter_.reset(
+        [[STWebHistory alloc] initWithBundleIdentifier:bundle_id error:&error]);
+    DCHECK(!error);
+  } else {
+    NOTIMPLEMENTED();
+  }
+}
+
+}  // namespace screentime
diff --git a/chrome/browser/ui/cocoa/screentime/screentime_features.cc b/chrome/browser/ui/cocoa/screentime/screentime_features.cc
new file mode 100644
index 0000000..6dba5e5
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/screentime_features.cc
@@ -0,0 +1,18 @@
+// 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/ui/cocoa/screentime/screentime_features.h"
+
+namespace screentime {
+
+const base::Feature kScreenTime{
+    "ScreenTime",
+    base::FEATURE_DISABLED_BY_DEFAULT,
+};
+
+bool IsScreenTimeEnabled() {
+  return base::FeatureList::IsEnabled(kScreenTime);
+}
+
+}  // namespace screentime
diff --git a/chrome/browser/ui/cocoa/screentime/screentime_features.h b/chrome/browser/ui/cocoa/screentime/screentime_features.h
new file mode 100644
index 0000000..ab1cc91
--- /dev/null
+++ b/chrome/browser/ui/cocoa/screentime/screentime_features.h
@@ -0,0 +1,18 @@
+// 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_COCOA_SCREENTIME_SCREENTIME_FEATURES_H_
+#define CHROME_BROWSER_UI_COCOA_SCREENTIME_SCREENTIME_FEATURES_H_
+
+#include "base/feature_list.h"
+
+namespace screentime {
+
+extern const base::Feature kScreenTime;
+
+bool IsScreenTimeEnabled();
+
+}  // namespace screentime
+
+#endif  // CHROME_BROWSER_UI_COCOA_SCREENTIME_SCREENTIME_FEATURES_H_
diff --git a/chrome/browser/ui/cocoa/screentime/screentime_tab_helper_unittest.cc b/chrome/browser/ui/cocoa/screentime/screentime_tab_helper_unittest.cc
index c00d2c03..e3ad27a 100644
--- a/chrome/browser/ui/cocoa/screentime/screentime_tab_helper_unittest.cc
+++ b/chrome/browser/ui/cocoa/screentime/screentime_tab_helper_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/cocoa/screentime/tab_helper.h"
 
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/cocoa/screentime/screentime_features.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -15,7 +16,7 @@
 
 TEST(ScreentimeTabHelperTest, NeverUsedInIncognito) {
   base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(TabHelper::kScreenTime);
+  features.InitAndEnableFeature(kScreenTime);
 
   content::BrowserTaskEnvironment task_environment;
 
diff --git a/chrome/browser/ui/cocoa/screentime/tab_helper.h b/chrome/browser/ui/cocoa/screentime/tab_helper.h
index 2432a83..cbab425 100644
--- a/chrome/browser/ui/cocoa/screentime/tab_helper.h
+++ b/chrome/browser/ui/cocoa/screentime/tab_helper.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "base/feature_list.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
@@ -27,8 +26,6 @@
 class TabHelper : public content::WebContentsObserver,
                   public content::WebContentsUserData<TabHelper> {
  public:
-  static const base::Feature kScreenTime;
-
   static void UseFakeWebpageControllerForTesting();
   static bool IsScreentimeEnabledForProfile(Profile* profile);
 
diff --git a/chrome/browser/ui/cocoa/screentime/tab_helper.mm b/chrome/browser/ui/cocoa/screentime/tab_helper.mm
index 38e4263a..50bd8ffd 100644
--- a/chrome/browser/ui/cocoa/screentime/tab_helper.mm
+++ b/chrome/browser/ui/cocoa/screentime/tab_helper.mm
@@ -7,6 +7,7 @@
 #include "base/command_line.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/cocoa/screentime/fake_webpage_controller.h"
+#include "chrome/browser/ui/cocoa/screentime/screentime_features.h"
 #include "chrome/browser/ui/cocoa/screentime/tab_helper.h"
 #include "chrome/browser/ui/cocoa/screentime/webpage_controller.h"
 #include "chrome/browser/ui/cocoa/screentime/webpage_controller_impl.h"
@@ -15,11 +16,6 @@
 
 namespace screentime {
 
-const base::Feature TabHelper::kScreenTime{
-    "ScreenTime",
-    base::FEATURE_DISABLED_BY_DEFAULT,
-};
-
 namespace {
 bool g_use_fake_webpage_controller = false;
 }
@@ -33,7 +29,7 @@
 bool TabHelper::IsScreentimeEnabledForProfile(Profile* profile) {
   if (profile->IsOffTheRecord())
     return false;
-  return base::FeatureList::IsEnabled(kScreenTime);
+  return IsScreenTimeEnabled();
 }
 
 TabHelper::TabHelper(content::WebContents* contents)
diff --git a/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.mm b/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.mm
index 0cbab2c..647e174 100644
--- a/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.mm
+++ b/chrome/browser/ui/cocoa/screentime/webpage_controller_impl.mm
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/cocoa/screentime/webpage_controller_impl.h"
 
+#include "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
 #include "net/base/mac/url_conversions.h"
 
 #include <ScreenTime/ScreenTime.h>
@@ -51,7 +53,12 @@
 WebpageControllerImpl::WebpageControllerImpl(
     const BlockedChangedCallback& blocked_changed_callback)
     : platform_controller_([[STWebpageController alloc] init]),
-      blocked_changed_callback_(blocked_changed_callback) {}
+      blocked_changed_callback_(blocked_changed_callback) {
+  NSError* error = nil;
+  NSString* bundle_id = base::SysUTF8ToNSString(base::mac::BaseBundleID());
+  [platform_controller_ setBundleIdentifier:bundle_id error:&error];
+}
+
 WebpageControllerImpl::~WebpageControllerImpl() = default;
 
 NSView* WebpageControllerImpl::GetView() {
diff --git a/chrome/browser/ui/global_media_controls/cast_media_session_controller.cc b/chrome/browser/ui/global_media_controls/cast_media_session_controller.cc
index e216e6de0..e16117b 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_session_controller.cc
+++ b/chrome/browser/ui/global_media_controls/cast_media_session_controller.cc
@@ -64,6 +64,9 @@
     case media_session::mojom::MediaSessionAction::kEnterPictureInPicture:
     case media_session::mojom::MediaSessionAction::kExitPictureInPicture:
     case media_session::mojom::MediaSessionAction::kSwitchAudioDevice:
+    case media_session::mojom::MediaSessionAction::kToggleMicrophone:
+    case media_session::mojom::MediaSessionAction::kToggleCamera:
+    case media_session::mojom::MediaSessionAction::kHangUp:
       NOTREACHED();
       return;
   }
diff --git a/chrome/browser/ui/tabs/tab_style.cc b/chrome/browser/ui/tabs/tab_style.cc
index ccf846d7..8ffa725 100644
--- a/chrome/browser/ui/tabs/tab_style.cc
+++ b/chrome/browser/ui/tabs/tab_style.cc
@@ -32,7 +32,7 @@
 
 // static
 int TabStyle::GetPinnedWidth() {
-  constexpr int kTabPinnedContentWidth = 23;
+  constexpr int kTabPinnedContentWidth = 24;
   return kTabPinnedContentWidth + GetContentsHorizontalInsetSize() * 2;
 }
 
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc
index 842823d..180167b 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc
@@ -74,59 +74,6 @@
 };
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-
-// The contents view for an App List Dialog, which covers the entire app list
-// and adds a close button.
-class AppListDialogContainer : public views::DialogDelegateView {
- public:
-  METADATA_HEADER(AppListDialogContainer);
-  explicit AppListDialogContainer(std::unique_ptr<views::View> dialog_body) {
-    SetButtons(ui::DIALOG_BUTTON_NONE);
-    SetModalType(kModalType);
-    SetBackground(std::make_unique<AppListOverlayBackground>());
-    dialog_body_ = AddChildView(std::move(dialog_body));
-    close_button_ = AddChildView(
-        views::BubbleFrameView::CreateCloseButton(base::BindRepeating(
-            [](AppListDialogContainer* container) {
-              container->GetWidget()->CloseWithReason(
-                  views::Widget::ClosedReason::kCloseButtonClicked);
-            },
-            base::Unretained(this))));
-  }
-  AppListDialogContainer(const AppListDialogContainer&) = delete;
-  AppListDialogContainer& operator=(const AppListDialogContainer&) = delete;
-  ~AppListDialogContainer() override = default;
-
- private:
-  // views::View:
-  void Layout() override {
-    // Margin of the close button from the top right-hand corner of the dialog.
-    const int kCloseButtonDialogMargin = 10;
-
-    close_button_->SetPosition(
-        gfx::Point(width() - close_button_->width() - kCloseButtonDialogMargin,
-                   kCloseButtonDialogMargin));
-
-    dialog_body_->SetBoundsRect(GetContentsBounds());
-    views::DialogDelegateView::Layout();
-  }
-
-  // views::WidgetDelegate:
-  std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView(
-      views::Widget* widget) override {
-    return std::make_unique<views::NativeFrameView>(widget);
-  }
-
-  views::View* dialog_body_;
-  views::Button* close_button_;
-};
-
-BEGIN_METADATA(AppListDialogContainer, views::DialogDelegateView)
-END_METADATA
-
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 // A BubbleFrameView that allows its client view to extend all the way to the
 // top of the dialog, overlapping the BubbleFrameView's close button. This
 // allows dialog content to appear closer to the top, in place of a title.
@@ -204,13 +151,6 @@
 
 }  // namespace
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-views::DialogDelegateView* CreateAppListContainerForView(
-    std::unique_ptr<views::View> view) {
-  return new AppListDialogContainer(std::move(view));
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 views::DialogDelegateView* CreateDialogContainerForView(
     std::unique_ptr<views::View> view,
     const gfx::Size& size,
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.h
index c5f039a..8f1fd96 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.h
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/callback_forward.h"
-#include "build/chromeos_buildflags.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace views {
@@ -16,15 +15,6 @@
 class View;
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-
-// Creates a new dialog containing |view| that can be displayed inside the app
-// list, covering the entire app list and adding a close button.
-views::DialogDelegateView* CreateAppListContainerForView(
-    std::unique_ptr<views::View> view);
-
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 // Creates a new native dialog of the given |size| containing |view| with a
 // close button and draggable titlebar.
 views::DialogDelegateView* CreateDialogContainerForView(
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
index 0398bbe..53df512 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc
@@ -95,21 +95,6 @@
   return CanPlatformShowAppInfoDialog();
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-void ShowAppInfoInAppList(gfx::NativeWindow parent,
-                          const gfx::Rect& app_info_bounds,
-                          Profile* profile,
-                          const extensions::Extension* app) {
-  views::DialogDelegate* dialog = CreateAppListContainerForView(
-      std::make_unique<AppInfoDialog>(profile, app));
-
-  views::Widget* dialog_widget =
-      constrained_window::CreateBrowserModalDialogViews(dialog, parent);
-  dialog_widget->SetBounds(app_info_bounds);
-  dialog_widget->Show();
-}
-#endif
-
 void ShowAppInfoInNativeDialog(content::WebContents* web_contents,
                                Profile* profile,
                                const extensions::Extension* app,
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.cc b/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.cc
index 5931708f..8e81252 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.cc
@@ -117,6 +117,9 @@
           views::BoxLayout::Orientation::kVertical));
   device_entry_views_container_->SetVisible(false);
 
+  if (entry_point_ == GlobalMediaControlsEntryPoint::kPresentation) {
+    ShowDevices();
+  }
   SetBackground(views::CreateSolidBackground(background_color_));
   // Set the size of this view
   SetPreferredSize(kExpandButtonStripSize);
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h b/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h
index 5698596..a47c1aa 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h
+++ b/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view.h
@@ -70,6 +70,8 @@
   FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
                            ExpandButtonOpensEntryContainer);
   FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
+                           DeviceEntryContainerVisibility);
+  FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
                            AudioDeviceButtonClickNotifiesContainer);
   FRIEND_TEST_ALL_PREFIXES(MediaNotificationDeviceSelectorViewTest,
                            CurrentAudioDeviceHighlighted);
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_unittest.cc b/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_unittest.cc
index a3d2964a..353ab8df 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_unittest.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_notification_device_selector_view_unittest.cc
@@ -180,11 +180,12 @@
       MockMediaNotificationDeviceSelectorViewDelegate* delegate,
       std::unique_ptr<MockCastDialogController> controller =
           std::make_unique<MockCastDialogController>(),
-      const std::string& device_description = "1") {
+      const std::string& device_description = "1",
+      GlobalMediaControlsEntryPoint entry_point =
+          GlobalMediaControlsEntryPoint::kToolbarIcon) {
     return std::make_unique<MediaNotificationDeviceSelectorView>(
         delegate, std::move(controller), device_description,
-        gfx::kPlaceholderColor, gfx::kPlaceholderColor,
-        GlobalMediaControlsEntryPoint::kToolbarIcon);
+        gfx::kPlaceholderColor, gfx::kPlaceholderColor, entry_point);
   }
 
   std::unique_ptr<MediaNotificationDeviceSelectorView> view_;
@@ -222,6 +223,24 @@
 }
 
 TEST_F(MediaNotificationDeviceSelectorViewTest,
+       DeviceEntryContainerVisibility) {
+  MockMediaNotificationDeviceSelectorViewDelegate delegate;
+  AddAudioDevices(delegate);
+
+  // The device entry container should be collapsed if the media dialog is
+  // opened from the toolbar or Chrome OS system tray.
+  view_ = CreateDeviceSelectorView(&delegate);
+  EXPECT_FALSE(view_->device_entry_views_container_->GetVisible());
+
+  // The device entry container should be expanded if the media dialog is opened
+  // for a presentation request.
+  view_ = CreateDeviceSelectorView(
+      &delegate, std::make_unique<MockCastDialogController>(), "1",
+      GlobalMediaControlsEntryPoint::kPresentation);
+  EXPECT_TRUE(view_->device_entry_views_container_->GetVisible());
+}
+
+TEST_F(MediaNotificationDeviceSelectorViewTest,
        AudioDeviceButtonClickNotifiesContainer) {
   // When buttons are clicked the media notification delegate should be
   // informed.
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.cc b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.cc
index 43ec85e8..fc15b68b 100644
--- a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.cc
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.cc
@@ -124,11 +124,17 @@
   }
 }
 
-void ProfileCustomizationBubbleSyncController::OnThemeSyncStarted() {
-  // Skip the bubble (and not use the default color) if the user got a
-  // non-default value from sync.
-  if (!theme_service_->UsingDefaultTheme() &&
-      !theme_service_->UsingSystemTheme()) {
+void ProfileCustomizationBubbleSyncController::OnThemeSyncStarted(
+    ThemeSyncableService::ThemeSyncState state) {
+  // Skip the bubble (and not use the default color) if the user got a custom
+  // value from sync (that is either already applied as a custom theme or
+  // triggered a custom theme installation).
+  const bool using_custom_theme = !theme_service_->UsingDefaultTheme() &&
+                                  !theme_service_->UsingSystemTheme();
+  const bool installing_custom_theme =
+      state ==
+      ThemeSyncableService::ThemeSyncState::kWaitingForExtensionInstallation;
+  if (using_custom_theme || installing_custom_theme) {
     SkipBubble();
     return;
   }
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h
index fe8619f1..f4d69b49 100644
--- a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller.h
@@ -67,7 +67,7 @@
   void OnStateChanged(syncer::SyncService* sync) override;
 
   // ThemeSyncableService::Observer:
-  void OnThemeSyncStarted() override;
+  void OnThemeSyncStarted(ThemeSyncableService::ThemeSyncState state) override;
 
   // This function may delete the object.
   void Init();
diff --git a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller_unittest.cc b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller_unittest.cc
index 7fcce49b..b3f38e2 100644
--- a/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller_unittest.cc
+++ b/chrome/browser/ui/views/profiles/profile_customization_bubble_sync_controller_unittest.cc
@@ -83,8 +83,12 @@
     fake_theme_service_.DoSetTheme(nullptr, false);
   }
 
-  void NotifyOnSyncStarted() {
-    theme_syncable_service_.NotifyOnSyncStartedForTesting();
+  void NotifyOnSyncStarted(bool waiting_for_extension_installation = false) {
+    theme_syncable_service_.NotifyOnSyncStartedForTesting(
+        waiting_for_extension_installation
+            ? ThemeSyncableService::ThemeSyncState::
+                  kWaitingForExtensionInstallation
+            : ThemeSyncableService::ThemeSyncState::kApplied);
   }
 
  protected:
@@ -141,6 +145,16 @@
 }
 
 TEST_F(ProfileCustomizationBubbleSyncControllerTest,
+       ShouldNotShowWhenSyncGetsCustomThemeToInstall) {
+  base::MockCallback<base::OnceCallback<void(bool)>> show_bubble;
+  EXPECT_CALL(show_bubble, Run(false));
+
+  ApplyColorAndShowBubbleWhenNoValueSynced(show_bubble.Get());
+  NotifyOnSyncStarted(/*waiting_for_extension_installation=*/true);
+  histogram_tester_.ExpectTotalCount("Profile.SyncCustomizationBubbleDelay", 1);
+}
+
+TEST_F(ProfileCustomizationBubbleSyncControllerTest,
        ShouldNotShowWhenSyncHasCustomPasshrase) {
   base::MockCallback<base::OnceCallback<void(bool)>> show_bubble;
   EXPECT_CALL(show_bubble, Run(false));
diff --git a/chrome/browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc b/chrome/browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc
index dee5043..feec153 100644
--- a/chrome/browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc
+++ b/chrome/browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc
@@ -245,6 +245,10 @@
       *accel = ui::Accelerator(ui::VKEY_SPACE,
                                ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN);
       return true;
+#elif BUILDFLAG(IS_CHROMEOS_ASH)
+      *accel = ui::Accelerator(ui::VKEY_SPACE,
+                               ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
+      return true;
 #else
       return false;
 #endif
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
index 1468cd9..c0e27981 100644
--- a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
@@ -40,6 +40,8 @@
     {"simDetectPageErrorTitle", IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_ERROR_TITLE},
     {"simDetectPageErrorMessage",
      IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_ERROR_MESSAGE},
+    {"simDetectPageFinalErrorMessage",
+     IDS_CELLULAR_SETUP_SIM_DETECT_PAGE_FINAL_ERROR_MESSAGE},
     {"provisioningPageLoadingTitle",
      IDS_CELLULAR_SETUP_PROVISIONING_PAGE_LOADING_TITLE},
     {"provisioningPageActiveTitle",
diff --git a/chrome/browser/ui/webui/chromeos/login/l10n_util.cc b/chrome/browser/ui/webui/chromeos/login/l10n_util.cc
index 7e4d4911..c7e06678 100644
--- a/chrome/browser/ui/webui/chromeos/login/l10n_util.cc
+++ b/chrome/browser/ui/webui/chromeos/login/l10n_util.cc
@@ -460,7 +460,7 @@
   input_method::InputMethodDescriptors descriptors =
       manager->GetXkbIMEAsInputMethodDescriptor();
   std::unique_ptr<base::ListValue> languages_list(GetLanguageList(
-      descriptors, l10n_util::GetAvailableLocales(),
+      descriptors, l10n_util::GetLocalesWithStrings(),
       most_relevant_language_codes
           ? *most_relevant_language_codes
           : StartupCustomizationDocument::GetInstance()->configured_locales(),
diff --git a/chrome/browser/ui/webui/chromeos/login/l10n_util_unittest.cc b/chrome/browser/ui/webui/chromeos/login/l10n_util_unittest.cc
index 313a878..fe337fe 100644
--- a/chrome/browser/ui/webui/chromeos/login/l10n_util_unittest.cc
+++ b/chrome/browser/ui/webui/chromeos/login/l10n_util_unittest.cc
@@ -31,8 +31,8 @@
     ASSERT_TRUE(list.GetDictionary(i, &dict));
     std::string code;
     ASSERT_TRUE(dict->GetString("code", &code));
-    EXPECT_NE("is", code)
-        << "Icelandic is an example language which has input method "
+    EXPECT_NE("ga", code)
+        << "Irish is an example language which has input method "
         << "but can't use it as UI language.";
   }
 }
@@ -86,7 +86,7 @@
   input_manager_->AddInputMethod("xkb:us::eng", "us", "en-US");
   input_manager_->AddInputMethod("xkb:fr::fra", "fr", "fr");
   input_manager_->AddInputMethod("xkb:be::fra", "be", "fr");
-  input_manager_->AddInputMethod("xkb:is::ice", "is", "is");
+  input_manager_->AddInputMethod("xkb:ie::ga", "ga", "ga");
 }
 
 void L10nUtilTest::SetInputMethods2() {
@@ -94,7 +94,7 @@
   input_manager_->AddInputMethod("xkb:ch:fr:fra", "ch(fr)", "fr");
   input_manager_->AddInputMethod("xkb:ch::ger", "ch", "de");
   input_manager_->AddInputMethod("xkb:it::ita", "it", "it");
-  input_manager_->AddInputMethod("xkb:is::ice", "is", "is");
+  input_manager_->AddInputMethod("xkb:ie::ga", "ga", "ga");
 }
 
 TEST_F(L10nUtilTest, GetUILanguageList) {
@@ -148,7 +148,7 @@
 const char kStartupManifest[] =
     "{\n"
     "  \"version\": \"1.0\",\n"
-    "  \"initial_locale\" : \"fr,en-US,de,is,it\",\n"
+    "  \"initial_locale\" : \"fr,en-US,de,ga,it\",\n"
     "  \"initial_timezone\" : \"Europe/Zurich\",\n"
     "  \"keyboard_layout\" : \"xkb:ch:fr:fra\",\n"
     "  \"registration_url\" : \"http://www.google.com\",\n"
@@ -169,7 +169,7 @@
 
   VerifyOnlyUILanguages(*list);
 
-  // (4 languages (except Icelandic) + divider) = 5 + all other languages
+  // (4 languages (except Irish) + divider) = 5 + all other languages
   ASSERT_LE(5u, list->GetSize());
 
   VerifyLanguageCode(*list, 0, "fr");
diff --git a/chrome/browser/ui/webui/feed_internals/feedv2_internals_page_handler.cc b/chrome/browser/ui/webui/feed_internals/feedv2_internals_page_handler.cc
index 1c695bf..14d881f 100644
--- a/chrome/browser/ui/webui/feed_internals/feedv2_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/feed_internals/feedv2_internals_page_handler.cc
@@ -13,8 +13,8 @@
 #include "components/feed/core/common/pref_names.h"
 #include "components/feed/core/proto/v2/ui.pb.h"
 #include "components/feed/core/shared_prefs/pref_names.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/public/feed_service.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
 #include "components/feed/core/v2/public/types.h"
 #include "components/feed/feed_feature_list.h"
 #include "components/offline_pages/core/prefetch/prefetch_prefs.h"
diff --git a/chrome/browser/ui/webui/feed_internals/feedv2_internals_page_handler.h b/chrome/browser/ui/webui/feed_internals/feedv2_internals_page_handler.h
index 0f252f19..7de929e 100644
--- a/chrome/browser/ui/webui/feed_internals/feedv2_internals_page_handler.h
+++ b/chrome/browser/ui/webui/feed_internals/feedv2_internals_page_handler.h
@@ -17,7 +17,7 @@
 class PrefService;
 namespace feed {
 class FeedService;
-class FeedStreamApi;
+class FeedApi;
 }  // namespace feed
 
 // Concrete implementation of feed_internals::mojom::PageHandler.
@@ -56,7 +56,7 @@
   mojo::Receiver<feed_internals::mojom::PageHandler> receiver_;
 
   // Services that provide the data and functionality.
-  feed::FeedStreamApi* feed_stream_;
+  feed::FeedApi* feed_stream_;
   PrefService* pref_service_;
 
   base::WeakPtrFactory<FeedV2InternalsPageHandler> weak_ptr_factory_{this};
diff --git a/chrome/browser/ui/webui/feedback/feedback_dialog.cc b/chrome/browser/ui/webui/feedback/feedback_dialog.cc
index 6462d864..8e7e396c8 100644
--- a/chrome/browser/ui/webui/feedback/feedback_dialog.cc
+++ b/chrome/browser/ui/webui/feedback/feedback_dialog.cc
@@ -7,9 +7,12 @@
 #include <memory>
 #include <utility>
 
+#include "base/json/json_writer.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/grit/generated_resources.h"
 #include "content/public/browser/browser_thread.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/views/widget/widget.h"
 #include "url/gurl.h"
 
@@ -24,13 +27,15 @@
 
 }  // namespace
 
+using extensions::api::feedback_private::FEEDBACK_FLOW_SADTABCRASH;
 using extensions::api::feedback_private::FeedbackInfo;
 
 // static
 FeedbackDialog* FeedbackDialog::current_instance_ = nullptr;
 
 // static
-void FeedbackDialog::CreateOrShow(const FeedbackInfo& info) {
+void FeedbackDialog::CreateOrShow(
+    const extensions::api::feedback_private::FeedbackInfo& info) {
   // Focus the window hosting the dialog that has already been created.
   if (current_instance_) {
     DCHECK(current_instance_->widget_);
@@ -40,12 +45,15 @@
 
   current_instance_ = new FeedbackDialog(info);
   gfx::NativeWindow window = chrome::ShowWebDialog(
-      nullptr /* parent */, ProfileManager::GetActiveUserProfile(),
-      current_instance_);
+      nullptr, ProfileManager::GetActiveUserProfile(), current_instance_);
   current_instance_->widget_ = views::Widget::GetWidgetForNativeWindow(window);
 }
 
-FeedbackDialog::FeedbackDialog(const FeedbackInfo& info) : widget_(nullptr) {
+FeedbackDialog::FeedbackDialog(
+    const extensions::api::feedback_private::FeedbackInfo& info)
+    : feedbackInfo_(info.ToValue()),
+      feedbackFlow_(info.flow),
+      widget_(nullptr) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   set_can_resize(false);
   set_can_minimize(true);
@@ -61,22 +69,29 @@
 }
 
 std::u16string FeedbackDialog::GetDialogTitle() const {
-  return std::u16string();
+  return l10n_util::GetStringUTF16(
+      (feedbackFlow_ == FEEDBACK_FLOW_SADTABCRASH)
+          ? IDS_FEEDBACK_REPORT_PAGE_TITLE_SAD_TAB_FLOW
+          : IDS_FEEDBACK_REPORT_PAGE_TITLE);
 }
 
 GURL FeedbackDialog::GetDialogContentURL() const {
   return GURL("chrome://feedback");
 }
 
-void FeedbackDialog::GetWebUIMessageHandlers(
-    std::vector<WebUIMessageHandler*>* handlers) const {}
-
 void FeedbackDialog::GetDialogSize(gfx::Size* size) const {
   size->SetSize(kDefaultWidth, kDefaultHeight);
 }
 
+void FeedbackDialog::GetWebUIMessageHandlers(
+    std::vector<WebUIMessageHandler*>* handlers) const {}
+
+// The feedbackInfo will be available to JS via
+// chrome.getVariableValue('dialogArguments')
 std::string FeedbackDialog::GetDialogArgs() const {
-  return std::string();
+  std::string data;
+  base::JSONWriter::Write(*feedbackInfo_, &data);
+  return data;
 }
 
 void FeedbackDialog::OnDialogClosed(const std::string& json_retval) {
diff --git a/chrome/browser/ui/webui/feedback/feedback_dialog.h b/chrome/browser/ui/webui/feedback/feedback_dialog.h
index a0384638..b034dbc 100644
--- a/chrome/browser/ui/webui/feedback/feedback_dialog.h
+++ b/chrome/browser/ui/webui/feedback/feedback_dialog.h
@@ -5,23 +5,17 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_FEEDBACK_FEEDBACK_DIALOG_H_
 #define CHROME_BROWSER_UI_WEBUI_FEEDBACK_FEEDBACK_DIALOG_H_
 
+#include <memory>
 #include <string>
 #include <vector>
 
+#include "extensions/common/api/feedback_private.h"
 #include "ui/web_dialogs/web_dialog_delegate.h"
 
 namespace views {
 class Widget;
 }
 
-namespace extensions {
-namespace api {
-namespace feedback_private {
-struct FeedbackInfo;
-}
-}  // namespace api
-}  // namespace extensions
-
 class FeedbackDialog : public ui::WebDialogDelegate {
  public:
   static void CreateOrShow(
@@ -39,10 +33,10 @@
   ui::ModalType GetDialogModalType() const override;
   std::u16string GetDialogTitle() const override;
   GURL GetDialogContentURL() const override;
-  void GetWebUIMessageHandlers(
-      std::vector<content::WebUIMessageHandler*>* handlers) const override;
   void GetDialogSize(gfx::Size* size) const override;
   std::string GetDialogArgs() const override;
+  void GetWebUIMessageHandlers(
+      std::vector<content::WebUIMessageHandler*>* handlers) const override;
   void OnDialogClosed(const std::string& json_retval) override;
   void OnCloseContents(content::WebContents* source,
                        bool* out_close_dialog) override;
@@ -50,6 +44,8 @@
   bool ShouldShowCloseButton() const override;
   ui::WebDialogDelegate::FrameKind GetWebDialogFrameKind() const override;
 
+  std::unique_ptr<base::DictionaryValue> feedbackInfo_;
+  extensions::api::feedback_private::FeedbackFlow feedbackFlow_;
   // Widget for the Feedback WebUI.
   views::Widget* widget_;
   static FeedbackDialog* current_instance_;
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index 7d256641..b863f320 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -226,7 +226,6 @@
       "chrome://add-supervision/*",
       "chrome://chrome-signin/*",
       "chrome://discards/*",
-      "chrome://hats/*",
       "chrome://mobilesetup/*",
       "chrome://oobe/*",
       "chrome://os-settings/*",
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index ea96052..b64008c 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -118,6 +118,16 @@
 // Boolean that is true when user feedback to Google is allowed.
 const char kUserFeedbackAllowed[] = "feedback_allowed";
 
+#if !defined(OS_ANDROID)
+// Used to store the value of the SerialAllowAllPortsForUrls policy.
+const char kManagedSerialAllowAllPortsForUrls[] =
+    "profile.managed.serial_allow_all_ports_for_urls";
+
+// Used to store the value of the SerialAllowUsbDevicesForUrls policy.
+const char kManagedSerialAllowUsbDevicesForUrls[] =
+    "profile.managed.serial_allow_usb_devices_for_urls";
+#endif  // !defined(OS_ANDROID)
+
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS) && BUILDFLAG(ENABLE_EXTENSIONS)
 // DictionaryValue that maps extension ids to the approved version of this
 // extension for a supervised user. Missing extensions are not approved.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 2556a59d7..a1f3d357 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -43,6 +43,10 @@
 extern const char kRestoreOnStartup[];
 extern const char kSessionExitedCleanly[];
 extern const char kSessionExitType[];
+#if !defined(OS_ANDROID)
+extern const char kManagedSerialAllowAllPortsForUrls[];
+extern const char kManagedSerialAllowUsbDevicesForUrls[];
+#endif  // !defined(OS_ANDROID)
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS) && BUILDFLAG(ENABLE_EXTENSIONS)
 extern const char kSupervisedUserApprovedExtensions[];
 #endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS) && BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index f3ec28a72..7eeb8570 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -363,8 +363,6 @@
     defined(OS_CHROMEOS)
 const char kChromeUIDiscardsHost[] = "discards";
 const char kChromeUIDiscardsURL[] = "chrome://discards/";
-const char kChromeUIHatsHost[] = "hats";
-const char kChromeUIHatsURL[] = "chrome://hats/";
 #endif
 
 #if !defined(OS_ANDROID)
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 09eb698..ef250e5 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -314,8 +314,6 @@
     defined(OS_CHROMEOS)
 extern const char kChromeUIDiscardsHost[];
 extern const char kChromeUIDiscardsURL[];
-extern const char kChromeUIHatsHost[];
-extern const char kChromeUIHatsURL[];
 #endif
 
 #if !defined(OS_ANDROID)
diff --git a/chrome/service/cloud_print/cloud_print_service_helpers_unittest.cc b/chrome/service/cloud_print/cloud_print_service_helpers_unittest.cc
index a7c8ded..4211b213 100644
--- a/chrome/service/cloud_print/cloud_print_service_helpers_unittest.cc
+++ b/chrome/service/cloud_print/cloud_print_service_helpers_unittest.cc
@@ -55,7 +55,7 @@
 
   std::string expected_list_string = base::StringPrintf(
       "chrome_version%ssystem_name%ssystem_version%stag1value1tag2value2",
-      chrome::GetVersionString().c_str(),
+      chrome::GetVersionString(chrome::WithExtendedStable(false)).c_str(),
       base::SysInfo::OperatingSystemName().c_str(),
       base::SysInfo::OperatingSystemVersion().c_str());
   EXPECT_EQ(base::MD5String(expected_list_string),
@@ -80,7 +80,7 @@
       "\r\n\r\n__cp__tag2=value2\r\n"
       "--test_mime_boundary\r\nContent-Disposition: form-data; name=\"tag\""
       "\r\n\r\n__cp__tagshash=%s\r\n",
-      chrome::GetVersionString().c_str(),
+      chrome::GetVersionString(chrome::WithExtendedStable(false)).c_str(),
       base::SysInfo::OperatingSystemName().c_str(),
       base::SysInfo::OperatingSystemVersion().c_str(),
       GetHashOfPrinterInfo(printer_info).c_str());
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ff95506..7ee32ad 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -589,6 +589,7 @@
       "../browser/ssl/crlset_browsertest.cc",
       "../browser/subresource_filter/subresource_filter_browser_test_harness.cc",
       "../browser/subresource_filter/subresource_filter_browser_test_harness.h",
+      "../browser/ui/autofill/payments/offer_notification_infobar_controller_impl_browsertest.cc",
       "android/browsertests_apk/android_browsertests_jni_onload.cc",
       "base/android/android_browser_test_browsertest_android.cc",
     ]
@@ -1098,6 +1099,7 @@
       "../browser/infobars/infobars_browsertest.cc",
       "../browser/installable/installable_manager_browsertest.cc",
       "../browser/invalidation/profile_invalidation_provider_factory_browsertest.cc",
+      "../browser/l10n_util_browsertest.cc",
       "../browser/lazyload/lazyload_browsertest.cc",
       "../browser/lifetime/browser_close_manager_browsertest.cc",
       "../browser/lifetime/browser_shutdown_browsertest.cc",
@@ -4625,6 +4627,7 @@
       "../browser/send_tab_to_self/send_tab_to_self_util_unittest.cc",
       "../browser/serial/serial_blocklist_unittest.cc",
       "../browser/serial/serial_chooser_context_unittest.cc",
+      "../browser/serial/serial_policy_allowed_ports_unittest.cc",
       "../browser/sessions/tab_restore_service_unittest.cc",
       "../browser/signin/signin_promo_unittest.cc",
       "../browser/speech/extension_api/extension_manifests_tts_unittest.cc",
@@ -5983,6 +5986,7 @@
       "../browser/ui/cocoa/profiles/profile_menu_controller_unittest.mm",
       "../browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_unittest.mm",
       "../browser/ui/cocoa/scoped_menu_bar_lock_unittest.mm",
+      "../browser/ui/cocoa/screentime/history_bridge_unittest.cc",
       "../browser/ui/cocoa/screentime/screentime_tab_helper_unittest.cc",
       "../browser/ui/cocoa/status_icons/status_icon_mac_unittest.mm",
       "../browser/ui/cocoa/tab_menu_bridge_unittest.mm",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
index 44bf645c..74344679 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
@@ -227,7 +227,7 @@
     public boolean waitForDeferredStartup() {
         CriteriaHelper.pollUiThread(() -> {
             getActivity().deferredStartupPostedForTesting();
-        }, ScalableTimeout.scaleTimeout(20000L), CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        }, 20000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         return DeferredStartupHandler.waitForDeferredStartupCompleteForTesting(
                 ScalableTimeout.scaleTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL));
     }
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 98dbc35..849320593 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -14,7 +14,7 @@
       "Defaults to empty if not specified."
     ],
     "official_only": "Whether this policy exists in official builds only. Defaults to |false| if not specified.",
-    "can_be_recommended": "Whether a recommended value may be set for the policy. Defaults to |false| if not specified.",
+    "can_be_recommended": "Whether a recommended value may be set for the policy. Defaults to |false| if not specified. Use |check_for_mandatory| and |check_for_recommended| if you want to provide separate test cases for mandatory/recommended. Otherwise, the same pref mapping is checked for both mandatory+recommended modes when enabled or only as mandatory if disabled.",
     "note": "If the policy affects any preferences, the following array should be specified with one entry per such preference.",
     "policy_pref_mapping_tests": [
       {
@@ -26,22 +26,12 @@
         "prefs": {
             "${pref}": {
               "value": "The value that |pref| should take on.",
-              "expect_default": "Whether or not the pref value should be the default one.",
+              "expect_default": "Whether or not the pref value should be the default one (i.e. unmanaged and user-modifiable). Defaults to false if not specified.",
               "location": "The location where the pref is registered, possible values are ['user_profile', 'signin_profile', 'local_state', 'cros_setting']. Defaults to 'user_profile' if not specified.",
-              "check_for_mandatory": "Should the preference be tested when a mandatory value is set for the policy? Defaults to |true| if not specified.",
-              "check_for_recommended": "Should the preference be tested when a recommended value is set for the policy? Defaults to |true| if not specified."
+              "check_for_mandatory": "Should the preference be tested when a mandatory value is set for the policy? Defaults to |true| if not specified. See |can_be_recommended|.",
+              "check_for_recommended": "Should the preference be tested when a recommended value is set for the policy? Defaults to |true| if not specified. See |can_be_recommended|."
             }
-          },
-        "indicator_tests": [
-          {
-            "pref": "The affected preference's name.",
-            "test_url": "The URL to navigate to in order to test the indicators. Defaults to |chrome://extensions-frame/| if not specified.",
-            "test_setup_js": "Any JavaScript that should be executed before testing the indicators. This should be specified only if an explicit user action must be simulated (e.g. clicking a button).",
-            "selector": "A CSS selector that locates all controlled setting indicators for |pref|. This is appended to the selector 'span.controlled-setting-indicator' and if not specified, defaults to '[pref=(the value of |pref|)', e.g. '[pref=homepage]'.",
-            "value": "The value that |pref| should take on. This must only be specified if |pref| has multiple controlled setting indicators, each corresponding to a specific value (e.g. indicators next to radio buttons).",
-            "readonly": "Whether setting the policy dictionary as recommended should cause |pref| to become read-only in the settings UI. This will be the case when the dictionary sets another policy that makes |pref| not applicable (e.g. setting 'homepage is NTP' makes the 'homepage URL' pref not applicable and read-only)."
           }
-        ]
       }
     ]
   },
@@ -216,14 +206,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "JavascriptEnabled": false },
-        "prefs": { "profile.managed_default_content_settings.javascript": {}},
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.javascript",
-            "selector": "[content-setting=javascript]",
-            "value": "block"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.javascript": {}}
       }
     ]
   },
@@ -849,13 +832,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "MetricsReportingEnabled": false },
-        "prefs": { "user_experience_metrics.reporting_enabled": { "location": "local_state" } },
-        "indicator_tests": [
-          {
-            "pref": "user_experience_metrics.reporting_enabled",
-            "selector": "#metrics-reporting-disabled-icon"
-          }
-        ]
+        "prefs": { "user_experience_metrics.reporting_enabled": { "location": "local_state" } }
       }
     ]
   },
@@ -1467,10 +1444,10 @@
 
   "DefaultDownloadDirectory": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "can_be_recommended": true,
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultDownloadDirectory": "${user_home}/test-downloads" },
-        "can_be_recommended": true,
         "prefs": {
           "download.default_directory": { "check_for_mandatory": false, "check_for_recommended": true },
           "savefile.default_directory": { "check_for_mandatory": false, "check_for_recommended": true }
@@ -1663,8 +1640,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "DeveloperToolsDisabled": true },
-        "prefs": { "devtools.availability": { "value": 2 }},
-        "indicator_tests": [{ "pref": "devtools.availability", "test_url": "chrome://extensions-frame/", "selector": "#dev-toggle-disabled-by-policy-indicator", "value": 2 }]
+        "prefs": { "devtools.availability": { "value": 2 }}
       }
     ]
   },
@@ -1674,8 +1650,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "DeveloperToolsAvailability": 2 },
-        "prefs": { "devtools.availability": { "value": 2 }},
-        "indicator_tests": [{ "pref": "devtools.availability", "test_url": "chrome://extensions-frame/", "selector": "#dev-toggle-disabled-by-policy-indicator", "value": 2 }]
+        "prefs": { "devtools.availability": { "value": 2 }}
       }
     ]
   },
@@ -1963,78 +1938,64 @@
 
   "DefaultCookiesSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultCookiesSetting": 1 },
-        "prefs": { "profile.managed_default_content_settings.cookies": { "value": 1 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.cookies", "selector": "[content-setting=cookies]", "value": 1 }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.cookies": { "value": 1 } }
       },
       {
         "policies": { "DefaultCookiesSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.cookies": { "value": 2 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.cookies", "selector": "[content-setting=cookies]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.cookies": { "value": 2 } }
       },
       {
         "policies": { "DefaultCookiesSetting": 3 },
-        "prefs": { "profile.managed_default_content_settings.cookies": { "value": 3 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.cookies", "selector": "[content-setting=cookies]", "value": "session_only" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.cookies": { "value": 3 } }
       }
     ]
   },
 
   "DefaultImagesSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultImagesSetting": 1 },
-        "prefs": { "profile.managed_default_content_settings.images": { "value": 1 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.images", "selector": "[content-setting=images]", "value": "allow" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.images": { "value": 1 } }
       },
       {
         "policies": { "DefaultImagesSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.images": { "value": 2 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.images", "selector": "[content-setting=images]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.images": { "value": 2 } }
       }
     ]
   },
 
   "DefaultInsecureContentSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultInsecureContentSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.insecure_content": { "value": 2 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.insecure_content", "selector": "[content-setting=mixed-script]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.insecure_content": { "value": 2 } }
       },
       {
         "policies": { "DefaultInsecureContentSetting": 3 },
-        "prefs": { "profile.managed_default_content_settings.insecure_content": { "value": 3 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.insecure_content", "selector": "[content-setting=mixed-script]", "value": "ask" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.insecure_content": { "value": 3 } }
       }
     ]
   },
 
   "DefaultJavaScriptSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultJavaScriptSetting": 1 },
-        "prefs": { "profile.managed_default_content_settings.javascript": { "value": 1 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.javascript", "selector": "[content-setting=javascript]", "value": "allow" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.javascript": { "value": 1 } }
       },
       {
         "policies": { "DefaultJavaScriptSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.javascript": { "value": 2 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.javascript", "selector": "[content-setting=javascript]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.javascript": { "value": 2 } }
       }
     ]
   },
@@ -2101,96 +2062,68 @@
 
   "DefaultPopupsSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultPopupsSetting": 1 },
-        "prefs": { "profile.managed_default_content_settings.popups": { "value": 1 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.popups", "selector": "[content-setting=popups]", "value": "allow" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.popups": { "value": 1 } }
       },
       {
         "policies": { "DefaultPopupsSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.popups": {} },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.popups","selector": "[content-setting=popups]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.popups": {} }
       }
     ]
   },
 
   "DefaultNotificationsSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultNotificationsSetting": 1 },
-        "prefs": { "profile.managed_default_content_settings.notifications": { "value": 1 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.notifications", "selector": "[content-setting=notifications]", "value": "allow" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.notifications": { "value": 1 } }
       },
       {
         "policies": { "DefaultNotificationsSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.notifications": { "value": 2 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.notifications", "selector": "[content-setting=notifications]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.notifications": { "value": 2 } }
       },
       {
         "policies": { "DefaultNotificationsSetting": 3 },
-        "prefs": { "profile.managed_default_content_settings.notifications": { "value": 3 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.notifications", "selector": "[content-setting=notifications]", "value": "ask" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.notifications": { "value": 3 } }
       }
     ]
   },
 
   "DefaultGeolocationSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultGeolocationSetting": 1 },
-        "prefs": { "profile.managed_default_content_settings.geolocation": { "value": 1 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.geolocation", "selector": "[content-setting=location]","value": "allow"}],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.geolocation": { "value": 1 } }
       },
       {
         "policies": { "DefaultGeolocationSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.geolocation": { "value": 2 } },
-        "indicator_tests": [{"pref": "profile.managed_default_content_settings.geolocation", "selector": "[content-setting=location]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.geolocation": { "value": 2 } }
       },
       {
         "policies": { "DefaultGeolocationSetting": 3 },
-        "prefs": { "profile.managed_default_content_settings.geolocation": { "value": 3 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.geolocation", "selector": "[content-setting=location]", "value": "ask" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.geolocation": { "value": 3 } }
       }
     ]
   },
 
   "DefaultMediaStreamSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultMediaStreamSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 2 } },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.media_stream",
-            "selector": "[content-setting=media-stream-mic],[content-setting=media-stream-camera]",
-            "value": "block"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 2 } }
       },
       {
         "policies": { "DefaultMediaStreamSetting": 3 },
-        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 3 } },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.media_stream",
-            "selector": "[content-setting=media-stream-mic],[content-setting=media-stream-camera]",
-            "value": "ask"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 3 } }
       }
     ]
   },
@@ -2200,36 +2133,15 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "AudioCaptureAllowed": false },
-        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 0, "expect_default": true } },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.media_stream",
-            "selector": "[content-setting=media-stream-mic]",
-            "value": "block"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 0, "expect_default": true } }
       },
       {
         "policies": { "DefaultMediaStreamSetting": 2, "AudioCaptureAllowed": true },
-        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 2 } },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.media_stream",
-            "selector": "[content-setting=media-stream-mic]",
-            "value": "block"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 2 } }
       },
       {
         "policies": { "DefaultMediaStreamSetting": 3, "AudioCaptureAllowed": true },
-        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 3 } },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.media_stream",
-            "selector": "[content-setting=media-stream-mic]",
-            "value": "ask"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 3 } }
       }
     ]
   },
@@ -2239,36 +2151,15 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "VideoCaptureAllowed": false },
-        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 0, "expect_default": true }},
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.media_stream",
-            "selector": "[content-setting=media-stream-mic]",
-            "value": "block"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 0, "expect_default": true }}
       },
       {
         "policies": { "DefaultMediaStreamSetting": 2, "VideoCaptureAllowed": true },
-        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 2 } },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.media_stream",
-            "selector": "[content-setting=media-stream-mic]",
-            "value": "block"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 2 } }
       },
       {
         "policies": { "DefaultMediaStreamSetting": 3, "VideoCaptureAllowed": true },
-        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 3 } },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.media_stream",
-            "selector": "[content-setting=media-stream-mic]",
-            "value": "ask"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.media_stream": { "value": 3 } }
       }
 
     ]
@@ -2276,18 +2167,15 @@
 
   "DefaultSensorsSetting": {
     "os": ["win", "linux", "mac"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultSensorsSetting": 1 },
-        "prefs": { "profile.managed_default_content_settings.sensors": { "value": 1 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.sensors", "selector": "[content-setting=sensors]", "value": "allow" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.sensors": { "value": 1 } }
       },
       {
         "policies": { "DefaultSensorsSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.sensors": { "value": 2 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.sensors", "selector": "[content-setting=sensors]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.sensors": { "value": 2 } }
       }
     ]
   },
@@ -2297,14 +2185,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "AudioCaptureAllowed": false },
-        "prefs": { "hardware.audio_capture_enabled": { "value": false } },
-        "indicator_tests": [
-          {
-            "pref": "hardware.audio_capture_enabled",
-            "selector": "[content-setting=media-stream-mic][value=block]",
-            "value": "block"
-          }
-        ]
+        "prefs": { "hardware.audio_capture_enabled": { "value": false } }
       }
     ]
   },
@@ -2314,8 +2195,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "AudioCaptureAllowedUrls": ["[*.]google.com"] },
-        "prefs": { "hardware.audio_capture_allowed_urls": {}},
-        "indicator_tests": [{ "pref": "hardware.audio_capture_allowed_urls", "selector": "[content-exception=media-stream-mic]" }]
+        "prefs": { "hardware.audio_capture_allowed_urls": {}}
       }
     ]
   },
@@ -2325,14 +2205,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "VideoCaptureAllowed": false },
-        "prefs": { "hardware.video_capture_enabled": { "value": false }},
-        "indicator_tests": [
-          {
-            "pref": "hardware.video_capture_enabled",
-            "selector": "[content-setting=media-stream-camera][value=block]",
-            "value": "block"
-          }
-        ]
+        "prefs": { "hardware.video_capture_enabled": { "value": false }}
       }
     ]
   },
@@ -2342,8 +2215,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "VideoCaptureAllowedUrls": ["[*.]google.com"] },
-        "prefs": { "hardware.video_capture_allowed_urls": {}},
-        "indicator_tests": [{ "pref": "hardware.video_capture_allowed_urls", "selector": "[content-exception=media-stream-camera]" }]
+        "prefs": { "hardware.video_capture_allowed_urls": {}}
       }
     ]
   },
@@ -2380,24 +2252,22 @@
 
   "DefaultSerialGuardSetting": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "DefaultSerialGuardSetting": 2 },
-        "prefs": { "profile.managed_default_content_settings.serial_guard": { "value": 2 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings_serial_ports", "selector": "[content-setting=serial-ports]", "value": "block" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.serial_guard": { "value": 2 } }
       },
       {
         "policies": { "DefaultSerialGuardSetting": 3 },
-        "prefs": { "profile.managed_default_content_settings.serial_guard": { "value": 3 } },
-        "indicator_tests": [{ "pref": "profile.managed_default_content_settings.serial_ports", "selector": "[content-setting=serial-ports]", "value": "ask" }],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_default_content_settings.serial_guard": { "value": 3 } }
       }
     ]
   },
 
   "AutoSelectCertificateForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -2405,176 +2275,112 @@
             "{'pattern':'https://example.com','filter':{'ISSUER':{'CN': 'issuer-name'}}}"
           ]
         },
-        "prefs": { "profile.managed_auto_select_certificate_for_urls": {}},
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_auto_select_certificate_for_urls": {}}
       }
     ]
   },
 
   "CookiesAllowedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "CookiesAllowedForUrls": ["[*.]google.com"] },
-        "prefs": { "profile.managed_cookies_allowed_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_cookies_allowed_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=cookies]').click();",
-            "selector": "[content-exception=cookies]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_cookies_allowed_for_urls": {} }
       }
     ]
   },
 
   "CookiesBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "CookiesBlockedForUrls": [ "[*.]google.com" ] },
-        "prefs": { "profile.managed_cookies_blocked_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_cookies_blocked_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=cookies]').click();",
-            "selector": "[content-exception=cookies]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_cookies_blocked_for_urls": {} }
       }
     ]
   },
 
   "CookiesSessionOnlyForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "CookiesSessionOnlyForUrls": ["[*.]google.com"] },
-        "prefs": { "profile.managed_cookies_sessiononly_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_cookies_sessiononly_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=cookies]').click();",
-            "selector": "[content-exception=cookies]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_cookies_sessiononly_for_urls": {} }
       }
     ]
   },
 
   "ImagesAllowedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "ImagesAllowedForUrls": ["[*.]google.com"] },
-        "prefs": { "profile.managed_images_allowed_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_images_allowed_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=images]').click();",
-            "selector": "[content-exception=images]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_images_allowed_for_urls": {} }
       }
     ]
   },
 
   "ImagesBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "ImagesBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_images_blocked_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_images_blocked_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=images]').click();",
-            "selector": "[content-exception=images]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_images_blocked_for_urls": {} }
       }
     ]
   },
 
   "InsecureContentAllowedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {
           "InsecureContentAllowedForUrls": ["[*.]google.com"]
         },
-        "prefs": { "profile.managed_insecure_content_allowed_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_insecure_content_allowed_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=mixed-script]').click();",
-            "selector": "[content-exception=mixed-script]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_insecure_content_allowed_for_urls": {} }
       }
     ]
   },
 
   "InsecureContentBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {
           "InsecureContentBlockedForUrls": ["[*.]google.com"]
         },
-        "prefs": { "profile.managed_insecure_content_blocked_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_insecure_content_blocked_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=mixed-script]').click();",
-            "selector": "[content-exception=mixed-script]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_insecure_content_blocked_for_urls": {} }
       }
     ]
   },
 
   "JavaScriptAllowedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {
           "JavaScriptAllowedForUrls": ["[*.]google.com"]
         },
-        "prefs": { "profile.managed_javascript_allowed_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_javascript_allowed_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=javascript]').click();",
-            "selector": "[content-exception=javascript]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_javascript_allowed_for_urls": {} }
       }
     ]
   },
 
   "JavaScriptBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "JavaScriptBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_javascript_blocked_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_javascript_blocked_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=javascript]').click();",
-            "selector": "[content-exception=javascript]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_javascript_blocked_for_urls": {} }
       }
     ]
   },
@@ -2597,108 +2403,66 @@
 
   "PopupsAllowedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "PopupsAllowedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_popups_allowed_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_popups_allowed_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=popups]').click();",
-            "selector": "[content-exception=popups]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_popups_allowed_for_urls": {} }
       }
     ]
   },
 
   "PopupsBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "PopupsBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_popups_blocked_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_popups_blocked_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=popups]').click();",
-            "selector": "[content-exception=popups]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_popups_blocked_for_urls": {} }
       }
     ]
   },
 
   "NotificationsAllowedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "NotificationsAllowedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_notifications_allowed_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_notifications_allowed_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=notifications]').click();",
-            "selector": "[content-exception=notifications]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_notifications_allowed_for_urls": {} }
       }
     ]
   },
 
   "NotificationsBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "NotificationsBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_notifications_blocked_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_notifications_blocked_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=notifications]').click();",
-            "selector": "[content-exception=notifications]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_notifications_blocked_for_urls": {} }
       }
     ]
   },
 
   "SensorsAllowedForUrls": {
     "os": ["win", "linux", "mac"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "SensorsAllowedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_sensors_allowed_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.sensors_allowed_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=sensors]').click();",
-            "selector": "[content-exception=sensors]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_sensors_allowed_for_urls": {} }
       }
     ]
   },
 
   "SensorsBlockedForUrls": {
     "os": ["win", "linux", "mac"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": { "SensorsBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_sensors_blocked_for_urls": {} },
-        "indicator_tests": [
-          {
-            "pref": "profile.sensors_blocked_for_urls",
-            "test_setup_js": "document.querySelector('button.exceptions-list-button[contentType=sensors]').click();",
-            "selector": "[content-exception=sensors]"
-          }
-        ],
-        "note": "TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_sensors_blocked_for_urls": {} }
       }
     ]
   },
@@ -2759,11 +2523,11 @@
 
   "WebUsbAskForUrls": {
     "os": ["win", "linux", "mac", "chromeos", "android"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {"WebUsbAskForUrls": ["[*.].com"]},
-        "prefs": { "profile.managed_web_usb_ask_for_urls": {}},
-        "note": "TODO(reillyg): Add indicator tests. TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_web_usb_ask_for_urls": {}}
       }
     ]
   },
@@ -2773,8 +2537,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": {"WebUsbBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_web_usb_blocked_for_urls": {} },
-        "note": "TODO(reillyg): Add indicator tests. TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_web_usb_blocked_for_urls": {} }
       }
     ]
   },
@@ -2784,19 +2547,47 @@
     "policy_pref_mapping_tests": [
       {
         "policies": {"SerialAskForUrls": ["[*.].com"]},
-        "prefs": { "profile.managed_serial_ask_for_urls": {}},
-        "note": "TODO(reillyg): Add indicator tests. TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_serial_ask_for_urls": {}}
       }
     ]
   },
 
   "SerialBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {"SerialBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_serial_blocked_for_urls": {} },
-        "note": "TODO(reillyg): Add indicator tests. TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_serial_blocked_for_urls": {} }
+      }
+    ]
+  },
+
+  "SerialAllowAllPortsForUrls": {
+    "os": ["win", "linux", "mac", "chromeos"],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {"SerialAllowAllPortsForUrls": ["https://www.google.com"]},
+        "prefs": { "profile.managed.serial_allow_all_ports_for_urls": {} }
+      }
+    ]
+  },
+
+  "SerialAllowUsbDevicesForUrls": {
+    "os": ["win", "linux", "mac", "chromeos"],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {"SerialAllowUsbDevicesForUrls": [
+          {
+            "devices": [{"vendor_id": 6353, "product_id": 19985}],
+            "urls": ["https://flash.android.com"]
+          },
+          {
+            "devices": [{"vendor_id": 6353}],
+            "urls": ["https://www.google.com"]
+          }
+        ]},
+        "prefs": { "profile.managed.serial_allow_usb_devices_for_urls": {} }
       }
     ]
   },
@@ -3182,11 +2973,13 @@
     "policy_pref_mapping_tests": [
       {
         "policies": {
-          "RegisteredProtocolHandlers": {
-            "protocol": "test",
-            "url": "http://example.com/%s",
-            "default": "true"
-          }
+          "RegisteredProtocolHandlers": [
+            {
+              "protocol": "test",
+              "url": "http://example.com/%s",
+              "default": true
+            }
+          ]
         },
         "prefs": { "custom_handlers.policy.registered_protocol_handlers": { "check_for_mandatory": false }}
       }
@@ -4087,10 +3880,7 @@
     "os": ["chromeos"],
     "pref_mappings": [
       {
-        "pref": "settings.a11y.floating_menu",
-        "indicator_tests": [
-          { "policy": {"FloatingAccessibilityMenuEnabled": true}}
-        ]
+        "pref": "settings.a11y.floating_menu"
       }
     ]
   },
@@ -4540,10 +4330,7 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "PrimaryMouseButtonSwitch": true },
-        "prefs": { "settings.mouse.primary_right": {} },
-        "indicator_tests": [
-          { "pref": "settings" }
-        ]
+        "prefs": { "settings.mouse.primary_right": {} }
       }
     ]
   },
@@ -4889,7 +4676,6 @@
 
   "UnifiedDesktopEnabledByDefault": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "UnifiedDesktopEnabledByDefault": true },
@@ -4900,7 +4686,6 @@
 
   "UserFeedbackAllowed": {
     "os": ["win", "linux", "mac", "chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "UserFeedbackAllowed": false },
@@ -4911,7 +4696,6 @@
 
   "ArcEnabled": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "ArcEnabled": false },
@@ -4922,7 +4706,6 @@
 
   "ArcPolicy": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "ArcPolicy": "" }
@@ -5015,7 +4798,6 @@
 
   "ArcCertificatesSyncMode": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "ArcCertificatesSyncMode": 0 }
@@ -5033,7 +4815,6 @@
 
   "ReportArcStatusEnabled": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "ReportArcStatusEnabled": false },
@@ -5044,7 +4825,6 @@
 
   "ReportCrostiniUsageEnabled": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "ReportCrostiniUsageEnabled": false },
@@ -5167,7 +4947,6 @@
 
   "AllowedLanguages": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -5183,7 +4962,6 @@
 
   "AllowedInputMethods": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -5590,7 +5368,6 @@
 
   "DisplayRotationDefault": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -5602,7 +5379,6 @@
 
   "DeviceDisplayResolution": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -5707,14 +5483,7 @@
         "prefs": { "cros.metrics.reportingEnabled": {
             "location": "cros_setting"
           }
-        },
-        "indicator_tests": [
-          {
-            "pref": "cros.metrics.reportingEnabled",
-            "test_setup_js": "var controllingPref = 'spellcheck.use_spelling_service'; var testedPref = 'cros.metrics.reportingEnabled'; Preferences.prefsChangedCallback([testedPref, Preferences.getInstance().registeredPreferences_[controllingPref].orig]); Preferences.getInstance().addEventListener(controllingPref, function(event) {Preferences.prefsChangedCallback([testedPref, {value: event.value.value, controlledBy: event.value.controlledBy, disabled: event.value.disabled}]);});"
-          }
-        ],
-        "note": "TODO(bartfab): The |test_setup_js| above is a hack that makes |cros.metrics.reportingEnabled| track the status of the entirely unrelated |spellcheck.use_spelling_service| pref. This is because cros settings cannot currently be made policy-controlled in browser tests. Remove this hack once that restriction is lifted."
+        }
       }
     ]
   },
@@ -5918,7 +5687,6 @@
 
   "DeviceQuirksDownloadEnabled": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -6420,25 +6188,11 @@
     "policy_pref_mapping_tests": [
       {
         "policies": { "AdsSettingForIntrusiveAdsSites": 1 },
-        "prefs": { "profile.managed_default_content_settings.ads": { "value": 1 }},
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.ads",
-            "selector": "[content-setting=ads]",
-            "value": "allow"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.ads": { "value": 1 }}
       },
       {
         "policies": { "AdsSettingForIntrusiveAdsSites": 2 },
-        "prefs": { "profile.managed_default_content_settings.ads":{ "value": 2 }},
-        "indicator_tests": [
-          {
-            "pref": "profile.managed_default_content_settings.ads",
-            "selector": "[content-setting=ads]",
-            "value": "block"
-          }
-        ]
+        "prefs": { "profile.managed_default_content_settings.ads":{ "value": 2 }}
       }
     ]
   },
@@ -6845,7 +6599,6 @@
             ]
           }
         },
-        "can_be_recommended": false,
         "prefs": { "screen_time.limit": {} }
       }
     ]
@@ -7383,7 +7136,6 @@
 
   "PerAppTimeLimits": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -7431,7 +7183,6 @@
   "PerAppTimeLimitsWhitelist" : {
     "note": "This policy is deprecated, see http://crbug.com/1103812.",
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -7463,7 +7214,6 @@
 
   "PerAppTimeLimitsAllowlist" : {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -7653,7 +7403,6 @@
 
   "ArcBackupRestoreServiceEnabled": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -7666,7 +7415,6 @@
 
   "ArcGoogleLocationServicesEnabled": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -7679,7 +7427,6 @@
 
   "SafeSitesFilterBehavior": {
     "os": ["win", "linux", "mac", "chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -7798,7 +7545,6 @@
 
   "VpnConfigAllowed": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": {
@@ -8462,41 +8208,41 @@
   },
   "FileSystemReadAskForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {"FileSystemReadAskForUrls": ["[*.].com"]},
-        "prefs": { "profile.managed_file_system_read_ask_for_urls": {}},
-        "note": "TODO(mek): Add indicator tests. TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_file_system_read_ask_for_urls": {}}
       }
     ]
   },
   "FileSystemReadBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {"FileSystemReadBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_file_system_read_blocked_for_urls": {} },
-        "note": "TODO(mek): Add indicator tests. TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_file_system_read_blocked_for_urls": {} }
       }
     ]
   },
   "FileSystemWriteAskForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {"FileSystemWriteAskForUrls": ["[*.].com"]},
-        "prefs": { "profile.managed_file_system_write_ask_for_urls": {}},
-        "note": "TODO(mek): Add indicator tests. TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_file_system_write_ask_for_urls": {}}
       }
     ]
   },
   "FileSystemWriteBlockedForUrls": {
     "os": ["win", "linux", "mac", "chromeos"],
+    "note": "TODO(http://crbug.com/106682): Flag this with can_be_recommended when bug is fixed.",
     "policy_pref_mapping_tests": [
       {
         "policies": {"FileSystemWriteBlockedForUrls": ["[*.]google.com"]},
-        "prefs": { "profile.managed_file_system_write_blocked_for_urls": {} },
-        "note": "TODO(mek): Add indicator tests. TODO(bartfab): Flag this with can_be_recommended when http://crbug.com/106682 is fixed."
+        "prefs": { "profile.managed_file_system_write_blocked_for_urls": {} }
       }
     ]
   },
@@ -8580,7 +8326,6 @@
   },
   "BrowsingDataLifetime": {
     "os": ["win", "linux", "mac", "chromeos", "android"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "BrowsingDataLifetime": [{"data_types": ["browsing_history"], "time_to_live_in_hours": 2}] },
@@ -8590,7 +8335,6 @@
   },
   "LacrosAllowed": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "LacrosAllowed": false },
@@ -8602,7 +8346,6 @@
   },
   "IntegratedWebAuthenticationAllowed": {
     "os": ["chromeos"],
-    "can_be_recommended": false,
     "policy_pref_mapping_test": [
       {
         "policies": { "IntegratedWebAuthenticationAllowed": false },
@@ -8623,7 +8366,6 @@
   },
   "ProfilePickerOnStartupAvailability": {
     "os": ["win", "linux", "mac"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "ProfilePickerOnStartupAvailability": 2 },
@@ -8635,7 +8377,6 @@
   },
   "SigninInterceptionEnabled": {
     "os": ["win", "linux", "mac"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests": [
       {
         "policies": { "SigninInterceptionEnabled": false },
@@ -8645,7 +8386,6 @@
   },
   "BrowserLabsEnabled":{
     "os": ["win", "linux", "mac"],
-    "can_be_recommended": false,
     "policy_pref_mapping_tests":[
       {
         "policies": {"BrowserLabsEnabled": false},
diff --git a/chrome/test/data/web_apps/site_a/bar/basic-192.png b/chrome/test/data/web_apps/site_a/bar/basic-192.png
new file mode 100644
index 0000000..fcd38798
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/bar/basic-192.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_a/bar/basic-48.png b/chrome/test/data/web_apps/site_a/bar/basic-48.png
new file mode 100644
index 0000000..2d6ebb3
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/bar/basic-48.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_a/bar/basic.html b/chrome/test/data/web_apps/site_a/bar/basic.html
new file mode 100644
index 0000000..48806737
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/bar/basic.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <link rel="manifest" href="basic.json">
+</head>
+<body>
+  <h1>Site A Bar</h1>
+  <script>
+    navigator.serviceWorker.register('service_worker.js');
+  </script>
+</body>
+</html>
diff --git a/chrome/test/data/web_apps/site_a/bar/basic.json b/chrome/test/data/web_apps/site_a/bar/basic.json
new file mode 100644
index 0000000..b16d00b
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/bar/basic.json
@@ -0,0 +1,18 @@
+{
+  "name": "Site A Bar",
+  "icons": [
+    {
+      "src": "basic-48.png",
+      "sizes": "48x48",
+      "type": "image/png"
+    },
+    {
+      "src": "basic-192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "/web_apps/site_a/bar/basic.html",
+  "scope" : "/web_apps/site_a/bar/",
+  "display": "standalone"
+}
diff --git a/chrome/test/data/web_apps/site_a/bar/service_worker.js b/chrome/test/data/web_apps/site_a/bar/service_worker.js
new file mode 100644
index 0000000..2e006aa
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/bar/service_worker.js
@@ -0,0 +1,32 @@
+// 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.
+
+'use strict';
+
+const urlsToCache = [
+  'basic-192.png',
+  'basic-48.png',
+  'basic.html',
+  'basic.json',
+];
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(
+    caches.open('basic-cache').then((cache) => {
+      return cache.addAll(urlsToCache);
+    })
+  );
+});
+
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(
+    caches.match(event.request).then((response) => {
+      if (response) {
+        return response;
+      }
+      return fetch(event.request);
+    })
+  );
+});
diff --git a/chrome/test/data/web_apps/site_a/basic-192.png b/chrome/test/data/web_apps/site_a/basic-192.png
new file mode 100644
index 0000000..fcd38798
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/basic-192.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_a/basic-48.png b/chrome/test/data/web_apps/site_a/basic-48.png
new file mode 100644
index 0000000..2d6ebb3
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/basic-48.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_a/basic.html b/chrome/test/data/web_apps/site_a/basic.html
new file mode 100644
index 0000000..85b6e8e2
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/basic.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <link rel="manifest" href="basic.json">
+</head>
+<body>
+  <h1>Site A</h1>
+  <script>
+    navigator.serviceWorker.register('service_worker.js');
+  </script>
+</body>
+</html>
diff --git a/chrome/test/data/web_apps/site_a/basic.json b/chrome/test/data/web_apps/site_a/basic.json
new file mode 100644
index 0000000..5c6d78a8
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/basic.json
@@ -0,0 +1,18 @@
+{
+  "name": "Site A",
+  "icons": [
+    {
+      "src": "basic-48.png",
+      "sizes": "48x48",
+      "type": "image/png"
+    },
+    {
+      "src": "basic-192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "/web_apps/site_a/basic.html",
+  "scope": "/web_apps/site_a/",
+  "display": "standalone"
+}
diff --git a/chrome/test/data/web_apps/site_a/foo/basic-192.png b/chrome/test/data/web_apps/site_a/foo/basic-192.png
new file mode 100644
index 0000000..fcd38798
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/foo/basic-192.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_a/foo/basic-48.png b/chrome/test/data/web_apps/site_a/foo/basic-48.png
new file mode 100644
index 0000000..2d6ebb3
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/foo/basic-48.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_a/foo/basic.html b/chrome/test/data/web_apps/site_a/foo/basic.html
new file mode 100644
index 0000000..7797d5f
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/foo/basic.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <link rel="manifest" href="basic.json">
+</head>
+<body>
+  <h1>Site A Foo</h1>
+  <script>
+    navigator.serviceWorker.register('service_worker.js');
+  </script>
+</body>
+</html>
diff --git a/chrome/test/data/web_apps/site_a/foo/basic.json b/chrome/test/data/web_apps/site_a/foo/basic.json
new file mode 100644
index 0000000..9cac2353
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/foo/basic.json
@@ -0,0 +1,18 @@
+{
+  "name": "Site A Foo",
+  "icons": [
+    {
+      "src": "basic-48.png",
+      "sizes": "48x48",
+      "type": "image/png"
+    },
+    {
+      "src": "basic-192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "/web_apps/site_a/foo/basic.html",
+  "scope": "/web_apps/site_a/foo/",
+  "display": "standalone"
+}
diff --git a/chrome/test/data/web_apps/site_a/foo/service_worker.js b/chrome/test/data/web_apps/site_a/foo/service_worker.js
new file mode 100644
index 0000000..2e006aa
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/foo/service_worker.js
@@ -0,0 +1,32 @@
+// 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.
+
+'use strict';
+
+const urlsToCache = [
+  'basic-192.png',
+  'basic-48.png',
+  'basic.html',
+  'basic.json',
+];
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(
+    caches.open('basic-cache').then((cache) => {
+      return cache.addAll(urlsToCache);
+    })
+  );
+});
+
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(
+    caches.match(event.request).then((response) => {
+      if (response) {
+        return response;
+      }
+      return fetch(event.request);
+    })
+  );
+});
diff --git a/chrome/test/data/web_apps/site_a/service_worker.js b/chrome/test/data/web_apps/site_a/service_worker.js
new file mode 100644
index 0000000..2e006aa
--- /dev/null
+++ b/chrome/test/data/web_apps/site_a/service_worker.js
@@ -0,0 +1,32 @@
+// 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.
+
+'use strict';
+
+const urlsToCache = [
+  'basic-192.png',
+  'basic-48.png',
+  'basic.html',
+  'basic.json',
+];
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(
+    caches.open('basic-cache').then((cache) => {
+      return cache.addAll(urlsToCache);
+    })
+  );
+});
+
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(
+    caches.match(event.request).then((response) => {
+      if (response) {
+        return response;
+      }
+      return fetch(event.request);
+    })
+  );
+});
diff --git a/chrome/test/data/web_apps/site_b/basic-192.png b/chrome/test/data/web_apps/site_b/basic-192.png
new file mode 100644
index 0000000..fcd38798
--- /dev/null
+++ b/chrome/test/data/web_apps/site_b/basic-192.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_b/basic-48.png b/chrome/test/data/web_apps/site_b/basic-48.png
new file mode 100644
index 0000000..2d6ebb3
--- /dev/null
+++ b/chrome/test/data/web_apps/site_b/basic-48.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_b/basic.html b/chrome/test/data/web_apps/site_b/basic.html
new file mode 100644
index 0000000..2e23e17
--- /dev/null
+++ b/chrome/test/data/web_apps/site_b/basic.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <link rel="manifest" href="basic.json">
+</head>
+<body>
+  <h1>Site B</h1>
+  <script>
+    navigator.serviceWorker.register('service_worker.js');
+  </script>
+</body>
+</html>
diff --git a/chrome/test/data/web_apps/site_b/basic.json b/chrome/test/data/web_apps/site_b/basic.json
new file mode 100644
index 0000000..e205f5dc
--- /dev/null
+++ b/chrome/test/data/web_apps/site_b/basic.json
@@ -0,0 +1,18 @@
+{
+  "name": "Site B",
+  "icons": [
+    {
+      "src": "basic-48.png",
+      "sizes": "48x48",
+      "type": "image/png"
+    },
+    {
+      "src": "basic-192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "/web_apps/site_b/basic.html",
+  "scope": "/web_apps/site_b/",
+  "display": "minimal-ui"
+}
diff --git a/chrome/test/data/web_apps/site_b/service_worker.js b/chrome/test/data/web_apps/site_b/service_worker.js
new file mode 100644
index 0000000..2e006aa
--- /dev/null
+++ b/chrome/test/data/web_apps/site_b/service_worker.js
@@ -0,0 +1,32 @@
+// 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.
+
+'use strict';
+
+const urlsToCache = [
+  'basic-192.png',
+  'basic-48.png',
+  'basic.html',
+  'basic.json',
+];
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(
+    caches.open('basic-cache').then((cache) => {
+      return cache.addAll(urlsToCache);
+    })
+  );
+});
+
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(
+    caches.match(event.request).then((response) => {
+      if (response) {
+        return response;
+      }
+      return fetch(event.request);
+    })
+  );
+});
diff --git a/chrome/test/data/web_apps/site_c/basic-192.png b/chrome/test/data/web_apps/site_c/basic-192.png
new file mode 100644
index 0000000..fcd38798
--- /dev/null
+++ b/chrome/test/data/web_apps/site_c/basic-192.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_c/basic-48.png b/chrome/test/data/web_apps/site_c/basic-48.png
new file mode 100644
index 0000000..2d6ebb3
--- /dev/null
+++ b/chrome/test/data/web_apps/site_c/basic-48.png
Binary files differ
diff --git a/chrome/test/data/web_apps/site_c/basic.html b/chrome/test/data/web_apps/site_c/basic.html
new file mode 100644
index 0000000..521b236f
--- /dev/null
+++ b/chrome/test/data/web_apps/site_c/basic.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <link rel="manifest" href="basic.json">
+</head>
+<body>
+  <h1>Site C</h1>
+  <script>
+    navigator.serviceWorker.register('service_worker.js');
+  </script>
+</body>
+</html>
diff --git a/chrome/test/data/web_apps/site_c/basic.json b/chrome/test/data/web_apps/site_c/basic.json
new file mode 100644
index 0000000..760e549
--- /dev/null
+++ b/chrome/test/data/web_apps/site_c/basic.json
@@ -0,0 +1,5 @@
+{
+  "name": "Site C",
+  "start_url": "/web_apps/site_c/basic.html",
+  "scope": "/web_apps/site_c/"
+}
diff --git a/chrome/test/data/web_apps/site_c/service_worker.js b/chrome/test/data/web_apps/site_c/service_worker.js
new file mode 100644
index 0000000..2e006aa
--- /dev/null
+++ b/chrome/test/data/web_apps/site_c/service_worker.js
@@ -0,0 +1,32 @@
+// 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.
+
+'use strict';
+
+const urlsToCache = [
+  'basic-192.png',
+  'basic-48.png',
+  'basic.html',
+  'basic.json',
+];
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(
+    caches.open('basic-cache').then((cache) => {
+      return cache.addAll(urlsToCache);
+    })
+  );
+});
+
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(
+    caches.match(event.request).then((response) => {
+      if (response) {
+        return response;
+      }
+      return fetch(event.request);
+    })
+  );
+});
diff --git a/chrome/test/data/webui/chromeos/scanning/scan_done_section_test.js b/chrome/test/data/webui/chromeos/scanning/scan_done_section_test.js
index dc27ece..bac81da5 100644
--- a/chrome/test/data/webui/chromeos/scanning/scan_done_section_test.js
+++ b/chrome/test/data/webui/chromeos/scanning/scan_done_section_test.js
@@ -82,9 +82,10 @@
       fileNotFoundEventFired = true;
     });
 
-    const lastScannedFilePath = {'path': '/test/path/scan.jpg'};
-    scanningBrowserProxy.setPathToFile(lastScannedFilePath.path);
-    scanDoneSection.lastScannedFilePath = lastScannedFilePath;
+    const scannedFilePaths =
+        [{'path': '/test/path/scan1.jpg'}, {'path': '/test/path/scan2.jpg'}];
+    scanningBrowserProxy.setPathToFile(scannedFilePaths[1].path);
+    scanDoneSection.scannedFilePaths = scannedFilePaths;
     scanDoneSection.numFilesSaved = 1;
     return flushTasks().then(() => {
       scanDoneSection.$$('#folderLink').click();
@@ -103,7 +104,7 @@
     });
 
     scanningBrowserProxy.setPathToFile('/wrong/path/file/so/not/found.jpg');
-    scanDoneSection.lastScannedFilePath = {'path': '/test/path/scan.jpg'};
+    scanDoneSection.scannedFilePaths = [{'path': '/test/path/scan.jpg'}];
     scanDoneSection.numFilesSaved = 1;
     return flushTasks().then(() => {
       scanDoneSection.$$('#folderLink').click();
diff --git a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
index 7db5106..f77aeb2 100644
--- a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
+++ b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
@@ -10,7 +10,7 @@
 import {tokenToString} from 'chrome://scanning/scanning_app_util.js';
 import {ScanningBrowserProxyImpl} from 'chrome://scanning/scanning_browser_proxy.js';
 
-import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
+import {assertArrayEquals, assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
 import {flushTasks, isVisible} from '../../test_util.m.js';
 
 import {changeSelect, createScanner, createScannerSource} from './scanning_app_test_utils.js';
@@ -179,11 +179,11 @@
 
   /**
    * @param {boolean} success
-   * @param {!mojoBase.mojom.FilePath} lastScannedFilePath
+   * @param {!Array<!mojoBase.mojom.FilePath>} scannedFilePaths
    * @return {!Promise}
    */
-  simulateScanComplete(success, lastScannedFilePath) {
-    this.scanJobObserverRemote_.onScanComplete(success, lastScannedFilePath);
+  simulateScanComplete(success, scannedFilePaths) {
+    this.scanJobObserverRemote_.onScanComplete(success, scannedFilePaths);
     return flushTasks();
   }
 
@@ -404,9 +404,9 @@
   }
 
   test('Scan', () => {
-
-    /** @type {!mojoBase.mojom.FilePath} */
-    const lastScannedFilePath = {'path': '/test/path/scan.jpg'};
+    /** @type {!Array<!mojoBase.mojom.FilePath>} */
+    const scannedFilePaths =
+        [{'path': '/test/path/scan1.jpg'}, {'path': '/test/path/scan2.jpg'}];
 
     return initializeScanningApp(expectedScanners, capabilities)
         .then(() => {
@@ -524,8 +524,7 @@
         })
         .then(() => {
           // Complete the scan.
-          return fakeScanService_.simulateScanComplete(
-              true, lastScannedFilePath);
+          return fakeScanService_.simulateScanComplete(true, scannedFilePaths);
         })
         .then(() => {
           assertTrue(isVisible(/** @type {!HTMLElement} */ (scannedImages)));
@@ -533,9 +532,9 @@
           assertTrue(isVisible(
               /** @type {!HTMLElement} */ (
                   scanningApp.$$('scan-done-section'))));
-          assertEquals(
-              lastScannedFilePath.path,
-              scanningApp.$$('scan-done-section').lastScannedFilePath.path);
+          assertArrayEquals(
+              scannedFilePaths,
+              scanningApp.$$('scan-done-section').scannedFilePaths);
 
           // Click the Done button to return to READY state.
           return clickDoneButton();
@@ -582,7 +581,7 @@
         })
         .then(() => {
           // Simulate the scan failing.
-          return fakeScanService_.simulateScanComplete(false, {'path': ''});
+          return fakeScanService_.simulateScanComplete(false, []);
         })
         .then(() => {
           // The scan failed dialog should open.
diff --git a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/psim_flow_ui_test.js b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/psim_flow_ui_test.js
index 8c390f8..98179ba 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/psim_flow_ui_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/psim_flow_ui_test.js
@@ -49,6 +49,9 @@
   /** @type {?chromeos.cellularSetup.mojom.ActivationDelegateReceiver} */
   let cellularActivationDelegate = null;
 
+  /** @type {function(Function, number)} */
+  let timeoutFunction = null;
+
   async function flushAsync() {
     Polymer.dom.flush();
     // Use setTimeout to wait for the next macrotask.
@@ -76,11 +79,19 @@
 
     pSimPage = document.createElement('psim-flow-ui');
     pSimPage.delegate = new cellular_setup.FakeCellularSetupDelegate();
+    pSimPage.setTimerFunctionForTest(function(fn, milliseconds) {
+      timeoutFunction = fn;
+      return 1;
+    });
     pSimPage.initSubflow();
     document.body.appendChild(pSimPage);
     Polymer.dom.flush();
   });
 
+  teardown(function() {
+    pSimPage.remove();
+  });
+
   test('Show provisioning page on activation finished', async () => {
     cellularActivationDelegate =
         cellularSetupRemote.getLastActivationDelegate();
@@ -112,8 +123,7 @@
     await flushAsync();
 
     // Simulate timeout.
-    pSimPage.clearTimer_();
-    pSimPage.onTimeout_();
+    timeoutFunction();
 
     assertTrue(
         pSimPage.state_ === cellularSetup.PSimUIState.TIMEOUT_START_ACTIVATION);
@@ -129,8 +139,7 @@
     await flushAsync();
 
     // Timeout again.
-    pSimPage.clearTimer_();
-    pSimPage.onTimeout_();
+    timeoutFunction();
 
     assertTrue(
         pSimPage.state_ === cellularSetup.PSimUIState.TIMEOUT_START_ACTIVATION);
@@ -146,12 +155,12 @@
     await flushAsync();
 
     // Timeout again.
-    pSimPage.clearTimer_();
-    pSimPage.onTimeout_();
+    timeoutFunction();
 
     // Should now be at the failure state.
     assertTrue(
-        pSimPage.state_ === cellularSetup.PSimUIState.ACTIVATION_FAILURE);
+        pSimPage.state_ ===
+        cellularSetup.PSimUIState.FINAL_TIMEOUT_START_ACTIVATION);
   });
 
   test('Carrier title on provisioning page', async () => {
diff --git a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/setup_loading_page_test.js b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/setup_loading_page_test.js
index ac728e9..e8c94fa9 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/setup_loading_page_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/setup_loading_page_test.js
@@ -16,7 +16,6 @@
   let simDetectPage;
   let basePage;
   let messageIcon;
-  let errorMessage = 'Please insert your SIM and try again';
 
   setup(function() {
     simDetectPage = document.createElement('setup-loading-page');
@@ -44,9 +43,17 @@
     assertFalse(messageIcon.hidden);
   });
 
-  test('Error message is shown', function() {
+  test('Retry error message is shown', function() {
     simDetectPage.state = LoadingPageState.SIM_DETECT_ERROR;
-    assertEquals(basePage.message, errorMessage);
+    assertEquals(
+        basePage.message, simDetectPage.i18n('simDetectPageErrorMessage'));
+    assertTrue(messageIcon.hidden);
+  });
+
+  test('Final error message is shown', function() {
+    simDetectPage.state = LoadingPageState.FINAL_SIM_DETECT_ERROR;
+    assertEquals(
+        basePage.message, simDetectPage.i18n('simDetectPageFinalErrorMessage'));
     assertTrue(messageIcon.hidden);
   });
 });
diff --git a/chrome/test/data/webui/cr_components/chromeos/network/network_siminfo_test.js b/chrome/test/data/webui/cr_components/chromeos/network/network_siminfo_test.js
index b7dd1ebc..29cdad9 100644
--- a/chrome/test/data/webui/cr_components/chromeos/network/network_siminfo_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/network/network_siminfo_test.js
@@ -7,16 +7,29 @@
 // #import 'chrome://resources/cr_components/chromeos/network/network_siminfo.m.js';
 
 // #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {OncMojo} from 'chrome://resources/cr_components/chromeos/network/onc_mojo.m.js';
 // clang-format on
 
 suite('NetworkSiminfoTest', function() {
   /** @type {!NetworkSiminfo|undefined} */
   let simInfo;
 
-  setup(function() {
+  const TEST_ICCID = '11111111111111111';
+  const mojom = chromeos.networkConfig.mojom;
+
+  setup(async function() {
     simInfo = document.createElement('network-simInfo');
+
+    const cellularNetwork =
+        OncMojo.getDefaultNetworkState(mojom.NetworkType.kCellular, 'cellular');
+    cellularNetwork.typeState.cellular.iccid = TEST_ICCID;
+
+    simInfo.networkState = cellularNetwork;
+    simInfo.deviceState =
+        createDeviceState(/*isPrimary=*/ true, /*lockEnabled=*/ true);
+
     document.body.appendChild(simInfo);
-    Polymer.dom.flush();
+    await flushAsync();
   });
 
   async function flushAsync() {
@@ -26,13 +39,28 @@
   }
 
   /**
+   *
+   * @param {boolean} isPrimary
+   * @param {boolean} lockEnabled
+   * @returns {OncMojo.DeviceStateProperties}
+   */
+  function createDeviceState(isPrimary, lockEnabled) {
+    return {
+      simInfos: [{
+        iccid: TEST_ICCID,
+        isPrimary: isPrimary,
+      }],
+      simLockStatus: {lockEnabled: lockEnabled, lockType: '', retriesLeft: 3}
+    };
+  }
+
+  /**
    * Utility function used to check if a dialog with name |dialogName|
    * can be opened by setting the device state to |deviceState|
    * @param {string} dialogName
    * @param {OncMojo.DeviceStateProperties} deviceState
    */
-  async function verifyDialogShown(deviceState, buttonName) {
-    simInfo.deviceState = deviceState;
+  async function verifyDialogShown(buttonName) {
     let btn = simInfo.$$(`#${buttonName}`);
     assertTrue(!!btn);
 
@@ -65,18 +93,32 @@
   });
 
   test('Show sim lock dialog when toggle is clicked', async function() {
-    const deviceState = {
-      simLockStatus: {lockEnabled: false, lockType: '', retriesLeft: 3}
-    };
-
-    verifyDialogShown(deviceState, 'simLockButton');
+    simInfo.deviceState =
+        createDeviceState(/*isPrimary=*/ true, /*lockEnabled=*/ false);
+    await flushAsync();
+    verifyDialogShown('simLockButton');
   });
 
-  test('Show sim lock dialog when change button is clicked', async function() {
-    const deviceState = {
-      simLockStatus: {lockEnabled: true, lockType: '', retriesLeft: 3}
-    };
-
-    verifyDialogShown(deviceState, 'changePinButton');
+  test('Show sim lock dialog when change button is clicked', function() {
+    verifyDialogShown('changePinButton');
   });
+
+  test(
+      'Hide change pin button and disable sim lock toggle if current slot is not primary',
+      async function() {
+        let changePinButton = simInfo.$$('#changePinButton');
+        let simLockButton = simInfo.$$('#simLockButton');
+        assertFalse(changePinButton.hidden);
+        assertFalse(simLockButton.disabled);
+
+        // Trigger device state change
+        simInfo.deviceState =
+            createDeviceState(/*isPrimary=*/ false, /*lockEnabled=*/ true);
+        await flushAsync();
+
+        changePinButton = simInfo.$$('#changePinButton');
+        simLockButton = simInfo.$$('#simLockButton');
+        assertTrue(changePinButton.hidden);
+        assertTrue(simLockButton.disabled);
+      });
 });
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/nearby_share_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/nearby_share_subpage_tests.js
index eba5ea0..ab6141f 100644
--- a/chrome/test/data/webui/settings/chromeos/nearby_share_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/nearby_share_subpage_tests.js
@@ -44,12 +44,8 @@
 suite('NearbyShare', function() {
   /** @type {?SettingsNearbyShareSubpage} */
   let subpage = null;
-  /** @type {?HTMLElement} */
-  let onOffText = null;
   /** @type {?SettingsToggleButtonElement} */
   let featureToggleButton = null;
-  /** @type {?HTMLElement} */
-  let toggleRow = null;
   /** @type {?FakeReceiveManager} */
   let fakeReceiveManager = null;
   /** @type {nearby_share.AccountManagerBrowserProxy} */
@@ -93,9 +89,7 @@
     document.body.appendChild(subpage);
     Polymer.dom.flush();
 
-    onOffText = subpage.$$('#onOff');
     featureToggleButton = subpage.$$('#featureToggleButton');
-    toggleRow = subpage.$$('#toggleRow');
   });
 
   teardown(function() {
@@ -113,7 +107,7 @@
 
     assertEquals(true, featureToggleButton.checked);
     assertEquals(true, subpage.prefs.nearby_sharing.enabled.value);
-    assertEquals('On', onOffText.textContent.trim());
+    assertEquals('On', featureToggleButton.label.trim());
     assertFalse(highVizToggle.disabled);
     assertFalse(editDeviceNameButton.disabled);
     assertFalse(editVisibilityButton.disabled);
@@ -123,7 +117,7 @@
 
     assertEquals(false, featureToggleButton.checked);
     assertEquals(false, subpage.prefs.nearby_sharing.enabled.value);
-    assertEquals('Off', onOffText.textContent.trim());
+    assertEquals('Off', featureToggleButton.label.trim());
     assertTrue(highVizToggle.disabled);
     assertTrue(editDeviceNameButton.disabled);
     assertTrue(editVisibilityButton.disabled);
@@ -133,13 +127,13 @@
   test('toggle row controls preference', function() {
     assertEquals(true, featureToggleButton.checked);
     assertEquals(true, subpage.prefs.nearby_sharing.enabled.value);
-    assertEquals('On', onOffText.textContent.trim());
+    assertEquals('On', featureToggleButton.label.trim());
 
-    toggleRow.click();
+    featureToggleButton.click();
 
     assertEquals(false, featureToggleButton.checked);
     assertEquals(false, subpage.prefs.nearby_sharing.enabled.value);
-    assertEquals('Off', onOffText.textContent.trim());
+    assertEquals('Off', featureToggleButton.label.trim());
   });
 
   suite('Deeplinking', () => {
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index f55d50a6..1306fa0b 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -295,6 +295,7 @@
       base::BindRepeating(&CastContentBrowserClient::GetCmaBackendFactory,
                           base::Unretained(this)),
       base::BindRepeating(&shell::CastSessionIdMap::GetSessionId),
+      base::BindRepeating(&shell::CastSessionIdMap::IsAudioOnlySession),
       GetMediaTaskRunner(),
       ServiceConnector::MakeRemote(kBrowserProcessClientId));
 #else
diff --git a/chromecast/browser/cast_media_blocker_unittest.cc b/chromecast/browser/cast_media_blocker_unittest.cc
index af41492f..8210dfa 100644
--- a/chromecast/browser/cast_media_blocker_unittest.cc
+++ b/chromecast/browser/cast_media_blocker_unittest.cc
@@ -57,6 +57,9 @@
   MOCK_METHOD0(EnterPictureInPicture, void());
   MOCK_METHOD0(ExitPictureInPicture, void());
   MOCK_METHOD1(SetAudioSinkId, void(const base::Optional<std::string>& id));
+  MOCK_METHOD0(ToggleMicrophone, void());
+  MOCK_METHOD0(ToggleCamera, void());
+  MOCK_METHOD0(HangUp, void());
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockMediaSession);
diff --git a/chromecast/browser/cast_navigation_ui_data.cc b/chromecast/browser/cast_navigation_ui_data.cc
index 6a1e3d8..afd43812 100644
--- a/chromecast/browser/cast_navigation_ui_data.cc
+++ b/chromecast/browser/cast_navigation_ui_data.cc
@@ -27,13 +27,14 @@
 }  // namespace
 
 // static
-void CastNavigationUIData::SetSessionIdForWebContents(
+void CastNavigationUIData::SetAppPropertiesForWebContents(
     content::WebContents* web_contents,
-    const std::string& session_id) {
+    const std::string& session_id,
+    bool is_audio_app) {
   DCHECK(web_contents);
   web_contents->SetUserData(kUserDataKey,
                             std::make_unique<SessionIdUserData>(session_id));
-  CastSessionIdMap::SetSessionId(session_id, web_contents);
+  CastSessionIdMap::SetAppProperties(session_id, is_audio_app, web_contents);
 }
 
 // static
diff --git a/chromecast/browser/cast_navigation_ui_data.h b/chromecast/browser/cast_navigation_ui_data.h
index 5ef47b4..6306ece 100644
--- a/chromecast/browser/cast_navigation_ui_data.h
+++ b/chromecast/browser/cast_navigation_ui_data.h
@@ -19,8 +19,9 @@
 
 class CastNavigationUIData : public content::NavigationUIData {
  public:
-  static void SetSessionIdForWebContents(content::WebContents* web_contents,
-                                         const std::string& session_id);
+  static void SetAppPropertiesForWebContents(content::WebContents* web_contents,
+                                             const std::string& session_id,
+                                             bool is_audio_app);
   static std::string GetSessionIdForWebContents(
       content::WebContents* web_contents);
 
diff --git a/chromecast/browser/cast_session_id_map.cc b/chromecast/browser/cast_session_id_map.cc
index 86dd645..050a825 100644
--- a/chromecast/browser/cast_session_id_map.cc
+++ b/chromecast/browser/cast_session_id_map.cc
@@ -49,8 +49,9 @@
 }
 
 // static
-void CastSessionIdMap::SetSessionId(std::string session_id,
-                                    content::WebContents* web_contents) {
+void CastSessionIdMap::SetAppProperties(std::string session_id,
+                                        bool is_audio_app,
+                                        content::WebContents* web_contents) {
   base::UnguessableToken group_id = web_contents->GetAudioGroupId();
   // Unretained is safe here, because the singleton CastSessionIdMap never gets
   // destroyed.
@@ -58,8 +59,8 @@
                                            base::Unretained(GetInstance()));
   auto group_observer = std::make_unique<GroupObserver>(
       web_contents, std::move(destroyed_callback));
-  GetInstance()->SetSessionIdInternal(session_id, group_id,
-                                      std::move(group_observer));
+  GetInstance()->SetAppPropertiesInternal(session_id, is_audio_app, group_id,
+                                          std::move(group_observer));
 }
 
 // static
@@ -67,6 +68,11 @@
   return GetInstance()->GetSessionIdInternal(group_id);
 }
 
+// static
+bool CastSessionIdMap::IsAudioOnlySession(const std::string& session_id) {
+  return GetInstance()->IsAudioOnlySessionInternal(session_id);
+}
+
 CastSessionIdMap::CastSessionIdMap(base::SequencedTaskRunner* task_runner)
     : task_runner_(task_runner) {
   DCHECK(task_runner_);
@@ -75,17 +81,19 @@
 
 CastSessionIdMap::~CastSessionIdMap() = default;
 
-void CastSessionIdMap::SetSessionIdInternal(
+void CastSessionIdMap::SetAppPropertiesInternal(
     std::string session_id,
+    bool is_audio_app,
     base::UnguessableToken group_id,
     std::unique_ptr<GroupObserver> group_observer) {
   if (!task_runner_->RunsTasksInCurrentSequence()) {
     // Unretained is safe here, because the singleton CastSessionIdMap never
     // gets destroyed.
     task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&CastSessionIdMap::SetSessionIdInternal,
-                                  base::Unretained(this), std::move(session_id),
-                                  group_id, std::move(group_observer)));
+        FROM_HERE,
+        base::BindOnce(&CastSessionIdMap::SetAppPropertiesInternal,
+                       base::Unretained(this), std::move(session_id),
+                       is_audio_app, group_id, std::move(group_observer)));
     return;
   }
 
@@ -98,6 +106,7 @@
            << " to group_id=" << group_id.ToString();
   auto group_data = std::make_pair(session_id, std::move(group_observer));
   mapping_.emplace(group_id.ToString(), std::move(group_data));
+  application_capability_mapping_.emplace(session_id, is_audio_app);
 }
 
 std::string CastSessionIdMap::GetSessionIdInternal(
@@ -109,6 +118,15 @@
   return std::string();
 }
 
+bool CastSessionIdMap::IsAudioOnlySessionInternal(
+    const std::string& session_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto it = application_capability_mapping_.find(session_id);
+  if (it != application_capability_mapping_.end())
+    return it->second;
+  return false;
+}
+
 void CastSessionIdMap::OnGroupDestroyed(base::UnguessableToken group_id) {
   // Unretained is safe here, because the singleton CastSessionIdMap never gets
   // destroyed.
@@ -124,6 +142,10 @@
   if (it != mapping_.end()) {
     DVLOG(1) << "Removing mapping for session_id=" << it->second.first
              << " to group_id=" << group_id.ToString();
+    auto it_app = application_capability_mapping_.find(it->second.first);
+    if (it_app != application_capability_mapping_.end()) {
+      application_capability_mapping_.erase(it_app);
+    }
     mapping_.erase(it);
   }
 }
diff --git a/chromecast/browser/cast_session_id_map.h b/chromecast/browser/cast_session_id_map.h
index c0ded70..e4ef034 100644
--- a/chromecast/browser/cast_session_id_map.h
+++ b/chromecast/browser/cast_session_id_map.h
@@ -36,13 +36,19 @@
   static CastSessionIdMap* GetInstance(
       base::SequencedTaskRunner* task_runner = nullptr);
   // Map a session id to a particular group id in the provided WebContents.
+  // Record whether the session is an audio only session.
   // Can be called on any thread.
-  static void SetSessionId(std::string session_id,
-                           content::WebContents* web_contents);
+  static void SetAppProperties(std::string session_id,
+                               bool is_audio_app,
+                               content::WebContents* web_contents);
   // Fetch the session id that is mapped to the provided group_id. Defaults to
   // empty string if the mapping is not found.
   // Must be called on the sequence for |task_runner_|.
   static std::string GetSessionId(const std::string& group_id);
+  // Fetch whether the session is an audio only session based on the provided
+  // session id. Defaults to false if the mapping is not found.
+  // Must be called on the sequence for |task_runner_|.
+  static bool IsAudioOnlySession(const std::string& session_id);
 
  private:
   class GroupObserver;
@@ -58,18 +64,27 @@
   // because it releases the GroupObserver who owns the destuctor callback.
   void RemoveGroupId(base::UnguessableToken group_id);
   // Maps the session id for the provided group id.
+  // Record whether the session is an audio only session.
   // This call be called on any thread.
-  void SetSessionIdInternal(std::string session_id,
-                            base::UnguessableToken group_id,
-                            std::unique_ptr<GroupObserver> group_observer);
+  void SetAppPropertiesInternal(std::string session_id,
+                                bool is_audio_app,
+                                base::UnguessableToken group_id,
+                                std::unique_ptr<GroupObserver> group_observer);
   // Retrieves the session id for the provided group id.
   // This must be called on the |task_runner_|.
   std::string GetSessionIdInternal(const std::string& group_id);
+  // Retrieves the session is an audio only session based on the provided
+  // session id.
+  // This must be called on the |task_runner_|.
+  bool IsAudioOnlySessionInternal(const std::string& session_id);
 
   base::flat_map<
       std::string,
       std::pair<std::string /* group_id */, std::unique_ptr<GroupObserver>>>
       mapping_;
+
+  base::flat_map<std::string /* session_id */, bool /* is_audio_app */>
+      application_capability_mapping_;
   base::SequencedTaskRunner* const task_runner_;
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/chromecast/browser/cast_session_id_map_unittest.cc b/chromecast/browser/cast_session_id_map_unittest.cc
index 7d48693..6e2b3369 100644
--- a/chromecast/browser/cast_session_id_map_unittest.cc
+++ b/chromecast/browser/cast_session_id_map_unittest.cc
@@ -51,7 +51,8 @@
 TEST_F(CastSessionIdMapTest, CanRetrieveSessionId) {
   auto web_contents = CreateTestWebContents();
   base::UnguessableToken group_id = web_contents->GetAudioGroupId();
-  CastSessionIdMap::SetSessionId(kTestSessionId, web_contents.get());
+  CastSessionIdMap::SetAppProperties(kTestSessionId, false /* is_audio_app */,
+                                     web_contents.get());
   task_runner_->RunUntilIdle();
 
   std::string saved_session_id =
@@ -62,7 +63,8 @@
 TEST_F(CastSessionIdMapTest, RemovesMappingOnWebContentsDestroyed) {
   auto web_contents = CreateTestWebContents();
   base::UnguessableToken group_id = web_contents->GetAudioGroupId();
-  CastSessionIdMap::SetSessionId(kTestSessionId, web_contents.get());
+  CastSessionIdMap::SetAppProperties(kTestSessionId, false /* is_audio_app */,
+                                     web_contents.get());
   task_runner_->RunUntilIdle();
 
   web_contents.reset();
@@ -80,9 +82,12 @@
   auto web_contents_1 = CreateTestWebContents();
   auto web_contents_2 = CreateTestWebContents();
   auto web_contents_3 = CreateTestWebContents();
-  CastSessionIdMap::SetSessionId(test_session_id_1, web_contents_1.get());
-  CastSessionIdMap::SetSessionId(test_session_id_2, web_contents_2.get());
-  CastSessionIdMap::SetSessionId(test_session_id_3, web_contents_3.get());
+  CastSessionIdMap::SetAppProperties(
+      test_session_id_1, false /* is_audio_app */, web_contents_1.get());
+  CastSessionIdMap::SetAppProperties(
+      test_session_id_2, false /* is_audio_app */, web_contents_2.get());
+  CastSessionIdMap::SetAppProperties(
+      test_session_id_3, false /* is_audio_app */, web_contents_3.get());
   task_runner_->RunUntilIdle();
 
   std::string saved_session_id = "";
diff --git a/chromecast/media/audio/BUILD.gn b/chromecast/media/audio/BUILD.gn
index 9160775..a8c4ef8 100644
--- a/chromecast/media/audio/BUILD.gn
+++ b/chromecast/media/audio/BUILD.gn
@@ -99,6 +99,8 @@
     "cast_audio_mixer.h",
     "cast_audio_output_stream.cc",
     "cast_audio_output_stream.h",
+    "cast_audio_output_utils.cc",
+    "cast_audio_output_utils.h",
     "cma_audio_output_stream.cc",
     "cma_audio_output_stream.h",
   ]
diff --git a/chromecast/media/audio/cast_audio_manager_android.cc b/chromecast/media/audio/cast_audio_manager_android.cc
index 575030b..77a0a06 100644
--- a/chromecast/media/audio/cast_audio_manager_android.cc
+++ b/chromecast/media/audio/cast_audio_manager_android.cc
@@ -10,6 +10,7 @@
 #include "chromecast/media/audio/audio_buildflags.h"
 #include "chromecast/media/audio/cast_audio_input_stream.h"
 #include "chromecast/media/audio/cast_audio_output_stream.h"
+#include "chromecast/media/audio/cast_audio_output_utils.h"
 #include "media/audio/android/audio_track_output_stream.h"
 #include "media/audio/audio_device_name.h"
 #include "media/base/audio_parameters.h"
@@ -27,8 +28,10 @@
 const int kCommunicationsInputBufferSize = 160;  // 10 ms.
 #endif  // BUILDFLAG(ENABLE_AUDIO_CAPTURE_SERVICE)
 
-bool ShouldUseCastAudioOutputStream(const ::media::AudioParameters& params) {
-  return params.effects() & ::media::AudioParameters::AUDIO_PREFETCH;
+bool ShouldUseCastAudioOutputStream(bool is_audio_app,
+                                    const ::media::AudioParameters& params) {
+  return is_audio_app ||
+         (params.effects() & ::media::AudioParameters::AUDIO_PREFETCH);
 }
 
 }  // namespace
@@ -38,6 +41,7 @@
     ::media::AudioLogFactory* audio_log_factory,
     base::RepeatingCallback<CmaBackendFactory*()> backend_factory_getter,
     CastAudioManagerHelper::GetSessionIdCallback get_session_id_callback,
+    GetApplicationCapabilityCallback get_application_capability_callback,
     scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
     mojo::PendingRemote<chromecast::mojom::ServiceConnector> connector)
     : ::media::AudioManagerAndroid(std::move(audio_thread), audio_log_factory),
@@ -45,7 +49,9 @@
               std::move(backend_factory_getter),
               std::move(get_session_id_callback),
               std::move(media_task_runner),
-              std::move(connector)) {}
+              std::move(connector)),
+      get_application_capability_callback_(
+          std::move(get_application_capability_callback)) {}
 
 CastAudioManagerAndroid::~CastAudioManagerAndroid() = default;
 
@@ -126,8 +132,11 @@
     const ::media::AudioParameters& params,
     const ::media::AudioManager::LogCallback& log_callback) {
   DCHECK_EQ(::media::AudioParameters::AUDIO_PCM_LINEAR, params.format());
-
-  if (ShouldUseCastAudioOutputStream(params)) {
+  // MakeLinearOutputStream is only used on Windows. In this case, we cannot get
+  // a valid session id based on kDefaultDeviceId. Therefore we cannot know
+  // whether it is an audio only session.
+  if (ShouldUseCastAudioOutputStream(false /* is_audio_app */, params)) {
+    LOG(WARNING) << __func__ << ": Cannot get valid session_id.";
     return new CastAudioOutputStream(
         &helper_, params, ::media::AudioDeviceDescription::kDefaultDeviceId,
         false /* use_mixer_service */);
@@ -142,8 +151,9 @@
     const std::string& device_id_or_group_id,
     const ::media::AudioManager::LogCallback& log_callback) {
   DCHECK_EQ(::media::AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
-
-  if (ShouldUseCastAudioOutputStream(params)) {
+  bool is_audio_app = get_application_capability_callback_.Run(
+      helper_.GetSessionId(GetGroupId(device_id_or_group_id)));
+  if (ShouldUseCastAudioOutputStream(is_audio_app, params)) {
     return new CastAudioOutputStream(
         &helper_, params,
         device_id_or_group_id.empty()
@@ -167,7 +177,9 @@
 ::media::AudioOutputStream* CastAudioManagerAndroid::MakeAudioOutputStreamProxy(
     const ::media::AudioParameters& params,
     const std::string& device_id) {
-  if (ShouldUseCastAudioOutputStream(params)) {
+  bool is_audio_app = get_application_capability_callback_.Run(
+      helper_.GetSessionId(GetGroupId(device_id)));
+  if (ShouldUseCastAudioOutputStream(is_audio_app, params)) {
     // Override to use MakeAudioOutputStream to prevent the audio output stream
     // from closing during pause/stop.
     return MakeAudioOutputStream(params, device_id,
diff --git a/chromecast/media/audio/cast_audio_manager_android.h b/chromecast/media/audio/cast_audio_manager_android.h
index 555ad4f..62ed891 100644
--- a/chromecast/media/audio/cast_audio_manager_android.h
+++ b/chromecast/media/audio/cast_audio_manager_android.h
@@ -18,11 +18,17 @@
 
 class CastAudioManagerAndroid : public ::media::AudioManagerAndroid {
  public:
+  // Callback to get whether the session is audio-only for the provided session
+  // ID.
+  using GetApplicationCapabilityCallback =
+      base::RepeatingCallback<bool(const std::string&)>;
+
   CastAudioManagerAndroid(
       std::unique_ptr<::media::AudioThread> audio_thread,
       ::media::AudioLogFactory* audio_log_factory,
       base::RepeatingCallback<CmaBackendFactory*()> backend_factory_getter,
       CastAudioManagerHelper::GetSessionIdCallback get_session_id_callback,
+      GetApplicationCapabilityCallback get_application_capability_callback,
       scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
       mojo::PendingRemote<chromecast::mojom::ServiceConnector> connector);
   ~CastAudioManagerAndroid() override;
@@ -63,6 +69,7 @@
       const ::media::AudioManager::LogCallback& log_callback) override;
 
   CastAudioManagerHelper helper_;
+  GetApplicationCapabilityCallback get_application_capability_callback_;
 
   CastAudioManagerAndroid(const CastAudioManagerAndroid&) = delete;
   CastAudioManagerAndroid& operator=(const CastAudioManagerAndroid&) = delete;
diff --git a/chromecast/media/audio/cast_audio_output_stream.cc b/chromecast/media/audio/cast_audio_output_stream.cc
index d238c88..782d9417 100644
--- a/chromecast/media/audio/cast_audio_output_stream.cc
+++ b/chromecast/media/audio/cast_audio_output_stream.cc
@@ -23,6 +23,7 @@
 #include "chromecast/common/mojom/constants.mojom.h"
 #include "chromecast/media/api/cma_backend_factory.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
+#include "chromecast/media/audio/cast_audio_output_utils.h"
 #include "chromecast/media/audio/cma_audio_output_stream.h"
 #include "chromecast/media/audio/mixer_service/mixer_service.pb.h"
 #include "chromecast/media/audio/mixer_service/output_stream_connection.h"
@@ -83,11 +84,6 @@
   }
 }
 
-bool IsValidDeviceId(const std::string& device_id) {
-  return device_id == ::media::AudioDeviceDescription::kDefaultDeviceId ||
-         device_id == ::media::AudioDeviceDescription::kCommunicationsDeviceId;
-}
-
 }  // namespace
 
 class CastAudioOutputStream::MixerServiceWrapper
@@ -307,8 +303,7 @@
       device_id_(IsValidDeviceId(device_id_or_group_id)
                      ? device_id_or_group_id
                      : ::media::AudioDeviceDescription::kDefaultDeviceId),
-      group_id_(IsValidDeviceId(device_id_or_group_id) ? ""
-                                                       : device_id_or_group_id),
+      group_id_(GetGroupId(device_id_or_group_id)),
       use_mixer_service_(use_mixer_service),
       audio_weak_factory_(this) {
   DCHECK(audio_manager_);
@@ -344,6 +339,7 @@
 
   const std::string application_session_id =
       audio_manager_->GetSessionId(group_id_);
+  LOG_IF(WARNING, application_session_id.empty()) << "Session id is empty.";
   DVLOG(1) << this << ": " << __func__
            << ", session_id=" << application_session_id;
 
diff --git a/chromecast/media/audio/cast_audio_output_utils.cc b/chromecast/media/audio/cast_audio_output_utils.cc
new file mode 100644
index 0000000..c8ff8122
--- /dev/null
+++ b/chromecast/media/audio/cast_audio_output_utils.cc
@@ -0,0 +1,22 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/audio/cast_audio_output_utils.h"
+
+#include "media/audio/audio_device_description.h"
+
+namespace chromecast {
+namespace media {
+
+bool IsValidDeviceId(const std::string& device_id) {
+  return device_id == ::media::AudioDeviceDescription::kDefaultDeviceId ||
+         device_id == ::media::AudioDeviceDescription::kCommunicationsDeviceId;
+}
+
+std::string GetGroupId(const std::string& device_id) {
+  return IsValidDeviceId(device_id) ? "" : device_id;
+}
+
+}  // namespace media
+}  // namespace chromecast
diff --git a/chromecast/media/audio/cast_audio_output_utils.h b/chromecast/media/audio/cast_audio_output_utils.h
new file mode 100644
index 0000000..ad041b6
--- /dev/null
+++ b/chromecast/media/audio/cast_audio_output_utils.h
@@ -0,0 +1,22 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_OUTPUT_UTILS_H_
+#define CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_OUTPUT_UTILS_H_
+
+#include <string>
+
+namespace chromecast {
+namespace media {
+
+// Returns whether the device is valid.
+bool IsValidDeviceId(const std::string& device_id);
+
+// Returns the group id for provided device id.
+std::string GetGroupId(const std::string& device_id);
+
+}  // namespace media
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_OUTPUT_UTILS_H_
diff --git a/chromeos/components/diagnostics_ui/backend/session_log_handler_unittest.cc b/chromeos/components/diagnostics_ui/backend/session_log_handler_unittest.cc
index badc0afe..5fd8a38 100644
--- a/chromeos/components/diagnostics_ui/backend/session_log_handler_unittest.cc
+++ b/chromeos/components/diagnostics_ui/backend/session_log_handler_unittest.cc
@@ -228,10 +228,7 @@
                 base::NumberToString(expected_cpu_max_clock_speed_khz),
             log_lines[7]);
   EXPECT_EQ("Milestone Version: " + expected_milestone_version, log_lines[8]);
-  EXPECT_EQ("Has Battery: " + base::NumberToString(expected_has_battery),
-            log_lines[9]);
-  EXPECT_EQ("Has Battery: " + base::NumberToString(expected_has_battery),
-            log_lines[9]);
+  EXPECT_EQ("Has Battery: true", log_lines[9]);
 
   const std::string expected_routine_log_header = "=== Routine Log ===";
   EXPECT_EQ(expected_routine_log_header, log_lines[10]);
diff --git a/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc b/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
index a43196d6f..8f9d2a69 100644
--- a/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
+++ b/chromeos/components/diagnostics_ui/backend/system_data_provider_unittest.cc
@@ -998,8 +998,7 @@
             log_contents[6]);
   EXPECT_EQ("Milestone Version: " + expected_milestone_version,
             log_contents[7]);
-  EXPECT_EQ("Has Battery: " + base::NumberToString(expected_has_battery),
-            log_contents[8]);
+  EXPECT_EQ("Has Battery: true", log_contents[8]);
 }
 
 }  // namespace diagnostics
diff --git a/chromeos/components/diagnostics_ui/backend/telemetry_log.cc b/chromeos/components/diagnostics_ui/backend/telemetry_log.cc
index e8236afd..576836c 100644
--- a/chromeos/components/diagnostics_ui/backend/telemetry_log.cc
+++ b/chromeos/components/diagnostics_ui/backend/telemetry_log.cc
@@ -7,6 +7,8 @@
 #include <sstream>
 #include <utility>
 
+#include "base/strings/string_number_conversions.h"
+
 namespace chromeos {
 namespace diagnostics {
 namespace {
@@ -47,9 +49,9 @@
 
 // CpuUsage constants:
 const char kCpuUsageSectionName[] = "--- Cpu Usage ---";
-const char kCpuUsageUserTitle[] = "Usage User: ";
-const char kCpuUsageSystemTitle[] = "Usage System: ";
-const char kCpuUsageFreeTitle[] = "Usage Free: ";
+const char kCpuUsageUserTitle[] = "Usage User (%): ";
+const char kCpuUsageSystemTitle[] = "Usage System (%): ";
+const char kCpuUsageFreeTitle[] = "Usage Free (%): ";
 const char kCpuUsageAvgTempTitle[] = "Avg Temp (C): ";
 const char kCpuUsageScalingFrequencyTitle[] =
     "Current scaled frequency (kHz): ";
@@ -100,8 +102,10 @@
            << kSystemInfoMilestoneVersionTitle
            << latest_system_info_->version_info->milestone_version << kNewline
            << kSystemInfoHasBatteryTitle
-           << latest_system_info_->device_capabilities->has_battery << kNewline
-           << kNewline;
+           << ((latest_system_info_->device_capabilities->has_battery)
+                   ? "true"
+                   : "false")
+           << kNewline << kNewline;
   }
   if (latest_battery_charge_status_) {
     output << kBatteryChargeStatusSectionName << kNewline
@@ -126,8 +130,9 @@
            << kNewline << kBatteryHealthCycleCountTitle
            << latest_battery_health_->cycle_count << kNewline
            << kBatteryHealthWearPercentageTitle
-           << latest_battery_health_->battery_wear_percentage << kNewline
-           << kNewline;
+           << base::NumberToString(
+                  latest_battery_health_->battery_wear_percentage)
+           << kNewline << kNewline;
   }
   if (latest_memory_usage_) {
     output << kMemoryUsageSectionName << kNewline
@@ -140,11 +145,12 @@
   }
   if (latest_cpu_usage_) {
     output << kCpuUsageSectionName << kNewline << kCpuUsageUserTitle
-           << latest_cpu_usage_->percent_usage_user << kNewline
-           << kCpuUsageSystemTitle << latest_cpu_usage_->percent_usage_system
+           << base::NumberToString(latest_cpu_usage_->percent_usage_user)
+           << kNewline << kCpuUsageSystemTitle
+           << base::NumberToString(latest_cpu_usage_->percent_usage_system)
            << kNewline << kCpuUsageFreeTitle
-           << latest_cpu_usage_->percent_usage_free << kNewline
-           << kCpuUsageAvgTempTitle
+           << base::NumberToString(latest_cpu_usage_->percent_usage_free)
+           << kNewline << kCpuUsageAvgTempTitle
            << latest_cpu_usage_->average_cpu_temp_celsius << kNewline
            << kCpuUsageScalingFrequencyTitle
            << latest_cpu_usage_->scaling_current_frequency_khz << kNewline
diff --git a/chromeos/components/diagnostics_ui/backend/telemetry_log_unittest.cc b/chromeos/components/diagnostics_ui/backend/telemetry_log_unittest.cc
index 18d464dc..e75a5f5 100644
--- a/chromeos/components/diagnostics_ui/backend/telemetry_log_unittest.cc
+++ b/chromeos/components/diagnostics_ui/backend/telemetry_log_unittest.cc
@@ -79,8 +79,7 @@
                 base::NumberToString(expected_cpu_max_clock_speed_khz),
             log_lines[6]);
   EXPECT_EQ("Milestone Version: " + expected_milestone_version, log_lines[7]);
-  EXPECT_EQ("Has Battery: " + base::NumberToString(expected_has_battery),
-            log_lines[8]);
+  EXPECT_EQ("Has Battery: true", log_lines[8]);
 }
 
 TEST_F(TelemetryLogTest, ChangeContents) {
@@ -109,8 +108,47 @@
 
   const std::string log_as_string = log.GetContents();
   const std::vector<std::string> log_lines = GetLogLines(log_as_string);
+}
 
-  EXPECT_EQ("Board Name: new board_name", log_lines[1]);
+TEST_F(TelemetryLogTest, CpuUsageUint8) {
+  const std::string expected_board_name = "board_name";
+  const std::string expected_marketing_name = "marketing_name";
+  const std::string expected_cpu_model = "cpu_model";
+  const uint32_t expected_total_memory_kib = 1234;
+  const uint16_t expected_cpu_threads_count = 5678;
+  const uint32_t expected_cpu_max_clock_speed_khz = 91011;
+  const bool expected_has_battery = true;
+  const std::string expected_milestone_version = "M99";
+  const uint8_t percent_usage_user = 10;
+  const uint8_t percent_usage_system = 20;
+  const uint8_t percent_usage_free = 80;
+  const uint16_t average_cpu_temp_celsius = 31;
+  const uint32_t scaling_current_frequency_khz = 500;
+
+  mojom::SystemInfoPtr test_info = CreateSystemInfoPtr(
+      expected_board_name, expected_marketing_name, expected_cpu_model,
+      expected_total_memory_kib, expected_cpu_threads_count,
+      expected_cpu_max_clock_speed_khz, expected_has_battery,
+      expected_milestone_version);
+
+  mojom::CpuUsagePtr cpu_usage = mojom::CpuUsage::New(
+      percent_usage_user, percent_usage_system, percent_usage_free,
+      average_cpu_temp_celsius, scaling_current_frequency_khz);
+
+  TelemetryLog log;
+
+  log.UpdateSystemInfo(test_info.Clone());
+  log.UpdateCpuUsage(cpu_usage.Clone());
+
+  const std::string log_as_string = log.GetContents();
+  const std::vector<std::string> log_lines = GetLogLines(log_as_string);
+
+  EXPECT_EQ("Usage User (%): " + base::NumberToString(percent_usage_user),
+            log_lines[10]);
+  EXPECT_EQ("Usage System (%): " + base::NumberToString(percent_usage_system),
+            log_lines[11]);
+  EXPECT_EQ("Usage Free (%): " + base::NumberToString(percent_usage_free),
+            log_lines[12]);
 }
 
 }  // namespace diagnostics
diff --git a/chromeos/components/scanning/mojom/scanning.mojom b/chromeos/components/scanning/mojom/scanning.mojom
index 4d54e8e1..3e6e7d1 100644
--- a/chromeos/components/scanning/mojom/scanning.mojom
+++ b/chromeos/components/scanning/mojom/scanning.mojom
@@ -107,9 +107,11 @@
   OnPageComplete(array<uint8> page_data);
 
   // Called when the scan is complete. |success| indicates whether the scan
-  // completed successfully. |last_scanned_file_path| is the file path of
-  // the last page scanned in a scan job.
-  OnScanComplete(bool success, mojo_base.mojom.FilePath last_scanned_file_path);
+  // completed successfully. |scanned_file_paths| contains all the file paths of
+  // the pages scanned in a scan job. |scanned_file_paths| is an empty array for
+  // failed scans.
+  OnScanComplete(bool success,
+      array<mojo_base.mojom.FilePath> scanned_file_paths);
 
   // Called when canceling the current scan job is complete. |success|
   // indicates whether the scan job was cancelled successfully.
diff --git a/chromeos/components/scanning/resources/scan_done_section.js b/chromeos/components/scanning/resources/scan_done_section.js
index 72b8c47..eafa1c6 100644
--- a/chromeos/components/scanning/resources/scan_done_section.js
+++ b/chromeos/components/scanning/resources/scan_done_section.js
@@ -32,8 +32,8 @@
     /** @type {number} */
     numFilesSaved: Number,
 
-    /** @type {?mojoBase.mojom.FilePath} */
-    lastScannedFilePath: Object,
+    /** @type {!Array<!mojoBase.mojom.FilePath>} */
+    scannedFilePaths: Array,
 
     /** @type {string} */
     selectedFolder: String,
@@ -57,7 +57,13 @@
 
   /** @private */
   showFileInLocation_() {
-    this.browserProxy_.showFileInLocation(this.lastScannedFilePath.path)
+    if (this.scannedFilePaths.length == 0) {
+      this.fire('file-not-found');
+      return;
+    }
+
+    this.browserProxy_
+        .showFileInLocation(this.scannedFilePaths.slice(-1)[0].path)
         .then(
             /* @type {boolean} */ (succesful) => {
               if (!succesful) {
diff --git a/chromeos/components/scanning/resources/scanning_app.html b/chromeos/components/scanning/resources/scanning_app.html
index a4049eb..b736c3b3 100644
--- a/chromeos/components/scanning/resources/scanning_app.html
+++ b/chromeos/components/scanning/resources/scanning_app.html
@@ -246,7 +246,7 @@
       <template is="dom-if" if="[[showDoneSection_]]">
         <scan-done-section num-files-saved="[[getNumFilesSaved_(pageNumber_)]]"
             on-done-click="onDoneClick_" on-file-not-found="onFileNotFound_"
-            last-scanned-file-path="[[lastScannedFilePath_]]"
+            scanned-file-paths="[[scannedFilePaths_]]"
             selected-folder="[[selectedFolder]]">
         </scan-done-section>
       </template>
diff --git a/chromeos/components/scanning/resources/scanning_app.js b/chromeos/components/scanning/resources/scanning_app.js
index 6bfae27..6ae0bf6 100644
--- a/chromeos/components/scanning/resources/scanning_app.js
+++ b/chromeos/components/scanning/resources/scanning_app.js
@@ -203,11 +203,13 @@
     },
 
     /**
-     * The file path of the last scanned page of a successful scan job. Used to
-     * open the Files app with the correct file highlighted.
-     * @private {?mojoBase.mojom.FilePath}
+     * The file paths of the scanned pages of a successful scan job.
+     * @private {!Array<!mojoBase.mojom.FilePath>}
      */
-    lastScannedFilePath_: Object,
+    scannedFilePaths_: {
+      type: Array,
+      value: () => [],
+    },
 
     /**
      * The key to retrieve the appropriate string to display in the toast.
@@ -304,15 +306,15 @@
   /**
    * Overrides chromeos.scanning.mojom.ScanJobObserverInterface.
    * @param {boolean} success
-   * @param {!mojoBase.mojom.FilePath} lastScannedFilePath
+   * @param {!Array<!mojoBase.mojom.FilePath>} scannedFilePaths
    */
-  onScanComplete(success, lastScannedFilePath) {
+  onScanComplete(success, scannedFilePaths) {
     if (!success || this.objectUrls_.length == 0) {
       this.$.scanFailedDialog.showModal();
       return;
     }
 
-    this.lastScannedFilePath_ = lastScannedFilePath;
+    this.scannedFilePaths_ = scannedFilePaths;
     this.setAppState_(AppState.DONE);
   },
 
diff --git a/chromeos/dbus/BUILD.gn b/chromeos/dbus/BUILD.gn
index 0c781be..f3685bc 100644
--- a/chromeos/dbus/BUILD.gn
+++ b/chromeos/dbus/BUILD.gn
@@ -199,6 +199,8 @@
     "//chromeos/dbus/tpm_manager:tpm_manager_proto",
     "//chromeos/dbus/u2f",
     "//chromeos/dbus/u2f:u2f_proto",
+    "//chromeos/dbus/userdataauth",
+    "//chromeos/dbus/userdataauth:userdataauth_proto",
     "//chromeos/tpm:test_support",
     "//components/account_id",
     "//dbus",
@@ -226,6 +228,7 @@
     "pipe_reader_unittest.cc",
     "tpm_manager/tpm_manager_client_unittest.cc",
     "update_engine_client_unittest.cc",
+    "userdataauth/userdataauth_client_unittest.cc",
     "util/version_loader_unittest.cc",
   ]
 }
diff --git a/chromeos/dbus/userdataauth/BUILD.gn b/chromeos/dbus/userdataauth/BUILD.gn
new file mode 100644
index 0000000..9c9596d
--- /dev/null
+++ b/chromeos/dbus/userdataauth/BUILD.gn
@@ -0,0 +1,39 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
+
+component("userdataauth") {
+  output_name = "chromeos_dbus_userdataauth"
+
+  defines = [ "IS_USERDATAAUTH_CLIENT_IMPL" ]
+
+  deps = [
+    ":userdataauth_proto",
+    "//base",
+    "//chromeos/dbus:common",
+    "//components/policy/proto",
+    "//dbus",
+  ]
+
+  sources = [
+    "fake_userdataauth_client.cc",
+    "fake_userdataauth_client.h",
+    "userdataauth_client.cc",
+    "userdataauth_client.h",
+  ]
+}
+
+proto_library("userdataauth_proto") {
+  deps = [ "//chromeos/dbus/cryptohome:cryptohome_proto" ]
+
+  sources = [
+    "//third_party/cros_system_api/dbus/cryptohome/UserDataAuth.proto",
+    "//third_party/cros_system_api/dbus/cryptohome/fido.proto",
+  ]
+
+  proto_out_dir = "chromeos/dbus/cryptohome"
+}
diff --git a/chromeos/dbus/userdataauth/fake_userdataauth_client.cc b/chromeos/dbus/userdataauth/fake_userdataauth_client.cc
new file mode 100644
index 0000000..cc6fe75
--- /dev/null
+++ b/chromeos/dbus/userdataauth/fake_userdataauth_client.cc
@@ -0,0 +1,21 @@
+// 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 "chromeos/dbus/userdataauth/fake_userdataauth_client.h"
+
+#include "base/notreached.h"
+
+namespace chromeos {
+
+FakeUserDataAuthClient::FakeUserDataAuthClient() = default;
+
+FakeUserDataAuthClient::~FakeUserDataAuthClient() = default;
+
+void FakeUserDataAuthClient::IsMounted(
+    const ::user_data_auth::IsMountedRequest& request,
+    IsMountedCallback callback) {
+  NOTIMPLEMENTED();
+}
+
+}  // namespace chromeos
diff --git a/chromeos/dbus/userdataauth/fake_userdataauth_client.h b/chromeos/dbus/userdataauth/fake_userdataauth_client.h
new file mode 100644
index 0000000..844c398
--- /dev/null
+++ b/chromeos/dbus/userdataauth/fake_userdataauth_client.h
@@ -0,0 +1,32 @@
+// 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 CHROMEOS_DBUS_USERDATAAUTH_FAKE_USERDATAAUTH_CLIENT_H_
+#define CHROMEOS_DBUS_USERDATAAUTH_FAKE_USERDATAAUTH_CLIENT_H_
+
+#include "chromeos/dbus/userdataauth/userdataauth_client.h"
+
+#include "base/component_export.h"
+#include "chromeos/dbus/cryptohome/UserDataAuth.pb.h"
+
+namespace chromeos {
+
+class COMPONENT_EXPORT(USERDATAAUTH_CLIENT) FakeUserDataAuthClient
+    : public UserDataAuthClient {
+ public:
+  FakeUserDataAuthClient();
+  ~FakeUserDataAuthClient() override;
+
+  // Not copyable or movable.
+  FakeUserDataAuthClient(const FakeUserDataAuthClient&) = delete;
+  FakeUserDataAuthClient& operator=(const FakeUserDataAuthClient&) = delete;
+
+  // UserDataAuthClient override:
+  void IsMounted(const ::user_data_auth::IsMountedRequest& request,
+                 IsMountedCallback callback) override;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_DBUS_USERDATAAUTH_FAKE_USERDATAAUTH_CLIENT_H_
diff --git a/chromeos/dbus/userdataauth/userdataauth_client.cc b/chromeos/dbus/userdataauth/userdataauth_client.cc
new file mode 100644
index 0000000..c920357
--- /dev/null
+++ b/chromeos/dbus/userdataauth/userdataauth_client.cc
@@ -0,0 +1,176 @@
+// 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 "chromeos/dbus/userdataauth/userdataauth_client.h"
+
+#include <utility>
+
+#include <google/protobuf/message_lite.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "chromeos/dbus/userdataauth/fake_userdataauth_client.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_path.h"
+#include "dbus/object_proxy.h"
+#include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"
+
+namespace chromeos {
+namespace {
+
+// The default timeout for all userdataauth method call.
+// Note that it is known that cryptohomed could be slow to respond to calls
+// certain conditions, especially Mount(). D-Bus call blocking for as long as 2
+// minutes have been observed in testing conditions/CQ.
+constexpr int kUserDataAuthDefaultTimeoutMS = 5 * 60 * 1000;
+
+UserDataAuthClient* g_instance = nullptr;
+
+// Tries to parse a proto message from |response| into |proto|.
+// Returns false if |response| is nullptr or the message cannot be parsed.
+bool ParseProto(dbus::Response* response,
+                google::protobuf::MessageLite* proto) {
+  if (!response) {
+    LOG(ERROR) << "Failed to call cryptohomed";
+    return false;
+  }
+
+  dbus::MessageReader reader(response);
+  if (!reader.PopArrayOfBytesAsProto(proto)) {
+    LOG(ERROR) << "Failed to parse response message from cryptohomed";
+    return false;
+  }
+
+  return true;
+}
+
+// "Real" implementation of UserDataAuthClient talking to the cryptohomed's
+// UserDataAuth interface on the Chrome OS side.
+class UserDataAuthClientImpl : public UserDataAuthClient {
+ public:
+  UserDataAuthClientImpl() = default;
+  ~UserDataAuthClientImpl() override = default;
+
+  // Not copyable or movable.
+  UserDataAuthClientImpl(const UserDataAuthClientImpl&) = delete;
+  UserDataAuthClientImpl& operator=(const UserDataAuthClientImpl&) = delete;
+
+  void Init(dbus::Bus* bus) {
+    proxy_ = bus->GetObjectProxy(
+        ::user_data_auth::kUserDataAuthServiceName,
+        dbus::ObjectPath(::user_data_auth::kUserDataAuthServicePath));
+  }
+
+  // UserDataAuthClient override:
+  void IsMounted(const ::user_data_auth::IsMountedRequest& request,
+                 IsMountedCallback callback) override {
+    CallProtoMethod(::user_data_auth::kIsMounted,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+ private:
+  // Calls cryptohomed's |method_name| method in |interface_name| interface,
+  // passing in |request| as input with |timeout_ms|. Once the (asynchronous)
+  // call finishes, |callback| is called with the response proto.
+  template <typename RequestType, typename ReplyType>
+  void CallProtoMethodWithTimeout(const char* method_name,
+                                  const char* interface_name,
+                                  int timeout_ms,
+                                  const RequestType& request,
+                                  DBusMethodCallback<ReplyType> callback) {
+    dbus::MethodCall method_call(interface_name, method_name);
+    dbus::MessageWriter writer(&method_call);
+    if (!writer.AppendProtoAsArrayOfBytes(request)) {
+      LOG(ERROR)
+          << "Failed to append protobuf when calling UserDataAuth method "
+          << method_name;
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
+      return;
+    }
+    // Bind with the weak pointer of |this| so the response is not
+    // handled once |this| is already destroyed.
+    proxy_->CallMethod(
+        &method_call, timeout_ms,
+        base::BindOnce(&UserDataAuthClientImpl::HandleResponse<ReplyType>,
+                       weak_factory_.GetWeakPtr(), std::move(callback)));
+  }
+
+  // Calls cryptohomed's |method_name| method in |interface_name| interface,
+  // passing in |request| as input with the default UserDataAuth timeout. Once
+  // the (asynchronous) call finishes, |callback| is called with the response
+  // proto.
+  template <typename RequestType, typename ReplyType>
+  void CallProtoMethod(const char* method_name,
+                       const char* interface_name,
+                       const RequestType& request,
+                       DBusMethodCallback<ReplyType> callback) {
+    CallProtoMethodWithTimeout(method_name, interface_name,
+                               kUserDataAuthDefaultTimeoutMS, request,
+                               std::move(callback));
+  }
+
+  // Parses the response proto message from |response| and calls |callback| with
+  // the decoded message. Calls |callback| with std::nullopt on error, including
+  // timeout.
+  template <typename ReplyType>
+  void HandleResponse(DBusMethodCallback<ReplyType> callback,
+                      dbus::Response* response) {
+    ReplyType reply_proto;
+    if (!ParseProto(response, &reply_proto)) {
+      LOG(ERROR) << "Failed to parse reply protobuf from UserDataAuth method";
+      std::move(callback).Run(base::nullopt);
+      return;
+    }
+    std::move(callback).Run(reply_proto);
+  }
+
+  // D-Bus proxy for cryptohomed, not owned.
+  dbus::ObjectProxy* proxy_ = nullptr;
+
+  base::WeakPtrFactory<UserDataAuthClientImpl> weak_factory_{this};
+};
+
+}  // namespace
+
+UserDataAuthClient::UserDataAuthClient() {
+  CHECK(!g_instance);
+  g_instance = this;
+}
+
+UserDataAuthClient::~UserDataAuthClient() {
+  CHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
+
+// static
+void UserDataAuthClient::Initialize(dbus::Bus* bus) {
+  CHECK(bus);
+  (new UserDataAuthClientImpl())->Init(bus);
+}
+
+// static
+void UserDataAuthClient::InitializeFake() {
+  new FakeUserDataAuthClient();
+}
+
+// static
+void UserDataAuthClient::Shutdown() {
+  CHECK(g_instance);
+  delete g_instance;
+  // The destructor resets |g_instance|.
+  DCHECK(!g_instance);
+}
+
+// static
+UserDataAuthClient* UserDataAuthClient::Get() {
+  return g_instance;
+}
+
+}  // namespace chromeos
diff --git a/chromeos/dbus/userdataauth/userdataauth_client.h b/chromeos/dbus/userdataauth/userdataauth_client.h
new file mode 100644
index 0000000..fd5770d0
--- /dev/null
+++ b/chromeos/dbus/userdataauth/userdataauth_client.h
@@ -0,0 +1,57 @@
+// 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 CHROMEOS_DBUS_USERDATAAUTH_USERDATAAUTH_CLIENT_H_
+#define CHROMEOS_DBUS_USERDATAAUTH_USERDATAAUTH_CLIENT_H_
+
+#include "base/callback.h"
+#include "base/component_export.h"
+#include "chromeos/dbus/cryptohome/UserDataAuth.pb.h"
+#include "chromeos/dbus/dbus_method_call_status.h"
+
+namespace dbus {
+class Bus;
+}
+
+namespace chromeos {
+
+// UserDataAuthClient is used to communicate with the org.chromium.UserDataAuth
+// service exposed by cryptohomed. All method should be called from the origin
+// thread (UI thread) which initializes the DBusThreadManager instance.
+class COMPONENT_EXPORT(USERDATAAUTH_CLIENT) UserDataAuthClient {
+ public:
+  using IsMountedCallback =
+      DBusMethodCallback<::user_data_auth::IsMountedReply>;
+
+  // Not copyable or movable.
+  UserDataAuthClient(const UserDataAuthClient&) = delete;
+  UserDataAuthClient& operator=(const UserDataAuthClient&) = delete;
+
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
+
+  // Creates and initializes a fake global instance if not already created.
+  static void InitializeFake();
+
+  // Destroys the global instance.
+  static void Shutdown();
+
+  // Returns the global instance which may be null if not initialized.
+  static UserDataAuthClient* Get();
+
+  // Actual DBus Methods:
+
+  // Queries if user's vault is mounted.
+  virtual void IsMounted(const ::user_data_auth::IsMountedRequest& request,
+                         IsMountedCallback callback) = 0;
+
+ protected:
+  // Initialize/Shutdown should be used instead.
+  UserDataAuthClient();
+  virtual ~UserDataAuthClient();
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_DBUS_USERDATAAUTH_USERDATAAUTH_CLIENT_H_
diff --git a/chromeos/dbus/userdataauth/userdataauth_client_unittest.cc b/chromeos/dbus/userdataauth/userdataauth_client_unittest.cc
new file mode 100644
index 0000000..cc258e7
--- /dev/null
+++ b/chromeos/dbus/userdataauth/userdataauth_client_unittest.cc
@@ -0,0 +1,148 @@
+// 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 "chromeos/dbus/userdataauth/userdataauth_client.h"
+
+#include <string>
+#include <utility>
+
+#include "base/test/task_environment.h"
+#include "chromeos/dbus/cryptohome/UserDataAuth.pb.h"
+#include "dbus/mock_bus.h"
+#include "dbus/mock_object_proxy.h"
+#include "dbus/object_path.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+namespace chromeos {
+
+namespace {
+
+// Runs |callback| with |response|. Needed due to ResponseCallback expecting a
+// bare pointer rather than an std::unique_ptr.
+void RunResponseCallback(dbus::ObjectProxy::ResponseCallback callback,
+                         std::unique_ptr<dbus::Response> response) {
+  std::move(callback).Run(response.get());
+}
+
+}  // namespace
+
+class UserDataAuthClientTest : public testing::Test {
+ public:
+  UserDataAuthClientTest() = default;
+  ~UserDataAuthClientTest() override = default;
+
+  void SetUp() override {
+    dbus::Bus::Options options;
+    options.bus_type = dbus::Bus::SYSTEM;
+    bus_ = new dbus::MockBus(options);
+
+    dbus::ObjectPath userdataauth_object_path =
+        dbus::ObjectPath(::user_data_auth::kUserDataAuthServicePath);
+    proxy_ = new dbus::MockObjectProxy(
+        bus_.get(), ::user_data_auth::kUserDataAuthServiceName,
+        userdataauth_object_path);
+
+    // Makes sure `GetObjectProxy()` is caled with the correct service name and
+    // path.
+    EXPECT_CALL(*bus_.get(),
+                GetObjectProxy(::user_data_auth::kUserDataAuthServiceName,
+                               userdataauth_object_path))
+        .WillRepeatedly(Return(proxy_.get()));
+
+    EXPECT_CALL(*proxy_.get(), DoCallMethod(_, _, _))
+        .WillRepeatedly(Invoke(this, &UserDataAuthClientTest::OnCallMethod));
+
+    UserDataAuthClient::Initialize(bus_.get());
+
+    // Execute callbacks posted by `client_->Init()`.
+    base::RunLoop().RunUntilIdle();
+
+    client_ = UserDataAuthClient::Get();
+  }
+
+  void TearDown() override { UserDataAuthClient::Shutdown(); }
+
+ protected:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+
+  // Mock bus and proxy for simulating calls.
+  scoped_refptr<dbus::MockBus> bus_;
+  scoped_refptr<dbus::MockObjectProxy> proxy_;
+
+  // Convenience pointer to the global instance.
+  UserDataAuthClient* client_;
+
+  // The expected replies to the respective D-Bus calls.
+  ::user_data_auth::IsMountedReply expected_is_mounted_reply_;
+
+  // When it is set `true`, an invalid array of bytes that cannot be parsed will
+  // be the response.
+  bool shall_message_parsing_fail_ = false;
+
+ private:
+  // Handles calls to |proxy_|'s `CallMethod()`.
+  void OnCallMethod(dbus::MethodCall* method_call,
+                    int timeout_ms,
+                    dbus::ObjectProxy::ResponseCallback* callback) {
+    std::unique_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+    dbus::MessageWriter writer(response.get());
+    if (shall_message_parsing_fail_) {
+      // 0x02 => Field 0, Type String
+      // (0xFF)*6 => Varint, the size of the string, it is not terminated and is
+      // a very large value so the parsing will fail.
+      constexpr uint8_t invalid_protobuf[] = {0x02, 0xFF, 0xFF, 0xFF,
+                                              0xFF, 0xFF, 0xFF};
+      writer.AppendArrayOfBytes(invalid_protobuf, sizeof(invalid_protobuf));
+    } else if (method_call->GetMember() == ::user_data_auth::kIsMounted) {
+      writer.AppendProtoAsArrayOfBytes(expected_is_mounted_reply_);
+    } else {
+      ASSERT_FALSE(true) << "Unrecognized member: " << method_call->GetMember();
+    }
+    task_environment_.GetMainThreadTaskRunner()->PostTask(
+        FROM_HERE, base::BindOnce(RunResponseCallback, std::move(*callback),
+                                  std::move(response)));
+  }
+};
+
+TEST_F(UserDataAuthClientTest, IsMounted) {
+  expected_is_mounted_reply_.set_is_mounted(true);
+  expected_is_mounted_reply_.set_is_ephemeral_mount(false);
+  base::Optional<::user_data_auth::IsMountedReply> result_reply = base::nullopt;
+  auto callback = base::BindOnce(
+      [](base::Optional<::user_data_auth::IsMountedReply>* result_reply,
+         base::Optional<::user_data_auth::IsMountedReply> reply) {
+        *result_reply = reply;
+      },
+      &result_reply);
+
+  client_->IsMounted(::user_data_auth::IsMountedRequest(), std::move(callback));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_NE(result_reply, base::nullopt);
+  EXPECT_TRUE(result_reply.value().is_mounted());
+  EXPECT_FALSE(result_reply.value().is_ephemeral_mount());
+}
+
+TEST_F(UserDataAuthClientTest, IsMountedInvalidProtobuf) {
+  shall_message_parsing_fail_ = true;
+  base::Optional<::user_data_auth::IsMountedReply> result_reply =
+      ::user_data_auth::IsMountedReply();
+  auto callback = base::BindOnce(
+      [](base::Optional<::user_data_auth::IsMountedReply>* result_reply,
+         base::Optional<::user_data_auth::IsMountedReply> reply) {
+        *result_reply = reply;
+      },
+      &result_reply);
+
+  client_->IsMounted(::user_data_auth::IsMountedRequest(), std::move(callback));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(result_reply, base::nullopt);
+}
+
+}  // namespace chromeos
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index ccc7d4df..ac8dc5e 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -342,26 +342,10 @@
 
 void AssistantManagerServiceImpl::UpdateInternalMediaPlayerStatus(
     MediaSessionAction action) {
-  switch (action) {
-    case MediaSessionAction::kPause:
-      media_host_->PauseInternalMediaPlayer();
-      break;
-    case MediaSessionAction::kPlay:
-      media_host_->ResumeInternalMediaPlayer();
-      break;
-    case MediaSessionAction::kPreviousTrack:
-    case MediaSessionAction::kNextTrack:
-    case MediaSessionAction::kSeekBackward:
-    case MediaSessionAction::kSeekForward:
-    case MediaSessionAction::kSkipAd:
-    case MediaSessionAction::kStop:
-    case MediaSessionAction::kSeekTo:
-    case MediaSessionAction::kScrubTo:
-    case MediaSessionAction::kEnterPictureInPicture:
-    case MediaSessionAction::kExitPictureInPicture:
-    case MediaSessionAction::kSwitchAudioDevice:
-      NOTIMPLEMENTED();
-      break;
+  if (action == MediaSessionAction::kPause) {
+    media_host_->PauseInternalMediaPlayer();
+  } else if (action == MediaSessionAction::kPlay) {
+    media_host_->ResumeInternalMediaPlayer();
   }
 }
 
@@ -515,46 +499,6 @@
   receive_url_response_ = url.spec();
 }
 
-void AssistantManagerServiceImpl::OnShowNotification(
-    const action::Notification& notification) {
-  ENSURE_MAIN_THREAD(&AssistantManagerServiceImpl::OnShowNotification,
-                     notification);
-
-  AssistantNotification assistant_notification;
-  assistant_notification.title = notification.title;
-  assistant_notification.message = notification.text;
-  assistant_notification.action_url = GURL(notification.action_url);
-  assistant_notification.client_id = notification.notification_id;
-  assistant_notification.server_id = notification.notification_id;
-  assistant_notification.consistency_token = notification.consistency_token;
-  assistant_notification.opaque_token = notification.opaque_token;
-  assistant_notification.grouping_key = notification.grouping_key;
-  assistant_notification.obfuscated_gaia_id = notification.obfuscated_gaia_id;
-  assistant_notification.from_server = true;
-
-  if (notification.expiry_timestamp_ms) {
-    assistant_notification.expiry_time =
-        base::Time::FromJavaTime(notification.expiry_timestamp_ms);
-  }
-
-  // The server sometimes sends an empty |notification_id|, but our client
-  // requires a non-empty |client_id| for notifications. Known instances in
-  // which the server sends an empty |notification_id| are for Reminders.
-  if (assistant_notification.client_id.empty()) {
-    assistant_notification.client_id =
-        base::UnguessableToken::Create().ToString();
-  }
-
-  for (const auto& button : notification.buttons) {
-    assistant_notification.buttons.push_back(
-        {button.label, GURL(button.action_url),
-         /*remove_notification_on_click=*/true});
-  }
-
-  assistant_notification_controller()->AddOrUpdateNotification(
-      std::move(assistant_notification));
-}
-
 void AssistantManagerServiceImpl::OnVerifyAndroidApp(
     const std::vector<AndroidAppInfo>& apps_info,
     const InteractionInfo& interaction) {
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index f318076..42e16dc 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -169,7 +169,6 @@
   GetPendingNotificationDelegate() override;
 
   // AssistantActionObserver overrides:
-  void OnShowNotification(const action::Notification& notification) override;
   void OnVerifyAndroidApp(const std::vector<AndroidAppInfo>& apps_info,
                           const InteractionInfo& interaction) override;
 
diff --git a/chromeos/services/assistant/media_host_unittest.cc b/chromeos/services/assistant/media_host_unittest.cc
index 97d9cad..02fdad16 100644
--- a/chromeos/services/assistant/media_host_unittest.cc
+++ b/chromeos/services/assistant/media_host_unittest.cc
@@ -95,6 +95,9 @@
   MOCK_METHOD(void, EnterPictureInPicture, ());
   MOCK_METHOD(void, ExitPictureInPicture, ());
   MOCK_METHOD(void, SetAudioSinkId, (const base::Optional<std::string>& id));
+  MOCK_METHOD(void, ToggleMicrophone, ());
+  MOCK_METHOD(void, ToggleCamera, ());
+  MOCK_METHOD(void, HangUp, ());
   void AddObserver(
       mojo::PendingRemote<media_session::mojom::MediaControllerObserver> remote)
       override {
diff --git a/chromeos/services/assistant/media_session/assistant_media_session.h b/chromeos/services/assistant/media_session/assistant_media_session.h
index 9c6e2b9..5c6597eb 100644
--- a/chromeos/services/assistant/media_session/assistant_media_session.h
+++ b/chromeos/services/assistant/media_session/assistant_media_session.h
@@ -54,6 +54,9 @@
   void EnterPictureInPicture() override {}
   void ExitPictureInPicture() override {}
   void SetAudioSinkId(const base::Optional<std::string>& sink_id) override {}
+  void ToggleMicrophone() override {}
+  void ToggleCamera() override {}
+  void HangUp() override {}
 
   // Requests/abandons audio focus to the AudioFocusManager.
   void RequestAudioFocus(media_session::mojom::AudioFocusType audio_focus_type);
diff --git a/chromeos/services/libassistant/conversation_controller.cc b/chromeos/services/libassistant/conversation_controller.cc
index eb8a138c..3f588213 100644
--- a/chromeos/services/libassistant/conversation_controller.cc
+++ b/chromeos/services/libassistant/conversation_controller.cc
@@ -54,6 +54,42 @@
 
   return result;
 }
+
+// Helper function to convert |action::Notification| to |AssistantNotification|.
+chromeos::assistant::AssistantNotification ToAssistantNotification(
+    const assistant::action::Notification& notification) {
+  chromeos::assistant::AssistantNotification assistant_notification;
+  assistant_notification.title = notification.title;
+  assistant_notification.message = notification.text;
+  assistant_notification.action_url = GURL(notification.action_url);
+  assistant_notification.client_id = notification.notification_id;
+  assistant_notification.server_id = notification.notification_id;
+  assistant_notification.consistency_token = notification.consistency_token;
+  assistant_notification.opaque_token = notification.opaque_token;
+  assistant_notification.grouping_key = notification.grouping_key;
+  assistant_notification.obfuscated_gaia_id = notification.obfuscated_gaia_id;
+  assistant_notification.from_server = true;
+
+  if (notification.expiry_timestamp_ms) {
+    assistant_notification.expiry_time =
+        base::Time::FromJavaTime(notification.expiry_timestamp_ms);
+  }
+
+  // The server sometimes sends an empty |notification_id|, but our client
+  // requires a non-empty |client_id| for notifications. Known instances in
+  // which the server sends an empty |notification_id| are for Reminders.
+  if (assistant_notification.client_id.empty()) {
+    assistant_notification.client_id =
+        base::UnguessableToken::Create().ToString();
+  }
+
+  for (const auto& button : notification.buttons) {
+    assistant_notification.buttons.push_back(
+        {button.label, GURL(button.action_url),
+         /*remove_notification_on_click=*/true});
+  }
+  return assistant_notification;
+}
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -267,7 +303,7 @@
 }
 
 void ConversationController::RetrieveNotification(
-    const AssistantNotification& notification,
+    AssistantNotification notification,
     int32_t action_index) {
   const std::string request_interaction =
       assistant::SerializeNotificationRequestInteraction(
@@ -280,7 +316,7 @@
 }
 
 void ConversationController::DismissNotification(
-    const AssistantNotification& notification) {
+    AssistantNotification notification) {
   // |assistant_manager_internal()| may not exist if we are dismissing
   // notifications as part of a shutdown sequence.
   if (!assistant_manager_internal())
@@ -410,6 +446,16 @@
     observer->OnWaitStarted();
 }
 
+// Called from Libassistant thread.
+void ConversationController::OnShowNotification(
+    const assistant::action::Notification& notification) {
+  ENSURE_MOJOM_THREAD(&ConversationController::OnShowNotification,
+                      notification);
+
+  notification_delegate_->AddOrUpdateNotification(
+      ToAssistantNotification(notification));
+}
+
 void ConversationController::SendVoicelessInteraction(
     const std::string& interaction,
     const std::string& description,
diff --git a/chromeos/services/libassistant/conversation_controller.h b/chromeos/services/libassistant/conversation_controller.h
index ae5d8dc..2c716ed 100644
--- a/chromeos/services/libassistant/conversation_controller.h
+++ b/chromeos/services/libassistant/conversation_controller.h
@@ -66,9 +66,9 @@
                      AssistantQuerySource source,
                      bool allow_tts) override;
   void StartEditReminderInteraction(const std::string& client_id) override;
-  void RetrieveNotification(const AssistantNotification& notification,
+  void RetrieveNotification(AssistantNotification notification,
                             int32_t action_index) override;
-  void DismissNotification(const AssistantNotification& notification) override;
+  void DismissNotification(AssistantNotification notification) override;
   void SendAssistantFeedback(const AssistantFeedback& feedback) override;
   void AddRemoteObserver(
       mojo::PendingRemote<mojom::ConversationObserver> observer) override;
@@ -85,6 +85,8 @@
       const chromeos::assistant::AndroidAppInfo& app_info,
       const chromeos::assistant::InteractionInfo& interaction) override;
   void OnScheduleWait(int id, int time_ms) override;
+  void OnShowNotification(
+      const assistant::action::Notification& notification) override;
 
   const mojo::RemoteSet<mojom::ConversationObserver>* conversation_observers() {
     return &observers_;
diff --git a/chromeos/services/libassistant/notification_delegate_unittest.cc b/chromeos/services/libassistant/notification_delegate_unittest.cc
index 5668ef3..1459c92b 100644
--- a/chromeos/services/libassistant/notification_delegate_unittest.cc
+++ b/chromeos/services/libassistant/notification_delegate_unittest.cc
@@ -3,9 +3,10 @@
 // found in the LICENSE file.
 
 #include "base/test/task_environment.h"
+#include "chromeos/assistant/internal/action/cros_action_module.h"
 #include "chromeos/assistant/internal/test_support/fake_assistant_manager_internal.h"
+#include "chromeos/services/libassistant/public/cpp/assistant_notification.h"
 #include "chromeos/services/libassistant/public/mojom/notification_delegate.mojom-forward.h"
-#include "chromeos/services/libassistant/public/mojom/notification_delegate.mojom.h"
 #include "chromeos/services/libassistant/test_support/libassistant_service_tester.h"
 #include "libassistant/shared/internal_api/assistant_manager_delegate.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -23,6 +24,9 @@
   ~NotificationDelegateMock() override = default;
 
   // mojom::NotificationDelegate implementation:
+  MOCK_METHOD(void,
+              AddOrUpdateNotification,
+              (assistant::AssistantNotification notification));
   MOCK_METHOD(void, RemoveAllNotifications, (bool from_server));
   MOCK_METHOD(void,
               RemoveNotificationByGroupingKey,
@@ -40,6 +44,32 @@
   mojo::Receiver<mojom::NotificationDelegate> receiver_{this};
 };
 
+// Helper class to fire interaction response handlers for tests.
+// TODO(meilinw): move it to a separate class since it is used both here and
+// by |ConversationObserverTest|.
+class CrosActionModuleHelper {
+ public:
+  explicit CrosActionModuleHelper(
+      assistant::action::CrosActionModule* action_module)
+      : action_module_(*action_module) {}
+  CrosActionModuleHelper(const CrosActionModuleHelper&) = delete;
+  CrosActionModuleHelper& operator=(const CrosActionModuleHelper&) = delete;
+  ~CrosActionModuleHelper() = default;
+
+  void ShowNotification(const assistant::action::Notification& notification) {
+    for (auto* observer : action_observers())
+      observer->OnShowNotification(notification);
+  }
+
+ private:
+  const std::vector<assistant::action::AssistantActionObserver*>&
+  action_observers() {
+    return action_module_.GetActionObserversForTesting();
+  }
+
+  const assistant::action::CrosActionModule& action_module_;
+};
+
 }  // namespace
 
 class NotificationDelegateTest : public ::testing::Test {
@@ -53,6 +83,9 @@
     delegate_mock_.Bind(
         service_tester_.GetNotificationDelegatePendingReceiver());
     service_tester_.Start();
+    action_module_helper_ = std::make_unique<CrosActionModuleHelper>(
+        static_cast<assistant::action::CrosActionModule*>(
+            service_tester_.assistant_manager_internal().action_module()));
   }
 
   assistant_client::AssistantManagerDelegate& assistant_manager_delegate() {
@@ -62,12 +95,52 @@
 
   NotificationDelegateMock& delegate_mock() { return delegate_mock_; }
 
+  CrosActionModuleHelper& action_module_helper() {
+    return *action_module_helper_.get();
+  }
+
  private:
   base::test::SingleThreadTaskEnvironment environment_;
   ::testing::StrictMock<NotificationDelegateMock> delegate_mock_;
   LibassistantServiceTester service_tester_;
+  std::unique_ptr<CrosActionModuleHelper> action_module_helper_;
 };
 
+TEST_F(NotificationDelegateTest, ShouldInvokeAddOrUpdateNotification) {
+  assistant::action::Notification input_notification{
+      /*title=*/"title",
+      /*text=*/"text",
+      /*action_url=*/"https://action-url/",
+      /*notification_id=*/"notification_id",
+      /*consistency_token=*/"consistency_token",
+      /*opaque_token=*/"opaque_token",
+      /*grouping_key=*/"grouping_key",
+      /*obfuscated_gaia_id=*/"obfuscated_gaia_id",
+      /*expiry_timestamp_ms=*/100,
+  };
+
+  EXPECT_CALL(delegate_mock(), AddOrUpdateNotification)
+      .WillOnce(testing::Invoke(
+          [&](const assistant::AssistantNotification& output_notification) {
+            EXPECT_EQ("title", output_notification.title);
+            EXPECT_EQ("text", output_notification.message);
+            EXPECT_EQ("notification_id", output_notification.server_id);
+            EXPECT_EQ("notification_id", output_notification.client_id);
+            EXPECT_EQ("consistency_token",
+                      output_notification.consistency_token);
+            EXPECT_EQ("opaque_token", output_notification.opaque_token);
+            EXPECT_EQ("grouping_key", output_notification.grouping_key);
+            EXPECT_EQ("obfuscated_gaia_id",
+                      output_notification.obfuscated_gaia_id);
+            EXPECT_EQ(base::Time::FromJavaTime(100),
+                      output_notification.expiry_time);
+            EXPECT_EQ(true, output_notification.from_server);
+          }));
+
+  action_module_helper().ShowNotification(input_notification);
+  delegate_mock().FlushForTesting();
+}
+
 TEST_F(NotificationDelegateTest, ShouldInvokeRemoveAllNotifications) {
   EXPECT_CALL(delegate_mock(), RemoveAllNotifications(/*from_server=*/true));
 
diff --git a/chromeos/services/libassistant/public/mojom/BUILD.gn b/chromeos/services/libassistant/public/mojom/BUILD.gn
index d973200..054b678 100644
--- a/chromeos/services/libassistant/public/mojom/BUILD.gn
+++ b/chromeos/services/libassistant/public/mojom/BUILD.gn
@@ -9,6 +9,7 @@
 
   sources = [
     "android_app_info.mojom",
+    "assistant_notification.mojom",
     "audio_input_controller.mojom",
     "audio_output_delegate.mojom",
     "authentication_state_observer.mojom",
@@ -51,6 +52,7 @@
         {
           mojom = "chromeos.libassistant.mojom.AssistantNotification"
           cpp = "::chromeos::assistant::AssistantNotification"
+          move_only = true
         },
         {
           mojom = "chromeos.libassistant.mojom.AssistantFeedback"
diff --git a/chromeos/services/libassistant/public/mojom/assistant_notification.mojom b/chromeos/services/libassistant/public/mojom/assistant_notification.mojom
new file mode 100644
index 0000000..5eab3b2
--- /dev/null
+++ b/chromeos/services/libassistant/public/mojom/assistant_notification.mojom
@@ -0,0 +1,63 @@
+// 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.
+
+module chromeos.libassistant.mojom;
+
+import "url/mojom/url.mojom";
+import "mojo/public/mojom/base/time.mojom";
+
+// Models an Assistant notification. This should be kept in sync with
+// |chromeos::assistant::AssistantNotification|. For now we only include
+// entries that are currently in use.
+struct AssistantNotification {
+  // Title of the notification.
+  string title;
+
+  // Body text of the notification.
+  string message;
+
+  // Optional URL to open when the tap action is invoked on the notification
+  // main UI.
+  url.mojom.Url action_url;
+
+  // An id that uniquely identifies a notification on the client.
+  string client_id;
+
+  // An id that uniquely identifies a notification on the server.
+  string server_id;
+
+  // Used to fetch notification contents.
+  string consistency_token;
+  string opaque_token;
+
+  // Key that can be used to group multiple notifications together.
+  string grouping_key;
+
+  // Obfuscated Gaia id of the intended recipient of the user.
+  string obfuscated_gaia_id;
+
+  // When the notification should expire.
+  // Expressed as milliseconds since Unix Epoch.
+  mojo_base.mojom.Time? expiry_time;
+
+  // List of buttons in the notification.
+  array<AssistantNotificationButton> buttons;
+
+  // If |true|, this notification was sent from the server. Clicking a
+  // notification that was sent from the server may result in a request to the
+  // server to retrieve the notification payload. One example of this would be
+  // notifications triggered by an Assistant reminder.
+  bool from_server = false;
+};
+
+struct AssistantNotificationButton {
+  // Display text of the button.
+  string label;
+
+  // Optional URL to open when the tap action is invoked on the button.
+  url.mojom.Url action_url;
+
+  // If |true|, the associated notification will be removed on button click.
+  bool remove_notification_on_click = true;
+};
diff --git a/chromeos/services/libassistant/public/mojom/conversation_controller.mojom b/chromeos/services/libassistant/public/mojom/conversation_controller.mojom
index 7c0cc707..2d47e0b 100644
--- a/chromeos/services/libassistant/public/mojom/conversation_controller.mojom
+++ b/chromeos/services/libassistant/public/mojom/conversation_controller.mojom
@@ -5,6 +5,8 @@
 module chromeos.libassistant.mojom;
 
 import "chromeos/services/libassistant/public/mojom/conversation_observer.mojom";
+import "chromeos/services/libassistant/public/mojom/notification_delegate.mojom";
+import "chromeos/services/libassistant/public/mojom/assistant_notification.mojom";
 
 // Interface for controller supporting conversation related functionalities.
 interface ConversationController {
@@ -38,24 +40,6 @@
   AddRemoteObserver(pending_remote<ConversationObserver> observer);
 };
 
-// Models an Assistant notification. This should be kept in sync with
-// |chromeos::assistant::AssistantNotification|. For now we only include
-// entries that are currently in use.
-struct AssistantNotification {
-  // An id that uniquely identifies a notification on the server.
-  string server_id;
-
-  // Used to fetch notification contents.
-  string consistency_token;
-  string opaque_token;
-
-  // Key that can be used to group multiple notifications together.
-  string grouping_key;
-
-  // Obfuscated Gaia id of the intended recipient of the user.
-  string obfuscated_gaia_id;
-};
-
 // Models an Assistant feedback. This should be kept in sync with
 // |chromeos::assistant::AssistantFeedback|.
 struct AssistantFeedback {
diff --git a/chromeos/services/libassistant/public/mojom/mojom_traits.cc b/chromeos/services/libassistant/public/mojom/mojom_traits.cc
index 44ed3faf..8f81340a 100644
--- a/chromeos/services/libassistant/public/mojom/mojom_traits.cc
+++ b/chromeos/services/libassistant/public/mojom/mojom_traits.cc
@@ -3,10 +3,11 @@
 // found in the LICENSE file.
 
 #include "chromeos/services/libassistant/public/mojom/mojom_traits.h"
-#include "mojo/public/cpp/base/time_mojom_traits.h"
 
 #include "base/notreached.h"
+#include "mojo/public/cpp/base/time_mojom_traits.h"
 #include "mojo/public/cpp/base/unguessable_token_mojom_traits.h"
+#include "url/gurl.h"
 #include "url/mojom/url_gurl_mojom_traits.h"
 
 namespace mojo {
@@ -28,11 +29,13 @@
 using chromeos::assistant::AssistantFeedback;
 using chromeos::assistant::AssistantInteractionMetadata;
 using chromeos::assistant::AssistantNotification;
+using chromeos::assistant::AssistantNotificationButton;
 using chromeos::assistant::AssistantSuggestion;
 using chromeos::assistant::AssistantTimer;
 using chromeos::libassistant::mojom::AndroidAppInfoDataView;
 using chromeos::libassistant::mojom::AssistantFeedbackDataView;
 using chromeos::libassistant::mojom::AssistantInteractionMetadataDataView;
+using chromeos::libassistant::mojom::AssistantNotificationButtonDataView;
 using chromeos::libassistant::mojom::AssistantNotificationDataView;
 using chromeos::libassistant::mojom::AssistantSuggestionDataView;
 using chromeos::libassistant::mojom::AssistantTimerDataView;
@@ -141,6 +144,30 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 const std::string&
+StructTraits<AssistantNotificationDataView, AssistantNotification>::title(
+    const AssistantNotification& input) {
+  return input.title;
+}
+
+const std::string&
+StructTraits<AssistantNotificationDataView, AssistantNotification>::message(
+    const AssistantNotification& input) {
+  return input.message;
+}
+
+const GURL&
+StructTraits<AssistantNotificationDataView, AssistantNotification>::action_url(
+    const AssistantNotification& input) {
+  return input.action_url;
+}
+
+const std::string&
+StructTraits<AssistantNotificationDataView, AssistantNotification>::client_id(
+    const AssistantNotification& input) {
+  return input.client_id;
+}
+
+const std::string&
 StructTraits<AssistantNotificationDataView, AssistantNotification>::server_id(
     const AssistantNotification& input) {
   return input.server_id;
@@ -170,9 +197,34 @@
   return input.obfuscated_gaia_id;
 }
 
+const base::Optional<base::Time>&
+StructTraits<AssistantNotificationDataView, AssistantNotification>::expiry_time(
+    const AssistantNotification& input) {
+  return input.expiry_time;
+}
+
+const std::vector<AssistantNotificationButton>&
+StructTraits<AssistantNotificationDataView, AssistantNotification>::buttons(
+    const AssistantNotification& input) {
+  return input.buttons;
+}
+
+bool StructTraits<AssistantNotificationDataView, AssistantNotification>::
+    from_server(const AssistantNotification& input) {
+  return input.from_server;
+}
+
 bool StructTraits<AssistantNotificationDataView, AssistantNotification>::Read(
     chromeos::libassistant::mojom::AssistantNotificationDataView data,
     AssistantNotification* output) {
+  if (!data.ReadTitle(&output->title))
+    return false;
+  if (!data.ReadMessage(&output->message))
+    return false;
+  if (!data.ReadActionUrl(&output->action_url))
+    return false;
+  if (!data.ReadClientId(&output->client_id))
+    return false;
   if (!data.ReadServerId(&output->server_id))
     return false;
   if (!data.ReadConsistencyToken(&output->consistency_token))
@@ -183,6 +235,46 @@
     return false;
   if (!data.ReadObfuscatedGaiaId(&output->obfuscated_gaia_id))
     return false;
+  if (!data.ReadExpiryTime(&output->expiry_time))
+    return false;
+  if (!data.ReadButtons(&output->buttons))
+    return false;
+  output->from_server = data.from_server();
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AssistantNotificationButton
+////////////////////////////////////////////////////////////////////////////////
+
+const std::string&
+StructTraits<AssistantNotificationButtonDataView, AssistantNotificationButton>::
+    label(const AssistantNotificationButton& input) {
+  return input.label;
+}
+
+const GURL&
+StructTraits<AssistantNotificationButtonDataView, AssistantNotificationButton>::
+    action_url(const AssistantNotificationButton& input) {
+  return input.action_url;
+}
+
+bool StructTraits<AssistantNotificationButtonDataView,
+                  AssistantNotificationButton>::
+    remove_notification_on_click(const AssistantNotificationButton& input) {
+  return input.remove_notification_on_click;
+}
+
+bool StructTraits<
+    AssistantNotificationButtonDataView,
+    AssistantNotificationButton>::Read(AssistantNotificationButtonDataView data,
+                                       AssistantNotificationButton* output) {
+  if (!data.ReadLabel(&output->label))
+    return false;
+  if (!data.ReadActionUrl(&output->action_url))
+    return false;
+  output->remove_notification_on_click = data.remove_notification_on_click();
+
   return true;
 }
 
diff --git a/chromeos/services/libassistant/public/mojom/mojom_traits.h b/chromeos/services/libassistant/public/mojom/mojom_traits.h
index c0c9102e..032b0d0d 100644
--- a/chromeos/services/libassistant/public/mojom/mojom_traits.h
+++ b/chromeos/services/libassistant/public/mojom/mojom_traits.h
@@ -6,8 +6,10 @@
 #define CHROMEOS_SERVICES_LIBASSISTANT_PUBLIC_MOJOM_MOJOM_TRAITS_H_
 
 #include <cstdint>
+#include <vector>
 
 #include "base/containers/span.h"
+#include "base/optional.h"
 #include "base/time/time.h"
 #include "chromeos/services/assistant/public/cpp/assistant_enums.h"
 #include "chromeos/services/libassistant/public/cpp/android_app_info.h"
@@ -57,6 +59,10 @@
     chromeos::assistant::AssistantNotification> {
   using AssistantNotification = chromeos::assistant::AssistantNotification;
 
+  static const std::string& title(const AssistantNotification& input);
+  static const std::string& message(const AssistantNotification& input);
+  static const GURL& action_url(const AssistantNotification& input);
+  static const std::string& client_id(const AssistantNotification& input);
   static const std::string& server_id(const AssistantNotification& input);
   static const std::string& consistency_token(
       const AssistantNotification& input);
@@ -64,6 +70,11 @@
   static const std::string& grouping_key(const AssistantNotification& input);
   static const std::string& obfuscated_gaia_id(
       const AssistantNotification& input);
+  static const base::Optional<base::Time>& expiry_time(
+      const AssistantNotification& input);
+  static const std::vector<chromeos::assistant::AssistantNotificationButton>&
+  buttons(const AssistantNotification& input);
+  static bool from_server(const AssistantNotification& input);
 
   static bool Read(
       chromeos::libassistant::mojom::AssistantNotificationDataView data,
@@ -71,6 +82,23 @@
 };
 
 template <>
+struct StructTraits<
+    chromeos::libassistant::mojom::AssistantNotificationButtonDataView,
+    chromeos::assistant::AssistantNotificationButton> {
+  using AssistantNotificationButton =
+      chromeos::assistant::AssistantNotificationButton;
+
+  static const std::string& label(const AssistantNotificationButton& input);
+  static const GURL& action_url(const AssistantNotificationButton& input);
+  static bool remove_notification_on_click(
+      const AssistantNotificationButton& input);
+
+  static bool Read(
+      chromeos::libassistant::mojom::AssistantNotificationButtonDataView data,
+      AssistantNotificationButton* output);
+};
+
+template <>
 struct StructTraits<chromeos::libassistant::mojom::AssistantFeedbackDataView,
                     chromeos::assistant::AssistantFeedback> {
   using AssistantFeedback = chromeos::assistant::AssistantFeedback;
diff --git a/chromeos/services/libassistant/public/mojom/notification_delegate.mojom b/chromeos/services/libassistant/public/mojom/notification_delegate.mojom
index c636b6a..7237bd92 100644
--- a/chromeos/services/libassistant/public/mojom/notification_delegate.mojom
+++ b/chromeos/services/libassistant/public/mojom/notification_delegate.mojom
@@ -4,9 +4,16 @@
 
 module chromeos.libassistant.mojom;
 
+import "chromeos/services/libassistant/public/mojom/assistant_notification.mojom";
+
 // Interface that allows Libassistant to make notification related changes on
 // ChromeOS.
 interface NotificationDelegate {
+  // Requests that the specified |notification| be added or updated. If the
+  // |notification.client_id| matches that of an existing notification,
+  // an update will occur. Otherwise, a new notification will be added.
+  AddOrUpdateNotification(AssistantNotification notification);
+
   // Requests to remove all notifications associated with |grouping_key|.
   // If |from_server| is true, the request to remove was initiated by the
   // server.
diff --git a/chromeos/services/tts/chrome_tts.h b/chromeos/services/tts/chrome_tts.h
index 6b74363..7cfbb4b7 100644
--- a/chromeos/services/tts/chrome_tts.h
+++ b/chromeos/services/tts/chrome_tts.h
@@ -19,8 +19,9 @@
                            int size);
 
 bool GoogleTtsInitBuffered(const uint8_t* text_jspb,
-                           const char* speaker_name,
-                           int text_jspb_len);
+                           const uint8_t* speaker_params_jspb,
+                           int text_jspb_len,
+                           int speaker_params_jspb_len);
 
 int GoogleTtsReadBuffered(float* audio_channel_buffer, size_t* frames_written);
 
diff --git a/chromeos/services/tts/google_tts_stream.cc b/chromeos/services/tts/google_tts_stream.cc
index 4b84da5..094a9d5 100644
--- a/chromeos/services/tts/google_tts_stream.cc
+++ b/chromeos/services/tts/google_tts_stream.cc
@@ -96,10 +96,11 @@
 }
 
 void GoogleTtsStream::Speak(const std::vector<uint8_t>& text_jspb,
-                            const std::string& speaker_name,
+                            const std::vector<uint8_t>& speaker_params_jspb,
                             SpeakCallback callback) {
   bool status = libchrometts_.GoogleTtsInitBuffered(
-      &text_jspb[0], speaker_name.c_str(), text_jspb.size());
+      &text_jspb[0], &speaker_params_jspb[0], text_jspb.size(),
+      speaker_params_jspb.size());
   if (!status) {
     stream_receiver_.reset();
     owner_->MaybeExit();
@@ -134,9 +135,8 @@
 }
 
 void GoogleTtsStream::ReadMoreFrames(bool is_first_buffer) {
-  if (!is_buffering_) {
+  if (!is_buffering_)
     return;
-  }
 
   TtsService::AudioBuffer buf;
   buf.frames.resize(libchrometts_.GoogleTtsGetFramesInAudioBuffer());
@@ -162,8 +162,12 @@
                 timepoint_index)));
   }
 
-  if (status <= 0)
+  // Ensure we always clean up given status 0 (done) or -1 (error).
+  if (status <= 0) {
+    is_buffering_ = false;
+    libchrometts_.GoogleTtsFinalizeBuffered();
     return;
+  }
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
diff --git a/chromeos/services/tts/google_tts_stream.h b/chromeos/services/tts/google_tts_stream.h
index 7f53589..7a68421 100644
--- a/chromeos/services/tts/google_tts_stream.h
+++ b/chromeos/services/tts/google_tts_stream.h
@@ -31,7 +31,7 @@
   void SelectVoice(const std::string& voice_name,
                    SelectVoiceCallback callback) override;
   void Speak(const std::vector<uint8_t>& text_jspb,
-             const std::string& speaker_name,
+             const std::vector<uint8_t>& speaker_params_jspb,
              SpeakCallback callback) override;
   void Stop() override;
   void SetVolume(float volume) override;
diff --git a/chromeos/services/tts/public/mojom/tts_service.mojom b/chromeos/services/tts/public/mojom/tts_service.mojom
index d12ceac4..c039a95 100644
--- a/chromeos/services/tts/public/mojom/tts_service.mojom
+++ b/chromeos/services/tts/public/mojom/tts_service.mojom
@@ -82,9 +82,10 @@
   SelectVoice(string voice_name) => (bool success);
 
   // Speak text described by a serialized proto.speech.tts.Text proto with the
-  // speaker given by speaker_name. The call will fail if no speaker is given
-  // and the voice model is a multi-speaker model.
-  Speak(array<uint8> text_jspb, string speaker_name)
+  // speaker params described by a serialized proto.speech.tts.SpeakerParams
+  // proto. The call will fail if no speaker name is given and the voice model
+  // is a multi-speaker model.
+  Speak(array<uint8> text_jspb, array<uint8> speaker_params_jspb)
       => (pending_receiver<TtsEventObserver> event_observer);
 
   // Stop speaking the currently speaking text, if any.
diff --git a/components/account_manager_core/account_manager_util.cc b/components/account_manager_core/account_manager_util.cc
index 4b477c5..4312eea 100644
--- a/components/account_manager_core/account_manager_util.cc
+++ b/components/account_manager_core/account_manager_util.cc
@@ -103,6 +103,9 @@
       return account_manager::AccountAdditionResult::Status::kCancelledByUser;
     case cm::AccountAdditionResult::Status::kNetworkError:
       return account_manager::AccountAdditionResult::Status::kNetworkError;
+    case cm::AccountAdditionResult::Status::kUnexpectedResponse:
+      return account_manager::AccountAdditionResult::Status::
+          kUnexpectedResponse;
     default:
       LOG(WARNING) << "Unknown crosapi::mojom::AccountAdditionResult::Status: "
                    << mojo_status;
diff --git a/components/autofill/core/browser/autofill_metrics.cc b/components/autofill/core/browser/autofill_metrics.cc
index 4395fb8..0aa01bd33 100644
--- a/components/autofill/core/browser/autofill_metrics.cc
+++ b/components/autofill/core/browser/autofill_metrics.cc
@@ -1132,6 +1132,25 @@
 }
 
 // static
+void AutofillMetrics::LogOfferNotificationInfoBarDeepLinkClicked() {
+  base::RecordAction(base::UserMetricsAction(
+      "Autofill_OfferNotificationInfoBar_DeepLinkClicked"));
+}
+
+// static
+void AutofillMetrics::LogOfferNotificationInfoBarResultMetric(
+    OfferNotificationInfoBarResultMetric metric) {
+  DCHECK_LE(metric, OfferNotificationInfoBarResultMetric::kMaxValue);
+  base::UmaHistogramEnumeration(
+      "Autofill.OfferNotificationInfoBarResult.CardLinkedOffer", metric);
+}
+
+void AutofillMetrics::LogOfferNotificationInfoBarShown() {
+  base::UmaHistogramBoolean(
+      "Autofill.OfferNotificationInfoBarOffer.CardLinkedOffer", true);
+}
+
+// static
 void AutofillMetrics::LogSaveCardWithFirstAndLastNameOffered(bool is_local) {
   std::string histogram_name = "Autofill.SaveCardWithFirstAndLastNameOffered.";
   histogram_name += is_local ? "Local" : "Server";
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index 235e6647..b2c0772 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -278,6 +278,20 @@
     kMaxValue = OFFER_NOTIFICATION_BUBBLE_LOST_FOCUS,
   };
 
+  // Metrics to track event when the offer notification infobar is closed.
+  enum class OfferNotificationInfoBarResultMetric {
+    // These values are persisted to logs. Entries should not be renumbered and
+    // numeric values should never be reused.
+
+    // User acknowledged the infobar by clicking the ok button.
+    OFFER_NOTIFICATION_INFOBAR_ACKNOWLEDGED = 0,
+    // User explicitly closed the infobar with the close button.
+    OFFER_NOTIFICATION_INFOBAR_CLOSED = 1,
+    // InfoBar was shown but user did not interact with the it.
+    OFFER_NOTIFICATION_INFOBAR_IGNORED = 2,
+    kMaxValue = OFFER_NOTIFICATION_INFOBAR_IGNORED,
+  };
+
   enum CreditCardUploadFeedbackMetric {
     // The loading indicator animation which indicates uploading is in progress
     // is successfully shown.
@@ -1137,6 +1151,10 @@
   static void LogOfferNotificationBubbleResultMetric(
       OfferNotificationBubbleResultMetric metric,
       bool is_reshow);
+  static void LogOfferNotificationInfoBarDeepLinkClicked();
+  static void LogOfferNotificationInfoBarResultMetric(
+      OfferNotificationInfoBarResultMetric metric);
+  static void LogOfferNotificationInfoBarShown();
 
   // Should be called when credit card scan is finished. |duration| should be
   // the time elapsed between launching the credit card scanner and getting back
diff --git a/components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.cc b/components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.cc
index ad4d817..8e3ec2e 100644
--- a/components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.cc
+++ b/components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.cc
@@ -36,13 +36,23 @@
     : credit_card_identifier_string_(
           card.CardIdentifierStringForAutofillDisplay()),
       network_icon_id_(CreditCard::IconResourceId(card.network())),
-      deep_link_url_(offer_details_url) {}
+      deep_link_url_(offer_details_url),
+      user_manually_closed_infobar_(false) {
+  AutofillMetrics::LogOfferNotificationInfoBarShown();
+}
 
 AutofillOfferNotificationInfoBarDelegateMobile::
-    ~AutofillOfferNotificationInfoBarDelegateMobile() {}
+    ~AutofillOfferNotificationInfoBarDelegateMobile() {
+  if (!user_manually_closed_infobar_) {
+    AutofillMetrics::LogOfferNotificationInfoBarResultMetric(
+        AutofillMetrics::OfferNotificationInfoBarResultMetric::
+            OFFER_NOTIFICATION_INFOBAR_IGNORED);
+  }
+}
 
 void AutofillOfferNotificationInfoBarDelegateMobile::OnOfferDeepLinkClicked(
     GURL url) {
+  AutofillMetrics::LogOfferNotificationInfoBarDeepLinkClicked();
   infobar()->owner()->OpenURL(url, WindowOpenDisposition::NEW_FOREGROUND_TAB);
 }
 
@@ -75,4 +85,19 @@
   return std::u16string();
 }
 
+void AutofillOfferNotificationInfoBarDelegateMobile::InfoBarDismissed() {
+  AutofillMetrics::LogOfferNotificationInfoBarResultMetric(
+      AutofillMetrics::OfferNotificationInfoBarResultMetric::
+          OFFER_NOTIFICATION_INFOBAR_CLOSED);
+  user_manually_closed_infobar_ = true;
+}
+
+bool AutofillOfferNotificationInfoBarDelegateMobile::Accept() {
+  AutofillMetrics::LogOfferNotificationInfoBarResultMetric(
+      AutofillMetrics::OfferNotificationInfoBarResultMetric::
+          OFFER_NOTIFICATION_INFOBAR_ACKNOWLEDGED);
+  user_manually_closed_infobar_ = true;
+  return true;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.h b/components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.h
index 1d04ab81..4f7b501a 100644
--- a/components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.h
+++ b/components/autofill/core/browser/payments/autofill_offer_notification_infobar_delegate_mobile.h
@@ -50,6 +50,8 @@
   infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
   int GetButtons() const override;
   std::u16string GetButtonLabel(InfoBarButton button) const override;
+  void InfoBarDismissed() override;
+  bool Accept() override;
 
  private:
   // Identifier for the credit card associated with the offer.
@@ -58,6 +60,9 @@
   int network_icon_id_;
   // URL that links to the offer details page in the Google Pay app.
   GURL deep_link_url_;
+  // Indicates whether the user manually closed the infobar by clicking on the X
+  // icon or the Got it button.
+  bool user_manually_closed_infobar_;
 };
 
 }  // namespace autofill
diff --git a/components/exo/seat.cc b/components/exo/seat.cc
index e95f443..3aea96f 100644
--- a/components/exo/seat.cc
+++ b/components/exo/seat.cc
@@ -124,9 +124,12 @@
 
 void Seat::SetSelection(DataSource* source) {
   Surface* focused_surface = GetFocusedSurface();
-  DCHECK(focused_surface);
-  if (!source || !source->CanBeDataSourceForCopy(focused_surface))
+  if (!source || !focused_surface ||
+      !source->CanBeDataSourceForCopy(focused_surface)) {
+    if (source)
+      source->Cancelled();
     return;
+  }
 
   if (selection_source_) {
     if (selection_source_->get() == source)
diff --git a/components/exo/seat_unittest.cc b/components/exo/seat_unittest.cc
index 99ee00d3..4a3dac7d 100644
--- a/components/exo/seat_unittest.cc
+++ b/components/exo/seat_unittest.cc
@@ -73,11 +73,13 @@
   void OnDndFinished() override {}
   void OnAction(DndAction dnd_action) override {}
   bool CanAcceptDataEventsForSurface(Surface* surface) const override {
-    return true;
+    return can_accept_;
   }
 
   void SetData(std::vector<uint8_t> data) { data_ = std::move(data); }
 
+  bool can_accept_ = true;
+
  private:
   bool cancelled_ = false;
   base::Optional<std::vector<uint8_t>> data_;
@@ -546,6 +548,32 @@
   EXPECT_EQ(clipboard, "Golden data");
 }
 
+TEST_F(SeatTest, SetSelection_NoFocusedSurface) {
+  TestSeat seat;
+  seat.set_focused_surface(nullptr);
+
+  TestDataSourceDelegate delegate;
+  DataSource source(&delegate);
+  source.Offer("text/plain;charset=utf-8");
+  seat.SetSelection(&source);
+
+  EXPECT_TRUE(delegate.cancelled());
+}
+
+TEST_F(SeatTest, SetSelection_ClientOutOfFocus) {
+  TestSeat seat;
+  Surface focused_surface;
+  seat.set_focused_surface(&focused_surface);
+
+  TestDataSourceDelegate delegate;
+  delegate.can_accept_ = false;
+  DataSource source(&delegate);
+  source.Offer("text/plain;charset=utf-8");
+  seat.SetSelection(&source);
+
+  EXPECT_TRUE(delegate.cancelled());
+}
+
 TEST_F(SeatTest, PressedKeys) {
   TestSeat seat;
   ui::KeyEvent press_a(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::DomCode::US_A, 0);
diff --git a/components/exo/wayland/clients/client_base.cc b/components/exo/wayland/clients/client_base.cc
index d50a3c26..9c042062 100644
--- a/components/exo/wayland/clients/client_base.cc
+++ b/components/exo/wayland/clients/client_base.cc
@@ -87,6 +87,9 @@
 // Specifies if client should y-invert the dmabuf surfaces.
 const char kYInvert[] = "y-invert";
 
+// Specifies if client should use xdg or zxdg_v6 or rely on wl_shell.
+const char kXdg[] = "xdg";
+
 }  // namespace switches
 
 namespace {
@@ -169,6 +172,12 @@
   } else if (strcmp(interface, "zcr_color_space_v1") == 0) {
     globals->color_space.reset(static_cast<zcr_color_space_v1*>(
         wl_registry_bind(registry, id, &zcr_color_space_v1_interface, 1)));
+  } else if (strcmp(interface, "zxdg_shell_v6") == 0) {
+    globals->xdg_shell_v6.reset(static_cast<zxdg_shell_v6*>(
+        wl_registry_bind(registry, id, &zxdg_shell_v6_interface, version)));
+  } else if (strcmp(interface, "xdg_wm_base") == 0) {
+    globals->xdg_wm_base.reset(static_cast<xdg_wm_base*>(
+        wl_registry_bind(registry, id, &xdg_wm_base_interface, version)));
   }
 }
 
@@ -389,6 +398,8 @@
       command_line.HasSwitch(switches::kTransparentBackground);
 
   y_invert = command_line.HasSwitch(switches::kYInvert);
+
+  use_xdg = command_line.HasSwitch(switches::kXdg);
   return true;
 }
 
@@ -617,23 +628,11 @@
     zwp_fullscreen_shell_v1_present_surface(globals_.fullscreen_shell.get(),
                                             surface_.get(), 0, nullptr);
 
-  } else {
+  } else if (!params.use_xdg) {
     if (!globals_.shell) {
       LOG(ERROR) << "Can't find shell interface";
       return false;
     }
-    if (!globals_.aura_shell) {
-      LOG(ERROR) << "Can't find aura shell interface";
-      return false;
-    }
-    static zaura_shell_listener kAuraShellListener = {
-        [](void* data, struct zaura_shell* zaura_shell, uint32_t layout_mode) {
-        },
-        [](void* data, struct zaura_shell* zaura_shell, uint32_t id) {
-          CastToClientBase(data)->bug_fix_ids_.insert(id);
-        }};
-    zaura_shell_add_listener(globals_.aura_shell.get(), &kAuraShellListener,
-                             this);
 
     std::unique_ptr<wl_shell_surface> shell_surface(
         static_cast<wl_shell_surface*>(
@@ -645,16 +644,7 @@
 
     wl_shell_surface_set_title(shell_surface.get(), params.title.c_str());
 
-    std::unique_ptr<zaura_surface> aura_surface(
-        static_cast<zaura_surface*>(zaura_shell_get_aura_surface(
-            globals_.aura_shell.get(), surface_.get())));
-    if (!aura_surface) {
-      LOG(ERROR) << "Can't get aura surface";
-      return false;
-    }
-
-    zaura_surface_set_frame(aura_surface.get(),
-                            ZAURA_SURFACE_FRAME_TYPE_NORMAL);
+    SetupAuraShellIfAvailable();
 
     if (fullscreen_) {
       wl_shell_surface_set_fullscreen(
@@ -663,6 +653,76 @@
     } else {
       wl_shell_surface_set_toplevel(shell_surface.get());
     }
+  } else {
+    if (!globals_.xdg_wm_base) {
+      // use zxdg
+      if (!globals_.xdg_shell_v6) {
+        LOG(ERROR) << "Can't find xdg_shell or zxdg_shell_v6 interface";
+        return false;
+      }
+
+      zxdg_surface_.reset(zxdg_shell_v6_get_xdg_surface(
+          globals_.xdg_shell_v6.get(), surface_.get()));
+      if (!zxdg_surface_) {
+        LOG(ERROR) << "Can't get zxdg surface";
+        return false;
+      }
+      static const zxdg_surface_v6_listener zxdg_surface_v6_listener = {
+          [](void* data, struct zxdg_surface_v6* zxdg_surface_v6,
+             uint32_t layout_mode) {
+            zxdg_surface_v6_ack_configure(zxdg_surface_v6, layout_mode);
+          },
+      };
+      zxdg_surface_v6_add_listener(zxdg_surface_.get(),
+                                   &zxdg_surface_v6_listener, this);
+      zxdg_toplevel_.reset(zxdg_surface_v6_get_toplevel(zxdg_surface_.get()));
+      if (!zxdg_toplevel_) {
+        LOG(ERROR) << "Can't get zxdg toplevel";
+        return false;
+      }
+      static const zxdg_toplevel_v6_listener zxdg_toplevel_v6_listener = {
+          [](void* data, struct zxdg_toplevel_v6* zxdg_toplevel_v6,
+             int32_t width, int32_t height, struct wl_array* states) {},
+          [](void* data, struct zxdg_toplevel_v6* zxdg_toplevel_v6) {}};
+      zxdg_toplevel_v6_add_listener(zxdg_toplevel_.get(),
+                                    &zxdg_toplevel_v6_listener, this);
+    } else {
+      // use xdg
+      xdg_surface_.reset(xdg_wm_base_get_xdg_surface(globals_.xdg_wm_base.get(),
+                                                     surface_.get()));
+      if (!xdg_surface_) {
+        LOG(ERROR) << "Can't get xdg surface";
+        return false;
+      }
+      static const xdg_surface_listener xdg_surface_listener = {
+          [](void* data, struct xdg_surface* xdg_surface,
+             uint32_t layout_mode) {
+            xdg_surface_ack_configure(xdg_surface, layout_mode);
+          },
+      };
+      xdg_surface_add_listener(xdg_surface_.get(), &xdg_surface_listener, this);
+      xdg_toplevel_.reset(xdg_surface_get_toplevel(xdg_surface_.get()));
+      if (!xdg_toplevel_) {
+        LOG(ERROR) << "Can't get xdg toplevel";
+        return false;
+      }
+      static const xdg_toplevel_listener xdg_toplevel_listener = {
+          [](void* data, struct xdg_toplevel* xdg_toplevel, int32_t width,
+             int32_t height, struct wl_array* states) {},
+          [](void* data, struct xdg_toplevel* xdg_toplevel) {}};
+      xdg_toplevel_add_listener(xdg_toplevel_.get(), &xdg_toplevel_listener,
+                                this);
+    }
+
+    if (fullscreen_) {
+      LOG(ERROR) << "full screen not supported yet.";
+      return false;
+    }
+
+    SetupAuraShellIfAvailable();
+
+    wl_surface_commit(surface_.get());
+    wl_display_flush(display_.get());
   }
 
   if (params.use_touch) {
@@ -1114,6 +1174,30 @@
   return buffer;
 }
 
+void ClientBase::SetupAuraShellIfAvailable() {
+  if (!globals_.aura_shell) {
+    LOG(ERROR) << "Can't find aura shell interface";
+    return;
+  }
+
+  static zaura_shell_listener kAuraShellListener = {
+      [](void* data, struct zaura_shell* zaura_shell, uint32_t layout_mode) {},
+      [](void* data, struct zaura_shell* zaura_shell, uint32_t id) {
+        CastToClientBase(data)->bug_fix_ids_.insert(id);
+      }};
+  zaura_shell_add_listener(globals_.aura_shell.get(), &kAuraShellListener,
+                           this);
+
+  std::unique_ptr<zaura_surface> aura_surface(static_cast<zaura_surface*>(
+      zaura_shell_get_aura_surface(globals_.aura_shell.get(), surface_.get())));
+  if (!aura_surface) {
+    LOG(ERROR) << "Can't get aura surface";
+    return;
+  }
+
+  zaura_surface_set_frame(aura_surface.get(), ZAURA_SURFACE_FRAME_TYPE_NORMAL);
+}
+
 }  // namespace clients
 }  // namespace wayland
 }  // namespace exo
diff --git a/components/exo/wayland/clients/client_base.h b/components/exo/wayland/clients/client_base.h
index ef720d7..45644f80 100644
--- a/components/exo/wayland/clients/client_base.h
+++ b/components/exo/wayland/clients/client_base.h
@@ -64,6 +64,7 @@
     bool use_memfd = false;
     bool use_touch = false;
     bool use_vulkan = false;
+    bool use_xdg = false;
   };
 
   struct Globals {
@@ -80,6 +81,8 @@
     std::unique_ptr<wl_subcompositor> subcompositor;
     std::unique_ptr<wl_touch> touch;
     std::unique_ptr<zaura_shell> aura_shell;
+    std::unique_ptr<zxdg_shell_v6> xdg_shell_v6;
+    std::unique_ptr<xdg_wm_base> xdg_wm_base;
     std::unique_ptr<zwp_fullscreen_shell_v1> fullscreen_shell;
     std::unique_ptr<zwp_input_timestamps_manager_v1> input_timestamps_manager;
     std::unique_ptr<zwp_linux_explicit_synchronization_v1>
@@ -197,6 +200,10 @@
   std::unique_ptr<wl_registry> registry_;
   std::unique_ptr<wl_surface> surface_;
   std::unique_ptr<wl_shell_surface> shell_surface_;
+  std::unique_ptr<xdg_surface> xdg_surface_;
+  std::unique_ptr<xdg_toplevel> xdg_toplevel_;
+  std::unique_ptr<zxdg_surface_v6> zxdg_surface_;
+  std::unique_ptr<zxdg_toplevel_v6> zxdg_toplevel_;
   Globals globals_;
 #if defined(USE_GBM)
   base::ScopedFD drm_fd_;
@@ -220,6 +227,7 @@
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ClientBase);
+  void SetupAuraShellIfAvailable();
 };
 
 }  // namespace clients
diff --git a/components/exo/wayland/clients/client_helper.cc b/components/exo/wayland/clients/client_helper.cc
index 8f3477c..81cd796 100644
--- a/components/exo/wayland/clients/client_helper.cc
+++ b/components/exo/wayland/clients/client_helper.cc
@@ -90,6 +90,10 @@
                 zwp_relative_pointer_manager_v1_destroy)
 DEFAULT_DELETER(zxdg_decoration_manager_v1, zxdg_decoration_manager_v1_destroy)
 DEFAULT_DELETER(zcr_extended_drag_v1, zcr_extended_drag_v1_destroy)
+DEFAULT_DELETER(xdg_surface, xdg_surface_destroy)
+DEFAULT_DELETER(xdg_toplevel, xdg_toplevel_destroy)
+DEFAULT_DELETER(zxdg_surface_v6, zxdg_surface_v6_destroy)
+DEFAULT_DELETER(zxdg_toplevel_v6, zxdg_toplevel_v6_destroy)
 
 #if defined(USE_GBM)
 DEFAULT_DELETER(gbm_bo, gbm_bo_destroy)
diff --git a/components/exo/wayland/clients/client_helper.h b/components/exo/wayland/clients/client_helper.h
index 097be2ac..cf27083 100644
--- a/components/exo/wayland/clients/client_helper.h
+++ b/components/exo/wayland/clients/client_helper.h
@@ -108,6 +108,10 @@
 DEFAULT_DELETER_FDECL(zwp_relative_pointer_manager_v1)
 DEFAULT_DELETER_FDECL(zxdg_decoration_manager_v1)
 DEFAULT_DELETER_FDECL(zcr_extended_drag_v1)
+DEFAULT_DELETER_FDECL(xdg_surface)
+DEFAULT_DELETER_FDECL(xdg_toplevel)
+DEFAULT_DELETER_FDECL(zxdg_surface_v6)
+DEFAULT_DELETER_FDECL(zxdg_toplevel_v6)
 
 #if defined(USE_GBM)
 DEFAULT_DELETER_FDECL(gbm_bo)
diff --git a/components/exo/wayland/zcr_remote_shell.cc b/components/exo/wayland/zcr_remote_shell.cc
index c54b84e8..85edcd3 100644
--- a/components/exo/wayland/zcr_remote_shell.cc
+++ b/components/exo/wayland/zcr_remote_shell.cc
@@ -917,7 +917,8 @@
 
   std::unique_ptr<ClientControlledShellSurface::Delegate>
   CreateShellSurfaceDelegate(wl_resource* resource) {
-    return std::make_unique<WaylandRemoteSurfaceDelegate>(this, resource);
+    return std::make_unique<WaylandRemoteSurfaceDelegate>(
+        weak_ptr_factory_.GetWeakPtr(), resource);
   }
 
   std::unique_ptr<NotificationSurface> CreateNotificationSurface(
@@ -1003,11 +1004,12 @@
   class WaylandRemoteSurfaceDelegate
       : public ClientControlledShellSurface::Delegate {
    public:
-    WaylandRemoteSurfaceDelegate(WaylandRemoteShell* shell,
+    WaylandRemoteSurfaceDelegate(base::WeakPtr<WaylandRemoteShell> shell,
                                  wl_resource* resource)
-        : shell_(shell), resource_(resource) {}
+        : shell_(std::move(shell)), resource_(resource) {}
     ~WaylandRemoteSurfaceDelegate() override {
-      shell_->OnRemoteSurfaceDestroyed(resource_);
+      if (shell_)
+        shell_->OnRemoteSurfaceDestroyed(resource_);
     }
     WaylandRemoteSurfaceDelegate(const WaylandRemoteSurfaceDelegate&) = delete;
     WaylandRemoteSurfaceDelegate& operator=(
@@ -1016,7 +1018,8 @@
    private:
     // ClientControlledShellSurfaceDelegate:
     void OnGeometryChanged(const gfx::Rect& geometry) override {
-      shell_->OnRemoteSurfaceGeometryChanged(resource_, geometry);
+      if (shell_)
+        shell_->OnRemoteSurfaceGeometryChanged(resource_, geometry);
     }
     void OnStateChanged(chromeos::WindowStateType old_state_type,
                         chromeos::WindowStateType new_state_type) override {
@@ -1029,9 +1032,11 @@
                          const gfx::Rect& bounds_in_display,
                          bool is_resize,
                          int bounds_change) override {
-      shell_->OnRemoteSurfaceBoundsChanged(
-          resource_, current_state, requested_state, display_id,
-          bounds_in_display, is_resize, bounds_change);
+      if (shell_) {
+        shell_->OnRemoteSurfaceBoundsChanged(
+            resource_, current_state, requested_state, display_id,
+            bounds_in_display, is_resize, bounds_change);
+      }
     }
     void OnDragStarted(int component) override {
       zcr_remote_surface_v1_send_drag_started(resource_,
@@ -1044,11 +1049,11 @@
       wl_client_flush(wl_resource_get_client(resource_));
     }
     void OnZoomLevelChanged(ZoomChange zoom_change) override {
-      if (wl_resource_get_version(resource_) >= 23)
+      if (wl_resource_get_version(resource_) >= 23 && shell_)
         shell_->OnRemoteSurfaceChangeZoomLevel(resource_, zoom_change);
     }
 
-    WaylandRemoteShell* shell_;
+    base::WeakPtr<WaylandRemoteShell> shell_;
     wl_resource* resource_;
   };
 
diff --git a/components/feed/core/v2/BUILD.gn b/components/feed/core/v2/BUILD.gn
index 27a9d849f..df5ca25c 100644
--- a/components/feed/core/v2/BUILD.gn
+++ b/components/feed/core/v2/BUILD.gn
@@ -31,13 +31,15 @@
     "proto_util.h",
     "protocol_translator.cc",
     "protocol_translator.h",
+    "public/feed_api.cc",
+    "public/feed_api.h",
     "public/feed_service.cc",
     "public/feed_service.h",
-    "public/feed_stream_api.cc",
-    "public/feed_stream_api.h",
     "public/persistent_key_value_store.cc",
     "public/persistent_key_value_store.h",
     "public/refresh_task_scheduler.h",
+    "public/types.cc",
+    "public/web_feed_subscriptions.h",
     "request_throttler.cc",
     "request_throttler.h",
     "stream_model.cc",
@@ -222,6 +224,7 @@
     sources = [
       "common_enums.h",
       "enums.h",
+      "public/types.h",
     ]
   }
 }
diff --git a/components/feed/core/v2/feed_store.h b/components/feed/core/v2/feed_store.h
index 8ee85c3..fb69822e 100644
--- a/components/feed/core/v2/feed_store.h
+++ b/components/feed/core/v2/feed_store.h
@@ -14,7 +14,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequenced_task_runner.h"
 #include "components/feed/core/proto/v2/store.pb.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/types.h"
 #include "components/leveldb_proto/public/proto_database.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
diff --git a/components/feed/core/v2/feed_store_unittest.cc b/components/feed/core/v2/feed_store_unittest.cc
index be84f157..aca417e 100644
--- a/components/feed/core/v2/feed_store_unittest.cc
+++ b/components/feed/core/v2/feed_store_unittest.cc
@@ -15,7 +15,7 @@
 #include "base/test/task_environment.h"
 #include "components/feed/core/proto/v2/wire/content_id.pb.h"
 #include "components/feed/core/v2/protocol_translator.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/test/callback_receiver.h"
 #include "components/feed/core/v2/test/proto_printer.h"
 #include "components/feed/core/v2/test/stream_builder.h"
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index 7ae176a..c415f239 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/notreached.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "components/feed/core/common/pref_names.h"
@@ -27,7 +28,7 @@
 #include "components/feed/core/v2/offline_page_spy.h"
 #include "components/feed/core/v2/prefs.h"
 #include "components/feed/core/v2/protocol_translator.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/public/refresh_task_scheduler.h"
 #include "components/feed/core/v2/public/types.h"
 #include "components/feed/core/v2/scheduling.h"
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index 0fdc8e8..ad2eadda 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -20,7 +20,7 @@
 #include "components/feed/core/v2/notice_card_tracker.h"
 #include "components/feed/core/v2/persistent_key_value_store_impl.h"
 #include "components/feed/core/v2/protocol_translator.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/request_throttler.h"
 #include "components/feed/core/v2/scheduling.h"
 #include "components/feed/core/v2/stream_model.h"
@@ -50,9 +50,9 @@
 class SurfaceUpdater;
 struct StreamModelUpdateRequest;
 
-// Implements FeedStreamApi. |FeedStream| additionally exposes functionality
+// Implements FeedApi. |FeedStream| additionally exposes functionality
 // needed by other classes within the Feed component.
-class FeedStream : public FeedStreamApi,
+class FeedStream : public FeedApi,
                    public offline_pages::TaskQueue::Delegate,
                    public StreamModel::StoreObserver {
  public:
@@ -126,7 +126,7 @@
   FeedStream(const FeedStream&) = delete;
   FeedStream& operator=(const FeedStream&) = delete;
 
-  // FeedStreamApi.
+  // FeedApi.
 
   bool IsActivityLoggingEnabled() const override;
   std::string GetSessionId() const override;
diff --git a/components/feed/core/v2/feed_stream_unittest.cc b/components/feed/core/v2/feed_stream_unittest.cc
index f2b6d8b..cafc2189 100644
--- a/components/feed/core/v2/feed_stream_unittest.cc
+++ b/components/feed/core/v2/feed_stream_unittest.cc
@@ -43,7 +43,7 @@
 #include "components/feed/core/v2/persistent_key_value_store_impl.h"
 #include "components/feed/core/v2/prefs.h"
 #include "components/feed/core/v2/protocol_translator.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/public/persistent_key_value_store.h"
 #include "components/feed/core/v2/public/refresh_task_scheduler.h"
 #include "components/feed/core/v2/scheduling.h"
diff --git a/components/feed/core/v2/metrics_reporter.cc b/components/feed/core/v2/metrics_reporter.cc
index efa7827..a475428 100644
--- a/components/feed/core/v2/metrics_reporter.cc
+++ b/components/feed/core/v2/metrics_reporter.cc
@@ -13,7 +13,7 @@
 #include "base/time/time.h"
 #include "components/feed/core/v2/common_enums.h"
 #include "components/feed/core/v2/prefs.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 
 namespace feed {
 namespace {
diff --git a/components/feed/core/v2/metrics_reporter.h b/components/feed/core/v2/metrics_reporter.h
index 6907e91..b48b995 100644
--- a/components/feed/core/v2/metrics_reporter.h
+++ b/components/feed/core/v2/metrics_reporter.h
@@ -13,7 +13,7 @@
 #include "base/time/time.h"
 #include "components/feed/core/v2/common_enums.h"
 #include "components/feed/core/v2/enums.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/types.h"
 
 class PrefService;
@@ -33,7 +33,7 @@
   MetricsReporter(const MetricsReporter&) = delete;
   MetricsReporter& operator=(const MetricsReporter&) = delete;
 
-  // User interactions. See |FeedStreamApi| for definitions.
+  // User interactions. See |FeedApi| for definitions.
 
   virtual void ContentSliceViewed(const StreamType& stream_type,
                                   int index_in_stream);
diff --git a/components/feed/core/v2/metrics_reporter_unittest.cc b/components/feed/core/v2/metrics_reporter_unittest.cc
index 4a4bb7b..b3f2c4b3 100644
--- a/components/feed/core/v2/metrics_reporter_unittest.cc
+++ b/components/feed/core/v2/metrics_reporter_unittest.cc
@@ -13,7 +13,7 @@
 #include "components/feed/core/common/pref_names.h"
 #include "components/feed/core/shared_prefs/pref_names.h"
 #include "components/feed/core/v2/common_enums.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/components/feed/core/v2/protocol_translator.h b/components/feed/core/v2/protocol_translator.h
index 50ef128..43e12bf 100644
--- a/components/feed/core/v2/protocol_translator.h
+++ b/components/feed/core/v2/protocol_translator.h
@@ -14,7 +14,7 @@
 #include "components/feed/core/proto/v2/store.pb.h"
 #include "components/feed/core/proto/v2/wire/data_operation.pb.h"
 #include "components/feed/core/proto/v2/wire/response.pb.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/scheduling.h"
 #include "components/feed/core/v2/types.h"
 
diff --git a/components/feed/core/v2/public/feed_stream_api.cc b/components/feed/core/v2/public/feed_api.cc
similarity index 89%
rename from components/feed/core/v2/public/feed_stream_api.cc
rename to components/feed/core/v2/public/feed_api.cc
index 8d48425..7fda6010 100644
--- a/components/feed/core/v2/public/feed_stream_api.cc
+++ b/components/feed/core/v2/public/feed_api.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 "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 
 namespace feed {
 
@@ -39,8 +39,8 @@
       return true;
   }
 }
-FeedStreamApi::FeedStreamApi() = default;
-FeedStreamApi::~FeedStreamApi() = default;
+FeedApi::FeedApi() = default;
+FeedApi::~FeedApi() = default;
 
 FeedStreamSurface::FeedStreamSurface(StreamType stream_type)
     : stream_type_(stream_type) {
diff --git a/components/feed/core/v2/public/feed_stream_api.h b/components/feed/core/v2/public/feed_api.h
similarity index 94%
rename from components/feed/core/v2/public/feed_stream_api.h
rename to components/feed/core/v2/public/feed_api.h
index 8ed3e58..678c142 100644
--- a/components/feed/core/v2/public/feed_stream_api.h
+++ b/components/feed/core/v2/public/feed_api.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 COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_STREAM_API_H_
-#define COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_STREAM_API_H_
+#ifndef COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_API_H_
+#define COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_API_H_
 
 #include <string>
 #include <vector>
@@ -15,6 +15,7 @@
 #include "components/feed/core/v2/common_enums.h"
 #include "components/feed/core/v2/public/refresh_task_scheduler.h"
 #include "components/feed/core/v2/public/types.h"
+#include "components/feed/core/v2/public/web_feed_subscriptions.h"
 #include "url/gurl.h"
 
 namespace feedui {
@@ -25,6 +26,7 @@
 }  // namespace feedstore
 
 namespace feed {
+class WebFeedSubscriptions;
 class PersistentKeyValueStore;
 
 // Selects the stream type.
@@ -104,14 +106,16 @@
 constexpr StreamType kWebFeedStream(StreamType::Type::kWebFeed);
 
 // This is the public access point for interacting with the Feed contents.
-// FeedStreamApi serves multiple streams of data, one for each StreamType.
-class FeedStreamApi {
+// FeedApi serves multiple streams of data, one for each StreamType.
+class FeedApi {
  public:
+  FeedApi();
+  virtual ~FeedApi();
+  FeedApi(const FeedApi&) = delete;
+  FeedApi& operator=(const FeedApi&) = delete;
 
-  FeedStreamApi();
-  virtual ~FeedStreamApi();
-  FeedStreamApi(const FeedStreamApi&) = delete;
-  FeedStreamApi& operator=(const FeedStreamApi&) = delete;
+  // TODO(crbug/1152592): Implement subscriptions().
+  // WebFeedSubscriptions& subscriptions() = 0;
 
   // Attach/detach a surface. Surfaces should be attached when content is
   // required for display, and detached when content is no longer shown.
@@ -235,4 +239,4 @@
 
 }  // namespace feed
 
-#endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_STREAM_API_H_
+#endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_FEED_API_H_
diff --git a/components/feed/core/v2/public/feed_service.cc b/components/feed/core/v2/public/feed_service.cc
index d487906..d83aa00 100644
--- a/components/feed/core/v2/public/feed_service.cc
+++ b/components/feed/core/v2/public/feed_service.cc
@@ -239,7 +239,7 @@
 
 FeedService::~FeedService() = default;
 
-FeedStreamApi* FeedService::GetStream() {
+FeedApi* FeedService::GetStream() {
   return stream_.get();
 }
 
diff --git a/components/feed/core/v2/public/feed_service.h b/components/feed/core/v2/public/feed_service.h
index 8673b476..ff5e7ed53 100644
--- a/components/feed/core/v2/public/feed_service.h
+++ b/components/feed/core/v2/public/feed_service.h
@@ -11,7 +11,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
 #include "build/build_config.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/public/types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/leveldb_proto/public/proto_database.h"
@@ -81,7 +81,7 @@
   // Used for testing only.
   explicit FeedService(std::unique_ptr<FeedStream> stream);
 
-  // Construct a new FeedStreamApi along with FeedService.
+  // Construct a new FeedApi along with FeedService.
   FeedService(
       std::unique_ptr<Delegate> delegate,
       std::unique_ptr<RefreshTaskScheduler> refresh_task_scheduler,
@@ -102,7 +102,7 @@
   FeedService(const FeedService&) = delete;
   FeedService& operator=(const FeedService&) = delete;
 
-  FeedStreamApi* GetStream();
+  FeedApi* GetStream();
 
   void ClearCachedData();
 
@@ -122,8 +122,8 @@
   void OnApplicationStateChange(base::android::ApplicationState state);
 #endif
 
-  // These components are owned for construction of |FeedStreamApi|. These will
-  // be null if |FeedStreamApi| is created externally.
+  // These components are owned for construction of |FeedApi|. These will
+  // be null if |FeedApi| is created externally.
   std::unique_ptr<Delegate> delegate_;
   std::unique_ptr<StreamDelegateImpl> stream_delegate_;
   std::unique_ptr<MetricsReporter> metrics_reporter_;
diff --git a/components/feed/core/v2/public/types.cc b/components/feed/core/v2/public/types.cc
new file mode 100644
index 0000000..429711c
--- /dev/null
+++ b/components/feed/core/v2/public/types.cc
@@ -0,0 +1,14 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/feed/core/v2/public/types.h"
+
+namespace feed {
+
+WebFeedMetadata::WebFeedMetadata(const WebFeedMetadata&) = default;
+WebFeedMetadata::WebFeedMetadata(WebFeedMetadata&&) = default;
+WebFeedMetadata& WebFeedMetadata::operator=(const WebFeedMetadata&) = default;
+WebFeedMetadata& WebFeedMetadata::operator=(WebFeedMetadata&&) = default;
+
+}  // namespace feed
diff --git a/components/feed/core/v2/public/types.h b/components/feed/core/v2/public/types.h
index 3cd14ca..fd765d8c 100644
--- a/components/feed/core/v2/public/types.h
+++ b/components/feed/core/v2/public/types.h
@@ -90,6 +90,49 @@
 base::Optional<DebugStreamData> DeserializeDebugStreamData(
     base::StringPiece base64_encoded);
 
+// Information about a web page which may be used to determine an associated web
+// feed.
+struct WebFeedPageInformation {
+  // The URL for the page.
+  GURL url;
+  // TODO(crbug/1152592): There will be additional optional information.
+};
+
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.feed.webfeed
+enum class WebFeedSubscriptionStatus {
+  kUnknown = 0,
+  kSubscribed = 1,
+  kNotSubscribed = 2,
+  kSubscribeInProgress = 3,
+  kUnsubscribeInProgress = 4,
+};
+
+// Information about a web feed.
+struct WebFeedMetadata {
+  WebFeedMetadata(const WebFeedMetadata&);
+  WebFeedMetadata(WebFeedMetadata&&);
+  WebFeedMetadata& operator=(const WebFeedMetadata&);
+  WebFeedMetadata& operator=(WebFeedMetadata&&);
+
+  // Unique ID of the web feed. Empty if the client knows of no web feed.
+  std::string web_feed_id;
+  // Whether the subscribed Web Feed has content available for fetching.
+  bool is_active = false;
+  std::string title;
+  GURL publisher_url;
+  WebFeedSubscriptionStatus subscription_status =
+      WebFeedSubscriptionStatus::kUnknown;
+};
+
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.feed.webfeed
+enum class WebFeedSubscriptionRequestStatus {
+  kUnknown = 0,
+  kSuccess = 1,
+  kFailedOffline = 2,
+  kFailedTooManySubscriptions = 3,
+  kFailedUnknownError = 4,
+};
+
 }  // namespace feed
 
 #endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_TYPES_H_
diff --git a/components/feed/core/v2/public/web_feed_subscriptions.h b/components/feed/core/v2/public/web_feed_subscriptions.h
new file mode 100644
index 0000000..6fba079
--- /dev/null
+++ b/components/feed/core/v2/public/web_feed_subscriptions.h
@@ -0,0 +1,65 @@
+// 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 COMPONENTS_FEED_CORE_V2_PUBLIC_WEB_FEED_SUBSCRIPTIONS_H_
+#define COMPONENTS_FEED_CORE_V2_PUBLIC_WEB_FEED_SUBSCRIPTIONS_H_
+
+#include <string>
+#include "base/callback.h"
+#include "components/feed/core/v2/public/types.h"
+
+namespace feed {
+
+// API to access Web Feed subscriptions.
+class WebFeedSubscriptions {
+ public:
+  struct FollowWebFeedResult {
+    WebFeedSubscriptionRequestStatus request_status;
+    // If followed, the metadata for the followed feed.
+    WebFeedMetadata web_feed_metadata;
+  };
+  // Follow a web feed given information about a web page. Calls `callback` when
+  // complete. The callback parameter reports whether the url is now considered
+  // followed.
+  virtual void FollowWebFeed(
+      const WebFeedPageInformation& page_info,
+      base::OnceCallback<void(FollowWebFeedResult)> callback) = 0;
+
+  // Follow a web feed given a web feed ID.
+  virtual void FollowWebFeed(
+      const std::string& web_feed_id,
+      base::OnceCallback<void(FollowWebFeedResult)> callback) = 0;
+
+  struct UnfollowWebFeedResult {
+    WebFeedSubscriptionRequestStatus request_status;
+  };
+
+  // Follow a web feed given a URL. Calls `callback` when complete. The callback
+  // parameter reports whether the url is now considered followed.
+  virtual void UnfollowWebFeed(
+      const std::string& web_feed_id,
+      base::OnceCallback<void(UnfollowWebFeedResult)> callback) = 0;
+
+  // Web Feed lookup for pages. These functions fetch `WebFeedMetadata` for any
+  // web feed which is recommended by the server, currently subscribed, or was
+  // recently subscribed. `callback` is given a nullptr if no web feed data is
+  // found.
+
+  // Look up web feed information for a web page.
+  virtual void FindWebFeedInfoForPage(
+      const WebFeedPageInformation& page_info,
+      base::OnceCallback<void(std::unique_ptr<WebFeedMetadata>)> callback) = 0;
+
+  // Look up web feed information for a web page given the `web_feed_id`.
+  virtual void FindWebFeedInfoForWebFeedId(
+      const std::string& web_feed_id,
+      base::OnceCallback<void(std::unique_ptr<WebFeedMetadata>)> callback) = 0;
+
+  // Returns all current subscriptions.
+  virtual void GetAllSubscriptions(
+      base::OnceCallback<void(std::vector<WebFeedMetadata>)> callback) = 0;
+};
+
+}  // namespace feed
+#endif  // COMPONENTS_FEED_CORE_V2_PUBLIC_WEB_FEED_SUBSCRIPTIONS_H_
diff --git a/components/feed/core/v2/stream_model.h b/components/feed/core/v2/stream_model.h
index 666d0a0..81bd01e05 100644
--- a/components/feed/core/v2/stream_model.h
+++ b/components/feed/core/v2/stream_model.h
@@ -16,7 +16,7 @@
 #include "components/feed/core/proto/v2/store.pb.h"
 #include "components/feed/core/proto/v2/wire/content_id.pb.h"
 #include "components/feed/core/v2/proto_util.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/stream_model/ephemeral_change.h"
 #include "components/feed/core/v2/stream_model/feature_tree.h"
 
diff --git a/components/feed/core/v2/surface_updater.h b/components/feed/core/v2/surface_updater.h
index 6881499..7f0b359 100644
--- a/components/feed/core/v2/surface_updater.h
+++ b/components/feed/core/v2/surface_updater.h
@@ -13,7 +13,7 @@
 #include "base/observer_list.h"
 #include "components/feed/core/proto/v2/ui.pb.h"
 #include "components/feed/core/v2/enums.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/stream_model.h"
 
 namespace feedui {
diff --git a/components/feed/core/v2/tasks/load_stream_from_store_task.cc b/components/feed/core/v2/tasks/load_stream_from_store_task.cc
index 4a50345..1a63087 100644
--- a/components/feed/core/v2/tasks/load_stream_from_store_task.cc
+++ b/components/feed/core/v2/tasks/load_stream_from_store_task.cc
@@ -14,7 +14,7 @@
 #include "components/feed/core/v2/feed_stream.h"
 #include "components/feed/core/v2/proto_util.h"
 #include "components/feed/core/v2/protocol_translator.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/scheduling.h"
 #include "components/feed/core/v2/types.h"
 
diff --git a/components/feed/core/v2/tasks/load_stream_task.cc b/components/feed/core/v2/tasks/load_stream_task.cc
index 0245760..906c960 100644
--- a/components/feed/core/v2/tasks/load_stream_task.cc
+++ b/components/feed/core/v2/tasks/load_stream_task.cc
@@ -19,7 +19,7 @@
 #include "components/feed/core/v2/metrics_reporter.h"
 #include "components/feed/core/v2/proto_util.h"
 #include "components/feed/core/v2/protocol_translator.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/stream_model.h"
 #include "components/feed/core/v2/tasks/upload_actions_task.h"
 
diff --git a/components/feed/core/v2/tasks/prefetch_images_task.cc b/components/feed/core/v2/tasks/prefetch_images_task.cc
index 6075a9a..bdd78ce 100644
--- a/components/feed/core/v2/tasks/prefetch_images_task.cc
+++ b/components/feed/core/v2/tasks/prefetch_images_task.cc
@@ -12,7 +12,7 @@
 #include "components/feed/core/v2/config.h"
 #include "components/feed/core/v2/feed_store.h"
 #include "components/feed/core/v2/feed_stream.h"
-#include "components/feed/core/v2/public/feed_stream_api.h"
+#include "components/feed/core/v2/public/feed_api.h"
 #include "components/feed/core/v2/stream_model.h"
 #include "components/feed/core/v2/tasks/load_stream_from_store_task.h"
 
diff --git a/components/heavy_ad_intervention/heavy_ad_helper.cc b/components/heavy_ad_intervention/heavy_ad_helper.cc
index e81f05c..7ff53ab 100644
--- a/components/heavy_ad_intervention/heavy_ad_helper.cc
+++ b/components/heavy_ad_intervention/heavy_ad_helper.cc
@@ -17,12 +17,6 @@
 
 namespace heavy_ads {
 
-// NOTE: If adding usage of more strings/resources here, make sure that they
-// are allowlisted in //weblayer/grit_{resources, strings}_allowlist.txt;
-// otherwise empty data will be used for the new resources/strings in WebLayer.
-// For strings there is a partial safeguard as //weblayer's integration tests
-// will crash if a new-but-not-allowlisted string is fetched in a codepath that
-// the presentation of the heavy ad page in those tests exercises.
 std::string PrepareHeavyAdPage(const std::string& application_locale) {
   int resource_id = IDR_SECURITY_INTERSTITIAL_QUIET_HTML;
   std::string uncompressed;
diff --git a/components/heavy_ad_intervention/heavy_ad_service.cc b/components/heavy_ad_intervention/heavy_ad_service.cc
index e43f82c..576d354 100644
--- a/components/heavy_ad_intervention/heavy_ad_service.cc
+++ b/components/heavy_ad_intervention/heavy_ad_service.cc
@@ -40,7 +40,6 @@
 
 void HeavyAdService::Initialize(const base::FilePath& profile_path) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(!profile_path.empty());
 
   if (!base::FeatureList::IsEnabled(features::kHeavyAdPrivacyMitigations))
     return;
diff --git a/components/language/core/common/locale_util.cc b/components/language/core/common/locale_util.cc
index 18675b083..99fb541 100644
--- a/components/language/core/common/locale_util.cc
+++ b/components/language/core/common/locale_util.cc
@@ -8,12 +8,20 @@
 
 #include <algorithm>
 
+#include "build/build_config.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace language {
 
 namespace {
 
+#if defined(OS_IOS)
+// iOS uses a different name for es-419 (es-MX).
+const char* const kEs419 = "es-MX";
+#else
+const char* const kEs419 = "es-419";
+#endif
+
 // Pair of locales, where the first element should fallback to the second one.
 struct LocaleUIFallbackPair {
   const char* const chosen_locale;
@@ -33,16 +41,16 @@
     {"en-IN", "en-GB"},
     {"en-NZ", "en-GB"},
     {"en-ZA", "en-GB"},
-    {"es-AR", "es-419"},
-    {"es-CL", "es-419"},
-    {"es-CO", "es-419"},
-    {"es-CR", "es-419"},
-    {"es-HN", "es-419"},
-    {"es-MX", "es-419"},
-    {"es-PE", "es-419"},
-    {"es-US", "es-419"},
-    {"es-UY", "es-419"},
-    {"es-VE", "es-419"},
+    {"es-AR", kEs419},
+    {"es-CL", kEs419},
+    {"es-CO", kEs419},
+    {"es-CR", kEs419},
+    {"es-HN", kEs419},
+    {"es-MX", kEs419},
+    {"es-PE", kEs419},
+    {"es-US", kEs419},
+    {"es-UY", kEs419},
+    {"es-VE", kEs419},
     {"it-CH", "it"},
     {"no", "nb"},
     {"pt", "pt-PT"}
@@ -62,7 +70,7 @@
 
 // Checks if |locale| is one of the actual locales supported as a UI languages.
 bool IsAvailableUILocale(base::StringPiece locale) {
-  for (const auto& ui_locale : l10n_util::GetAvailableLocales()) {
+  for (const auto& ui_locale : l10n_util::GetLocalesWithStrings()) {
     if (ui_locale == locale)
       return true;
   }
@@ -96,6 +104,8 @@
   return SplitIntoMainAndTail(language_code).first;
 }
 
+// TODO(mlcui): Replace this with l10n_util::CheckAndResolveLocale.
+// Note that CheckAndResolveLocale has different behaviour for "pt".
 bool ConvertToActualUILocale(std::string* input_locale) {
   if (ConvertToFallbackUILocale(input_locale))
     return true;
diff --git a/components/language/core/common/locale_util_unittest.cc b/components/language/core/common/locale_util_unittest.cc
index 6fb63d8..186f71f0 100644
--- a/components/language/core/common/locale_util_unittest.cc
+++ b/components/language/core/common/locale_util_unittest.cc
@@ -58,7 +58,12 @@
     locale = es_locale;
     is_ui = ConvertToActualUILocale(&locale);
     EXPECT_TRUE(is_ui) << es_locale;
+#if defined(OS_IOS)
+    // iOS uses a different name for es-419 (es-MX).
+    EXPECT_EQ("es-MX", locale) << es_locale;
+#else
     EXPECT_EQ("es-419", locale) << es_locale;
+#endif
   }
 
   // English falls back to US.
diff --git a/components/media_message_center/media_notification_view_impl.cc b/components/media_message_center/media_notification_view_impl.cc
index 8abd663..fb7a317 100644
--- a/components/media_message_center/media_notification_view_impl.cc
+++ b/components/media_message_center/media_notification_view_impl.cc
@@ -101,6 +101,9 @@
     case MediaSessionAction::kSeekTo:
     case MediaSessionAction::kScrubTo:
     case MediaSessionAction::kSwitchAudioDevice:
+    case MediaSessionAction::kToggleMicrophone:
+    case MediaSessionAction::kToggleCamera:
+    case MediaSessionAction::kHangUp:
       NOTREACHED();
       break;
   }
diff --git a/components/media_message_center/media_notification_view_modern_impl.cc b/components/media_message_center/media_notification_view_modern_impl.cc
index 957efc4..da3260d 100644
--- a/components/media_message_center/media_notification_view_modern_impl.cc
+++ b/components/media_message_center/media_notification_view_modern_impl.cc
@@ -128,6 +128,9 @@
     case MediaSessionAction::kSeekTo:
     case MediaSessionAction::kScrubTo:
     case MediaSessionAction::kSwitchAudioDevice:
+    case MediaSessionAction::kToggleMicrophone:
+    case MediaSessionAction::kToggleCamera:
+    case MediaSessionAction::kHangUp:
       NOTREACHED();
       break;
   }
diff --git a/components/ntp_snippets_strings.grdp b/components/ntp_snippets_strings.grdp
index 9a1bdba..5def5b3e 100644
--- a/components/ntp_snippets_strings.grdp
+++ b/components/ntp_snippets_strings.grdp
@@ -23,18 +23,6 @@
     </message>
   </if>
 
-  <if expr="use_titlecase">
-    <message name="IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER_BRANDED" desc="Branded version of the the articles section header, which is a list of news articles displayed as cards on the New Tab Page.">
-      Suggestions by Google
-    </message>
-  </if>
-
-  <if expr="not use_titlecase">
-    <message name="IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER_BRANDED" desc="Branded version of the the articles section header, which is a list of news articles displayed as cards on the New Tab Page." formatter_data="android_java">
-      Suggestions by Google
-    </message>
-  </if>
-
   <message name="IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_EMPTY" desc="On the New Tab Page, text of the card explaining to the user that they can expect to see suggested articles in this area in the future." formatter_data="android_java">
     Your suggested articles appear here
   </message>
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 6404c1d..58262f7 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -66,7 +66,10 @@
     "url_pattern_with_wildcards.h",
   ]
 
-  public_deps = [ "//third_party/re2" ]
+  public_deps = [
+    "//components/optimization_guide:machine_learning_tflite_buildflags",
+    "//third_party/re2",
+  ]
 
   deps = [
     "//base",
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 4a87ff727..bdf801a8 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -13,6 +13,7 @@
 #include "build/build_config.h"
 #include "components/optimization_guide/core/optimization_guide_constants.h"
 #include "components/optimization_guide/core/optimization_guide_switches.h"
+#include "components/optimization_guide/machine_learning_tflite_buildflags.h"
 #include "components/variations/hashing.h"
 #include "google_apis/google_api_keys.h"
 #include "net/base/url_util.h"
@@ -62,7 +63,13 @@
 
 // Enables the downloading of models.
 const base::Feature kOptimizationGuideModelDownloading{
-    "OptimizationGuideModelDownloading", base::FEATURE_DISABLED_BY_DEFAULT};
+  "OptimizationGuideModelDownloading",
+#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else   // BUILD_WITH_TFLITE_LIB
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif  // !BUILD_WITH_TFLITE_LIB
+};
 
 // Enables page content to be annotated.
 const base::Feature kPageContentAnnotations{"PageContentAnnotations",
diff --git a/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewPlayerTest.java b/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewPlayerTest.java
index 9dfe0c7..3d372e64a 100644
--- a/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewPlayerTest.java
+++ b/components/paint_preview/player/android/javatests/src/org/chromium/components/paintpreview/player/PaintPreviewPlayerTest.java
@@ -30,7 +30,6 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisableIf;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.test.util.DummyUiActivityTestCase;
 import org.chromium.url.GURL;
@@ -42,7 +41,7 @@
  */
 @RunWith(BaseJUnit4ClassRunner.class)
 public class PaintPreviewPlayerTest extends DummyUiActivityTestCase {
-    private static final long TIMEOUT_MS = ScalableTimeout.scaleTimeout(5000);
+    private static final long TIMEOUT_MS = 5000;
 
     private static final String TEST_DIRECTORY_KEY = "test_dir";
     private static final String TEST_URL = "https://www.chromium.org";
diff --git a/components/performance_manager/BUILD.gn b/components/performance_manager/BUILD.gn
index 772ff117..2d265e7 100644
--- a/components/performance_manager/BUILD.gn
+++ b/components/performance_manager/BUILD.gn
@@ -295,6 +295,7 @@
     "test_support:test_support",
     "test_support:test_support_common",
     "//base/test:test_support",
+    "//base/util/memory_pressure:test_support",
     "//content/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/components/performance_manager/graph/system_node_impl.cc b/components/performance_manager/graph/system_node_impl.cc
index ab5a089..8e174eb1 100644
--- a/components/performance_manager/graph/system_node_impl.cc
+++ b/components/performance_manager/graph/system_node_impl.cc
@@ -18,7 +18,11 @@
 
 namespace performance_manager {
 
-SystemNodeImpl::SystemNodeImpl() = default;
+SystemNodeImpl::SystemNodeImpl() {
+  memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>(
+      FROM_HERE, base::BindRepeating(&SystemNodeImpl::OnMemoryPressure,
+                                     base::Unretained(this)));
+}
 
 SystemNodeImpl::~SystemNodeImpl() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -30,4 +34,15 @@
     observer->OnProcessMemoryMetricsAvailable(this);
 }
 
+void SystemNodeImpl::OnMemoryPressure(MemoryPressureLevel new_level) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  for (auto* observer : GetObservers()) {
+    observer->OnBeforeMemoryPressure(new_level);
+  }
+  for (auto* observer : GetObservers()) {
+    observer->OnMemoryPressure(new_level);
+  }
+}
+
 }  // namespace performance_manager
diff --git a/components/performance_manager/graph/system_node_impl.h b/components/performance_manager/graph/system_node_impl.h
index 1e0c859..6398ff8c 100644
--- a/components/performance_manager/graph/system_node_impl.h
+++ b/components/performance_manager/graph/system_node_impl.h
@@ -10,10 +10,12 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "base/memory/memory_pressure_listener.h"
 #include "base/memory/weak_ptr.h"
 #include "base/process/process_handle.h"
 #include "base/time/time.h"
 #include "components/performance_manager/graph/node_base.h"
+#include "components/performance_manager/graph/properties.h"
 #include "components/performance_manager/public/graph/system_node.h"
 
 namespace performance_manager {
@@ -31,12 +33,21 @@
   // nodes.
   void OnProcessMemoryMetricsAvailable();
 
+  void OnMemoryPressureForTesting(MemoryPressureLevel new_level) {
+    OnMemoryPressure(new_level);
+  }
+
   base::WeakPtr<SystemNodeImpl> GetWeakPtr() {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     return weak_factory_.GetWeakPtr();
   }
 
  private:
+  void OnMemoryPressure(MemoryPressureLevel new_level);
+
+  // The memory pressure listener.
+  std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
+
   base::WeakPtrFactory<SystemNodeImpl> weak_factory_
       GUARDED_BY_CONTEXT(sequence_checker_){this};
 
diff --git a/components/performance_manager/graph/system_node_impl_unittest.cc b/components/performance_manager/graph/system_node_impl_unittest.cc
index c641f924..2017e2e48 100644
--- a/components/performance_manager/graph/system_node_impl_unittest.cc
+++ b/components/performance_manager/graph/system_node_impl_unittest.cc
@@ -4,6 +4,9 @@
 
 #include "components/performance_manager/graph/system_node_impl.h"
 
+#include "base/memory/memory_pressure_listener.h"
+#include "base/run_loop.h"
+#include "base/util/memory_pressure/fake_memory_pressure_monitor.h"
 #include "components/performance_manager/graph/frame_node_impl.h"
 #include "components/performance_manager/graph/page_node_impl.h"
 #include "components/performance_manager/graph/process_node_impl.h"
@@ -58,6 +61,10 @@
   MOCK_METHOD1(OnSystemNodeAdded, void(const SystemNode*));
   MOCK_METHOD1(OnBeforeSystemNodeRemoved, void(const SystemNode*));
   MOCK_METHOD1(OnProcessMemoryMetricsAvailable, void(const SystemNode*));
+  MOCK_METHOD1(OnMemoryPressure,
+               void(base::MemoryPressureListener::MemoryPressureLevel));
+  MOCK_METHOD1(OnBeforeMemoryPressure,
+               void(base::MemoryPressureListener::MemoryPressureLevel));
 
   void SetNotifiedSystemNode(const SystemNode* system_node) {
     notified_system_node_ = system_node;
@@ -77,6 +84,7 @@
 
 using testing::_;
 using testing::Invoke;
+using testing::InvokeWithoutArgs;
 
 }  // namespace
 
@@ -95,6 +103,17 @@
   SystemNodeImpl::FromNode(system_node)->OnProcessMemoryMetricsAvailable();
   EXPECT_EQ(system_node, obs.TakeNotifiedSystemNode());
 
+  EXPECT_CALL(obs, OnBeforeMemoryPressure(
+                       base::MemoryPressureListener::MemoryPressureLevel::
+                           MEMORY_PRESSURE_LEVEL_CRITICAL));
+  EXPECT_CALL(
+      obs, OnMemoryPressure(base::MemoryPressureListener::MemoryPressureLevel::
+                                MEMORY_PRESSURE_LEVEL_CRITICAL));
+  SystemNodeImpl::FromNode(system_node)
+      ->OnMemoryPressureForTesting(
+          base::MemoryPressureListener::MemoryPressureLevel::
+              MEMORY_PRESSURE_LEVEL_CRITICAL);
+
   // Release the system node and expect a call to "OnBeforeSystemNodeRemoved".
   EXPECT_CALL(obs, OnBeforeSystemNodeRemoved(_))
       .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedSystemNode));
@@ -104,4 +123,55 @@
   graph()->RemoveSystemNodeObserver(&obs);
 }
 
+TEST_F(SystemNodeImplTest, MemoryPressureNotifiation) {
+  MockObserver obs;
+  graph()->AddSystemNodeObserver(&obs);
+
+  EXPECT_CALL(obs, OnSystemNodeAdded(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedSystemNode));
+  const SystemNode* system_node = graph()->FindOrCreateSystemNode();
+  EXPECT_EQ(system_node, obs.TakeNotifiedSystemNode());
+
+  util::test::FakeMemoryPressureMonitor mem_pressure_monitor;
+
+  {
+    base::RunLoop run_loop;
+    auto quit_closure = run_loop.QuitClosure();
+    EXPECT_CALL(obs, OnBeforeMemoryPressure(
+                         base::MemoryPressureListener::MemoryPressureLevel::
+                             MEMORY_PRESSURE_LEVEL_CRITICAL))
+        .WillOnce(InvokeWithoutArgs([&]() { std::move(quit_closure).Run(); }));
+    EXPECT_CALL(obs, OnMemoryPressure(
+                         base::MemoryPressureListener::MemoryPressureLevel::
+                             MEMORY_PRESSURE_LEVEL_CRITICAL));
+    mem_pressure_monitor.SetAndNotifyMemoryPressure(
+        base::MemoryPressureListener::MemoryPressureLevel::
+            MEMORY_PRESSURE_LEVEL_CRITICAL);
+    run_loop.Run();
+  }
+
+  {
+    base::RunLoop run_loop;
+    auto quit_closure = run_loop.QuitClosure();
+    EXPECT_CALL(obs, OnBeforeMemoryPressure(
+                         base::MemoryPressureListener::MemoryPressureLevel::
+                             MEMORY_PRESSURE_LEVEL_MODERATE))
+        .WillOnce(InvokeWithoutArgs([&]() { std::move(quit_closure).Run(); }));
+    EXPECT_CALL(obs, OnMemoryPressure(
+                         base::MemoryPressureListener::MemoryPressureLevel::
+                             MEMORY_PRESSURE_LEVEL_MODERATE));
+    mem_pressure_monitor.SetAndNotifyMemoryPressure(
+        base::MemoryPressureListener::MemoryPressureLevel::
+            MEMORY_PRESSURE_LEVEL_MODERATE);
+    run_loop.Run();
+  }
+
+  EXPECT_CALL(obs, OnBeforeSystemNodeRemoved(_))
+      .WillOnce(Invoke(&obs, &MockObserver::SetNotifiedSystemNode));
+  graph()->ReleaseSystemNodeForTesting();
+  EXPECT_EQ(system_node, obs.TakeNotifiedSystemNode());
+
+  graph()->RemoveSystemNodeObserver(&obs);
+}
+
 }  // namespace performance_manager
diff --git a/components/performance_manager/public/graph/system_node.h b/components/performance_manager/public/graph/system_node.h
index dacf1f03..82f6f24 100644
--- a/components/performance_manager/public/graph/system_node.h
+++ b/components/performance_manager/public/graph/system_node.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_GRAPH_SYSTEM_NODE_H_
 
 #include "base/macros.h"
+#include "base/memory/memory_pressure_listener.h"
 #include "components/performance_manager/public/graph/node.h"
 
 namespace performance_manager {
@@ -17,6 +18,7 @@
 class SystemNode : public Node {
  public:
   using Observer = SystemNodeObserver;
+  using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
   class ObserverDefaultImpl;
 
   SystemNode();
@@ -45,6 +47,24 @@
   virtual void OnProcessMemoryMetricsAvailable(
       const SystemNode* system_node) = 0;
 
+  // Called before OnMemoryPressure(). This can be used to track state before
+  // memory start being released in response to memory pressure.
+  //
+  // Note: This is guaranteed to be invoked before OnMemoryPressure(), but
+  // will not necessarily be called before base::MemoryPressureListeners
+  // are notified.
+  virtual void OnBeforeMemoryPressure(
+      base::MemoryPressureListener::MemoryPressureLevel new_level) = 0;
+
+  // Called when the system is under memory pressure. Observers may start
+  // releasing memory in response to memory pressure.
+  //
+  // NOTE: This isn't called for a transition to the MEMORY_PRESSURE_LEVEL_NONE
+  // level. For this reason there's no corresponding property in this node and
+  // the response to these notifications should be stateless.
+  virtual void OnMemoryPressure(
+      base::MemoryPressureListener::MemoryPressureLevel new_level) = 0;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(SystemNodeObserver);
 };
@@ -62,6 +82,10 @@
   void OnBeforeSystemNodeRemoved(const SystemNode* system_node) override {}
   void OnProcessMemoryMetricsAvailable(const SystemNode* system_node) override {
   }
+  void OnBeforeMemoryPressure(
+      base::MemoryPressureListener::MemoryPressureLevel new_level) override {}
+  void OnMemoryPressure(
+      base::MemoryPressureListener::MemoryPressureLevel new_level) override {}
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ObserverDefaultImpl);
diff --git a/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java b/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
index 94f127a..7ac9c0e 100644
--- a/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
+++ b/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
@@ -16,6 +16,7 @@
 
 import org.chromium.base.BuildConfig;
 import org.chromium.base.ContextUtils;
+import org.chromium.base.StrictModeContext;
 import org.chromium.base.ThreadUtils;
 
 import java.util.List;
@@ -62,16 +63,21 @@
             Context context = ContextUtils.getApplicationContext();
             // Policy cache is not accessiable without application context.
             if (context == null) return null;
-            mSharedPreferences = context.getSharedPreferences(POLICY_PREF, Context.MODE_PRIVATE);
+            try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
+                mSharedPreferences =
+                        context.getSharedPreferences(POLICY_PREF, Context.MODE_PRIVATE);
+            }
         }
         return mSharedPreferences;
     }
 
     private SharedPreferences.Editor getSharedPreferencesEditor() {
         mThreadChecker.assertOnValidThread();
-        return ContextUtils.getApplicationContext()
-                .getSharedPreferences(POLICY_PREF, Context.MODE_PRIVATE)
-                .edit();
+        try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
+            return ContextUtils.getApplicationContext()
+                    .getSharedPreferences(POLICY_PREF, Context.MODE_PRIVATE)
+                    .edit();
+        }
     }
 
     public static PolicyCache get() {
diff --git a/components/policy/core/browser/policy_pref_mapping_test.cc b/components/policy/core/browser/policy_pref_mapping_test.cc
index 55a74bb..73fab09 100644
--- a/components/policy/core/browser/policy_pref_mapping_test.cc
+++ b/components/policy/core/browser/policy_pref_mapping_test.cc
@@ -64,48 +64,44 @@
   return policy_name_decorated;
 }
 
-// Contains the details of a single test case verifying that the controlled
-// setting indicators for a pref affected by a policy work correctly. This is
-// part of the data loaded from chrome/test/data/policy/policy_test_cases.json.
-class PrefIndicatorTest {
- public:
-  explicit PrefIndicatorTest(const base::Value& indicator_test) {
-    base::Optional<bool> readonly = indicator_test.FindBoolKey("readonly");
-    const std::string* value = indicator_test.FindStringKey("value");
-    const std::string* selector = indicator_test.FindStringKey("selector");
-    const std::string* test_url = indicator_test.FindStringKey("test_url");
-    const std::string* pref = indicator_test.FindStringKey("pref");
-    const std::string* test_setup_js =
-        indicator_test.FindStringKey("test_setup_js");
+void CheckPrefHasValue(const PrefService::Preference* pref,
+                       const base::Value* expected_value) {
+  EXPECT_TRUE(pref->GetValue()->Equals(expected_value))
+      << *pref->GetValue() << " != " << *expected_value;
+}
 
-    readonly_ = readonly.value_or(false);
-    value_ = value ? *value : std::string();
-    test_url_ = test_url ? *test_url : std::string();
-    test_setup_js_ = test_setup_js ? *test_setup_js : std::string();
-    selector_ = selector ? *selector : std::string();
-    pref_ = pref ? *pref : std::string();
-  }
+void CheckPrefHasDefaultValue(const PrefService::Preference* pref,
+                              const base::Value* expected_value = nullptr) {
+  EXPECT_TRUE(pref->IsDefaultValue());
+  EXPECT_TRUE(pref->IsUserModifiable());
+  EXPECT_FALSE(pref->IsUserControlled());
+  EXPECT_FALSE(pref->IsManaged());
+  EXPECT_FALSE(pref->IsRecommended());
+  if (expected_value)
+    CheckPrefHasValue(pref, expected_value);
+}
 
-  ~PrefIndicatorTest() = default;
+void CheckPrefHasRecommendedValue(const PrefService::Preference* pref,
+                                  const base::Value* expected_value = nullptr) {
+  EXPECT_FALSE(pref->IsDefaultValue());
+  EXPECT_TRUE(pref->IsUserModifiable());
+  EXPECT_FALSE(pref->IsUserControlled());
+  EXPECT_FALSE(pref->IsManaged());
+  EXPECT_TRUE(pref->IsRecommended());
+  if (expected_value)
+    CheckPrefHasValue(pref, expected_value);
+}
 
-  const std::string& value() const { return value_; }
-  const std::string& test_url() const { return test_url_; }
-  const std::string& test_setup_js() const { return test_setup_js_; }
-  const std::string& selector() const { return selector_; }
-  const std::string& pref() const { return pref_; }
-
-  bool readonly() const { return readonly_; }
-
- private:
-  bool readonly_;
-  std::string value_;
-  std::string test_url_;
-  std::string test_setup_js_;
-  std::string selector_;
-  std::string pref_;
-
-  DISALLOW_COPY_AND_ASSIGN(PrefIndicatorTest);
-};
+void CheckPrefHasMandatoryValue(const PrefService::Preference* pref,
+                                const base::Value* expected_value = nullptr) {
+  EXPECT_FALSE(pref->IsDefaultValue());
+  EXPECT_FALSE(pref->IsUserModifiable());
+  EXPECT_FALSE(pref->IsUserControlled());
+  EXPECT_TRUE(pref->IsManaged());
+  EXPECT_FALSE(pref->IsRecommended());
+  if (expected_value)
+    CheckPrefHasValue(pref, expected_value);
+}
 
 // Contains the testing details for a single pref affected by one or multiple
 // policies. This is part of the data loaded from
@@ -114,7 +110,6 @@
  public:
   explicit PrefTestCase(const std::string& name, const base::Value& settings) {
     const base::Value* value = settings.FindKey("value");
-    const base::Value* indicator_test = settings.FindDictKey("indicator_test");
     location_ = GetPrefLocation(settings);
     check_for_mandatory_ =
         settings.FindBoolKey("check_for_mandatory").value_or(true);
@@ -125,10 +120,6 @@
     pref_ = name;
     if (value)
       value_ = value->CreateDeepCopy();
-    if (indicator_test) {
-      pref_indicator_test_ =
-          std::make_unique<PrefIndicatorTest>(*indicator_test);
-    }
   }
 
   ~PrefTestCase() = default;
@@ -146,10 +137,6 @@
 
   bool expect_default() const { return expect_default_; }
 
-  const PrefIndicatorTest* indicator_test_case() const {
-    return pref_indicator_test_.get();
-  }
-
  private:
   std::string pref_;
   std::unique_ptr<base::Value> value_;
@@ -157,7 +144,6 @@
   bool check_for_mandatory_;
   bool check_for_recommended_;
   bool expect_default_;
-  std::unique_ptr<PrefIndicatorTest> pref_indicator_test_;
 };
 
 // Contains the testing details for a single pref affected by a policy. This is
@@ -460,10 +446,14 @@
 
       for (const auto& pref_mapping : pref_mappings) {
         for (const auto& pref_case : pref_mapping->prefs()) {
-          // Skip preferences that should not be checked when the policy is set
-          // to a mandatory value.
-          if (!pref_case->check_for_mandatory())
-            continue;
+          const bool check_recommended = test_case->can_be_recommended() &&
+                                         pref_case->check_for_recommended();
+          const bool check_mandatory = pref_case->check_for_mandatory();
+
+          EXPECT_TRUE(check_recommended || check_mandatory)
+              << "pref mapping test for " << policy.first << "(pref "
+              << pref_case->pref()
+              << ") has to either be for recommended/mandatory or both";
 
           PrefService* prefs = nullptr;
           switch (pref_case->location()) {
@@ -488,7 +478,12 @@
           if (!prefs)
             continue;
 
-          LOG(INFO) << "Testing policy: " << policy.first;
+          LOG(INFO) << "Testing policy " << policy.first << " (pref "
+                    << pref_case->pref() << " with "
+                    << ((check_recommended) ? "recommended" : "")
+                    << ((check_recommended && check_mandatory) ? " & " : "")
+                    << ((check_mandatory) ? "mandatory" : "")
+                    << " policy values)";
 
           if (!preprocessor_macros_checker.SupportsTest(pref_mapping.get())) {
             LOG(INFO) << " Skipping policy_pref_mapping_test because of "
@@ -497,34 +492,34 @@
           }
           // The preference must have been registered.
           const PrefService::Preference* pref =
-              prefs->FindPreference(pref_case->pref().c_str());
+              prefs->FindPreference(pref_case->pref());
           ASSERT_TRUE(pref)
-              << "Pref " << pref_case->pref().c_str() << " not registered";
+              << "Pref " << pref_case->pref() << " not registered";
 
-          // Verify that setting the policy overrides the pref.
           provider->UpdateChromePolicy(PolicyMap());
-          prefs->ClearPref(pref_case->pref().c_str());
-          EXPECT_TRUE(pref->IsDefaultValue());
-          EXPECT_TRUE(pref->IsUserModifiable());
-          EXPECT_FALSE(pref->IsUserControlled());
-          EXPECT_FALSE(pref->IsManaged());
+          prefs->ClearPref(pref_case->pref());
+          CheckPrefHasDefaultValue(pref);
 
-          ASSERT_NO_FATAL_FAILURE(SetProviderPolicy(
-              provider, pref_mapping->policies(), POLICY_LEVEL_MANDATORY));
-          if (pref_case->expect_default()) {
-            EXPECT_TRUE(pref->IsDefaultValue());
-            EXPECT_TRUE(pref->IsUserModifiable());
-            EXPECT_FALSE(pref->IsUserControlled());
-            EXPECT_FALSE(pref->IsManaged());
-          } else {
-            EXPECT_FALSE(pref->IsDefaultValue());
-            EXPECT_FALSE(pref->IsUserModifiable());
-            EXPECT_FALSE(pref->IsUserControlled());
-            EXPECT_TRUE(pref->IsManaged());
+          const base::Value* expected_value = pref_case->value();
+
+          if (check_recommended) {
+            ASSERT_NO_FATAL_FAILURE(SetProviderPolicy(
+                provider, pref_mapping->policies(), POLICY_LEVEL_RECOMMENDED));
+            if (pref_case->expect_default()) {
+              CheckPrefHasDefaultValue(pref, expected_value);
+            } else {
+              CheckPrefHasRecommendedValue(pref, expected_value);
+            }
           }
-          if (pref_case->value()) {
-            EXPECT_TRUE(pref->GetValue()->Equals(pref_case->value()))
-                << *pref->GetValue() << " != " << *pref_case->value();
+
+          if (check_mandatory) {
+            ASSERT_NO_FATAL_FAILURE(SetProviderPolicy(
+                provider, pref_mapping->policies(), POLICY_LEVEL_MANDATORY));
+            if (pref_case->expect_default()) {
+              CheckPrefHasDefaultValue(pref, expected_value);
+            } else {
+              CheckPrefHasMandatoryValue(pref, expected_value);
+            }
           }
         }
       }
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 7f9f1e5..4b3c6fe 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -574,6 +574,8 @@
         'WebUsbBlockedForUrls',
         'SerialAskForUrls',
         'SerialBlockedForUrls',
+        'SerialAllowAllPortsForUrls',
+        'SerialAllowUsbDevicesForUrls',
       ],
     },
     {
@@ -6155,9 +6157,9 @@
 
       Deprecated: The USB permission model used to support specifying both the requesting and embedding URLs. This is deprecated and only supported for backwards compatiblity in this manner: if both a requesting and embedding URL is specified, then the embedding URL will be granted the permission as top-level origin and the requsting URL will be ignored entirely.
 
-      Leaving the policy unset means <ph name="DEFAULT_WEB_USB_GUARD_SETTING_POLICY_NAME">DefaultWebUsbGuardSetting</ph> applies, if it's set. If not, the user's personal setting applies.
+      This policy overrides <ph name="DEFAULT_WEB_USB_GUARD_SETTING_POLICY_NAME">DefaultWebUsbGuardSetting</ph>, <ph name="WEB_USB_ASK_FOR_URLS_POLICY_NAME">WebUsbAskForUrls</ph>, <ph name="WEB_USB_BLOCKED_FOR_URLS_POLICY_NAME">WebUsbBlockedForUrls</ph> and the user's preferences.
 
-      URL patterns in this policy shouldn't conflict with those configured through <ph name="WEB_USB_BLOCKED_FOR_URLS_POLICY_NAME">WebUsbBlockedForUrls</ph>. If they do, this policy takes precedence over <ph name="WEB_USB_BLOCKED_FOR_URLS_POLICY_NAME">WebUsbBlockedForUrls</ph> and <ph name="WEB_USB_ASK_FOR_URLS_POLICY_NAME">WebUsbAskForUrls</ph>.''',
+      This policy only affects access to USB devices through the WebUSB API. To grant access to USB devices through the Web Serial API see the <ph name="SERIAL_ALLOW_USB_DEVICES_FOR_URLS_POLICY_NAME">SerialAllowUsbDevicesForUrls</ph> policy.''',
     },
     {
       'name': 'DeviceLoginScreenWebUsbAllowDevicesForUrls',
@@ -6346,6 +6348,83 @@
       For detailed information on valid <ph name="URL_LABEL">url</ph> patterns, please see https://cloud.google.com/docs/chrome-enterprise/policies/url-patterns. <ph name="WILDCARD_VALUE">*</ph> is not an accepted value for this policy.''',
     },
     {
+      'name': 'SerialAllowAllPortsForUrls',
+      'owners': ['reillyg@chromium.org', 'file://content/browser/serial/OWNERS'],
+      'type': 'list',
+      'schema': {
+        'type': 'array',
+        'items': { 'type': 'string' },
+      },
+      'future_on': ['chrome_os', 'chrome.*'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': ['https://www.example.com'],
+      'id': 837,
+      'caption': '''Automatically grant permission to sites to connect all serial ports.''',
+      'tags': [],
+      'desc': '''Setting the policy allows you to list sites which are automatically granted permission to access all available serial ports.
+
+      The URLs must be valid, otherwise the policy is ignored. Only the origin (scheme, host and port) of the URL is considered.
+
+      This policy overrides <ph name="DEFAULT_SERIAL_GUARD_SETTING_POLICY_NAME">DefaultSerialGuardSetting</ph>, <ph name="SERIAL_ASK_FOR_URLS_POLICY_NAME">SerialAskForUrls</ph>, <ph name="SERIAL_BLOCKED_FOR_URLS_POLICY_NAME">SerialBlockedForUrls</ph> and the user's preferences.''',
+    },
+    {
+      'name': 'SerialAllowUsbDevicesForUrls',
+      'owners': ['reillyg@chromium.org', 'file://content/browser/serial/OWNERS'],
+      'type': 'dict',
+      'schema': {
+        'type': 'array',
+        'items': {
+          'type': 'object',
+          'properties': {
+            'devices': {
+              'type': 'array',
+              'items': {
+                'type': 'object',
+                'properties': {
+                  'vendor_id': { 'type': 'integer', 'minimum': 0, 'maximum': 65535 },
+                  'product_id': { 'type': 'integer', 'minimum': 0, 'maximum': 65535 }
+                },
+                'required': ['vendor_id']
+              }
+            },
+            'urls': {
+              'type': 'array',
+              'items': { 'type': 'string' }
+            }
+          },
+          'required': ['devices', 'urls']
+        }
+      },
+      'future_on': ['chrome_os', 'chrome.*'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': [
+        {
+          'devices': [{'vendor_id': 1234, 'product_id': 5678}],
+          'urls': ['https://specific-device.example.com']
+        },
+        {
+          'devices': [{'vendor_id': 1234}],
+          'urls': ['https://all-vendor-devices.example.com']
+        }
+      ],
+      'id': 838,
+      'caption': '''Automatically grant permission to sites to connect to USB serial devices.''',
+      'tags': ['website-sharing'],
+      'desc': '''Setting the policy allows you to list sites which are automatically granted permission to access USB serial devices with vendor and product IDs matching the <ph name="VENDOR_ID_FIELD_NAME">vendor_id</ph> and <ph name="PRODUCT_ID_FIELD_NAME">product_id</ph> fields. Omitting the <ph name="PRODUCT_ID_FIELD_NAME">product_id</ph> field allows the given sites permission to access devices with a vendor ID matching the <ph name="VENDOR_ID_FIELD_NAME">vendor_id</ph> field and any product ID.
+
+      The URLs must be valid, otherwise the policy is ignored. Only the origin (scheme, host and port) of the URL is considered.
+
+      This policy overrides <ph name="DEFAULT_SERIAL_GUARD_SETTING_POLICY_NAME">DefaultSerialGuardSetting</ph>, <ph name="SERIAL_ASK_FOR_URLS_POLICY_NAME">SerialAskForUrls</ph>, <ph name="SERIAL_BLOCKED_FOR_URLS_POLICY_NAME">SerialBlockedForUrls</ph> and the user's preferences.
+
+      This policy only affects access to USB devices through the Web Serial API. To grant access to USB devices through the WebUSB API see the <ph name="WEB_USB_ALLOW_DEVICES_FOR_URLS_POLICY_NAME">WebUsbAllowDevicesForUrls</ph> policy.''',
+    },
+    {
       'name': 'DefaultFileSystemReadGuardSetting',
       'owners': ['mek@chromium.org', 'file://content/browser/file_system_access/OWNERS'],
       'type': 'int-enum',
@@ -25984,6 +26063,6 @@
   'placeholders': [],
   'deleted_policy_ids': [114, 115, 204, 205, 206, 412, 476, 544, 546, 562, 569, 578, 583, 585, 586, 587, 588, 589, 590, 591, 600, 668, 669],
   'deleted_atomic_policy_group_ids': [19],
-  'highest_id_currently_used': 836,
+  'highest_id_currently_used': 838,
   'highest_atomic_group_id_currently_used': 40
 }
diff --git a/components/safe_browsing/core/db/v4_local_database_manager.cc b/components/safe_browsing/core/db/v4_local_database_manager.cc
index 508deed..0b1ac50d 100644
--- a/components/safe_browsing/core/db/v4_local_database_manager.cc
+++ b/components/safe_browsing/core/db/v4_local_database_manager.cc
@@ -300,7 +300,9 @@
                        : base::ThreadPool::CreateSequencedTaskRunner(
                              {base::MayBlock(),
                               base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
-  RenameOldStoreFiles();
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&V4LocalDatabaseManager::RenameOldStoreFiles,
+                                weak_factory_.GetWeakPtr()));
 
   DCHECK(!base_path_.empty());
   DCHECK(!list_infos_.empty());
@@ -935,7 +937,6 @@
     const base::StringPiece& new_name = pair.second;
 
     const base::FilePath old_store_path = base_path_.AppendASCII(old_name);
-
     // Is the old filename also being used for a valid V4Store?
     auto it = std::find_if(
         std::begin(list_infos_), std::end(list_infos_),
@@ -967,9 +968,7 @@
       continue;
     }
 
-    task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&V4LocalDatabaseManager::RenameStoreFile,
-                                  old_store_path, new_store_path));
+    RenameStoreFile(old_store_path, new_store_path);
   }
 }
 
@@ -979,7 +978,7 @@
   base::File::Error error = base::File::FILE_OK;
   base::ReplaceFile(old_path, new_path, &error);
 
-  UMA_HISTOGRAM_ENUMERATION(
+  base::UmaHistogramExactLinear(
       "SafeBrowsing.V4Store.RenameStatus" + GetUmaSuffixForStore(new_path),
       -error, -base::File::FILE_ERROR_MAX);
 }
diff --git a/components/safe_browsing/core/db/v4_local_database_manager_unittest.cc b/components/safe_browsing/core/db/v4_local_database_manager_unittest.cc
index 8a634b836..cd7c4c27 100644
--- a/components/safe_browsing/core/db/v4_local_database_manager_unittest.cc
+++ b/components/safe_browsing/core/db/v4_local_database_manager_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/containers/fixed_flat_map.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
@@ -1538,14 +1539,12 @@
   base::WriteFile(old_store_path, "", 0);
   ASSERT_TRUE(base::PathExists(old_store_path));
 
-  // Reset the database manager so that RenameOldStoreFiles() is called.
-  ResetLocalDatabaseManager();
   WaitForTasksOnTaskRunner();
   ASSERT_FALSE(base::PathExists(old_store_path));
 
-  auto new_file_path =
+  auto new_store_path =
       base_dir_.GetPath().AppendASCII(new_store_name + ".store");
-  ASSERT_TRUE(base::PathExists(new_file_path));
+  ASSERT_TRUE(base::PathExists(new_store_path));
 
   histograms.ExpectTotalCount(old_name_in_use_histogram, 1);
   histograms.ExpectBucketCount(old_name_in_use_histogram, false, 1);
@@ -1560,7 +1559,89 @@
   histograms.ExpectBucketCount(rename_status_histogram, 0, 1);
 
   // Cleanup
-  base::DeleteFile(new_file_path);
+  base::DeleteFile(new_store_path);
+}
+
+TEST_F(V4LocalDatabaseManagerTest, RenameStoreFile_RenameSuccessMultiple) {
+  const std::string prefix = "SafeBrowsing.V4Store.";
+  const std::string old_name_in_use_prefix = prefix + "OldFileNameInUse.";
+  const std::string old_name_exists_prefix = prefix + "OldFileNameExists.";
+  const std::string new_name_exists_prefix = prefix + "NewFileNameExists.";
+  const std::string rename_status_prefix = prefix + "RenameStatus.";
+
+  const auto kStoreFilesToRename =
+      base::MakeFixedFlatMap<std::string, std::string>({
+          {"CertCsdDownloadWhitelist", "CertCsdDownloadAllowlist"},
+          {"UrlCsdDownloadWhitelist", "UrlCsdDownloadAllowlist"},
+          {"UrlCsdWhitelist", "UrlCsdAllowlist"},
+      });
+
+  base::HistogramTester histograms;
+  for (auto const& pair : kStoreFilesToRename) {
+    const std::string& old_store_name = pair.first;
+    const std::string& new_store_name = pair.second;
+
+    std::string old_name_in_use_histogram =
+        old_name_in_use_prefix + old_store_name;
+    histograms.ExpectTotalCount(old_name_in_use_histogram, 0);
+    std::string old_name_exists_histogram =
+        old_name_exists_prefix + old_store_name;
+    histograms.ExpectTotalCount(old_name_exists_histogram, 0);
+
+    std::string new_name_exists_histogram =
+        new_name_exists_prefix + new_store_name;
+    histograms.ExpectTotalCount(new_name_exists_histogram, 0);
+    std::string rename_status_histogram = rename_status_prefix + new_store_name;
+    histograms.ExpectTotalCount(rename_status_histogram, 0);
+
+    auto old_store_path =
+        base_dir_.GetPath().AppendASCII(old_store_name + ".store");
+    ASSERT_FALSE(base::PathExists(old_store_path));
+
+    auto new_store_path =
+        base_dir_.GetPath().AppendASCII(new_store_name + ".store");
+    ASSERT_FALSE(base::PathExists(new_store_path));
+
+    // Now write an empty file at |old_store_path|.
+    base::WriteFile(old_store_path, "", 0);
+    ASSERT_TRUE(base::PathExists(old_store_path));
+  }
+
+  WaitForTasksOnTaskRunner();
+  for (auto const& pair : kStoreFilesToRename) {
+    const std::string& old_store_name = pair.first;
+    const std::string& new_store_name = pair.second;
+
+    auto old_store_path =
+        base_dir_.GetPath().AppendASCII(old_store_name + ".store");
+    ASSERT_FALSE(base::PathExists(old_store_path));
+
+    auto new_store_path =
+        base_dir_.GetPath().AppendASCII(new_store_name + ".store");
+    ASSERT_TRUE(base::PathExists(new_store_path));
+
+    std::string old_name_in_use_histogram =
+        old_name_in_use_prefix + old_store_name;
+    histograms.ExpectTotalCount(old_name_in_use_histogram, 1);
+    histograms.ExpectBucketCount(old_name_in_use_histogram, false, 1);
+
+    std::string old_name_exists_histogram =
+        old_name_exists_prefix + old_store_name;
+    histograms.ExpectTotalCount(old_name_exists_histogram, 1);
+    histograms.ExpectBucketCount(old_name_exists_histogram, true, 1);
+
+    std::string new_name_exists_histogram =
+        new_name_exists_prefix + new_store_name;
+    histograms.ExpectTotalCount(new_name_exists_histogram, 1);
+    histograms.ExpectBucketCount(new_name_exists_histogram, false, 1);
+
+    std::string rename_status_histogram = rename_status_prefix + new_store_name;
+    histograms.ExpectTotalCount(rename_status_histogram, 1);
+    histograms.ExpectBucketCount(rename_status_histogram, 0, 1);
+
+    // Cleanup
+    base::DeleteFile(new_store_path);
+  }
 }
 
 TEST_F(V4LocalDatabaseManagerTest,
@@ -1587,8 +1668,6 @@
       base_dir_.GetPath().AppendASCII(old_store_name + ".store");
   ASSERT_FALSE(base::PathExists(old_store_path));
 
-  // Reset the database manager so that RenameOldStoreFiles() is called.
-  ResetLocalDatabaseManager();
   WaitForTasksOnTaskRunner();
 
   histograms.ExpectTotalCount(old_name_in_use_histogram, 1);
@@ -1639,8 +1718,6 @@
   base::WriteFile(new_store_path, "", 0);
   ASSERT_TRUE(base::PathExists(new_store_path));
 
-  // Reset the database manager so that RenameOldStoreFiles() is called.
-  ResetLocalDatabaseManager();
   WaitForTasksOnTaskRunner();
 
   histograms.ExpectTotalCount(old_name_in_use_histogram, 1);
diff --git a/components/sync/engine/commit_contribution_impl.h b/components/sync/engine/commit_contribution_impl.h
index 4d092ac6..d37b8ab 100644
--- a/components/sync/engine/commit_contribution_impl.h
+++ b/components/sync/engine/commit_contribution_impl.h
@@ -77,7 +77,8 @@
   // all (i.e. there is no internet connection).
   base::OnceCallback<void(SyncCommitError)> on_full_commit_failure_callback_;
 
-  // A non-owned pointer to cryptographer to encrypt entities.
+  // Null if |type_| is not encrypted. Otherwise this is used to encrypt the
+  // committed entities.
   Cryptographer* const cryptographer_;
 
   const PassphraseType passphrase_type_;
diff --git a/components/sync/engine/model_type_registry.cc b/components/sync/engine/model_type_registry.cc
index f85be165..2e53429a 100644
--- a/components/sync/engine/model_type_registry.cc
+++ b/components/sync/engine/model_type_registry.cc
@@ -51,14 +51,19 @@
 
 }  // namespace
 
-ModelTypeRegistry::ModelTypeRegistry(NudgeHandler* nudge_handler,
-                                     CancelationSignal* cancelation_signal,
-                                     KeystoreKeysHandler* keystore_keys_handler)
+ModelTypeRegistry::ModelTypeRegistry(
+    NudgeHandler* nudge_handler,
+    CancelationSignal* cancelation_signal,
+    SyncEncryptionHandler* sync_encryption_handler)
     : nudge_handler_(nudge_handler),
       cancelation_signal_(cancelation_signal),
-      keystore_keys_handler_(keystore_keys_handler) {}
+      sync_encryption_handler_(sync_encryption_handler) {
+  sync_encryption_handler_->AddObserver(this);
+}
 
-ModelTypeRegistry::~ModelTypeRegistry() = default;
+ModelTypeRegistry::~ModelTypeRegistry() {
+  sync_encryption_handler_->RemoveObserver(this);
+}
 
 void ModelTypeRegistry::ConnectDataType(
     ModelType type,
@@ -75,22 +80,16 @@
   bool initial_sync_done =
       activation_response->model_type_state.initial_sync_done();
 
-  DCHECK(!encrypted_types_.Has(type) || cryptographer_)
-      << "Connecting encrypted type " << ModelTypeToString(type)
-      << " but the cryptographer isn't set";
-
   auto worker = std::make_unique<ModelTypeWorker>(
       type, activation_response->model_type_state,
       /*trigger_initial_sync=*/!initial_sync_done,
-      encrypted_types_.Has(type) ? cryptographer_->Clone() : nullptr,
+      encrypted_types_.Has(type) ? sync_encryption_handler_->GetCryptographer()
+                                 : nullptr,
       passphrase_type_, nudge_handler_,
       std::move(activation_response->type_processor), cancelation_signal_);
 
-  // If the cryptographer wasn't set yet, it will be informed to this |worker|
-  // as soon as it's set in OnCryptographerStateChanged().
-  if (cryptographer_) {
-    worker->UpdateFallbackCryptographerForUma(cryptographer_->Clone());
-  }
+  worker->SetFallbackCryptographerForUma(
+      sync_encryption_handler_->GetCryptographer());
 
   // Save a raw pointer and add the worker to our structures.
   ModelTypeWorker* worker_ptr = worker.get();
@@ -171,7 +170,7 @@
 }
 
 KeystoreKeysHandler* ModelTypeRegistry::keystore_keys_handler() {
-  return keystore_keys_handler_;
+  return sync_encryption_handler_->GetKeystoreKeysHandler();
 }
 
 bool ModelTypeRegistry::HasUnsyncedItems() const {
@@ -217,22 +216,24 @@
 
 void ModelTypeRegistry::OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                                 bool encrypt_everything) {
-  // TODO(skym): This does not handle reducing the number of encrypted types
-  // correctly. They're removed from |encrypted_types_| but corresponding
-  // workers never have their Cryptographers removed. This probably is not a use
-  // case that currently needs to be supported, but it should be guarded against
-  // here.
+  DCHECK(encrypted_types.HasAll(encrypted_types_))
+      << "ModelTypeRegistry doesn't support removing encrypted types.";
+  for (const auto& worker : connected_model_type_workers_) {
+    if (encrypted_types.Has(worker->GetModelType()) &&
+        !encrypted_types_.Has(worker->GetModelType())) {
+      worker->EnableEncryption(sync_encryption_handler_->GetCryptographer());
+    }
+  }
   encrypted_types_ = encrypted_types;
-  UpdateCryptographerForConnectedEncryptedTypes();
 }
 
 void ModelTypeRegistry::OnCryptographerStateChanged(
     Cryptographer* cryptographer,
     bool has_pending_keys) {
-  cryptographer_ = cryptographer->Clone();
-  UpdateCryptographerForConnectedEncryptedTypes();
   for (const auto& worker : connected_model_type_workers_) {
-    worker->UpdateFallbackCryptographerForUma(cryptographer_->Clone());
+    if (encrypted_types_.Has(worker->GetModelType())) {
+      worker->OnCryptographerChange();
+    }
   }
 }
 
@@ -246,15 +247,4 @@
   }
 }
 
-void ModelTypeRegistry::UpdateCryptographerForConnectedEncryptedTypes() {
-  for (const auto& worker : connected_model_type_workers_) {
-    if (encrypted_types_.Has(worker->GetModelType())) {
-      DCHECK(cryptographer_)
-          << ModelTypeToString(worker->GetModelType())
-          << " is a connected encrypted type but there's no cryptographer";
-      worker->UpdateCryptographer(cryptographer_->Clone());
-    }
-  }
-}
-
 }  // namespace syncer
diff --git a/components/sync/engine/model_type_registry.h b/components/sync/engine/model_type_registry.h
index fef8ad0..a3da783 100644
--- a/components/sync/engine/model_type_registry.h
+++ b/components/sync/engine/model_type_registry.h
@@ -23,7 +23,7 @@
 
 class CancelationSignal;
 class CommitContributor;
-class KeystoreKeysHandler;
+class SyncEncryptionHandler;
 class ModelTypeWorker;
 class UpdateHandler;
 
@@ -34,9 +34,11 @@
 class ModelTypeRegistry : public ModelTypeConnector,
                           public SyncEncryptionHandler::Observer {
  public:
+  // |nudge_handler|, |cancelation_signal| and |sync_encryption_handler| must
+  // outlive this object.
   ModelTypeRegistry(NudgeHandler* nudge_handler,
                     CancelationSignal* cancelation_signal,
-                    KeystoreKeysHandler* keystore_keys_handler);
+                    SyncEncryptionHandler* sync_encryption_handler);
   ~ModelTypeRegistry() override;
 
   // Implementation of ModelTypeConnector.
@@ -86,9 +88,6 @@
   base::WeakPtr<ModelTypeConnector> AsWeakPtr();
 
  private:
-  // Called when either |cryptographer_| or |encrypted_types_| change.
-  void UpdateCryptographerForConnectedEncryptedTypes();
-
   // Enabled proxy types, which don't have a worker.
   ModelTypeSet enabled_proxy_types_;
 
@@ -99,9 +98,6 @@
   UpdateHandlerMap update_handler_map_;
   CommitContributorMap commit_contributor_map_;
 
-  // A copy of the most recent cryptographer.
-  std::unique_ptr<Cryptographer> cryptographer_;
-
   // A copy of the most recent passphrase type.
   PassphraseType passphrase_type_ =
       SyncEncryptionHandler::kInitialPassphraseType;
@@ -115,7 +111,7 @@
   // ModelTypeWorker to cancel blocking operation.
   CancelationSignal* const cancelation_signal_;
 
-  KeystoreKeysHandler* const keystore_keys_handler_;
+  SyncEncryptionHandler* const sync_encryption_handler_;
 
   base::WeakPtrFactory<ModelTypeRegistry> weak_ptr_factory_{this};
 
diff --git a/components/sync/engine/model_type_worker.cc b/components/sync/engine/model_type_worker.cc
index 95bda2bf..ec1e5c6 100644
--- a/components/sync/engine/model_type_worker.cc
+++ b/components/sync/engine/model_type_worker.cc
@@ -153,7 +153,7 @@
     ModelType type,
     const sync_pb::ModelTypeState& initial_state,
     bool trigger_initial_sync,
-    std::unique_ptr<Cryptographer> cryptographer,
+    Cryptographer* cryptographer,
     PassphraseType passphrase_type,
     NudgeHandler* nudge_handler,
     std::unique_ptr<ModelTypeProcessor> model_type_processor,
@@ -161,7 +161,7 @@
     : type_(type),
       model_type_state_(initial_state),
       model_type_processor_(std::move(model_type_processor)),
-      cryptographer_(std::move(cryptographer)),
+      cryptographer_(cryptographer),
       passphrase_type_(passphrase_type),
       nudge_handler_(nudge_handler),
       min_gu_responses_to_ignore_key_(kMinGuResponsesToIgnoreKey),
@@ -183,15 +183,15 @@
   // type state that has already done its initial sync, and is going to be
   // tracking metadata changes, however it does not have the most recent
   // encryption key name. The cryptographer was updated while the worker was not
-  // around, and we're not going to receive the normal UpdateCryptographer() or
+  // around, and we're not going to receive the usual OnCryptographerChange() or
   // EncryptionAcceptedApplyUpdates() calls to drive this process.
   //
   // If |cryptographer_->CanEncrypt()| is false, all the rest of this logic can
-  // be safely skipped, since |UpdateCryptographer(...)| must be called first
+  // be safely skipped, since |OnCryptographerChange()| must be called first
   // and things should be driven normally after that.
   //
   // If |model_type_state_.initial_sync_done()| is false, |model_type_state_|
-  // may still need to be updated, since UpdateCryptographer() is never going to
+  // may still need to be updated, since OnCryptographerChange() will never
   // happen, but we can assume PassiveApplyUpdates(...) will push the state to
   // the processor, and we should not push it now. In fact, doing so now would
   // violate the processor's assumption that the first OnUpdateReceived is will
@@ -215,22 +215,29 @@
   return type_;
 }
 
-void ModelTypeWorker::UpdateCryptographer(
-    std::unique_ptr<Cryptographer> cryptographer) {
+void ModelTypeWorker::EnableEncryption(Cryptographer* cryptographer) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!cryptographer_);
   DCHECK(cryptographer);
-  cryptographer_ = std::move(cryptographer);
+  cryptographer_ = cryptographer;
+  OnCryptographerChange();
+}
+
+void ModelTypeWorker::SetFallbackCryptographerForUma(
+    Cryptographer* fallback_cryptographer_for_uma) {
+  DCHECK(!fallback_cryptographer_for_uma_);
+  DCHECK(fallback_cryptographer_for_uma);
+  fallback_cryptographer_for_uma_ = fallback_cryptographer_for_uma;
+}
+
+void ModelTypeWorker::OnCryptographerChange() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(cryptographer_);
   UpdateEncryptionKeyName();
   DecryptStoredEntities();
   NudgeIfReadyToCommit();
 }
 
-void ModelTypeWorker::UpdateFallbackCryptographerForUma(
-    std::unique_ptr<Cryptographer> fallback_cryptographer_for_uma) {
-  DCHECK(fallback_cryptographer_for_uma);
-  fallback_cryptographer_for_uma_ = std::move(fallback_cryptographer_for_uma);
-}
-
 void ModelTypeWorker::UpdatePassphraseType(PassphraseType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   passphrase_type_ = type;
@@ -281,8 +288,8 @@
     }
 
     UpdateResponseData response_data;
-    switch (PopulateUpdateResponseData(cryptographer_.get(), type_,
-                                       *update_entity, &response_data)) {
+    switch (PopulateUpdateResponseData(cryptographer_, type_, *update_entity,
+                                       &response_data)) {
       case SUCCESS:
         pending_updates_.push_back(std::move(response_data));
         // Override any previously undecryptable update for the same id.
@@ -536,8 +543,7 @@
                      weak_ptr_factory_.GetWeakPtr()),
       base::BindOnce(&ModelTypeWorker::OnFullCommitFailure,
                      weak_ptr_factory_.GetWeakPtr()),
-      cryptographer_.get(), passphrase_type_,
-      CommitOnlyTypes().Has(GetModelType()));
+      cryptographer_, passphrase_type_, CommitOnlyTypes().Has(GetModelType()));
 }
 
 bool ModelTypeWorker::HasLocalChangesForTest() const {
@@ -613,8 +619,8 @@
     const sync_pb::SyncEntity& encrypted_update = it->second;
 
     UpdateResponseData response_data;
-    switch (PopulateUpdateResponseData(cryptographer_.get(), type_,
-                                       encrypted_update, &response_data)) {
+    switch (PopulateUpdateResponseData(cryptographer_, type_, encrypted_update,
+                                       &response_data)) {
       case SUCCESS:
         pending_updates_.push_back(std::move(response_data));
         it = entries_pending_decryption_.erase(it);
@@ -802,7 +808,7 @@
   // |fallback_cryptographer_for_uma_| can decrypt the data.
   for (const auto& id_and_pending_update : entries_pending_decryption_) {
     UpdateResponseData ignored;
-    if (PopulateUpdateResponseData(fallback_cryptographer_for_uma_.get(), type_,
+    if (PopulateUpdateResponseData(fallback_cryptographer_for_uma_, type_,
                                    id_and_pending_update.second,
                                    &ignored) == SUCCESS) {
       base::UmaHistogramEnumeration(
diff --git a/components/sync/engine/model_type_worker.h b/components/sync/engine/model_type_worker.h
index 1e128092..e14a3bdc 100644
--- a/components/sync/engine/model_type_worker.h
+++ b/components/sync/engine/model_type_worker.h
@@ -60,10 +60,13 @@
   // Public for testing.
   enum DecryptionStatus { SUCCESS, DECRYPTION_PENDING, FAILED_TO_DECRYPT };
 
+  // |nudge_handler| and |cancelation_signal| must outlive this object.
+  // |cryptographer| must either outlive this object or be null. Passing a
+  // a null cryptographer means this type won't use encryption.
   ModelTypeWorker(ModelType type,
                   const sync_pb::ModelTypeState& initial_state,
                   bool trigger_initial_sync,
-                  std::unique_ptr<Cryptographer> cryptographer,
+                  Cryptographer* cryptographer,
                   PassphraseType passphrase_type,
                   NudgeHandler* nudge_handler,
                   std::unique_ptr<ModelTypeProcessor> model_type_processor,
@@ -81,12 +84,22 @@
 
   ModelType GetModelType() const;
 
-  void UpdateCryptographer(std::unique_ptr<Cryptographer> cryptographer);
-  // Causes a worker without cryptographer to record whether
-  // |fallback_cryptographer_for_uma| would have been able to decrypt incoming
-  // encrypted updates. |fallback_cryptographer_for_uma| must be non-null.
-  void UpdateFallbackCryptographerForUma(
-      std::unique_ptr<Cryptographer> fallback_cryptographer_for_uma);
+  // Will start using |cryptographer| to encrypt/decrypt data. Must be called at
+  // most once, if the type transitioned from non-encrypted to encrypted.
+  // |cryptographer| must outlive this object.
+  void EnableEncryption(Cryptographer* cryptographer);
+
+  // Always called in production, may not be called in tests. Causes a worker
+  // without cryptographer to log whether |fallback_cryptographer_for_uma| would
+  // have been able to decrypt incoming encrypted updates.
+  // |fallback_cryptographer_for_uma| must outlive this object.
+  void SetFallbackCryptographerForUma(
+      Cryptographer* fallback_cryptographer_for_uma);
+
+  // Must only be called if there is a cryptographer. Called on every change to
+  // its state.
+  void OnCryptographerChange();
+
   void UpdatePassphraseType(PassphraseType type);
 
   // UpdateHandler implementation.
@@ -209,24 +222,23 @@
   // Pointer to the ModelTypeProcessor associated with this worker. Never null.
   std::unique_ptr<ModelTypeProcessor> model_type_processor_;
 
-  // A private copy of the most recent cryptographer known to sync.
-  // Initialized at construction time and updated with UpdateCryptographer().
-  // null if encryption is not enabled for this type.
-  std::unique_ptr<Cryptographer> cryptographer_;
+  // Initialized on construction or later via InitCryptographer(). Null as long
+  // as encryption is not enabled for this type.
+  Cryptographer* cryptographer_ = nullptr;
 
   // Used to investigate an issue where the worker receives encrypted updates
   // despite not having a |cryptographer_| (|type_| is not an encrypted type).
   // In those cases, this will eventually hold the underlying cryptographer used
   // by other types. The worker then records whether that cryptographer is able
   // to decrypt the updates. See crbug.com/1178418.
-  std::unique_ptr<Cryptographer> fallback_cryptographer_for_uma_;
+  Cryptographer* fallback_cryptographer_for_uma_ = nullptr;
 
   // A private copy of the most recent passphrase type. Initialized at
   // construction time and updated with UpdatePassphraseType().
   PassphraseType passphrase_type_;
 
   // Interface used to access and send nudges to the sync scheduler. Not owned.
-  NudgeHandler* nudge_handler_;
+  NudgeHandler* const nudge_handler_;
 
   // A map of sync entities, keyed by server_id. Holds updates encrypted with
   // pending keys. Entries are stored in a map for de-duplication (applying only
@@ -258,7 +270,7 @@
 
   // Cancellation signal is used to cancel blocking operation on engine
   // shutdown.
-  CancelationSignal* cancelation_signal_;
+  CancelationSignal* const cancelation_signal_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/components/sync/engine/model_type_worker_unittest.cc b/components/sync/engine/model_type_worker_unittest.cc
index 8d427a0..ff71b18 100644
--- a/components/sync/engine/model_type_worker_unittest.cc
+++ b/components/sync/engine/model_type_worker_unittest.cc
@@ -115,8 +115,12 @@
   const ClientTagHash kHash2 = GenerateTagHash(kTag2);
   const ClientTagHash kHash3 = GenerateTagHash(kTag3);
 
-  explicit ModelTypeWorkerTest(ModelType model_type = PREFERENCES)
+  ModelTypeWorkerTest()
+      : ModelTypeWorkerTest(PREFERENCES, /*is_encrypted_type=*/false) {}
+
+  ModelTypeWorkerTest(ModelType model_type, bool is_encrypted_type)
       : model_type_(model_type),
+        is_encrypted_type_(is_encrypted_type),
         foreign_encryption_key_index_(0),
         update_encryption_filter_index_(0),
         mock_type_processor_(nullptr),
@@ -177,14 +181,26 @@
 
     worker_ = std::make_unique<ModelTypeWorker>(
         type, state, !state.initial_sync_done(),
-        cryptographer_ ? cryptographer_->Clone() : nullptr,
+        is_encrypted_type_ ? &cryptographer_ : nullptr,
         PassphraseType::kImplicitPassphrase, &mock_nudge_handler_,
         std::move(processor), &cancelation_signal_);
   }
 
-  void InitializeCryptographer() {
-    if (!cryptographer_) {
-      cryptographer_ = std::make_unique<FakeCryptographer>();
+  // If the type isn't encrypted yet, makes the cryptographer available to the
+  // worker and marks the type as encrypted. Otherwise, just notifies a change
+  // in the cryptographer state.
+  void EnableEncryptionOrNotify() {
+    if (!worker()) {
+      // No worker to notify, just ensure |is_encrypted_type_| is true.
+      is_encrypted_type_ = true;
+      return;
+    }
+
+    if (is_encrypted_type_) {
+      worker()->OnCryptographerChange();
+    } else {
+      is_encrypted_type_ = true;
+      worker()->EnableEncryption(&cryptographer_);
     }
   }
 
@@ -192,32 +208,23 @@
   // the cryptographer becomes unusable (no default key until the issue gets
   // resolved, via DecryptPendingKey()).
   void AddPendingKey() {
-    InitializeCryptographer();
-
     foreign_encryption_key_index_++;
-    cryptographer_->ClearDefaultEncryptionKey();
-
-    // Update the worker with the latest cryptographer.
-    if (worker()) {
-      worker()->UpdateCryptographer(cryptographer_->Clone());
-    }
+    cryptographer_.ClearDefaultEncryptionKey();
+    EnableEncryptionOrNotify();
   }
 
   // Update the local cryptographer with all relevant keys.
   void DecryptPendingKey() {
     DCHECK_NE(foreign_encryption_key_index_, 0);
-    InitializeCryptographer();
-
     std::string last_key_name;
     for (int i = 1; i <= foreign_encryption_key_index_; ++i) {
       last_key_name = GetNthKeyName(i);
-      cryptographer_->AddEncryptionKey(last_key_name);
+      cryptographer_.AddEncryptionKey(last_key_name);
     }
-    cryptographer_->SelectDefaultEncryptionKey(last_key_name);
+    cryptographer_.SelectDefaultEncryptionKey(last_key_name);
 
-    // Update the worker with the latest cryptographer.
+    EnableEncryptionOrNotify();
     if (worker()) {
-      worker()->UpdateCryptographer(cryptographer_->Clone());
       worker()->EncryptionAcceptedMaybeApplyUpdates();
     }
   }
@@ -402,27 +409,23 @@
 
   void ResetWorker() { worker_.reset(); }
 
-  // Returns the name of the encryption key in the cryptographer last passed to
-  // the CommitQueue. Returns an empty string if no cryptographer is
-  // in use. See also: DecryptPendingKey().
-  std::string GetLocalCryptographerKeyName() const {
-    if (!cryptographer_) {
-      return std::string();
-    }
-    return cryptographer_->GetDefaultEncryptionKeyName();
-  }
-
   MockModelTypeProcessor* processor() { return mock_type_processor_; }
   ModelTypeWorker* worker() { return worker_.get(); }
   SingleTypeMockServer* server() { return mock_server_.get(); }
   MockNudgeHandler* nudge_handler() { return &mock_nudge_handler_; }
   StatusController* status_controller() { return &status_controller_; }
+  std::string default_encryption_key_name() {
+    return cryptographer_.GetDefaultEncryptionKeyName();
+  }
 
  private:
   const ModelType model_type_;
 
-  // The cryptographer itself. Null if we're not encrypting the type.
-  std::unique_ptr<FakeCryptographer> cryptographer_;
+  FakeCryptographer cryptographer_;
+
+  // Determines whether |worker_| has access to the cryptographer or not.
+  // Can be set to true via EnableEncryptionOrNotify().
+  bool is_encrypted_type_;
 
   // The number of the most recent foreign encryption key known to our
   // cryptographer. Note that not all of these will be decryptable.
@@ -969,7 +972,7 @@
   AddPendingKey();
   DecryptPendingKey();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(GetLocalCryptographerKeyName(),
+  EXPECT_EQ(default_encryption_key_name(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 
   // Normal commit request stuff.
@@ -1001,7 +1004,7 @@
   AddPendingKey();
   DecryptPendingKey();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(GetLocalCryptographerKeyName(),
+  EXPECT_EQ(default_encryption_key_name(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 
   // Normal commit request stuff.
@@ -1150,7 +1153,7 @@
   // possible, so that it will have the chance to re-encrypt local data if
   // necessary.
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(GetLocalCryptographerKeyName(),
+  EXPECT_EQ(default_encryption_key_name(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 }
 
@@ -1168,7 +1171,7 @@
   // Init the cryptographer, it'll push the EKN.
   DecryptPendingKey();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(GetLocalCryptographerKeyName(),
+  EXPECT_EQ(default_encryption_key_name(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 }
 
@@ -1187,7 +1190,7 @@
   // Now perform first sync and make sure the EKN makes it.
   TriggerTypeRootUpdateFromServer();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(GetLocalCryptographerKeyName(),
+  EXPECT_EQ(default_encryption_key_name(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 }
 
@@ -1205,7 +1208,7 @@
   // Now perform first sync and make sure the EKN makes it.
   TriggerTypeRootUpdateFromServer();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(GetLocalCryptographerKeyName(),
+  EXPECT_EQ(default_encryption_key_name(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 }
 
@@ -1233,7 +1236,7 @@
   const UpdateResponseData& update = processor()->GetUpdateResponse(kHash1);
   EXPECT_EQ(kTag1, update.entity.specifics.preference().name());
   EXPECT_EQ(kValue1, update.entity.specifics.preference().value());
-  EXPECT_EQ(GetLocalCryptographerKeyName(), update.encryption_key_name);
+  EXPECT_EQ(default_encryption_key_name(), update.encryption_key_name);
 }
 
 TEST_F(ModelTypeWorkerTest, OverwriteUndecryptableUpdateWithDecryptableOne) {
@@ -1298,9 +1301,10 @@
 
   // This isn't an encrypted type, so this worker has no cryptographer. Under
   // the hood however, the overall client does have a cryptographer containing
-  // key 1. That one is injected with UpdateFallbackCryptographerForUma().
-  worker()->UpdateFallbackCryptographerForUma(
-      FakeCryptographer::FromSingleDefaultKey(GetNthKeyName(1)));
+  // key 1. That one is injected with SetFallbackCryptographerForUma().
+  std::unique_ptr<Cryptographer> cryptographer_for_uma =
+      FakeCryptographer::FromSingleDefaultKey(GetNthKeyName(1));
+  worker()->SetFallbackCryptographerForUma(cryptographer_for_uma.get());
 
   // Send an update encrypted with key 1 and another encrypted with an unknown
   // key 2.
@@ -1322,6 +1326,9 @@
   histogram_tester.ExpectUniqueSample(
       "Sync.ModelTypeBlockedDueToUndecryptableUpdate",
       ModelTypeHistogramValue(worker()->GetModelType()), 1);
+
+  // The worker must not outlive |cryptographer_for_uma|.
+  ResetWorker();
 }
 
 TEST_F(ModelTypeWorkerTest, TimeUntilEncryptionKeyFoundMetric) {
@@ -1874,9 +1881,8 @@
  protected:
   const std::string kPassword = "SomePassword";
 
-  ModelTypeWorkerPasswordsTest() : ModelTypeWorkerTest(PASSWORDS) {
-    InitializeCryptographer();
-  }
+  ModelTypeWorkerPasswordsTest()
+      : ModelTypeWorkerTest(PASSWORDS, /*is_encrypted_type=*/true) {}
 };
 
 // Similar to EncryptedCommit but tests PASSWORDS specifically, which use a
@@ -1890,7 +1896,7 @@
   AddPendingKey();
   DecryptPendingKey();
   ASSERT_EQ(1U, processor()->GetNumUpdateResponses());
-  EXPECT_EQ(GetLocalCryptographerKeyName(),
+  EXPECT_EQ(default_encryption_key_name(),
             processor()->GetNthUpdateState(0).encryption_key_name());
 
   EntitySpecifics specifics;
@@ -2065,7 +2071,8 @@
 // to test some special encryption requirements for BOOKMARKS.
 class ModelTypeWorkerBookmarksTest : public ModelTypeWorkerTest {
  protected:
-  ModelTypeWorkerBookmarksTest() : ModelTypeWorkerTest(BOOKMARKS) {}
+  ModelTypeWorkerBookmarksTest()
+      : ModelTypeWorkerTest(BOOKMARKS, /*is_encrypted_type=*/false) {}
 };
 
 TEST_F(ModelTypeWorkerBookmarksTest, CanDecryptUpdateWithMissingBookmarkGUID) {
diff --git a/components/sync/engine/nigori/cryptographer.h b/components/sync/engine/nigori/cryptographer.h
index 9b471cb..f014664 100644
--- a/components/sync/engine/nigori/cryptographer.h
+++ b/components/sync/engine/nigori/cryptographer.h
@@ -19,8 +19,6 @@
   Cryptographer();
   virtual ~Cryptographer();
 
-  virtual std::unique_ptr<Cryptographer> Clone() const = 0;
-
   // Returns whether this cryptographer is ready to encrypt data, using
   // EncryptString(). This usually means that a default encryption key is
   // available and there are no pending keys.
diff --git a/components/sync/engine/sync_encryption_handler.h b/components/sync/engine/sync_encryption_handler.h
index f214158..017c0d5c 100644
--- a/components/sync/engine/sync_encryption_handler.h
+++ b/components/sync/engine/sync_encryption_handler.h
@@ -123,6 +123,10 @@
   // even delete this API altogether.
   virtual bool Init() = 0;
 
+  // TODO(crbug.com/1178418): Add similar getters for the rest of the state
+  // notified to the observers.
+  virtual Cryptographer* GetCryptographer() = 0;
+
   // Attempts to re-encrypt encrypted data types using the passphrase provided.
   // Notifies observers of the result of the operation via OnPassphraseAccepted
   // or OnPassphraseRequired, updates the nigori node, and does re-encryption as
diff --git a/components/sync/engine/sync_manager_impl.cc b/components/sync/engine/sync_manager_impl.cc
index a8288c0..1e4a843 100644
--- a/components/sync/engine/sync_manager_impl.cc
+++ b/components/sync/engine/sync_manager_impl.cc
@@ -197,9 +197,7 @@
   }
 
   model_type_registry_ = std::make_unique<ModelTypeRegistry>(
-      this, args->cancelation_signal,
-      sync_encryption_handler_->GetKeystoreKeysHandler());
-  sync_encryption_handler_->AddObserver(model_type_registry_.get());
+      this, args->cancelation_signal, sync_encryption_handler_);
 
   // Build a SyncCycleContext and store the worker in it.
   DVLOG(1) << "Sync is bringing up SyncCycleContext.";
@@ -340,9 +338,6 @@
   scheduler_.reset();
   cycle_context_.reset();
 
-  if (model_type_registry_)
-    sync_encryption_handler_->RemoveObserver(model_type_registry_.get());
-
   model_type_registry_.reset();
 
   if (sync_encryption_handler_) {
diff --git a/components/sync/nigori/cryptographer_impl.cc b/components/sync/nigori/cryptographer_impl.cc
index 443adf68..05dca19 100644
--- a/components/sync/nigori/cryptographer_impl.cc
+++ b/components/sync/nigori/cryptographer_impl.cc
@@ -33,15 +33,12 @@
 std::unique_ptr<CryptographerImpl> CryptographerImpl::FromProto(
     const sync_pb::CryptographerData& proto) {
   NigoriKeyBag key_bag = NigoriKeyBag::CreateFromProto(proto.key_bag());
-  std::string default_encryption_key_name = proto.default_key_name();
-  if (!default_encryption_key_name.empty() &&
-      !key_bag.HasKey(default_encryption_key_name)) {
-    // A default key name was specified but not present among keys.
-    return nullptr;
-  }
-
-  return base::WrapUnique(new CryptographerImpl(
-      std::move(key_bag), std::move(default_encryption_key_name)));
+  // TODO(crbug.com/1109221): An invalid local state should be handled in the
+  // caller instead of CHECK-ing here, e.g. by resetting the local state.
+  CHECK(proto.default_key_name().empty() ||
+        key_bag.HasKey(proto.default_key_name()));
+  return base::WrapUnique(
+      new CryptographerImpl(std::move(key_bag), proto.default_key_name()));
 }
 
 CryptographerImpl::CryptographerImpl(NigoriKeyBag key_bag,
@@ -79,10 +76,21 @@
   default_encryption_key_name_ = key_name;
 }
 
+void CryptographerImpl::EmplaceKeysAndSelectDefaultKeyFrom(
+    const CryptographerImpl& other) {
+  EmplaceKeysFrom(other.key_bag_);
+  SelectDefaultEncryptionKey(other.default_encryption_key_name_);
+}
+
 void CryptographerImpl::ClearDefaultEncryptionKey() {
   default_encryption_key_name_.clear();
 }
 
+void CryptographerImpl::ClearAllKeys() {
+  default_encryption_key_name_.clear();
+  key_bag_ = NigoriKeyBag::CreateEmpty();
+}
+
 bool CryptographerImpl::HasKey(const std::string& key_name) const {
   return key_bag_.HasKey(key_name);
 }
@@ -92,7 +100,7 @@
   return key_bag_.ExportKey(default_encryption_key_name_);
 }
 
-std::unique_ptr<CryptographerImpl> CryptographerImpl::CloneImpl() const {
+std::unique_ptr<CryptographerImpl> CryptographerImpl::Clone() const {
   return base::WrapUnique(
       new CryptographerImpl(key_bag_.Clone(), default_encryption_key_name_));
 }
@@ -101,10 +109,6 @@
   return key_bag_.size();
 }
 
-std::unique_ptr<Cryptographer> CryptographerImpl::Clone() const {
-  return CloneImpl();
-}
-
 bool CryptographerImpl::CanEncrypt() const {
   return !default_encryption_key_name_.empty();
 }
diff --git a/components/sync/nigori/cryptographer_impl.h b/components/sync/nigori/cryptographer_impl.h
index d988518..f5d478f 100644
--- a/components/sync/nigori/cryptographer_impl.h
+++ b/components/sync/nigori/cryptographer_impl.h
@@ -61,10 +61,22 @@
   // return true. |key_name| must not be empty and must represent a known key.
   void SelectDefaultEncryptionKey(const std::string& key_name);
 
+  // Adds all keys in |other| that weren't previously known, and selects the
+  // same default key. |other| must have selected a default key.
+  void EmplaceKeysAndSelectDefaultKeyFrom(const CryptographerImpl& other);
+
   // Clears the default encryption key, which causes CanEncrypt() to return
   // false.
   void ClearDefaultEncryptionKey();
 
+  // Reverts the cryptographer to an empty one, i.e. what would be returned by
+  // CreateEmpty(). The default key is also cleared.
+  // The set of known encryption keys shouldn't decrease in general, since this
+  // may lead to data becoming undecryptable. This method can be called a) if
+  // sync is disabled, or b) if sync finds an error that can be solved by
+  // resetting the encryption state.
+  void ClearAllKeys();
+
   // Determines whether |key_name| represents a known key.
   bool HasKey(const std::string& key_name) const;
 
@@ -72,13 +84,11 @@
   // have a default encryption key set, as reflected by CanEncrypt().
   sync_pb::NigoriKey ExportDefaultKey() const;
 
-  // Similar to Clone() but returns CryptographerImpl.
-  std::unique_ptr<CryptographerImpl> CloneImpl() const;
+  std::unique_ptr<CryptographerImpl> Clone() const;
 
   size_t KeyBagSizeForTesting() const;
 
   // Cryptographer overrides.
-  std::unique_ptr<Cryptographer> Clone() const override;
   bool CanEncrypt() const override;
   bool CanDecrypt(const sync_pb::EncryptedData& encrypted) const override;
   std::string GetDefaultEncryptionKeyName() const override;
diff --git a/components/sync/nigori/keystore_keys_cryptographer.cc b/components/sync/nigori/keystore_keys_cryptographer.cc
index 2b54650..b236a50 100644
--- a/components/sync/nigori/keystore_keys_cryptographer.cc
+++ b/components/sync/nigori/keystore_keys_cryptographer.cc
@@ -72,13 +72,13 @@
 
 std::unique_ptr<KeystoreKeysCryptographer> KeystoreKeysCryptographer::Clone()
     const {
-  return base::WrapUnique(new KeystoreKeysCryptographer(
-      cryptographer_->CloneImpl(), keystore_keys_));
+  return base::WrapUnique(
+      new KeystoreKeysCryptographer(cryptographer_->Clone(), keystore_keys_));
 }
 
 std::unique_ptr<CryptographerImpl>
 KeystoreKeysCryptographer::ToCryptographerImpl() const {
-  return cryptographer_->CloneImpl();
+  return cryptographer_->Clone();
 }
 
 bool KeystoreKeysCryptographer::EncryptKeystoreDecryptorToken(
diff --git a/components/sync/nigori/nigori_key_bag.h b/components/sync/nigori/nigori_key_bag.h
index c11991d..809867d7 100644
--- a/components/sync/nigori/nigori_key_bag.h
+++ b/components/sync/nigori/nigori_key_bag.h
@@ -30,6 +30,8 @@
   NigoriKeyBag(NigoriKeyBag&& other);
   ~NigoriKeyBag();
 
+  NigoriKeyBag& operator=(NigoriKeyBag&&) = default;
+
   void CopyFrom(const NigoriKeyBag& other);
 
   // Serialization to proto.
diff --git a/components/sync/nigori/nigori_state.cc b/components/sync/nigori/nigori_state.cc
index 2e6b79d..37db313 100644
--- a/components/sync/nigori/nigori_state.cc
+++ b/components/sync/nigori/nigori_state.cc
@@ -276,7 +276,7 @@
 
 NigoriState NigoriState::Clone() const {
   NigoriState result;
-  result.cryptographer = cryptographer->CloneImpl();
+  result.cryptographer = cryptographer->Clone();
   result.pending_keys = pending_keys;
   result.passphrase_type = passphrase_type;
   result.keystore_migration_time = keystore_migration_time;
diff --git a/components/sync/nigori/nigori_state.h b/components/sync/nigori/nigori_state.h
index 02ea4a3..37a739d8 100644
--- a/components/sync/nigori/nigori_state.h
+++ b/components/sync/nigori/nigori_state.h
@@ -51,6 +51,8 @@
 
   bool NeedsKeystoreReencryption() const;
 
+  // TODO(crbug.com/1109221): Make this const unique_ptr to avoid the object
+  // being destroyed after it's been injected to the ModelTypeWorker-s.
   std::unique_ptr<CryptographerImpl> cryptographer;
 
   // Pending keys represent a remote update that contained a keybag that cannot
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index 204793c..bd0c5dc 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -659,6 +659,11 @@
   return this;
 }
 
+Cryptographer* NigoriSyncBridgeImpl::GetCryptographer() {
+  DCHECK(state_.cryptographer);
+  return state_.cryptographer.get();
+}
+
 bool NigoriSyncBridgeImpl::NeedKeystoreKey() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Explicitly asks the server for keystore keys if it's first-time sync, i.e.
@@ -1024,7 +1029,7 @@
   // storing explicit passphrase key in prefs, we need to find better solution.
   storage_->ClearData();
   state_.keystore_keys_cryptographer = KeystoreKeysCryptographer::CreateEmpty();
-  state_.cryptographer = CryptographerImpl::CreateEmpty();
+  state_.cryptographer->ClearAllKeys();
   state_.pending_keys.reset();
   state_.pending_keystore_decryptor_token.reset();
   state_.passphrase_type = NigoriSpecifics::UNKNOWN;
@@ -1040,7 +1045,7 @@
                                                   false);
 }
 
-const CryptographerImpl& NigoriSyncBridgeImpl::GetCryptographerForTesting()
+const CryptographerImpl& NigoriSyncBridgeImpl::GetCryptographerImplForTesting()
     const {
   return *state_.cryptographer;
 }
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.h b/components/sync/nigori/nigori_sync_bridge_impl.h
index 92c34b0c..342e34d8 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.h
+++ b/components/sync/nigori/nigori_sync_bridge_impl.h
@@ -68,6 +68,7 @@
       const std::vector<std::vector<uint8_t>>& keys) override;
   base::Time GetKeystoreMigrationTime() const override;
   KeystoreKeysHandler* GetKeystoreKeysHandler() override;
+  Cryptographer* GetCryptographer() override;
 
   // KeystoreKeysHandler implementation.
   bool NeedKeystoreKey() const override;
@@ -81,10 +82,8 @@
   std::unique_ptr<EntityData> GetData() override;
   void ApplyDisableSyncChanges() override;
 
-  // TODO(crbug.com/922900): investigate whether we need this getter outside of
-  // tests and decide whether this method should be a part of
-  // SyncEncryptionHandler interface.
-  const CryptographerImpl& GetCryptographerForTesting() const;
+  const CryptographerImpl& GetCryptographerImplForTesting() const;
+  // TODO(crbug.com/922900): Move these getters to SyncEncryptionHandler.
   sync_pb::NigoriSpecifics::PassphraseType GetPassphraseTypeForTesting() const;
   ModelTypeSet GetEncryptedTypesForTesting() const;
   bool HasPendingKeysForTesting() const;
diff --git a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
index 6cc0cd5..d994c96 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
@@ -361,6 +361,7 @@
   MockNigoriLocalChangeProcessor* processor() { return processor_; }
   MockObserver* observer() { return &observer_; }
   MockNigoriStorage* storage() { return storage_; }
+  Cryptographer* cryptographer() { return bridge_->GetCryptographer(); }
 
   const std::vector<uint8_t> kRawKeystoreKey = {0, 1, 2, 3, 4};
   const std::vector<uint8_t> kTrustedVaultKey = {2, 3, 4, 5, 6};
@@ -430,9 +431,8 @@
                                NotNull(), /*has_pending_keys=*/false));
   bridge()->SetDecryptionPassphrase(kKeyParams.password);
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeyParams));
 }
 
 // Simplest case of keystore Nigori: we have only one keystore key and no old
@@ -463,9 +463,8 @@
   EXPECT_TRUE(bridge()->Init());
   EXPECT_THAT(bridge()->GetKeystoreMigrationTime(), Not(NullTime()));
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests that client can properly process remote updates with rotated keystore
@@ -486,10 +485,9 @@
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kOldKeyParams));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kCurrentKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kCurrentKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kOldKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kCurrentKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kCurrentKeyParams));
 }
 
 // In the backward compatible mode keystore Nigori's keystore_decryptor_token
@@ -509,10 +507,9 @@
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kGaiaKeyParams));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kGaiaKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kGaiaKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kGaiaKeyParams));
 }
 
 TEST_F(NigoriSyncBridgeImplTest, ShouldExposeBackwardCompatibleKeystoreNigori) {
@@ -576,11 +573,10 @@
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
   for (const KeyParams& key_params : kAllKeyParams) {
-    EXPECT_THAT(cryptographer, CanDecryptWith(key_params));
+    EXPECT_THAT(*cryptographer(), CanDecryptWith(key_params));
   }
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kCurrentKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kCurrentKeyParams));
 }
 
 // Tests that we build keystore Nigori, put it to processor, initialize the
@@ -610,9 +606,8 @@
   EXPECT_EQ(bridge()->GetPassphraseTypeForTesting(),
             sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests that upon receiving Nigori corrupted due to absence of
@@ -676,10 +671,9 @@
   EXPECT_THAT(bridge()->ApplySyncChanges(base::nullopt), Eq(base::nullopt));
   EXPECT_THAT(bridge()->GetData(), HasKeystoreNigori());
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams1));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams2));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams2));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams1));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams2));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams2));
 }
 
 // This test emulates late arrival of keystore keys, so neither
@@ -704,15 +698,14 @@
           EncryptedDataEq(entity_data.specifics.nigori().encryption_keybag())));
   EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_FALSE(cryptographer.CanEncrypt());
+  EXPECT_FALSE(cryptographer()->CanEncrypt());
 
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
   EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // This test emulates late arrival of keystore keys in backward-compatible
@@ -740,10 +733,9 @@
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
   bridge()->SetDecryptionPassphrase(kPassphraseKeyParams.password);
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kPassphraseKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kPassphraseKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kPassphraseKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kPassphraseKeyParams));
 
   // Regression part of the test, SetKeystoreKeys() call in this scenario used
   // to cause the crash (see crbug.com/1042203).
@@ -778,9 +770,8 @@
           EncryptedDataEq(expected_pending_keys)));
   bridge()->SetDecryptionPassphrase("wrong_passphrase");
 
-  const CryptographerImpl& cryptographer =
-      bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer.KeyBagSizeForTesting(), Eq(size_t(0)));
+  EXPECT_THAT(bridge()->GetCryptographerImplForTesting().KeyBagSizeForTesting(),
+              Eq(size_t(0)));
 }
 
 // Tests that attempt to SetEncryptionPassphrase() has no effect (at least
@@ -804,9 +795,8 @@
   // implemented. They might be not properly working with deferred state
   // change.
   EXPECT_THAT(bridge()->GetData(), HasKeystoreNigori());
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests that we can perform initial sync with custom passphrase Nigori.
@@ -964,11 +954,11 @@
   ASSERT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
   ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
               Eq(base::nullopt));
-  ASSERT_TRUE(bridge()->GetCryptographerForTesting().CanEncrypt());
+  ASSERT_TRUE(cryptographer()->CanEncrypt());
 
   EXPECT_CALL(*storage(), ClearData);
   bridge()->ApplyDisableSyncChanges();
-  EXPECT_FALSE(bridge()->GetCryptographerForTesting().CanEncrypt());
+  EXPECT_FALSE(cryptographer()->CanEncrypt());
 }
 
 // Tests decryption logic for explicit passphrase. In order to check that we're
@@ -1001,10 +991,10 @@
                                                    PASSPHRASE_BOOTSTRAP_TOKEN));
   bridge()->SetDecryptionPassphrase(passphrase_key_params.password);
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kOldKeyParams));
-  EXPECT_THAT(cryptographer, CanDecryptWith(passphrase_key_params));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(passphrase_key_params));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kOldKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(passphrase_key_params));
+  EXPECT_THAT(*cryptographer(),
+              HasDefaultKeyDerivedFrom(passphrase_key_params));
 }
 
 INSTANTIATE_TEST_SUITE_P(Scrypt,
@@ -1056,9 +1046,9 @@
   const KeyParams passphrase_key_params = {
       bridge()->GetCustomPassphraseKeyDerivationParamsForTesting(),
       kCustomPassphrase};
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(passphrase_key_params));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(passphrase_key_params));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(passphrase_key_params));
+  EXPECT_THAT(*cryptographer(),
+              HasDefaultKeyDerivedFrom(passphrase_key_params));
 }
 
 // Tests that pending local change with setting custom passphrase is applied,
@@ -1119,11 +1109,11 @@
   const KeyParams passphrase_key_params = {
       bridge()->GetCustomPassphraseKeyDerivationParamsForTesting(),
       kCustomPassphrase};
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams1));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams2));
-  EXPECT_THAT(cryptographer, CanDecryptWith(passphrase_key_params));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(passphrase_key_params));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams1));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams2));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(passphrase_key_params));
+  EXPECT_THAT(*cryptographer(),
+              HasDefaultKeyDerivedFrom(passphrase_key_params));
 }
 
 // Tests that SetEncryptionPassphrase() call doesn't lead to custom passphrase
@@ -1170,9 +1160,9 @@
   EXPECT_CALL(observer, OnPassphraseRequired).Times(0);
   ASSERT_THAT(bridge->MergeSyncData(std::move(entity_data)), Eq(base::nullopt));
 
-  const Cryptographer& cryptographer = bridge->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeyParams));
+  EXPECT_THAT(*bridge->GetCryptographer(), CanDecryptWith(kKeyParams));
+  EXPECT_THAT(*bridge->GetCryptographer(),
+              HasDefaultKeyDerivedFrom(kKeyParams));
   bridge->RemoveObserver(&observer);
 }
 
@@ -1238,9 +1228,9 @@
       /*packed_keystore_keys=*/std::string());
 
   // Verify that we restored Cryptographer state.
-  const Cryptographer& cryptographer = bridge2->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  EXPECT_THAT(*bridge2->GetCryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*bridge2->GetCryptographer(),
+              HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Commit with keystore Nigori initialization might be not completed before
@@ -1285,9 +1275,9 @@
   EXPECT_EQ(bridge->GetPassphraseTypeForTesting(),
             sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
 
-  const Cryptographer& cryptographer = bridge->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  EXPECT_THAT(*bridge->GetCryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*bridge->GetCryptographer(),
+              HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests the initial sync with a trusted vault Nigori. Observers should be
@@ -1508,10 +1498,9 @@
               Eq(AlwaysEncryptedUserTypes()));
   EXPECT_FALSE(bridge()->HasPendingKeysForTesting());
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kTrustedVaultKeyParams));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kTrustedVaultKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*cryptographer(), HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests processing of remote incremental update that transits from trusted
@@ -1562,10 +1551,9 @@
                                                    PASSPHRASE_BOOTSTRAP_TOKEN));
   bridge()->SetDecryptionPassphrase(kCustomPassphraseKeyParams.password);
 
-  const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kTrustedVaultKeyParams));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kCustomPassphraseKeyParams));
-  EXPECT_THAT(cryptographer,
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kTrustedVaultKeyParams));
+  EXPECT_THAT(*cryptographer(), CanDecryptWith(kCustomPassphraseKeyParams));
+  EXPECT_THAT(*cryptographer(),
               HasDefaultKeyDerivedFrom(kCustomPassphraseKeyParams));
 }
 
@@ -1744,7 +1732,7 @@
   ASSERT_FALSE(bridge()->HasPendingKeysForTesting());
 
   const CryptographerImpl& cryptographer =
-      bridge()->GetCryptographerForTesting();
+      bridge()->GetCryptographerImplForTesting();
   ASSERT_THAT(cryptographer,
               CanDecryptWith(TrustedVaultKeyParams(kTrustedVaultKey1)));
   EXPECT_THAT(cryptographer,
@@ -1805,11 +1793,12 @@
   EXPECT_THAT(bridge2->ApplySyncChanges(base::nullopt), Eq(base::nullopt));
   EXPECT_THAT(bridge2->GetData(), HasKeystoreNigori());
 
-  // Ensure that |cryptographer| corresponds to full keystore Nigori.
-  const Cryptographer& cryptographer = bridge2->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kPassphraseKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
+  // Ensure the cryptographer corresponds to full keystore Nigori.
+  EXPECT_THAT(*bridge2->GetCryptographer(), CanDecryptWith(kKeystoreKeyParams));
+  EXPECT_THAT(*bridge2->GetCryptographer(),
+              CanDecryptWith(kPassphraseKeyParams));
+  EXPECT_THAT(*bridge2->GetCryptographer(),
+              HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
 }
 
 // Tests that upon startup bridge adds keystore keys into cryptographer, so it
@@ -1857,15 +1846,16 @@
       /*packed_explicit_passphrase_key=*/std::string(),
       /*packed_keystore_keys=*/std::string());
 
-  // Ensure that |cryptographer| can decrypt with keystore keys, but still
-  // has default key derived from custom passphrase.
+  // Ensure the cryptographer can decrypt with keystore keys, but still has
+  // default key derived from custom passphrase.
   const KeyParams kPassphraseKeyParams = {
       bridge2->GetCustomPassphraseKeyDerivationParamsForTesting(), kPassphrase};
-  const Cryptographer& cryptographer = bridge2->GetCryptographerForTesting();
-  EXPECT_THAT(cryptographer,
+  EXPECT_THAT(*bridge2->GetCryptographer(),
               CanDecryptWith(KeystoreKeyParams(kRawKeystoreKey)));
-  EXPECT_THAT(cryptographer, CanDecryptWith(kPassphraseKeyParams));
-  EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kPassphraseKeyParams));
+  EXPECT_THAT(*bridge2->GetCryptographer(),
+              CanDecryptWith(kPassphraseKeyParams));
+  EXPECT_THAT(*bridge2->GetCryptographer(),
+              HasDefaultKeyDerivedFrom(kPassphraseKeyParams));
 }
 
 }  // namespace
diff --git a/components/sync/nigori/pending_local_nigori_commit.cc b/components/sync/nigori/pending_local_nigori_commit.cc
index b9a0af3..c8b1bea 100644
--- a/components/sync/nigori/pending_local_nigori_commit.cc
+++ b/components/sync/nigori/pending_local_nigori_commit.cc
@@ -121,10 +121,12 @@
       return false;
     }
 
+    std::unique_ptr<CryptographerImpl> cryptographer =
+        state->keystore_keys_cryptographer->ToCryptographerImpl();
+    DCHECK(!cryptographer->GetDefaultEncryptionKeyName().empty());
+    state->cryptographer->EmplaceKeysAndSelectDefaultKeyFrom(*cryptographer);
     state->passphrase_type = NigoriSpecifics::KEYSTORE_PASSPHRASE;
     state->keystore_migration_time = base::Time::Now();
-    state->cryptographer =
-        state->keystore_keys_cryptographer->ToCryptographerImpl();
     return true;
   }
 
diff --git a/components/sync/test/engine/fake_cryptographer.cc b/components/sync/test/engine/fake_cryptographer.cc
index b729093..e86a3b6 100644
--- a/components/sync/test/engine/fake_cryptographer.cc
+++ b/components/sync/test/engine/fake_cryptographer.cc
@@ -44,13 +44,6 @@
   default_key_name_.clear();
 }
 
-std::unique_ptr<Cryptographer> FakeCryptographer::Clone() const {
-  auto new_cryptographer = std::make_unique<FakeCryptographer>();
-  new_cryptographer->known_key_names_ = known_key_names_;
-  new_cryptographer->default_key_name_ = default_key_name_;
-  return new_cryptographer;
-}
-
 bool FakeCryptographer::CanEncrypt() const {
   return !default_key_name_.empty();
 }
diff --git a/components/sync/test/engine/fake_cryptographer.h b/components/sync/test/engine/fake_cryptographer.h
index 0754cdd..ed14710 100644
--- a/components/sync/test/engine/fake_cryptographer.h
+++ b/components/sync/test/engine/fake_cryptographer.h
@@ -38,7 +38,6 @@
   void ClearDefaultEncryptionKey();
 
   // Cryptographer implementation.
-  std::unique_ptr<Cryptographer> Clone() const override;
   bool CanEncrypt() const override;
   bool CanDecrypt(const sync_pb::EncryptedData& encrypted) const override;
   std::string GetDefaultEncryptionKeyName() const override;
diff --git a/components/sync/test/fake_sync_encryption_handler.cc b/components/sync/test/fake_sync_encryption_handler.cc
index 3a61407..2385ed3 100644
--- a/components/sync/test/fake_sync_encryption_handler.cc
+++ b/components/sync/test/fake_sync_encryption_handler.cc
@@ -71,4 +71,9 @@
   return this;
 }
 
+Cryptographer* FakeSyncEncryptionHandler::GetCryptographer() {
+  // GetCryptographer() must never return null.
+  return &fake_cryptographer_;
+}
+
 }  // namespace syncer
diff --git a/components/sync/test/fake_sync_encryption_handler.h b/components/sync/test/fake_sync_encryption_handler.h
index 9e2902a..7d9897b 100644
--- a/components/sync/test/fake_sync_encryption_handler.h
+++ b/components/sync/test/fake_sync_encryption_handler.h
@@ -14,6 +14,7 @@
 #include "base/time/time.h"
 #include "components/sync/engine/nigori/keystore_keys_handler.h"
 #include "components/sync/engine/sync_encryption_handler.h"
+#include "components/sync/test/engine/fake_cryptographer.h"
 
 namespace syncer {
 
@@ -39,6 +40,7 @@
       const std::vector<std::vector<uint8_t>>& keys) override;
   base::Time GetKeystoreMigrationTime() const override;
   KeystoreKeysHandler* GetKeystoreKeysHandler() override;
+  Cryptographer* GetCryptographer() override;
 
   // KeystoreKeysHandler implementation.
   bool NeedKeystoreKey() const override;
@@ -47,6 +49,7 @@
  private:
   base::ObserverList<SyncEncryptionHandler::Observer>::Unchecked observers_;
   std::vector<uint8_t> keystore_key_;
+  FakeCryptographer fake_cryptographer_;
 };
 
 }  // namespace syncer
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler.cc b/components/sync_bookmarks/bookmark_remote_updates_handler.cc
index b7f0a3f..8d25d4d 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler.cc
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler.cc
@@ -65,13 +65,6 @@
   base::UmaHistogramEnumeration("Sync.ProblematicServerSideBookmarks", problem);
 }
 
-void LogDuplicateBookmarkEntityOnRemoteUpdateCondition(
-    BookmarkRemoteUpdatesHandler::DuplicateBookmarkEntityOnRemoteUpdateCondition
-        condition) {
-  base::UmaHistogramEnumeration(
-      "Sync.DuplicateBookmarkEntityOnRemoteUpdateCondition", condition);
-}
-
 // Recursive method to traverse a forest created by ReorderUpdates() to to
 // emit updates in top-down order. |ordered_updates| must not be null because
 // traversed updates are appended to |*ordered_updates|.
@@ -446,127 +439,68 @@
     bool* should_ignore_update) {
   *should_ignore_update = false;
 
-  const SyncedBookmarkTracker::Entity* const tracked_entity =
+  // If there's nothing other than a server ID to issue a lookup, just do that
+  // and return immediately. This is the case for permanent nodes and possibly
+  // tombstones (at least the LoopbackServer only sets the server ID).
+  if (update_entity.originator_client_item_id.empty() &&
+      update_entity.client_tag_hash.value().empty()) {
+    return bookmark_tracker_->GetEntityForSyncId(update_entity.id);
+  }
+
+  // Parse the client tag hash in the update or infer it from the originator
+  // information (all of which are immutable properties of a sync entity).
+  const syncer::ClientTagHash client_tag_hash_in_update =
+      !update_entity.client_tag_hash.value().empty()
+          ? update_entity.client_tag_hash
+          : SyncedBookmarkTracker::GetClientTagHashFromGUID(
+                InferGuidFromLegacyOriginatorId(
+                    update_entity.originator_cache_guid,
+                    update_entity.originator_client_item_id));
+
+  const SyncedBookmarkTracker::Entity* const tracked_entity_by_client_tag =
+      bookmark_tracker_->GetEntityForClientTagHash(client_tag_hash_in_update);
+  const SyncedBookmarkTracker::Entity* const tracked_entity_by_sync_id =
       bookmark_tracker_->GetEntityForSyncId(update_entity.id);
-  // The GUID cannot have changed, after all validation earlier, since the
-  // GUID-validation logic uses immutable properties of a sync entity at the
-  // protocol level.
-  //
-  // However, with a bad-behaving server, in theory there could be weird cases
-  // like originator_client_item_id changing, for a given sync ID.
-  // TODO(crbug.com/1143246): Switch to a DCHECK instead  once the lookup for
-  // |tracked_entity| switches to an approach that guarantees the invariant
-  // regardless of server bugs, possibly by adopting client tags.
-  const base::GUID remote_guid =
-      base::GUID::ParseLowercase(update_entity.specifics.bookmark().guid());
-  if (tracked_entity && !update_entity.is_deleted() &&
-      update_entity.server_defined_unique_tag.empty()) {
-    DCHECK(remote_guid.is_valid());
 
-    if (tracked_entity->GetClientTagHash() !=
-        SyncedBookmarkTracker::GetClientTagHashFromGUID(remote_guid)) {
-      // This should be practically unreachable, but guard against misbehaving
-      // servers.
-      DLOG(ERROR) << "Ignoring remote bookmark update with protocol violation: "
-                     "GUID must be immutable";
-      LogProblematicBookmark(
-          RemoteBookmarkUpdateError::kGuidChangedForTrackedServerId);
-      *should_ignore_update = true;
-      return nullptr;
-    }
+  // The most common scenario is that both lookups, client-tag-based and
+  // server-ID-based, refer to the same tracked entity or both lookups fail. In
+  // that case there's nothing to reconcile and the function can return
+  // trivially.
+  if (tracked_entity_by_client_tag == tracked_entity_by_sync_id) {
+    return tracked_entity_by_client_tag;
   }
 
-  // If a commit succeeds, but the response does not come back fast enough
-  // (e.g. before shutdown or crash), then the |bookmark_tracker_| might
-  // assume that it was never committed. The server will track the client that
-  // sent up the original commit and return this in a get updates response. We
-  // need to check if we have an entry that didn't get its server id updated
-  // correctly. The server sends down |original_client_item_id| (regular case)
-  // or |client_provided_unique_tag| (experimental). If the tracker contains
-  // a matching entry, it should be treated as update.
-  const SyncedBookmarkTracker::Entity* old_tracked_entity =
-      bookmark_tracker_->GetEntityForSyncId(
-          update_entity.originator_client_item_id);
-  if (!old_tracked_entity && !update_entity.client_tag_hash.value().empty()) {
-    old_tracked_entity = bookmark_tracker_->GetEntityForClientTagHash(
-        syncer::ClientTagHash::FromHashed(
-            update_entity.client_tag_hash.value()));
+  // Client-tags (GUIDs) are known at all times and immutable (as opposed to
+  // server IDs which get a temp value for local creations), so they cannot have
+  // changed.
+  if (tracked_entity_by_sync_id &&
+      tracked_entity_by_sync_id->GetClientTagHash() !=
+          client_tag_hash_in_update) {
+    // The client tag has changed for an already-tracked entity, which is a
+    // protocol violation. This should be practically unreachable, but guard
+    // against misbehaving servers.
+    DLOG(ERROR) << "Ignoring remote bookmark update with protocol violation: "
+                   "GUID must be immutable";
+    LogProblematicBookmark(
+        RemoteBookmarkUpdateError::kGuidChangedForTrackedServerId);
+    *should_ignore_update = true;
+    return nullptr;
   }
 
-  // Do another lookup by GUID, in case the remote client tag is not set.
-  // TODO(crbug.com/1143246): Unify with the above by computing the inferred
-  // client tag, which requires logic analogous to what
-  // HasExpectedBookmarkGuid() uses internally.
-  if (!old_tracked_entity && !update_entity.is_deleted()) {
-    old_tracked_entity = bookmark_tracker_->GetEntityForClientTagHash(
-        SyncedBookmarkTracker::GetClientTagHashFromGUID(remote_guid));
-  }
+  // At this point |tracked_entity_by_client_tag| must be non-null because
+  // otherwise one of the two codepaths above would have returned early.
+  DCHECK(tracked_entity_by_client_tag);
+  DCHECK(!tracked_entity_by_sync_id);
 
-  if (!old_tracked_entity || old_tracked_entity == tracked_entity) {
-    return tracked_entity;
-  }
-
-  // TODO(crbug.com/1143246): UMA data supports the idea that this can be
-  // transformed into a DCHECK. However, strictly speaking, this can only be
-  // safely done once the tracker supports lookups based on client tag
-  // hashes.
-  if (tracked_entity) {
-    DCHECK_NE(tracked_entity, old_tracked_entity);
-    // We generally shouldn't have an entry for both the old ID and the new
-    // ID, but it could happen due to some past bug (see crbug.com/1004205).
-    // In that case, the two entries should be duplicates in the sense that
-    // they have the same URL.
-    // TODO(crbug.com/516866): Clean up the workaround once this has been
-    // resolved.
-    const bookmarks::BookmarkNode* old_node =
-        old_tracked_entity->bookmark_node();
-    const bookmarks::BookmarkNode* new_node = tracked_entity->bookmark_node();
-    if (new_node == nullptr) {
-      // This might happen in case a synced bookmark (with a non-temporary
-      // server ID but no known client tag) was deleted locally and then
-      // recreated locally while the commit is in flight. This leads to two
-      // entities in the tracker (for the same bookmark GUID), one of them
-      // being a tombstone (|tracked_entity|). The commit response (for the
-      // deletion) may never be received (e.g. network issues) and instead a
-      // remote update is received (possibly our own reflection). Resolving
-      // a situation with duplicate entries is simple as long as at least
-      // one of the two (it could also be both) is a tombstone: one of the
-      // entities can be simply untracked.
-      //
-      // In the current case the |old_tracked_entity| must be a new entity
-      // since it does not have server ID yet. The |new_node| must be always
-      // a tombstone (the bookmark which was deleted). Just remove the
-      // tombstone and continue applying current update (even if |old_node|
-      // is a tombstone too).
-      bookmark_tracker_->Remove(tracked_entity);
-      LogDuplicateBookmarkEntityOnRemoteUpdateCondition(
-          DuplicateBookmarkEntityOnRemoteUpdateCondition::kServerIdTombstone);
-    } else {
-      // |old_node| may be null when |old_entity| is a tombstone pending
-      // commit.
-      if (old_node != nullptr) {
-        DCHECK_NE(old_node, new_node);
-        bookmark_model_->Remove(old_node);
-        LogDuplicateBookmarkEntityOnRemoteUpdateCondition(
-            DuplicateBookmarkEntityOnRemoteUpdateCondition::
-                kBothEntitiesNonTombstone);
-      } else {
-        LogDuplicateBookmarkEntityOnRemoteUpdateCondition(
-            DuplicateBookmarkEntityOnRemoteUpdateCondition::
-                kTempSyncIdTombstone);
-      }
-      bookmark_tracker_->Remove(old_tracked_entity);
-      *should_ignore_update = true;
-      return nullptr;
-    }
-  }
-
+  // The server ID has changed for a tracked entity (matched via client tag).
+  // This can happen if a commit succeeds, but the response does not come back
+  // fast enough(e.g. before shutdown or crash), then the |bookmark_tracker_|
+  // might assume that it was never committed. The server will track the client
+  // that sent up the original commit and return this in a get updates response.
   bookmark_tracker_->UpdateSyncIdForLocalCreationIfNeeded(
-      old_tracked_entity,
+      tracked_entity_by_client_tag,
       /*sync_id=*/update_entity.id);
-
-  // The tracker has changed. Re-retrieve the |tracker_entity|.
-  return bookmark_tracker_->GetEntityForSyncId(update_entity.id);
+  return tracked_entity_by_client_tag;
 }
 
 const SyncedBookmarkTracker::Entity*
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler.h b/components/sync_bookmarks/bookmark_remote_updates_handler.h
index fba3d004..c050df6 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler.h
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler.h
@@ -27,14 +27,6 @@
 // server.
 class BookmarkRemoteUpdatesHandler {
  public:
-  enum class DuplicateBookmarkEntityOnRemoteUpdateCondition {
-    kServerIdTombstone = 0,
-    kTempSyncIdTombstone = 1,
-    kBothEntitiesNonTombstone = 2,
-
-    kMaxValue = kBothEntitiesNonTombstone,
-  };
-
   // |bookmark_model|, |favicon_service| and |bookmark_tracker| must not be null
   // and must outlive this object.
   BookmarkRemoteUpdatesHandler(bookmarks::BookmarkModel* bookmark_model,
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc b/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
index 4356e23..11b0168 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
@@ -371,51 +371,81 @@
   const std::string kId1 = "id1";
   const std::string kId2 = "id2";
 
+  const base::GUID kGuid0 = base::GUID::GenerateRandomV4();
+  const base::GUID kGuid1 = base::GUID::GenerateRandomV4();
+  const base::GUID kGuid2 = base::GUID::GenerateRandomV4();
+
   // Construct the updates list to create that structure
   syncer::UpdateResponseDataList updates;
-  updates.push_back(
-      CreateUpdateResponseData(/*server_id=*/kId0,
-                               /*parent_id=*/kBookmarkBarId,
-                               /*guid=*/base::GUID::GenerateRandomV4(),
-                               /*is_deletion=*/false));
-  updates.push_back(
-      CreateUpdateResponseData(/*server_id=*/kId1,
-                               /*parent_id=*/kId0,
-                               /*guid=*/base::GUID::GenerateRandomV4(),
-                               /*is_deletion=*/false));
-  updates.push_back(
-      CreateUpdateResponseData(/*server_id=*/kId2,
-                               /*parent_id=*/kId1,
-                               /*guid=*/base::GUID::GenerateRandomV4(),
-                               /*is_deletion=*/false));
+  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
+                                             /*parent_id=*/kBookmarkBarId,
+                                             /*guid=*/kGuid0,
+                                             /*is_deletion=*/false));
+  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId1,
+                                             /*parent_id=*/kId0,
+                                             /*guid=*/kGuid1,
+                                             /*is_deletion=*/false));
+  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId2,
+                                             /*parent_id=*/kId1,
+                                             /*guid=*/kGuid2,
+                                             /*is_deletion=*/false));
 
   updates_handler()->Process(updates,
                              /*got_new_encryption_requirements=*/false);
 
   // All nodes should be tracked including the "bookmark bar", "other
   // bookmarks" node and "mobile bookmarks".
-  EXPECT_THAT(tracker()->TrackedEntitiesCountForTest(), Eq(6U));
+  ASSERT_THAT(tracker()->TrackedEntitiesCountForTest(), Eq(6U));
 
   // Construct the updates list to have random deletions order.
   updates.clear();
-  updates.push_back(
-      CreateUpdateResponseData(/*server_id=*/kId1,
-                               /*parent_id=*/kId0,
-                               /*guid=*/base::GUID::GenerateRandomV4(),
-                               /*is_deletion=*/true,
-                               /*version=*/1));
-  updates.push_back(
-      CreateUpdateResponseData(/*server_id=*/kId0,
-                               /*parent_id=*/kBookmarksRootId,
-                               /*guid=*/base::GUID::GenerateRandomV4(),
-                               /*is_deletion=*/true,
-                               /*version=*/1));
-  updates.push_back(
-      CreateUpdateResponseData(/*server_id=*/kId2,
-                               /*parent_id=*/kId1,
-                               /*guid=*/base::GUID::GenerateRandomV4(),
-                               /*is_deletion=*/true,
-                               /*version=*/1));
+  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId1,
+                                             /*parent_id=*/kId0,
+                                             /*guid=*/kGuid1,
+                                             /*is_deletion=*/true,
+                                             /*version=*/1));
+  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
+                                             /*parent_id=*/kBookmarksRootId,
+                                             /*guid=*/kGuid0,
+                                             /*is_deletion=*/true,
+                                             /*version=*/1));
+  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId2,
+                                             /*parent_id=*/kId1,
+                                             /*guid=*/kGuid2,
+                                             /*is_deletion=*/true,
+                                             /*version=*/1));
+
+  updates_handler()->Process(updates,
+                             /*got_new_encryption_requirements=*/false);
+
+  // |tracker| should have only permanent nodes now.
+  EXPECT_THAT(tracker()->TrackedEntitiesCountForTest(), Eq(3U));
+}
+
+TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
+       ShouldProcessDeletionWithServerIdOnly) {
+  const std::string kId0 = "id0";
+  const base::GUID kGuid0 = base::GUID::GenerateRandomV4();
+
+  // Construct the updates list to create that structure
+  syncer::UpdateResponseDataList updates;
+  updates.push_back(CreateUpdateResponseData(/*server_id=*/kId0,
+                                             /*parent_id=*/kBookmarkBarId,
+                                             /*guid=*/kGuid0,
+                                             /*is_deletion=*/false));
+  updates_handler()->Process(updates,
+                             /*got_new_encryption_requirements=*/false);
+
+  // The node should be tracked including the "bookmark bar", "other
+  // bookmarks" node and "mobile bookmarks".
+  ASSERT_THAT(tracker()->TrackedEntitiesCountForTest(), Eq(4U));
+
+  // Construct the updates list with one minimalistic deletion (server ID only).
+  updates.clear();
+  updates.emplace_back();
+  updates.back().entity.id = kId0;
+  updates.back().response_version = 1;
+  ASSERT_TRUE(updates.back().entity.is_deleted());
 
   updates_handler()->Process(updates,
                              /*got_new_encryption_requirements=*/false);
@@ -986,8 +1016,8 @@
 TEST_F(BookmarkRemoteUpdatesHandlerWithInitialMergeTest,
        ShouldUpdateSyncIdWhenRecevingUpdateForNewlyCreatedLocalNode) {
   const std::string kCacheGuid = "generated_id";
-  const std::string kOriginatorClientItemId =
-      base::GUID::GenerateRandomV4().AsLowercaseString();
+  const base::GUID kBookmarkGuid = base::GUID::GenerateRandomV4();
+  const std::string kOriginatorClientItemId = kBookmarkGuid.AsLowercaseString();
   const std::string kSyncId = "server_id";
   const int64_t kServerVersion = 1000;
   const base::Time kModificationTime(base::Time::Now() -
@@ -1002,8 +1032,7 @@
   bookmark_specifics->set_guid(
       base::GUID::GenerateRandomV4().AsLowercaseString());
   bookmark_specifics->set_legacy_canonicalized_title("Title");
-  bookmarks::BookmarkNode node(/*id=*/1, base::GUID::GenerateRandomV4(),
-                               GURL());
+  bookmarks::BookmarkNode node(/*id=*/1, kBookmarkGuid, GURL());
   // Track a sync entity (similar to what happens after a local creation). The
   // |originator_client_item_id| is used a temp sync id and mark the entity that
   // it needs to be committed..
@@ -1309,13 +1338,14 @@
        ShouldResolveConflictBetweenLocalAndRemoteDeletionsByMatchingThem) {
   const std::string kId = "id";
   const std::string kTitle = "title";
+  const base::GUID kGuid = base::GUID::GenerateRandomV4();
 
   syncer::UpdateResponseDataList updates;
 
   updates.push_back(CreateUpdateResponseData(
       /*server_id=*/kId,
       /*parent_id=*/kBookmarkBarId,
-      /*guid=*/base::GUID::GenerateRandomV4(),
+      /*guid=*/kGuid,
       /*title=*/kTitle,
       /*is_deletion=*/false,
       /*version=*/0,
@@ -1347,7 +1377,7 @@
   updates.push_back(CreateUpdateResponseData(
       /*server_id=*/kId,
       /*parent_id=*/kBookmarkBarId,
-      /*guid=*/base::GUID::GenerateRandomV4(),
+      /*guid=*/kGuid,
       /*title=*/std::string(),
       /*is_deletion=*/true,
       /*version=*/1,
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions.cc b/components/sync_bookmarks/bookmark_specifics_conversions.cc
index 197a4630..55351ad 100644
--- a/components/sync_bookmarks/bookmark_specifics_conversions.cc
+++ b/components/sync_bookmarks/bookmark_specifics_conversions.cc
@@ -403,6 +403,20 @@
   return is_valid;
 }
 
+base::GUID InferGuidFromLegacyOriginatorId(
+    const std::string& originator_cache_guid,
+    const std::string& originator_client_item_id) {
+  // Bookmarks created around 2016, between [M44..M52) use an uppercase GUID
+  // as originator client item ID, so it requires case-insensitive parsing.
+  base::GUID guid = base::GUID::ParseCaseInsensitive(originator_client_item_id);
+  if (guid.is_valid()) {
+    return guid;
+  }
+
+  return base::GUID::ParseLowercase(InferGuidForLegacyBookmark(
+      originator_cache_guid, originator_client_item_id));
+}
+
 bool HasExpectedBookmarkGuid(const sync_pb::BookmarkSpecifics& specifics,
                              const syncer::ClientTagHash& client_tag_hash,
                              const std::string& originator_cache_guid,
@@ -415,16 +429,9 @@
     return true;
   }
 
-  if (base::GUID::ParseCaseInsensitive(originator_client_item_id).is_valid()) {
-    // Bookmarks created around 2016, between [M44..M52) use an uppercase GUID
-    // as originator client item ID, so it needs to be lowercased to adhere to
-    // the invariant that GUIDs in specifics are canonicalized.
-    return specifics.guid() == base::ToLowerASCII(originator_client_item_id);
-  }
-
-  return specifics.guid() ==
-         InferGuidForLegacyBookmark(originator_cache_guid,
-                                    originator_client_item_id);
+  return base::GUID::ParseLowercase(specifics.guid()) ==
+         InferGuidFromLegacyOriginatorId(originator_cache_guid,
+                                         originator_client_item_id);
 }
 
 }  // namespace sync_bookmarks
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions.h b/components/sync_bookmarks/bookmark_specifics_conversions.h
index d0c13a92..e78b4f9 100644
--- a/components/sync_bookmarks/bookmark_specifics_conversions.h
+++ b/components/sync_bookmarks/bookmark_specifics_conversions.h
@@ -83,6 +83,11 @@
 bool IsValidBookmarkSpecifics(const sync_pb::BookmarkSpecifics& specifics,
                               bool is_folder);
 
+// Returns the inferred GUID for given remote update's originator information.
+base::GUID InferGuidFromLegacyOriginatorId(
+    const std::string& originator_cache_guid,
+    const std::string& originator_client_item_id);
+
 // Checks if bookmark specifics contain a GUID that matches the value that would
 // be inferred from other redundant fields. |specifics| must be valid as per
 // IsValidBookmarkSpecifics().
diff --git a/components/test/data/optimization_guide/bert_page_topics_model.tflite b/components/test/data/optimization_guide/bert_page_topics_model.tflite
index c81ca9f..3d03eb4 100644
--- a/components/test/data/optimization_guide/bert_page_topics_model.tflite
+++ b/components/test/data/optimization_guide/bert_page_topics_model.tflite
Binary files differ
diff --git a/components/viz/common/BUILD.gn b/components/viz/common/BUILD.gn
index b5dd4f3..905f5371 100644
--- a/components/viz/common/BUILD.gn
+++ b/components/viz/common/BUILD.gn
@@ -144,10 +144,6 @@
   sources = [
     "constants.cc",
     "constants.h",
-    "delegated_ink_metadata.cc",
-    "delegated_ink_metadata.h",
-    "delegated_ink_point.cc",
-    "delegated_ink_point.h",
     "delegated_ink_prediction_configuration.h",
     "display/de_jelly.cc",
     "display/de_jelly.h",
diff --git a/components/viz/common/quads/compositor_frame_metadata.cc b/components/viz/common/quads/compositor_frame_metadata.cc
index 9327d3b..0984c0e 100644
--- a/components/viz/common/quads/compositor_frame_metadata.cc
+++ b/components/viz/common/quads/compositor_frame_metadata.cc
@@ -45,7 +45,7 @@
       display_transform_hint(other.display_transform_hint),
       transition_directives(other.transition_directives) {
   if (other.delegated_ink_metadata) {
-    delegated_ink_metadata = std::make_unique<DelegatedInkMetadata>(
+    delegated_ink_metadata = std::make_unique<gfx::DelegatedInkMetadata>(
         *other.delegated_ink_metadata.get());
   }
 }
diff --git a/components/viz/common/quads/compositor_frame_metadata.h b/components/viz/common/quads/compositor_frame_metadata.h
index c0eb6b7..cf503f4 100644
--- a/components/viz/common/quads/compositor_frame_metadata.h
+++ b/components/viz/common/quads/compositor_frame_metadata.h
@@ -13,7 +13,6 @@
 #include "base/optional.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/quads/compositor_frame_transition_directive.h"
 #include "components/viz/common/quads/frame_deadline.h"
@@ -21,6 +20,7 @@
 #include "components/viz/common/surfaces/surface_range.h"
 #include "components/viz/common/viz_common_export.h"
 #include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/geometry/size_f.h"
 #include "ui/gfx/geometry/vector2d_f.h"
@@ -163,7 +163,7 @@
   // The ink trail created with this metadata will only last for a single frame
   // before it disappears, regardless of whether or not the next frame contains
   // delegated ink metadata.
-  std::unique_ptr<DelegatedInkMetadata> delegated_ink_metadata;
+  std::unique_ptr<gfx::DelegatedInkMetadata> delegated_ink_metadata;
 
   // This represents a list of directives to execute in order to support the
   // document transitions.
diff --git a/components/viz/common/quads/compositor_frame_metadata_unittest.cc b/components/viz/common/quads/compositor_frame_metadata_unittest.cc
index 8f9ff6f..c4afe57 100644
--- a/components/viz/common/quads/compositor_frame_metadata_unittest.cc
+++ b/components/viz/common/quads/compositor_frame_metadata_unittest.cc
@@ -7,7 +7,6 @@
 #include "base/optional.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/quads/compositor_frame_transition_directive.h"
 #include "components/viz/common/quads/frame_deadline.h"
@@ -16,6 +15,7 @@
 #include "components/viz/common/viz_common_export.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/geometry/size_f.h"
 #include "ui/gfx/geometry/vector2d_f.h"
@@ -40,8 +40,8 @@
          a.gesture_scroll_id() == b.gesture_scroll_id();
 }
 
-bool AreDelegatedInkMetadataEqual(const DelegatedInkMetadata& a,
-                                  const DelegatedInkMetadata& b) {
+bool AreDelegatedInkMetadataEqual(const gfx::DelegatedInkMetadata& a,
+                                  const gfx::DelegatedInkMetadata& b) {
   return a.point() == b.point() && a.diameter() == b.diameter() &&
          a.color() == b.color() && a.timestamp() == b.timestamp() &&
          a.presentation_area() == b.presentation_area() &&
@@ -84,7 +84,7 @@
   metadata.preferred_frame_interval.emplace(
       base::TimeDelta::FromMilliseconds(11));
   metadata.display_transform_hint = gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL;
-  metadata.delegated_ink_metadata = std::make_unique<DelegatedInkMetadata>(
+  metadata.delegated_ink_metadata = std::make_unique<gfx::DelegatedInkMetadata>(
       gfx::PointF(88.8, 44.4), 1.f, SK_ColorRED,
       base::TimeTicks() + base::TimeDelta::FromSeconds(125),
       gfx::RectF(1, 2, 3, 4), true);
diff --git a/components/viz/service/display/aggregated_frame.h b/components/viz/service/display/aggregated_frame.h
index e8b0f80..a1a1aa2 100644
--- a/components/viz/service/display/aggregated_frame.h
+++ b/components/viz/service/display/aggregated_frame.h
@@ -9,9 +9,9 @@
 #include <vector>
 
 #include "base/optional.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "components/viz/common/quads/aggregated_render_pass.h"
 #include "components/viz/service/viz_service_export.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/latency/latency_info.h"
 
@@ -61,7 +61,7 @@
   // The ink trail created with this metadata will only last for a single frame
   // before it disappears, regardless of whether or not the next frame contains
   // delegated ink metadata.
-  std::unique_ptr<DelegatedInkMetadata> delegated_ink_metadata;
+  std::unique_ptr<gfx::DelegatedInkMetadata> delegated_ink_metadata;
 
   AggregatedRenderPassList render_pass_list;
 };
diff --git a/components/viz/service/display/delegated_ink_point_pixel_test_helper.cc b/components/viz/service/display/delegated_ink_point_pixel_test_helper.cc
index 30c6f30..3f21399 100644
--- a/components/viz/service/display/delegated_ink_point_pixel_test_helper.cc
+++ b/components/viz/service/display/delegated_ink_point_pixel_test_helper.cc
@@ -39,11 +39,11 @@
     base::TimeTicks timestamp,
     const gfx::RectF& presentation_area) {
   DCHECK(renderer_);
-  metadata_ =
-      DelegatedInkMetadata(point, diameter, color, timestamp, presentation_area,
-                           base::TimeTicks::Now(), /*hovering*/ false);
+  metadata_ = gfx::DelegatedInkMetadata(
+      point, diameter, color, timestamp, presentation_area,
+      base::TimeTicks::Now(), /*hovering*/ false);
   GetInkRenderer()->SetDelegatedInkMetadata(
-      std::make_unique<DelegatedInkMetadata>(metadata_));
+      std::make_unique<gfx::DelegatedInkMetadata>(metadata_));
 }
 
 void DelegatedInkPointPixelTestHelper::CreateAndSendMetadataFromLastPoint() {
diff --git a/components/viz/service/display/delegated_ink_point_pixel_test_helper.h b/components/viz/service/display/delegated_ink_point_pixel_test_helper.h
index c172238b..d13fa68 100644
--- a/components/viz/service/display/delegated_ink_point_pixel_test_helper.h
+++ b/components/viz/service/display/delegated_ink_point_pixel_test_helper.h
@@ -8,11 +8,14 @@
 #include <unordered_map>
 #include <vector>
 
-#include "components/viz/common/delegated_ink_metadata.h"
+#include "ui/gfx/delegated_ink_metadata.h"
+
+namespace gfx {
+class DelegatedInkPoint;
+}  // namespace gfx
 
 namespace viz {
 
-class DelegatedInkPoint;
 class DelegatedInkPointRendererBase;
 class DirectRenderer;
 
@@ -56,12 +59,12 @@
   gfx::Rect GetDelegatedInkDamageRect();
   gfx::Rect GetDelegatedInkDamageRect(int32_t pointer_id);
 
-  const DelegatedInkMetadata& metadata() { return metadata_; }
+  const gfx::DelegatedInkMetadata& metadata() { return metadata_; }
 
  private:
   DirectRenderer* renderer_ = nullptr;
-  std::unordered_map<int32_t, std::vector<DelegatedInkPoint>> ink_points_;
-  DelegatedInkMetadata metadata_;
+  std::unordered_map<int32_t, std::vector<gfx::DelegatedInkPoint>> ink_points_;
+  gfx::DelegatedInkMetadata metadata_;
 };
 
 }  // namespace viz
diff --git a/components/viz/service/display/delegated_ink_point_renderer_base.cc b/components/viz/service/display/delegated_ink_point_renderer_base.cc
index ba8d1905..16a282c 100644
--- a/components/viz/service/display/delegated_ink_point_renderer_base.cc
+++ b/components/viz/service/display/delegated_ink_point_renderer_base.cc
@@ -5,8 +5,8 @@
 #include "components/viz/service/display/delegated_ink_point_renderer_base.h"
 
 #include "base/trace_event/trace_event.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "components/viz/service/display/delegated_ink_trail_data.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 
 namespace viz {
 
@@ -29,7 +29,7 @@
 }
 
 void DelegatedInkPointRendererBase::SetDelegatedInkMetadata(
-    std::unique_ptr<DelegatedInkMetadata> metadata) {
+    std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
   // Frame time is set later than everything else due to what is available
   // at time of creation, so confirm that it was actually set.
   DCHECK_NE(metadata->frame_time(), base::TimeTicks());
@@ -56,7 +56,8 @@
   pointer_id_ = base::nullopt;
 }
 
-std::vector<DelegatedInkPoint> DelegatedInkPointRendererBase::FilterPoints() {
+std::vector<gfx::DelegatedInkPoint>
+DelegatedInkPointRendererBase::FilterPoints() {
   if (pointer_ids_.size() == 0)
     return {};
 
@@ -99,7 +100,7 @@
 
   // Any remaining points must be the points that should be part of the
   // delegated ink trail
-  std::vector<DelegatedInkPoint> points_to_draw;
+  std::vector<gfx::DelegatedInkPoint> points_to_draw;
   for (auto it : trail_data.GetPoints())
     points_to_draw.emplace_back(it.second, it.first, pointer_id_.value());
 
@@ -110,7 +111,7 @@
 }
 
 void DelegatedInkPointRendererBase::PredictPoints(
-    std::vector<DelegatedInkPoint>* ink_points_to_draw) {
+    std::vector<gfx::DelegatedInkPoint>* ink_points_to_draw) {
   DCHECK(metadata_);
 
   if (!pointer_id_.has_value() ||
@@ -129,7 +130,7 @@
 }
 
 void DelegatedInkPointRendererBase::StoreDelegatedInkPoint(
-    const DelegatedInkPoint& point) {
+    const gfx::DelegatedInkPoint& point) {
   TRACE_EVENT_INSTANT1("viz",
                        "DelegatedInkPointRendererImpl::StoreDelegatedInkPoint",
                        TRACE_EVENT_SCOPE_THREAD, "point", point.ToString());
diff --git a/components/viz/service/display/delegated_ink_point_renderer_base.h b/components/viz/service/display/delegated_ink_point_renderer_base.h
index 25f505e..aa28cdd 100644
--- a/components/viz/service/display/delegated_ink_point_renderer_base.h
+++ b/components/viz/service/display/delegated_ink_point_renderer_base.h
@@ -14,10 +14,14 @@
 #include "components/viz/service/display/delegated_ink_trail_data.h"
 #include "components/viz/service/viz_service_export.h"
 #include "mojo/public/cpp/bindings/receiver.h"
-#include "services/viz/public/mojom/compositing/delegated_ink_point.mojom.h"
+#include "services/viz/public/mojom/compositing/delegated_ink_point_renderer.mojom.h"
+
+namespace gfx {
+class DelegatedInkMetadata;
+class DelegatedInkPoint;
+}  // namespace gfx
 
 namespace viz {
-class DelegatedInkMetadata;
 
 // This is the base class used for rendering delegated ink trails on the end of
 // strokes to reduce user perceived latency. On initialization, it binds the
@@ -38,8 +42,9 @@
   void InitMessagePipeline(
       mojo::PendingReceiver<mojom::DelegatedInkPointRenderer> receiver);
 
-  void StoreDelegatedInkPoint(const DelegatedInkPoint& point) override;
-  void SetDelegatedInkMetadata(std::unique_ptr<DelegatedInkMetadata> metadata);
+  void StoreDelegatedInkPoint(const gfx::DelegatedInkPoint& point) override;
+  void SetDelegatedInkMetadata(
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata);
 
   virtual void FinalizePathForDraw() = 0;
   virtual gfx::Rect GetDamageRect() = 0;
@@ -50,12 +55,12 @@
   // ink trail. However, if a point has a timestamp that is earlier than the
   // timestamp on the metadata, then the point has already been drawn, and
   // therefore should be removed from |points_| before drawing.
-  std::vector<DelegatedInkPoint> FilterPoints();
+  std::vector<gfx::DelegatedInkPoint> FilterPoints();
 
-  void PredictPoints(std::vector<DelegatedInkPoint>* ink_points_to_draw);
+  void PredictPoints(std::vector<gfx::DelegatedInkPoint>* ink_points_to_draw);
   void ResetPrediction() override;
 
-  std::unique_ptr<DelegatedInkMetadata> metadata_;
+  std::unique_ptr<gfx::DelegatedInkMetadata> metadata_;
 
  private:
   friend class SkiaDelegatedInkRendererTest;
@@ -65,7 +70,7 @@
     return pointer_ids_;
   }
 
-  const DelegatedInkMetadata* GetMetadataForTest() const {
+  const gfx::DelegatedInkMetadata* GetMetadataForTest() const {
     return metadata_.get();
   }
 
diff --git a/components/viz/service/display/delegated_ink_point_renderer_skia.cc b/components/viz/service/display/delegated_ink_point_renderer_skia.cc
index c0c94c62..e248daa3 100644
--- a/components/viz/service/display/delegated_ink_point_renderer_skia.cc
+++ b/components/viz/service/display/delegated_ink_point_renderer_skia.cc
@@ -6,8 +6,8 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/trace_event/trace_event.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "third_party/skia/include/core/SkCanvas.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/skia_util.h"
 
@@ -61,8 +61,8 @@
 }
 
 base::TimeDelta GetImprovement(
-    const std::vector<DelegatedInkPoint>* points_to_draw,
-    const DelegatedInkMetadata* metadata) {
+    const std::vector<gfx::DelegatedInkPoint>* points_to_draw,
+    const gfx::DelegatedInkMetadata* metadata) {
   if (points_to_draw->size() == 0)
     return base::TimeDelta::FromMilliseconds(0);
 
@@ -70,7 +70,7 @@
 }
 
 std::vector<SkPoint> DelegatedInkPointRendererSkia::GetPointsToDraw() {
-  std::vector<DelegatedInkPoint> ink_points_to_draw = FilterPoints();
+  std::vector<gfx::DelegatedInkPoint> ink_points_to_draw = FilterPoints();
   UMA_HISTOGRAM_TIMES(
       "Renderer.DelegatedInkTrail.LatencyImprovement.Skia.WithoutPrediction",
       GetImprovement(&ink_points_to_draw, metadata_.get()));
@@ -78,7 +78,7 @@
   PredictPoints(&ink_points_to_draw);
 
   std::vector<SkPoint> sk_points;
-  for (DelegatedInkPoint ink_point : ink_points_to_draw)
+  for (gfx::DelegatedInkPoint ink_point : ink_points_to_draw)
     sk_points.push_back(gfx::PointFToSkPoint(ink_point.point()));
 
   return sk_points;
diff --git a/components/viz/service/display/delegated_ink_trail_data.cc b/components/viz/service/display/delegated_ink_trail_data.cc
index f56c740..51ecdff 100644
--- a/components/viz/service/display/delegated_ink_trail_data.cc
+++ b/components/viz/service/display/delegated_ink_trail_data.cc
@@ -11,10 +11,10 @@
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/trace_event/trace_event.h"
-#include "components/viz/common/delegated_ink_metadata.h"
-#include "components/viz/common/delegated_ink_point.h"
 #include "components/viz/common/features.h"
 #include "ui/base/prediction/kalman_predictor.h"
+#include "ui/gfx/delegated_ink_metadata.h"
+#include "ui/gfx/delegated_ink_point.h"
 
 namespace viz {
 
@@ -36,7 +36,7 @@
 DelegatedInkTrailData::PredictionHandler::PredictionHandler() = default;
 DelegatedInkTrailData::PredictionHandler::~PredictionHandler() = default;
 
-void DelegatedInkTrailData::AddPoint(const DelegatedInkPoint& point) {
+void DelegatedInkTrailData::AddPoint(const gfx::DelegatedInkPoint& point) {
   if (static_cast<int>(points_.size()) == 0)
     pointer_id_ = point.pointer_id();
   else
@@ -56,8 +56,8 @@
 }
 
 void DelegatedInkTrailData::PredictPoints(
-    std::vector<DelegatedInkPoint>* ink_points_to_draw,
-    DelegatedInkMetadata* metadata) {
+    std::vector<gfx::DelegatedInkPoint>* ink_points_to_draw,
+    gfx::DelegatedInkMetadata* metadata) {
   TRACE_EVENT0("viz", "DelegatedInkTrailData::PredictPoints");
   // Base name used for the histograms that measure the latency improvement from
   // the prediction done for different experiments.
@@ -97,9 +97,9 @@
               predicted_point->time_stamp - metadata->timestamp();
           if (should_draw_predicted_ink_points.has_value() &&
               experiment == should_draw_predicted_ink_points.value()) {
-            ink_points_to_draw->push_back(
-                DelegatedInkPoint(predicted_point->pos,
-                                  predicted_point->time_stamp, pointer_id_));
+            ink_points_to_draw->push_back(gfx::DelegatedInkPoint(
+                predicted_point->pos, predicted_point->time_stamp,
+                pointer_id_));
           }
         } else {
           // HasPrediction() can return true while GeneratePrediction() fails to
@@ -130,7 +130,7 @@
 }
 
 bool DelegatedInkTrailData::ContainsMatchingPoint(
-    DelegatedInkMetadata* metadata) const {
+    gfx::DelegatedInkMetadata* metadata) const {
   auto point = points_.find(metadata->timestamp());
   if (point == points_.end())
     return false;
@@ -139,7 +139,7 @@
 }
 
 void DelegatedInkTrailData::ErasePointsOlderThanMetadata(
-    DelegatedInkMetadata* metadata) {
+    gfx::DelegatedInkMetadata* metadata) {
   // Any points with a timestamp earlier than the metadata have already been
   // drawn by the app. Since the metadata timestamp will only increase, we can
   // safely erase every point earlier than it and be left only with the points
@@ -149,7 +149,7 @@
     points_.erase(points_.begin());
 }
 
-void DelegatedInkTrailData::UpdateMetrics(DelegatedInkMetadata* metadata) {
+void DelegatedInkTrailData::UpdateMetrics(gfx::DelegatedInkMetadata* metadata) {
   for (auto& handler : prediction_handlers_) {
     for (auto it : points_)
       handler.metrics_handler->AddRealEvent(it.second, it.first,
diff --git a/components/viz/service/display/delegated_ink_trail_data.h b/components/viz/service/display/delegated_ink_trail_data.h
index 288fed8..24b33888 100644
--- a/components/viz/service/display/delegated_ink_trail_data.h
+++ b/components/viz/service/display/delegated_ink_trail_data.h
@@ -15,14 +15,16 @@
 #include "ui/base/prediction/prediction_metrics_handler.h"
 #include "ui/gfx/geometry/point_f.h"
 
+namespace gfx {
+class DelegatedInkMetadata;
+class DelegatedInkPoint;
+}  // namespace gfx
+
 namespace ui {
 class InputPredictor;
 }  // namespace ui
 
 namespace viz {
-class DelegatedInkMetadata;
-class DelegatedInkPoint;
-
 // The maximum number of delegated ink points that will be stored at a time.
 // When this is hit, the oldest one will be removed each time a new one is
 // added.
@@ -33,13 +35,13 @@
   DelegatedInkTrailData();
   ~DelegatedInkTrailData();
 
-  void AddPoint(const DelegatedInkPoint& point);
-  void PredictPoints(std::vector<DelegatedInkPoint>* ink_points_to_draw,
-                     DelegatedInkMetadata* metadata);
+  void AddPoint(const gfx::DelegatedInkPoint& point);
+  void PredictPoints(std::vector<gfx::DelegatedInkPoint>* ink_points_to_draw,
+                     gfx::DelegatedInkMetadata* metadata);
   void Reset();
-  bool ContainsMatchingPoint(DelegatedInkMetadata* metadata) const;
-  void ErasePointsOlderThanMetadata(DelegatedInkMetadata* metadata);
-  void UpdateMetrics(DelegatedInkMetadata* metadata);
+  bool ContainsMatchingPoint(gfx::DelegatedInkMetadata* metadata) const;
+  void ErasePointsOlderThanMetadata(gfx::DelegatedInkMetadata* metadata);
+  void UpdateMetrics(gfx::DelegatedInkMetadata* metadata);
 
   const std::map<base::TimeTicks, gfx::PointF>& GetPoints() const {
     return points_;
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index ca746e0..0dafacec 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -995,7 +995,7 @@
 }
 
 void DirectRenderer::SetDelegatedInkMetadata(
-    std::unique_ptr<DelegatedInkMetadata> metadata) {
+    std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
   if (!GetDelegatedInkPointRenderer() && !CreateDelegatedInkPointRenderer())
     return;
 
diff --git a/components/viz/service/display/direct_renderer.h b/components/viz/service/display/direct_renderer.h
index f5a72d7..79b344bb 100644
--- a/components/viz/service/display/direct_renderer.h
+++ b/components/viz/service/display/direct_renderer.h
@@ -15,7 +15,6 @@
 #include "base/macros.h"
 #include "base/optional.h"
 #include "build/build_config.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "components/viz/common/quads/aggregated_render_pass.h"
 #include "components/viz/common/quads/tile_draw_quad.h"
 #include "components/viz/service/display/aggregated_frame.h"
@@ -25,6 +24,7 @@
 #include "components/viz/service/display/overlay_processor_interface.h"
 #include "components/viz/service/viz_service_export.h"
 #include "gpu/command_buffer/common/texture_in_use_response.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/geometry/quad_f.h"
 #include "ui/gfx/geometry/rect.h"
@@ -142,7 +142,8 @@
   }
 
   virtual DelegatedInkPointRendererBase* GetDelegatedInkPointRenderer();
-  void SetDelegatedInkMetadata(std::unique_ptr<DelegatedInkMetadata> metadata);
+  void SetDelegatedInkMetadata(
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata);
 
   // Returns true if composite time tracing is enabled. This measures a detailed
   // trace log for draw time spent per quad.
diff --git a/components/viz/service/display/display_unittest.cc b/components/viz/service/display/display_unittest.cc
index 40fb0fb..d332963e 100644
--- a/components/viz/service/display/display_unittest.cc
+++ b/components/viz/service/display/display_unittest.cc
@@ -19,8 +19,6 @@
 #include "base/test/null_task_runner.h"
 #include "cc/base/math_util.h"
 #include "cc/test/scheduler_test_common.h"
-#include "components/viz/common/delegated_ink_metadata.h"
-#include "components/viz/common/delegated_ink_point.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "components/viz/common/frame_sinks/copy_output_result.h"
@@ -56,6 +54,8 @@
 #include "gpu/GLES2/gl2extchromium.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/delegated_ink_metadata.h"
+#include "ui/gfx/delegated_ink_point.h"
 #include "ui/gfx/overlay_transform.h"
 #include "ui/gfx/presentation_feedback.h"
 
@@ -4588,16 +4588,16 @@
 
   void StoreAlreadyCreatedDelegatedInkPoints(int32_t pointer_id) {
     DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
-    for (DelegatedInkPoint ink_point : ink_points_[pointer_id])
+    for (gfx::DelegatedInkPoint ink_point : ink_points_[pointer_id])
       ink_renderer()->StoreDelegatedInkPoint(ink_point);
   }
 
-  void SendMetadata(DelegatedInkMetadata metadata) {
+  void SendMetadata(gfx::DelegatedInkMetadata metadata) {
     ink_renderer()->SetDelegatedInkMetadata(
-        std::make_unique<DelegatedInkMetadata>(metadata));
+        std::make_unique<gfx::DelegatedInkMetadata>(metadata));
   }
 
-  DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint(
+  gfx::DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint(
       int index,
       float diameter,
       SkColor color,
@@ -4607,7 +4607,7 @@
         ink_points_.begin()->first, index, diameter, color, presentation_area);
   }
 
-  DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint(
+  gfx::DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint(
       int32_t pointer_id,
       int index,
       float diameter,
@@ -4617,11 +4617,11 @@
     EXPECT_GE(index, 0);
     EXPECT_LT(index, ink_points_size(pointer_id));
 
-    DelegatedInkMetadata metadata(ink_points_[pointer_id][index].point(),
-                                  diameter, color,
-                                  ink_points_[pointer_id][index].timestamp(),
-                                  presentation_area, base::TimeTicks::Now(),
-                                  /*hovering*/ false);
+    gfx::DelegatedInkMetadata metadata(
+        ink_points_[pointer_id][index].point(), diameter, color,
+        ink_points_[pointer_id][index].timestamp(), presentation_area,
+        base::TimeTicks::Now(),
+        /*hovering*/ false);
     SendMetadata(metadata);
     return metadata;
   }
@@ -4664,23 +4664,23 @@
   int GetPathPointCount() { return ink_renderer()->GetPathPointCountForTest(); }
 
   // Explicitly get the metadata that is stored on the renderer.
-  const DelegatedInkMetadata* GetMetadataFromRenderer() {
+  const gfx::DelegatedInkMetadata* GetMetadataFromRenderer() {
     return ink_renderer()->GetMetadataForTest();
   }
 
-  const DelegatedInkPoint& ink_point(int index) {
+  const gfx::DelegatedInkPoint& ink_point(int index) {
     DCHECK_EQ(static_cast<int>(ink_points_.size()), 1);
     return ink_point(ink_points_.begin()->first, index);
   }
 
-  const DelegatedInkPoint& ink_point(int32_t pointer_id, int index) {
+  const gfx::DelegatedInkPoint& ink_point(int32_t pointer_id, int index) {
     DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
     EXPECT_GE(index, 0);
     EXPECT_LT(index, ink_points_size(pointer_id));
     return ink_points_[pointer_id][index];
   }
 
-  const DelegatedInkPoint& last_ink_point(int32_t pointer_id) {
+  const gfx::DelegatedInkPoint& last_ink_point(int32_t pointer_id) {
     DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
     return ink_points_[pointer_id].back();
   }
@@ -4696,7 +4696,7 @@
   }
 
  private:
-  std::unordered_map<int32_t, std::vector<DelegatedInkPoint>> ink_points_;
+  std::unordered_map<int32_t, std::vector<gfx::DelegatedInkPoint>> ink_points_;
 };
 
 // Testing filtering points in the the delegated ink renderer when the skia
@@ -4734,7 +4734,7 @@
   // confirm that earlier points are removed and later points remain.
   const int kInkPointForMetadata = 1;
   const float kDiameter = 1.f;
-  DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint(
+  gfx::DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint(
       kInkPointForMetadata, kDiameter, SK_ColorBLACK, gfx::RectF());
 
   // The histogram should count one in the bucket that is the difference between
@@ -4830,7 +4830,7 @@
   // later points remain.
   const int kInkPointForMetadata = 1;
   const float kDiameter = 1.f;
-  DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint(
+  gfx::DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint(
       kPointerIds[0], kInkPointForMetadata, kDiameter, SK_ColorBLACK,
       gfx::RectF());
 
@@ -4869,7 +4869,7 @@
   // DelegatedInkPoint and confirm that it doesn't cause any changes to the
   // stored values. *WithoutPrediction histogram should record 0 improvement,
   // *WithPrediction* shouldn't record anything due to no valid pointer id.
-  SendMetadata(DelegatedInkMetadata(
+  SendMetadata(gfx::DelegatedInkMetadata(
       gfx::PointF(100, 100), 5.6f, SK_ColorBLACK, base::TimeTicks::Min(),
       gfx::RectF(), base::TimeTicks::Min(), /*hovering*/ false));
   FinalizePathAndCheckHistograms(base::TimeDelta::FromMilliseconds(0),
@@ -4884,7 +4884,7 @@
   // Finally, send a metadata with a timestamp beyond all of the stored points.
   // This should result in all of the points being erased, but the pointer ids
   // will still exist as they contains the predictors as well.
-  SendMetadata(DelegatedInkMetadata(
+  SendMetadata(gfx::DelegatedInkMetadata(
       gfx::PointF(100, 100), 5.6f, SK_ColorBLACK,
       base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(1000),
       gfx::RectF(), base::TimeTicks::Now(), /*hovering*/ false));
diff --git a/components/viz/service/display/overlay_unittest.cc b/components/viz/service/display/overlay_unittest.cc
index 37a809bf..ae1e6e7 100644
--- a/components/viz/service/display/overlay_unittest.cc
+++ b/components/viz/service/display/overlay_unittest.cc
@@ -157,6 +157,8 @@
     }
 
     for (const auto& r : expected_rects_) {
+      SCOPED_TRACE(r.ToString());
+
       if (std::abs(r.x() - candidate.display_rect.x()) <= kAbsoluteError &&
           std::abs(r.y() - candidate.display_rect.y()) <= kAbsoluteError &&
           std::abs(r.width() - candidate.display_rect.width()) <=
@@ -568,6 +570,8 @@
     const AggregatedRenderPassList& actual_list) {
   EXPECT_EQ(expected_list.size(), actual_list.size());
   for (size_t i = 0; i < actual_list.size(); ++i) {
+    SCOPED_TRACE(i);
+
     AggregatedRenderPass* expected = expected_list[i].get();
     AggregatedRenderPass* actual = actual_list[i].get();
 
@@ -1012,6 +1016,8 @@
 
   AddExpectedRectToOverlayProcessor(gfx::RectF(kOverlayDisplayRect));
   for (size_t i = 0; i < base::size(kDamageRect); ++i) {
+    SCOPED_TRACE(i);
+
     auto pass = CreateRenderPass();
     SharedQuadState* damaged_shared_quad_state =
         pass->shared_quad_state_list.AllocateAndCopyFrom(
@@ -1885,6 +1891,8 @@
   ResourceId previous_resource_id;
   int64_t frame_counter = 0;
   for (size_t i = 0; i < 3 + kFramesSkippedBeforeNotPromoting; ++i) {
+    SCOPED_TRACE(i);
+
     auto pass = CreateRenderPass();
     AggregatedRenderPass* main_pass = pass.get();
 
@@ -1982,6 +1990,8 @@
   nearly_occluding_quad.Inset(1, 1);
 
   for (size_t i = 0; i < kSlowFrameTestEnd; ++i) {
+    SCOPED_TRACE(i);
+
     if (i >= kSlowFrameTestStart && i < kSlowFrameTestEnd) {
       wait_4_frames();
     } else {
@@ -2114,6 +2124,8 @@
   static const int kOverlayFrameStart = 3;
   static const int kOverlayFrameEnd = 5;
   for (int i = 0; i < 8; ++i) {
+    SCOPED_TRACE(i);
+
     auto pass = CreateRenderPass();
     damage_rect_ = kOverlayTopLeftRect;
 
@@ -2210,6 +2222,8 @@
 TEST_F(UnderlayTest, DamageExcludedForConsecutiveIdenticalUnderlays) {
   static const int kDemotionFrame = 3;
   for (int i = 0; i < 5; ++i) {
+    SCOPED_TRACE(i);
+
     auto pass = CreateRenderPass();
     damage_rect_ = kOverlayTopLeftRect;
 
@@ -2270,6 +2284,8 @@
                                kOverlayTopLeftRect, kOverlayRect};
   gfx::Rect prev_rect;
   for (auto&& curr_rect : overlay_rects) {
+    SCOPED_TRACE(curr_rect.ToString());
+
     AddExpectedRectToOverlayProcessor(gfx::RectF(curr_rect));
 
     auto pass = CreateRenderPass();
@@ -2311,6 +2327,8 @@
   bool has_fullscreen_candidate[] = {true, false, true, true, true, false};
 
   for (int i = 0; i < 3; ++i) {
+    SCOPED_TRACE(i);
+
     auto pass = CreateRenderPass();
     damage_rect_ = kOverlayRect;
 
@@ -2356,6 +2374,8 @@
 
 TEST_F(UnderlayTest, DamageExcludedForCandidateAndThoseOccluded) {
   for (int i = 0; i < 3; ++i) {
+    SCOPED_TRACE(i);
+
     auto pass = CreateRenderPass();
     damage_rect_ = kOverlayRect;
     SurfaceDamageRectList surface_damage_rect_list;
@@ -2548,6 +2568,8 @@
   int fake_display_area = 256 * 256;
   // We test internal hysteresis state by running this test twice.
   for (int j = 0; j < 2; j++) {
+    SCOPED_TRACE(j);
+
     // First setup a 60fps high damage candidate.
     for (int i = 0; i < OverlayCandidateTemporalTracker::kNumRecords; i++) {
       wait_1_frame();
@@ -2718,6 +2740,8 @@
   size_t expected_candidate_size[] = {1, 1, 0};
 
   for (size_t i = 0; i < base::size(expected_damages); ++i) {
+    SCOPED_TRACE(i);
+
     auto pass = CreateRenderPass();
     SurfaceDamageRectList surface_damage_rect_list;
     SharedQuadState* default_damaged_shared_quad_state =
@@ -2764,6 +2788,8 @@
 // damage.
 TEST_F(UnderlayTest, CandidateNoDamageWhenQuadSharedStateNoOccludingDamage) {
   for (int i = 0; i < 4; ++i) {
+    SCOPED_TRACE(i);
+
     auto pass = CreateRenderPass();
     SurfaceDamageRectList surface_damage_rect_list;
 
@@ -4269,6 +4295,8 @@
 
   SurfaceDamageRectList surface_damage_rect_list;
   for (size_t i = 0; i < base::size(kCandidateRects); ++i) {
+    SCOPED_TRACE(i);
+
     // Create fake surface damage for this candidate.
     SharedQuadState* damaged_shared_quad_state =
         pass->shared_quad_state_list.AllocateAndCopyFrom(
@@ -4353,6 +4381,8 @@
 
   float initial_scalings[] = {0.5f, 0.625f, 0.5f};
   for (auto initial_scaling : initial_scalings) {
+    SCOPED_TRACE(initial_scaling);
+
     auto pass = CreateRenderPass();
 
     AggregatedRenderPassId render_pass_id{3};
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index 2959e10..25ff18b 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -2030,7 +2030,7 @@
 // aggregated frame, after which the member is then cleared.
 void SurfaceAggregator::TransformAndStoreDelegatedInkMetadata(
     const gfx::Transform& parent_quad_to_root_target_transform,
-    std::unique_ptr<DelegatedInkMetadata> metadata) {
+    std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
   if (delegated_ink_metadata_) {
     // This member could already be populated in two scenarios:
     //   1. The delegated ink metadata was committed to a frame's metadata that
@@ -2053,7 +2053,7 @@
   gfx::RectF area(metadata->presentation_area());
   parent_quad_to_root_target_transform.TransformPoint(&point);
   parent_quad_to_root_target_transform.TransformRect(&area);
-  delegated_ink_metadata_ = std::make_unique<DelegatedInkMetadata>(
+  delegated_ink_metadata_ = std::make_unique<gfx::DelegatedInkMetadata>(
       point, metadata->diameter(), metadata->color(), metadata->timestamp(),
       area, metadata->frame_time(), metadata->is_hovering());
 
diff --git a/components/viz/service/display/surface_aggregator.h b/components/viz/service/display/surface_aggregator.h
index 40e8427..00acfaf2 100644
--- a/components/viz/service/display/surface_aggregator.h
+++ b/components/viz/service/display/surface_aggregator.h
@@ -15,7 +15,6 @@
 #include "base/containers/flat_set.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/quads/draw_quad.h"
 #include "components/viz/common/resources/transferable_resource.h"
@@ -24,6 +23,7 @@
 #include "components/viz/service/display/aggregated_frame.h"
 #include "components/viz/service/display/render_pass_id_remapper.h"
 #include "components/viz/service/viz_service_export.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/overlay_transform.h"
 
@@ -301,7 +301,7 @@
   // then store it in the |delegated_ink_metadata_| member.
   void TransformAndStoreDelegatedInkMetadata(
       const gfx::Transform& parent_quad_to_root_target_transform,
-      std::unique_ptr<DelegatedInkMetadata> metadata);
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata);
 
   // Preliminary check to see if a surface contained in |surface_quad| can
   // potentially merge its root render pass. If so, returns true.
@@ -471,7 +471,7 @@
   // the final aggregated frame. This is only populated during aggregation when
   // a surface contains delegated ink metadata on its frame, and it is cleared
   // after it is placed on the final aggregated frame during aggregation.
-  std::unique_ptr<DelegatedInkMetadata> delegated_ink_metadata_;
+  std::unique_ptr<gfx::DelegatedInkMetadata> delegated_ink_metadata_;
   // Whether the last aggregated frame contained delegated ink metadata or not.
   // Used to determine if the root render pass needs to remain expanded by the
   // target damage or not, because that allows a frame to be drawn after inking
diff --git a/components/viz/service/display/surface_aggregator_unittest.cc b/components/viz/service/display/surface_aggregator_unittest.cc
index 524a150..7cbe095 100644
--- a/components/viz/service/display/surface_aggregator_unittest.cc
+++ b/components/viz/service/display/surface_aggregator_unittest.cc
@@ -8293,8 +8293,8 @@
             hit_test_region_index);
 }
 
-void ExpectDelegatedInkMetadataIsEqual(const DelegatedInkMetadata& lhs,
-                                       const DelegatedInkMetadata& rhs) {
+void ExpectDelegatedInkMetadataIsEqual(const gfx::DelegatedInkMetadata& lhs,
+                                       const gfx::DelegatedInkMetadata& rhs) {
   EXPECT_FLOAT_EQ(lhs.point().y(), rhs.point().y());
   EXPECT_FLOAT_EQ(lhs.point().x(), rhs.point().x());
   EXPECT_EQ(lhs.diameter(), rhs.diameter());
@@ -8319,11 +8319,11 @@
       Pass(child_quads, CompositorRenderPassId{1}, gfx::Size(100, 100))};
 
   CompositorFrame child_frame = MakeEmptyCompositorFrame();
-  DelegatedInkMetadata metadata(
+  gfx::DelegatedInkMetadata metadata(
       gfx::PointF(100, 100), 1.5, SK_ColorRED, base::TimeTicks::Now(),
       gfx::RectF(10, 10, 200, 200), base::TimeTicks::Now(), /*hovering*/ true);
   child_frame.metadata.delegated_ink_metadata =
-      std::make_unique<DelegatedInkMetadata>(metadata);
+      std::make_unique<gfx::DelegatedInkMetadata>(metadata);
   AddPasses(&child_frame.render_pass_list, child_passes,
             &child_frame.metadata.referenced_surfaces);
 
@@ -8364,7 +8364,7 @@
   root_frame.render_pass_list[0]
       ->shared_quad_state_list.ElementAt(0)
       ->quad_to_target_transform.TransformRect(&area);
-  metadata = DelegatedInkMetadata(
+  metadata = gfx::DelegatedInkMetadata(
       pt, metadata.diameter(), metadata.color(), metadata.timestamp(), area,
       metadata.frame_time(), metadata.is_hovering());
 
@@ -8375,7 +8375,7 @@
                             root_local_surface_id_);
   auto aggregated_frame = AggregateFrame(root_surface_id);
 
-  std::unique_ptr<DelegatedInkMetadata> actual_metadata =
+  std::unique_ptr<gfx::DelegatedInkMetadata> actual_metadata =
       std::move(aggregated_frame.delegated_ink_metadata);
   EXPECT_TRUE(actual_metadata);
   ExpectDelegatedInkMetadataIsEqual(*actual_metadata.get(), metadata);
@@ -8397,12 +8397,12 @@
   std::vector<Pass> greatgrandchild_passes = {Pass(
       greatgrandchild_quads, CompositorRenderPassId{1}, gfx::Size(100, 100))};
 
-  DelegatedInkMetadata metadata(
+  gfx::DelegatedInkMetadata metadata(
       gfx::PointF(100, 100), 1.5, SK_ColorRED, base::TimeTicks::Now(),
       gfx::RectF(10, 10, 200, 200), base::TimeTicks::Now(), /*hovering*/ false);
   CompositorFrame greatgrandchild_frame = MakeEmptyCompositorFrame();
   greatgrandchild_frame.metadata.delegated_ink_metadata =
-      std::make_unique<DelegatedInkMetadata>(metadata);
+      std::make_unique<gfx::DelegatedInkMetadata>(metadata);
   AddPasses(&greatgrandchild_frame.render_pass_list, greatgrandchild_passes,
             &greatgrandchild_frame.metadata.referenced_surfaces);
 
@@ -8518,11 +8518,11 @@
                             root_local_surface_id_);
   auto aggregated_frame = AggregateFrame(root_surface_id);
 
-  metadata = DelegatedInkMetadata(
+  metadata = gfx::DelegatedInkMetadata(
       pt, metadata.diameter(), metadata.color(), metadata.timestamp(), area,
       metadata.frame_time(), metadata.is_hovering());
 
-  std::unique_ptr<DelegatedInkMetadata> actual_metadata =
+  std::unique_ptr<gfx::DelegatedInkMetadata> actual_metadata =
       std::move(aggregated_frame.delegated_ink_metadata);
   EXPECT_TRUE(actual_metadata);
   ExpectDelegatedInkMetadataIsEqual(*actual_metadata.get(), metadata);
@@ -8565,12 +8565,12 @@
   std::vector<Pass> child_2_passes = {
       Pass(child_2_quads, CompositorRenderPassId{1}, gfx::Size(100, 100))};
 
-  DelegatedInkMetadata metadata = DelegatedInkMetadata(
+  gfx::DelegatedInkMetadata metadata(
       gfx::PointF(88, 34), 1.8, SK_ColorBLACK, base::TimeTicks::Now(),
       gfx::RectF(50, 50, 300, 300), base::TimeTicks::Now(), /*hovering*/ true);
   CompositorFrame child_2_frame = MakeEmptyCompositorFrame();
   child_2_frame.metadata.delegated_ink_metadata =
-      std::make_unique<DelegatedInkMetadata>(metadata);
+      std::make_unique<gfx::DelegatedInkMetadata>(metadata);
   AddPasses(&child_2_frame.render_pass_list, child_2_passes,
             &child_2_frame.metadata.referenced_surfaces);
 
@@ -8650,11 +8650,11 @@
                             root_local_surface_id_);
   auto aggregated_frame = AggregateFrame(root_surface_id);
 
-  metadata = DelegatedInkMetadata(
+  metadata = gfx::DelegatedInkMetadata(
       pt, metadata.diameter(), metadata.color(), metadata.timestamp(), area,
       metadata.frame_time(), metadata.is_hovering());
 
-  std::unique_ptr<DelegatedInkMetadata> actual_metadata =
+  std::unique_ptr<gfx::DelegatedInkMetadata> actual_metadata =
       std::move(aggregated_frame.delegated_ink_metadata);
   EXPECT_TRUE(actual_metadata);
   ExpectDelegatedInkMetadataIsEqual(*actual_metadata.get(), metadata);
@@ -8702,10 +8702,10 @@
   // the metadata with the later timestamp. Specifically setting the
   // later_metadata timestamp to be 50 microseconds later than Now() to avoid
   // issues with both metadatas sometimes having the same time in Release.
-  DelegatedInkMetadata early_metadata = DelegatedInkMetadata(
+  gfx::DelegatedInkMetadata early_metadata(
       gfx::PointF(88, 34), 1.8, SK_ColorBLACK, base::TimeTicks::Now(),
       gfx::RectF(50, 50, 300, 300), base::TimeTicks::Now(), /*hovering*/ false);
-  DelegatedInkMetadata later_metadata = DelegatedInkMetadata(
+  gfx::DelegatedInkMetadata later_metadata(
       gfx::PointF(92, 35), 0.08, SK_ColorYELLOW,
       base::TimeTicks::Now() + base::TimeDelta::FromMicroseconds(50),
       gfx::RectF(35, 55, 128, 256),
@@ -8714,7 +8714,7 @@
 
   CompositorFrame child_2_frame = MakeEmptyCompositorFrame();
   child_2_frame.metadata.delegated_ink_metadata =
-      std::make_unique<DelegatedInkMetadata>(later_metadata);
+      std::make_unique<gfx::DelegatedInkMetadata>(later_metadata);
   AddPasses(&child_2_frame.render_pass_list, child_2_passes,
             &child_2_frame.metadata.referenced_surfaces);
 
@@ -8734,7 +8734,7 @@
 
   CompositorFrame child_3_frame = MakeEmptyCompositorFrame();
   child_3_frame.metadata.delegated_ink_metadata =
-      std::make_unique<DelegatedInkMetadata>(early_metadata);
+      std::make_unique<gfx::DelegatedInkMetadata>(early_metadata);
   AddPasses(&child_3_frame.render_pass_list, child_3_passes,
             &child_3_frame.metadata.referenced_surfaces);
 
@@ -8797,12 +8797,12 @@
                             root_local_surface_id_);
   auto aggregated_frame = AggregateFrame(root_surface_id);
 
-  DelegatedInkMetadata expected_metadata = DelegatedInkMetadata(
+  gfx::DelegatedInkMetadata expected_metadata(
       pt, later_metadata.diameter(), later_metadata.color(),
       later_metadata.timestamp(), area, later_metadata.frame_time(),
       later_metadata.is_hovering());
 
-  std::unique_ptr<DelegatedInkMetadata> actual_metadata =
+  std::unique_ptr<gfx::DelegatedInkMetadata> actual_metadata =
       std::move(aggregated_frame.delegated_ink_metadata);
   EXPECT_TRUE(actual_metadata);
   ExpectDelegatedInkMetadataIsEqual(*actual_metadata.get(), expected_metadata);
@@ -8823,12 +8823,12 @@
       Pass(child_quads, CompositorRenderPassId{1}, gfx::Size(100, 100))};
 
   CompositorFrame child_frame = MakeEmptyCompositorFrame();
-  DelegatedInkMetadata metadata(gfx::PointF(34, 89), 1.597, SK_ColorBLUE,
-                                base::TimeTicks::Now(),
-                                gfx::RectF(2.3, 3.2, 177, 212),
-                                base::TimeTicks::Now(), /*hovering*/ false);
+  gfx::DelegatedInkMetadata metadata(
+      gfx::PointF(34, 89), 1.597, SK_ColorBLUE, base::TimeTicks::Now(),
+      gfx::RectF(2.3, 3.2, 177, 212), base::TimeTicks::Now(),
+      /*hovering*/ false);
   child_frame.metadata.delegated_ink_metadata =
-      std::make_unique<DelegatedInkMetadata>(metadata);
+      std::make_unique<gfx::DelegatedInkMetadata>(metadata);
   AddPasses(&child_frame.render_pass_list, child_passes,
             &child_frame.metadata.referenced_surfaces);
 
@@ -8879,7 +8879,7 @@
 
   aggregated_frame = AggregateFrame(root_surface_id);
 
-  std::unique_ptr<DelegatedInkMetadata> actual_metadata =
+  std::unique_ptr<gfx::DelegatedInkMetadata> actual_metadata =
       std::move(aggregated_frame.delegated_ink_metadata);
   EXPECT_TRUE(actual_metadata);
   ExpectDelegatedInkMetadataIsEqual(*actual_metadata.get(), metadata);
diff --git a/components/viz/service/surfaces/surface.cc b/components/viz/service/surfaces/surface.cc
index d6d18879..ce9d185 100644
--- a/components/viz/service/surfaces/surface.cc
+++ b/components/viz/service/surfaces/surface.cc
@@ -795,7 +795,7 @@
   ActivatePendingFrameForDeadline();
 }
 
-std::unique_ptr<DelegatedInkMetadata> Surface::TakeDelegatedInkMetadata() {
+std::unique_ptr<gfx::DelegatedInkMetadata> Surface::TakeDelegatedInkMetadata() {
   DCHECK(active_frame_data_);
   return active_frame_data_->TakeDelegatedInkMetadata();
 }
diff --git a/components/viz/service/surfaces/surface.h b/components/viz/service/surfaces/surface.h
index 101b595..14530e76 100644
--- a/components/viz/service/surfaces/surface.h
+++ b/components/viz/service/surfaces/surface.h
@@ -258,7 +258,7 @@
 
   void ActivateIfDeadlinePassed();
 
-  std::unique_ptr<DelegatedInkMetadata> TakeDelegatedInkMetadata();
+  std::unique_ptr<gfx::DelegatedInkMetadata> TakeDelegatedInkMetadata();
 
   SurfaceSavedFrameStorage* GetSurfaceSavedFrameStorage();
 
@@ -285,7 +285,7 @@
 
     // Delegated ink metadata should only be used for a single frame, so it
     // should be taken from the FrameData to use.
-    std::unique_ptr<DelegatedInkMetadata> TakeDelegatedInkMetadata() {
+    std::unique_ptr<gfx::DelegatedInkMetadata> TakeDelegatedInkMetadata() {
       return std::move(frame.metadata.delegated_ink_metadata);
     }
 
diff --git a/content/browser/conversions/conversion_storage_sql.cc b/content/browser/conversions/conversion_storage_sql.cc
index 96e2f1e..0df238fb 100644
--- a/content/browser/conversions/conversion_storage_sql.cc
+++ b/content/browser/conversions/conversion_storage_sql.cc
@@ -65,11 +65,15 @@
 // Version 2 adds a new impression column "conversion_destination" based on
 // registerable domain which is used for attribution instead of
 // "conversion_origin".
-const int kCurrentVersionNumber = 2;
+//
+// Version 3 - 2021/03/08 - https://crrev.com/c/2743337
+//
+// Version 3 adds new impression columns source_type and attributed_truthfully.
+const int kCurrentVersionNumber = 3;
 
 // Earliest version which can use a |kCurrentVersionNumber| database
 // without failing.
-const int kCompatibleVersionNumber = 2;
+const int kCompatibleVersionNumber = 3;
 
 // Latest version of the database that cannot be upgraded to
 // |kCurrentVersionNumber| without razing the database. No versions are
@@ -159,8 +163,9 @@
       "INSERT INTO impressions"
       "(impression_data, impression_origin, conversion_origin, "
       "conversion_destination, "
-      "reporting_origin, impression_time, expiry_time) "
-      "VALUES (?,?,?,?,?,?,?)";
+      "reporting_origin, impression_time, expiry_time, source_type, "
+      "attributed_truthfully) "
+      "VALUES (?,?,?,?,?,?,?,?,?)";
   sql::Statement statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kInsertImpressionSql));
   statement.BindString(0, impression.impression_data());
@@ -170,6 +175,8 @@
   statement.BindString(4, serialized_reporting_origin);
   statement.BindInt64(5, SerializeTime(impression.impression_time()));
   statement.BindInt64(6, SerializeTime(impression.expiry_time()));
+  statement.BindInt(7, static_cast<int>(impression.source_type()));
+  statement.BindInt(8, 1 /*true*/);
   statement.Run();
 
   transaction.Commit();
@@ -817,6 +824,11 @@
   //     in StoreImpression().
   //   - An impression has expired but still has unsent conversions in the
   //     conversions table meaning it cannot be deleted yet.
+  // |source_type| is the type of the source of the impression, currently always
+  // |kNavigation|.
+  // |attributed_truthfully| is whether the impression was noisily attributed:
+  // the impression was either marked inactive to start so it would never send a
+  // report, or a fake conversion report was generated for the impression.
   const char kImpressionTableSql[] =
       "CREATE TABLE IF NOT EXISTS impressions"
       "(impression_id INTEGER PRIMARY KEY,"
@@ -828,7 +840,9 @@
       "expiry_time INTEGER NOT NULL,"
       "num_conversions INTEGER DEFAULT 0,"
       "active INTEGER DEFAULT 1,"
-      "conversion_destination TEXT NOT NULL)";
+      "conversion_destination TEXT NOT NULL,"
+      "source_type INTEGER NOT NULL,"
+      "attributed_truthfully INTEGER NOT NULL)";
   if (!db_->Execute(kImpressionTableSql))
     return false;
 
diff --git a/content/browser/conversions/conversion_storage_sql_migrations.cc b/content/browser/conversions/conversion_storage_sql_migrations.cc
index 47d772d7..5ab821e 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations.cc
+++ b/content/browser/conversions/conversion_storage_sql_migrations.cc
@@ -7,6 +7,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
 #include "content/browser/conversions/conversion_storage_sql.h"
+#include "content/browser/conversions/storable_impression.h"
 #include "sql/database.h"
 #include "sql/meta_table.h"
 #include "sql/statement.h"
@@ -24,6 +25,10 @@
     if (!MigrateToVersion2(conversion_storage, db, meta_table))
       return false;
   }
+  if (meta_table->GetVersionNumber() == 2) {
+    if (!MigrateToVersion3(conversion_storage, db, meta_table))
+      return false;
+  }
   // Add similar if () blocks for new versions here.
 
   base::UmaHistogramMediumTimes("Conversions.Storage.MigrationTime",
@@ -156,4 +161,85 @@
   return transaction.Commit();
 }
 
+bool ConversionStorageSqlMigrations::MigrateToVersion3(
+    ConversionStorageSql* conversion_storage,
+    sql::Database* db,
+    sql::MetaTable* meta_table) {
+  // Wrap each migration in its own transaction. See comment in
+  // |MigrateToVersion2|.
+  sql::Transaction transaction(db);
+  if (!transaction.Begin())
+    return false;
+
+  // Add new source_type and attributed_truthfully columns to the impressions
+  // table. This follows the steps documented at
+  // https://sqlite.org/lang_altertable.html#otheralter. Other approaches, like
+  // using "ALTER ... ADD COLUMN" require setting a DEFAULT value for the column
+  // which is undesirable.
+  const char kNewImpressionTableSql[] =
+      "CREATE TABLE IF NOT EXISTS new_impressions"
+      "(impression_id INTEGER PRIMARY KEY,"
+      "impression_data TEXT NOT NULL,"
+      "impression_origin TEXT NOT NULL,"
+      "conversion_origin TEXT NOT NULL,"
+      "reporting_origin TEXT NOT NULL,"
+      "impression_time INTEGER NOT NULL,"
+      "expiry_time INTEGER NOT NULL,"
+      "num_conversions INTEGER DEFAULT 0,"
+      "active INTEGER DEFAULT 1,"
+      "conversion_destination TEXT NOT NULL,"
+      "source_type INTEGER NOT NULL,"
+      "attributed_truthfully INTEGER NOT NULL)";
+  if (!db->Execute(kNewImpressionTableSql))
+    return false;
+
+  // Transfer the existing rows to the new table, inserting default values for
+  // the source_type and attributed_truthfully columns.
+  const char kPopulateNewImpressionTableSql[] =
+      "INSERT INTO new_impressions SELECT "
+      "impression_id,impression_data,impression_origin,"
+      "conversion_origin,reporting_origin,impression_time,"
+      "expiry_time,num_conversions,active,conversion_destination,?,? "
+      "FROM impressions";
+  sql::Statement populate_statement(
+      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewImpressionTableSql));
+  // Only navigation type was supported prior to this column being added.
+  populate_statement.BindInt(
+      0, static_cast<int>(StorableImpression::SourceType::kNavigation));
+  populate_statement.BindBool(1, true);
+  if (!populate_statement.Run())
+    return false;
+
+  const char kDropOldImpressionTableSql[] = "DROP TABLE impressions";
+  if (!db->Execute(kDropOldImpressionTableSql))
+    return false;
+
+  const char kRenameImpressionTableSql[] =
+      "ALTER TABLE new_impressions RENAME TO impressions";
+  if (!db->Execute(kRenameImpressionTableSql))
+    return false;
+
+  // Create the pre-existing impression table indices on the new table.
+  const char kImpressionExpiryIndexSql[] =
+      "CREATE INDEX IF NOT EXISTS impression_expiry_idx "
+      "ON impressions(expiry_time)";
+  if (!db->Execute(kImpressionExpiryIndexSql))
+    return false;
+
+  const char kImpressionOriginIndexSql[] =
+      "CREATE INDEX IF NOT EXISTS impression_origin_idx "
+      "ON impressions(impression_origin)";
+  if (!db->Execute(kImpressionOriginIndexSql))
+    return false;
+
+  const char kConversionDestinationIndexSql[] =
+      "CREATE INDEX IF NOT EXISTS conversion_destination_idx "
+      "ON impressions(active, conversion_destination, reporting_origin)";
+  if (!db->Execute(kConversionDestinationIndexSql))
+    return false;
+
+  meta_table->SetVersionNumber(3);
+  return transaction.Commit();
+}
+
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql_migrations.h b/content/browser/conversions/conversion_storage_sql_migrations.h
index ca0a391..796b97e 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations.h
+++ b/content/browser/conversions/conversion_storage_sql_migrations.h
@@ -58,6 +58,9 @@
   static bool MigrateToVersion2(ConversionStorageSql* conversion_storage,
                                 sql::Database* db,
                                 sql::MetaTable* meta_table);
+  static bool MigrateToVersion3(ConversionStorageSql* conversion_storage,
+                                sql::Database* db,
+                                sql::MetaTable* meta_table);
 };
 
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc b/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
index afa9940d..839b90a 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
+++ b/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
@@ -29,7 +29,7 @@
   return output;
 }
 
-const int kCurrentVersionNumber = 2;
+const int kCurrentVersionNumber = 3;
 
 }  // namespace
 
@@ -57,7 +57,7 @@
   std::string GetCurrentSchema() {
     base::FilePath current_version_path = temp_directory_.GetPath().Append(
         FILE_PATH_LITERAL("TestCurrentVersion.db"));
-    LoadDatabase(FILE_PATH_LITERAL("version_2.sql"), current_version_path);
+    LoadDatabase(FILE_PATH_LITERAL("version_3.sql"), current_version_path);
     sql::Database db;
     EXPECT_TRUE(db.Open(current_version_path));
     return db.GetSchema();
@@ -196,4 +196,59 @@
   histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
 }
 
+TEST_F(ConversionStorageSqlMigrationsTest, MigrateVersion2ToCurrent) {
+  base::HistogramTester histograms;
+  LoadDatabase(FILE_PATH_LITERAL("version_2.sql"), DbPath());
+
+  // Verify pre-conditions.
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(DbPath()));
+
+    ASSERT_FALSE(db.DoesColumnExist("impressions", "source_type"));
+    ASSERT_FALSE(db.DoesColumnExist("impressions", "attributed_truthfully"));
+  }
+
+  MigrateDatabase();
+
+  // Verify schema is current.
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(DbPath()));
+
+    // Check version.
+    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
+
+    // Check that expected tables are present.
+    EXPECT_TRUE(db.DoesTableExist("conversions"));
+    EXPECT_TRUE(db.DoesTableExist("impressions"));
+    EXPECT_TRUE(db.DoesTableExist("meta"));
+
+    // Compare without quotes as sometimes migrations cause table names to be
+    // string literals.
+    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
+
+    // Check that the relevant schema changes are made.
+    EXPECT_TRUE(db.DoesColumnExist("impressions", "source_type"));
+    EXPECT_TRUE(db.DoesColumnExist("impressions", "attributed_truthfully"));
+
+    // Verify that data is preserved across the migration.
+    size_t rows = 0;
+    sql::test::CountTableRows(&db, "impressions", &rows);
+    EXPECT_EQ(1u, rows);
+
+    sql::Statement s(db.GetUniqueStatement(
+        "SELECT source_type, attributed_truthfully FROM impressions"));
+
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ(0, s.ColumnInt(0));
+    ASSERT_EQ(true, s.ColumnBool(1));
+    ASSERT_FALSE(s.Step());
+  }
+
+  // DB migration histograms should be recorded.
+  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
+  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
+}
+
 }  // namespace content
diff --git a/content/browser/conversions/storable_impression.h b/content/browser/conversions/storable_impression.h
index be72ac1..6cb6bc7 100644
--- a/content/browser/conversions/storable_impression.h
+++ b/content/browser/conversions/storable_impression.h
@@ -20,6 +20,15 @@
 // should be sanitized before creating this object.
 class CONTENT_EXPORT StorableImpression {
  public:
+  // Denotes the type of source for this impression. This allows different types
+  // of impressions to be processed differently by storage and attribution
+  // logic.
+  enum class SourceType {
+    // An impression which was associated with a top-level navigation.
+    kNavigation = 0,
+    kMaxValue = kNavigation,
+  };
+
   // If |impression_id| is not available, 0 should be provided.
   StorableImpression(const std::string& impression_data,
                      const url::Origin& impression_origin,
@@ -46,6 +55,8 @@
 
   base::Optional<int64_t> impression_id() const { return impression_id_; }
 
+  SourceType source_type() const { return source_type_; }
+
   // Returns the schemeful site of |conversion_origin|.
   //
   // TODO(johnidel): Consider storing the SchemefulSite as a separate member so
@@ -60,6 +71,7 @@
   url::Origin reporting_origin_;
   base::Time impression_time_;
   base::Time expiry_time_;
+  SourceType source_type_ = SourceType::kNavigation;
 
   // If null, an ID has not been assigned yet.
   base::Optional<int64_t> impression_id_;
diff --git a/content/browser/device/device_service.cc b/content/browser/device/device_service.cc
index e5f903c..7ae7c84 100644
--- a/content/browser/device/device_service.cc
+++ b/content/browser/device/device_service.cc
@@ -79,14 +79,6 @@
 
 void BindDeviceServiceReceiver(
     mojo::PendingReceiver<device::mojom::DeviceService> receiver) {
-  // This task runner may be used by some device service implementation bits
-  // to interface with dbus client code, which in turn imposes some subtle
-  // thread affinity on the clients. We therefore require a single-thread
-  // runner.
-  scoped_refptr<base::SingleThreadTaskRunner> device_blocking_task_runner =
-      base::ThreadPool::CreateSingleThreadTaskRunner(
-          {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
-
   // Bind the lifetime of the service instance to that of the sequence it's
   // running on.
   static base::NoDestructor<
@@ -99,36 +91,39 @@
     return;
   }
 
+  auto params = std::make_unique<device::DeviceServiceParams>();
+
+  // This task runner may be used by some device service implementation bits
+  // to interface with dbus client code, which in turn imposes some subtle
+  // thread affinity on the clients. We therefore require a single-thread
+  // runner.
+  params->file_task_runner = base::ThreadPool::CreateSingleThreadTaskRunner(
+      {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
+
+  params->io_task_runner = GetIOThreadTaskRunner({});
+  params->url_loader_factory =
+      base::MakeRefCounted<DeviceServiceURLLoaderFactory>();
+  params->network_connection_tracker = content::GetNetworkConnectionTracker();
+  params->geolocation_api_key =
+      GetContentClient()->browser()->GetGeolocationApiKey();
+  params->custom_location_provider_callback =
+      base::BindRepeating(&ContentBrowserClient::OverrideSystemLocationProvider,
+                          base::Unretained(GetContentClient()->browser()));
+  params->location_permission_manager =
+      GetContentClient()->browser()->GetLocationPermissionManager();
+
 #if defined(OS_ANDROID)
   JNIEnv* env = base::android::AttachCurrentThread();
-  base::android::ScopedJavaGlobalRef<jobject> java_nfc_delegate;
-  java_nfc_delegate.Reset(Java_ContentNfcDelegate_create(env));
-  DCHECK(!java_nfc_delegate.is_null());
+  params->java_nfc_delegate = Java_ContentNfcDelegate_create(env);
+  DCHECK(!params->java_nfc_delegate.is_null());
 
-  // See the comments on wake_lock_context_host.h, content_browser_client.h
-  // and ContentNfcDelegate.java respectively for comments on those
-  // parameters.
-  service = device::CreateDeviceService(
-      device_blocking_task_runner, GetIOThreadTaskRunner({}),
-      base::MakeRefCounted<DeviceServiceURLLoaderFactory>(),
-      content::GetNetworkConnectionTracker(),
-      GetContentClient()->browser()->GetGeolocationApiKey(),
-      GetContentClient()->browser()->ShouldUseGmsCoreGeolocationProvider(),
-      base::BindRepeating(&WakeLockContextHost::GetNativeViewForContext),
-      base::BindRepeating(&ContentBrowserClient::OverrideSystemLocationProvider,
-                          base::Unretained(GetContentClient()->browser())),
-      std::move(java_nfc_delegate), std::move(receiver));
-#else
-  service = device::CreateDeviceService(
-      device_blocking_task_runner, GetIOThreadTaskRunner({}),
-      base::MakeRefCounted<DeviceServiceURLLoaderFactory>(),
-      content::GetNetworkConnectionTracker(),
-      GetContentClient()->browser()->GetGeolocationApiKey(),
-      GetContentClient()->browser()->GetLocationPermissionManager(),
-      base::BindRepeating(&ContentBrowserClient::OverrideSystemLocationProvider,
-                          base::Unretained(GetContentClient()->browser())),
-      std::move(receiver));
+  params->wake_lock_context_callback =
+      base::BindRepeating(&WakeLockContextHost::GetNativeViewForContext);
+  params->use_gms_core_location_provider =
+      GetContentClient()->browser()->ShouldUseGmsCoreGeolocationProvider();
 #endif
+
+  service = device::CreateDeviceService(std::move(params), std::move(receiver));
 }
 
 }  // namespace
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 65af410..4571b064 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -165,6 +165,11 @@
 void OnNavigationResponseReceived(
     const NavigationRequest& nav_request,
     const network::mojom::URLResponseHead& response) {
+  // This response is artificial (see CachedNavigationURLLoader), so we don't
+  // want to report it.
+  if (nav_request.IsServedFromBackForwardCache())
+    return;
+
   FrameTreeNode* ftn = nav_request.frame_tree_node();
   std::string id = nav_request.devtools_navigation_token().ToString();
   std::string frame_id = ftn->devtools_frame_token().ToString();
@@ -245,6 +250,11 @@
                                 inspector_issue.get());
   }
 
+  // If a BFCache navigation fails, it will be restarted as a regular
+  // navigation, so we don't want to report this failure.
+  if (nav_request.IsServedFromBackForwardCache())
+    return;
+
   DispatchToAgents(ftn, &protocol::NetworkHandler::LoadingComplete, id,
                    protocol::Network::ResourceTypeEnum::Document, status);
 }
@@ -626,6 +636,11 @@
     agent_host->OnNavigationRequestWillBeSent(navigation_request);
   }
 
+  // We use CachedNavigationURLLoader for BFCache navigations and don't actually
+  // send a network request, so we don't report this request to DevTools.
+  if (navigation_request.IsServedFromBackForwardCache())
+    return;
+
   // Make sure both back-ends yield the same timestamp.
   auto timestamp = base::TimeTicks::Now();
   DispatchToAgents(navigation_request.frame_tree_node(),
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index 2425c2b9..25a5780 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -248,7 +248,6 @@
     switches::kDisableShaderNameHashing,
     switches::kDisableSkiaRuntimeOpts,
     switches::kDisableWebRtcHWEncoding,
-    switches::kEnableBackgroundThreadPool,
     switches::kEnableGpuRasterization,
     switches::kEnableLogging,
     switches::kEnableDeJelly,
diff --git a/content/browser/indexed_db/indexed_db_backing_store_unittest.cc b/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
index 0d587e82..576fe4a6 100644
--- a/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_backing_store_unittest.cc
@@ -53,7 +53,6 @@
 #include "third_party/blink/public/common/indexeddb/web_idb_types.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
 
-using base::ASCIIToUTF16;
 using blink::IndexedDBDatabaseMetadata;
 using blink::IndexedDBIndexMetadata;
 using blink::IndexedDBKey;
@@ -323,7 +322,7 @@
     value2_ = IndexedDBValue("value2", {});
 
     key1_ = IndexedDBKey(99, blink::mojom::IDBKeyType::Number);
-    key2_ = IndexedDBKey(ASCIIToUTF16("key2"));
+    key2_ = IndexedDBKey(u"key2");
   }
 
   void CreateFactoryAndBackingStore() {
@@ -494,25 +493,24 @@
     const int64_t kTime2 = 13287455133000000ll;
     // useful keys and values during tests
     if (IncludesBlobs()) {
+      external_objects_.push_back(CreateBlobInfo(u"blob type", 1));
       external_objects_.push_back(
-          CreateBlobInfo(base::UTF8ToUTF16("blob type"), 1));
-      external_objects_.push_back(CreateBlobInfo(
-          base::UTF8ToUTF16("file name"), base::UTF8ToUTF16("file type"),
-          base::Time::FromDeltaSinceWindowsEpoch(
-              base::TimeDelta::FromMicroseconds(kTime1)),
-          kBlobFileData1.size()));
-      external_objects_.push_back(CreateBlobInfo(
-          base::UTF8ToUTF16("file name"), base::UTF8ToUTF16("file type"),
-          base::Time::FromDeltaSinceWindowsEpoch(
-              base::TimeDelta::FromMicroseconds(kTime2)),
-          kBlobFileData2.size()));
+          CreateBlobInfo(u"file name", u"file type",
+                         base::Time::FromDeltaSinceWindowsEpoch(
+                             base::TimeDelta::FromMicroseconds(kTime1)),
+                         kBlobFileData1.size()));
+      external_objects_.push_back(
+          CreateBlobInfo(u"file name", u"file type",
+                         base::Time::FromDeltaSinceWindowsEpoch(
+                             base::TimeDelta::FromMicroseconds(kTime2)),
+                         kBlobFileData2.size()));
     }
     if (IncludesFileSystemAccessHandles()) {
       external_objects_.push_back(CreateFileSystemAccessHandle());
       external_objects_.push_back(CreateFileSystemAccessHandle());
     }
     value3_ = IndexedDBValue("value3", external_objects_);
-    key3_ = IndexedDBKey(ASCIIToUTF16("key3"));
+    key3_ = IndexedDBKey(u"key3");
   }
 
   IndexedDBExternalObject CreateBlobInfo(const std::u16string& file_name,
@@ -934,8 +932,8 @@
 // not delete blobs that were just written.
 TEST_P(IndexedDBBackingStoreTestWithExternalObjects, BlobWriteCleanup) {
   const std::vector<IndexedDBKey> keys = {
-      IndexedDBKey(ASCIIToUTF16("key0")), IndexedDBKey(ASCIIToUTF16("key1")),
-      IndexedDBKey(ASCIIToUTF16("key2")), IndexedDBKey(ASCIIToUTF16("key3"))};
+      IndexedDBKey(u"key0"), IndexedDBKey(u"key1"), IndexedDBKey(u"key2"),
+      IndexedDBKey(u"key3")};
 
   const int64_t database_id = 1;
   const int64_t object_store_id = 1;
@@ -993,8 +991,8 @@
 
 TEST_P(IndexedDBBackingStoreTestWithExternalObjects, DeleteRange) {
   const std::vector<IndexedDBKey> keys = {
-      IndexedDBKey(ASCIIToUTF16("key0")), IndexedDBKey(ASCIIToUTF16("key1")),
-      IndexedDBKey(ASCIIToUTF16("key2")), IndexedDBKey(ASCIIToUTF16("key3"))};
+      IndexedDBKey(u"key0"), IndexedDBKey(u"key1"), IndexedDBKey(u"key2"),
+      IndexedDBKey(u"key3")};
   const IndexedDBKeyRange ranges[] = {
       IndexedDBKeyRange(keys[1], keys[2], false, false),
       IndexedDBKeyRange(keys[1], keys[2], false, false),
@@ -1090,9 +1088,8 @@
 
 TEST_P(IndexedDBBackingStoreTestWithExternalObjects, DeleteRangeEmptyRange) {
   const std::vector<IndexedDBKey> keys = {
-      IndexedDBKey(ASCIIToUTF16("key0")), IndexedDBKey(ASCIIToUTF16("key1")),
-      IndexedDBKey(ASCIIToUTF16("key2")), IndexedDBKey(ASCIIToUTF16("key3")),
-      IndexedDBKey(ASCIIToUTF16("key4"))};
+      IndexedDBKey(u"key0"), IndexedDBKey(u"key1"), IndexedDBKey(u"key2"),
+      IndexedDBKey(u"key3"), IndexedDBKey(u"key4")};
   const IndexedDBKeyRange ranges[] = {
       IndexedDBKeyRange(keys[3], keys[4], true, false),
       IndexedDBKeyRange(keys[2], keys[1], false, false),
@@ -1499,21 +1496,20 @@
   base::RunLoop loop;
   idb_context_->IDBTaskRunner()->PostTask(
       FROM_HERE, base::BindLambdaForTesting([&]() {
-        const std::u16string database_name(ASCIIToUTF16("db1"));
+        const std::u16string database_name(u"db1");
         int64_t database_id;
         const int64_t version = 9;
 
         const int64_t object_store_id = 99;
-        const std::u16string object_store_name(ASCIIToUTF16("object_store1"));
+        const std::u16string object_store_name(u"object_store1");
         const bool auto_increment = true;
-        const IndexedDBKeyPath object_store_key_path(
-            ASCIIToUTF16("object_store_key"));
+        const IndexedDBKeyPath object_store_key_path(u"object_store_key");
 
         const int64_t index_id = 999;
-        const std::u16string index_name(ASCIIToUTF16("index1"));
+        const std::u16string index_name(u"index1");
         const bool unique = true;
         const bool multi_entry = true;
-        const IndexedDBKeyPath index_key_path(ASCIIToUTF16("index_key"));
+        const IndexedDBKeyPath index_key_path(u"index_key");
 
         IndexedDBMetadataCoding metadata_coding;
 
@@ -1594,12 +1590,12 @@
 }
 
 TEST_F(IndexedDBBackingStoreTest, GetDatabaseNames) {
-  const std::u16string db1_name(ASCIIToUTF16("db1"));
+  const std::u16string db1_name(u"db1");
   const int64_t db1_version = 1LL;
 
   // Database records with DEFAULT_VERSION represent
   // stale data, and should not be enumerated.
-  const std::u16string db2_name(ASCIIToUTF16("db2"));
+  const std::u16string db2_name(u"db2");
   const int64_t db2_version = IndexedDBDatabaseMetadata::DEFAULT_VERSION;
   IndexedDBMetadataCoding metadata_coding;
 
@@ -1721,13 +1717,12 @@
 
   // The database metadata needs to be written so we can verify the blob entry
   // keys are not detected.
-  const std::u16string database_name(ASCIIToUTF16("db1"));
+  const std::u16string database_name(u"db1");
   const int64_t version = 9;
 
-  const std::u16string object_store_name(ASCIIToUTF16("object_store1"));
+  const std::u16string object_store_name(u"object_store1");
   const bool auto_increment = true;
-  const IndexedDBKeyPath object_store_key_path(
-      ASCIIToUTF16("object_store_key"));
+  const IndexedDBKeyPath object_store_key_path(u"object_store_key");
 
   IndexedDBMetadataCoding metadata_coding;
 
@@ -1828,13 +1823,12 @@
 
   // The database metadata needs to be written so the blob entry keys can
   // be detected.
-  const std::u16string database_name(ASCIIToUTF16("db1"));
+  const std::u16string database_name(u"db1");
   const int64_t version = 9;
 
-  const std::u16string object_store_name(ASCIIToUTF16("object_store1"));
+  const std::u16string object_store_name(u"object_store1");
   const bool auto_increment = true;
-  const IndexedDBKeyPath object_store_key_path(
-      ASCIIToUTF16("object_store_key"));
+  const IndexedDBKeyPath object_store_key_path(u"object_store_key");
 
   IndexedDBMetadataCoding metadata_coding;
 
@@ -1946,13 +1940,12 @@
   int64_t database_id;
   const int64_t object_store_id = 99;
 
-  const std::u16string database_name(ASCIIToUTF16("db1"));
+  const std::u16string database_name(u"db1");
   const int64_t version = 9;
 
-  const std::u16string object_store_name(ASCIIToUTF16("object_store1"));
+  const std::u16string object_store_name(u"object_store1");
   const bool auto_increment = true;
-  const IndexedDBKeyPath object_store_key_path(
-      ASCIIToUTF16("object_store_key"));
+  const IndexedDBKeyPath object_store_key_path(u"object_store_key");
 
   IndexedDBMetadataCoding metadata_coding;
 
@@ -2092,20 +2085,18 @@
   int64_t database_id;
   const int64_t object_store_id = 99;
 
-  const std::u16string database_name(ASCIIToUTF16("db1"));
+  const std::u16string database_name(u"db1");
   const int64_t version = 9;
 
-  const std::u16string object_store_name(ASCIIToUTF16("object_store1"));
+  const std::u16string object_store_name(u"object_store1");
   const bool auto_increment = true;
-  const IndexedDBKeyPath object_store_key_path(
-      ASCIIToUTF16("object_store_key"));
+  const IndexedDBKeyPath object_store_key_path(u"object_store_key");
 
   // Add an empty blob here to test with.  Empty blobs are not written
   // to disk, so it's important to verify that a database with empty blobs
   // should be considered still valid.
-  external_objects().push_back(CreateBlobInfo(base::UTF8ToUTF16("empty blob"),
-                                              base::UTF8ToUTF16("file type"),
-                                              base::Time::Now(), 0u));
+  external_objects().push_back(
+      CreateBlobInfo(u"empty blob", u"file type", base::Time::Now(), 0u));
   // The V5 migration checks files on disk, so make sure our fake blob
   // context writes something there to check.
   blob_context_->SetWriteFilesToDisk(true);
@@ -2151,7 +2142,7 @@
   transaction->Begin(CreateDummyLock());
   IndexedDBBackingStore::RecordIdentifier record;
 
-  IndexedDBKey key = IndexedDBKey(ASCIIToUTF16("key"));
+  IndexedDBKey key = IndexedDBKey(u"key");
   IndexedDBValue value = IndexedDBValue("value3", external_objects());
 
   EXPECT_TRUE(backing_store()
@@ -2227,8 +2218,8 @@
 // TODO(enne): we could use more comprehensive testing for ClearObjectStore.
 TEST_P(IndexedDBBackingStoreTestWithExternalObjects, ClearObjectStoreObjects) {
   const std::vector<IndexedDBKey> keys = {
-      IndexedDBKey(ASCIIToUTF16("key0")), IndexedDBKey(ASCIIToUTF16("key1")),
-      IndexedDBKey(ASCIIToUTF16("key2")), IndexedDBKey(ASCIIToUTF16("key3"))};
+      IndexedDBKey(u"key0"), IndexedDBKey(u"key1"), IndexedDBKey(u"key2"),
+      IndexedDBKey(u"key3")};
 
   const int64_t database_id = 777;
   const int64_t object_store_id = 999;
diff --git a/content/browser/indexed_db/indexed_db_browsertest.cc b/content/browser/indexed_db/indexed_db_browsertest.cc
index ec3ae44..61b1c3c 100644
--- a/content/browser/indexed_db/indexed_db_browsertest.cc
+++ b/content/browser/indexed_db/indexed_db_browsertest.cc
@@ -59,7 +59,6 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
-using base::ASCIIToUTF16;
 using storage::DatabaseUtil;
 using storage::QuotaManager;
 using storage::mojom::FailClass;
@@ -144,7 +143,7 @@
     if (hash)
       url = GURL(url.spec() + hash);
 
-    std::u16string expected_title16(ASCIIToUTF16(expected_string));
+    std::u16string expected_title16(base::ASCIIToUTF16(expected_string));
     TitleWatcher title_watcher(shell->web_contents(), expected_title16);
     EXPECT_TRUE(NavigateToURL(shell, url));
     EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
@@ -287,7 +286,7 @@
 
 IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTestIncognito) {
   SimpleTest(GetTestUrl("indexeddb", "cursor_test.html"),
-             true /* incognito */);
+             /*incognito=*/true);
 }
 
 IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorPrefetch) {
@@ -1105,7 +1104,7 @@
   NavigateAndWaitForTitle(new_shell, "version_change_blocked.html", "#tab2",
                           "setVersion(3) blocked");
 
-  std::u16string expected_title16(ASCIIToUTF16("setVersion(3) complete"));
+  std::u16string expected_title16(u"setVersion(3) complete");
   TitleWatcher title_watcher(new_shell->web_contents(), expected_title16);
 
   shell()->web_contents()->GetMainFrame()->GetProcess()->Shutdown(0);
@@ -1120,9 +1119,9 @@
   constexpr char kFilename[] = "force_close_event.html";
   NavigateAndWaitForTitle(shell(), kFilename, nullptr, "connection ready");
   DeleteForOrigin(url::Origin::Create(GetTestUrl("indexeddb", kFilename)));
-  std::u16string expected_title16(ASCIIToUTF16("connection closed"));
+  std::u16string expected_title16(u"connection closed");
   TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
-  title_watcher.AlsoWaitForTitle(ASCIIToUTF16("connection closed with error"));
+  title_watcher.AlsoWaitForTitle(u"connection closed with error");
   EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
 }
 
diff --git a/content/browser/indexed_db/indexed_db_callbacks.cc b/content/browser/indexed_db/indexed_db_callbacks.cc
index 3fb3e87..2ba13149 100644
--- a/content/browser/indexed_db/indexed_db_callbacks.cc
+++ b/content/browser/indexed_db/indexed_db_callbacks.cc
@@ -12,7 +12,6 @@
 
 #include "base/bind.h"
 #include "base/sequenced_task_runner.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
diff --git a/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc b/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc
index 32acd16..e0b50ef 100644
--- a/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_cleanup_on_io_error_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/task_environment.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "components/services/storage/indexed_db/leveldb/fake_leveldb_factory.h"
diff --git a/content/browser/indexed_db/indexed_db_connection_coordinator.cc b/content/browser/indexed_db/indexed_db_connection_coordinator.cc
index 258104c..ddd7f0aa 100644
--- a/content/browser/indexed_db/indexed_db_connection_coordinator.cc
+++ b/content/browser/indexed_db/indexed_db_connection_coordinator.cc
@@ -12,7 +12,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/utf_string_conversions.h"
 #include "components/services/storage/indexed_db/scopes/leveldb_scope.h"
 #include "components/services/storage/indexed_db/scopes/leveldb_scopes.h"
 #include "components/services/storage/indexed_db/scopes/scopes_lock_manager.h"
@@ -31,7 +30,6 @@
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
 #include "third_party/leveldatabase/env_chromium.h"
 
-using base::ASCIIToUTF16;
 using base::NumberToString16;
 using blink::IndexedDBDatabaseMetadata;
 using leveldb::Status;
@@ -148,12 +146,11 @@
         // TODO(jsbell): Consider including sanitized leveldb status message.
         std::u16string message;
         if (pending_->version == IndexedDBDatabaseMetadata::NO_VERSION) {
-          message = ASCIIToUTF16(
-              "Internal error opening database with no version specified.");
-        } else {
           message =
-              ASCIIToUTF16("Internal error opening database with version ") +
-              NumberToString16(pending_->version);
+              u"Internal error opening database with no version specified.";
+        } else {
+          message = u"Internal error opening database with version " +
+                    NumberToString16(pending_->version);
         }
         pending_->callbacks->OnError(IndexedDBDatabaseError(
             blink::mojom::IDBException::kUnknownError, message));
@@ -202,10 +199,9 @@
       DCHECK(!is_new_database);
       pending_->callbacks->OnError(IndexedDBDatabaseError(
           blink::mojom::IDBException::kVersionError,
-          ASCIIToUTF16("The requested version (") +
-              NumberToString16(pending_->version) +
-              ASCIIToUTF16(") is less than the existing version (") +
-              NumberToString16(db_->metadata_.version) + ASCIIToUTF16(").")));
+          u"The requested version (" + NumberToString16(pending_->version) +
+              u") is less than the existing version (" +
+              NumberToString16(db_->metadata_.version) + u")."));
       state_ = RequestState::kDone;
       return;
     }
diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc
index 1daf306..e8e2b184 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.cc
+++ b/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -19,7 +19,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "base/threading/thread_restrictions.h"
diff --git a/content/browser/indexed_db/indexed_db_database.cc b/content/browser/indexed_db/indexed_db_database.cc
index bd882b84..b409178 100644
--- a/content/browser/indexed_db/indexed_db_database.cc
+++ b/content/browser/indexed_db/indexed_db_database.cc
@@ -22,7 +22,6 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/utf_string_conversions.h"
 #include "components/services/storage/indexed_db/scopes/leveldb_scope.h"
 #include "components/services/storage/indexed_db/scopes/leveldb_scopes.h"
 #include "components/services/storage/indexed_db/scopes/scope_lock.h"
diff --git a/content/browser/indexed_db/indexed_db_database_unittest.cc b/content/browser/indexed_db/indexed_db_database_unittest.cc
index b1c86761..8aa06bb 100644
--- a/content/browser/indexed_db/indexed_db_database_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_database_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
@@ -37,7 +36,6 @@
 #include "content/browser/indexed_db/mock_indexed_db_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using base::ASCIIToUTF16;
 using blink::IndexedDBDatabaseMetadata;
 using blink::IndexedDBIndexKeys;
 using blink::IndexedDBKey;
@@ -64,7 +62,7 @@
     leveldb::Status s;
 
     std::tie(db_, s) = IndexedDBClassFactory::Get()->CreateIndexedDBDatabase(
-        ASCIIToUTF16("db"), backing_store_.get(), factory_.get(),
+        u"db", backing_store_.get(), factory_.get(),
         base::BindRepeating(&IndexedDBDatabaseTest::RunTasksForDatabase,
                             weak_factory_.GetWeakPtr(), true),
         std::move(metadata_coding), IndexedDBDatabase::Identifier(),
@@ -472,7 +470,7 @@
     metadata_coding_ = metadata_coding.get();
     leveldb::Status s;
     std::tie(db_, s) = IndexedDBClassFactory::Get()->CreateIndexedDBDatabase(
-        ASCIIToUTF16("db"), backing_store_.get(), factory_.get(),
+        u"db", backing_store_.get(), factory_.get(),
         base::BindRepeating(
             &IndexedDBDatabaseOperationTest::RunTasksForDatabase,
             base::Unretained(this), true),
@@ -496,7 +494,7 @@
 
     EXPECT_TRUE(request_->connection());
     transaction_ = request_->connection()->CreateTransaction(
-        transaction_id, std::set<int64_t>() /*scope*/,
+        transaction_id, /*scope=*/std::set<int64_t>(),
         blink::mojom::IDBTransactionMode::VersionChange,
         new IndexedDBFakeBackingStore::FakeTransaction(commit_success_));
     db_->RegisterAndScheduleTransaction(transaction_);
@@ -563,9 +561,9 @@
 TEST_F(IndexedDBDatabaseOperationTest, CreateObjectStore) {
   EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
   const int64_t store_id = 1001;
-  leveldb::Status s = db_->CreateObjectStoreOperation(
-      store_id, ASCIIToUTF16("store"), IndexedDBKeyPath(),
-      false /*auto_increment*/, transaction_);
+  leveldb::Status s =
+      db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
+                                      /*auto_increment=*/false, transaction_);
   EXPECT_TRUE(s.ok());
   transaction_->SetCommitFlag();
   RunPostedTasks();
@@ -576,15 +574,15 @@
 TEST_F(IndexedDBDatabaseOperationTest, CreateIndex) {
   EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
   const int64_t store_id = 1001;
-  leveldb::Status s = db_->CreateObjectStoreOperation(
-      store_id, ASCIIToUTF16("store"), IndexedDBKeyPath(),
-      false /*auto_increment*/, transaction_);
+  leveldb::Status s =
+      db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
+                                      /*auto_increment=*/false, transaction_);
   EXPECT_TRUE(s.ok());
   EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
   const int64_t index_id = 2002;
-  s = db_->CreateIndexOperation(store_id, index_id, ASCIIToUTF16("index"),
-                                IndexedDBKeyPath(), false /*unique*/,
-                                false /*multi_entry*/, transaction_);
+  s = db_->CreateIndexOperation(store_id, index_id, u"index",
+                                IndexedDBKeyPath(), /*unique=*/false,
+                                /*multi_entry=*/false, transaction_);
   EXPECT_TRUE(s.ok());
   EXPECT_EQ(
       1ULL,
@@ -612,9 +610,9 @@
 TEST_F(IndexedDBDatabaseOperationAbortTest, CreateObjectStore) {
   EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
   const int64_t store_id = 1001;
-  leveldb::Status s = db_->CreateObjectStoreOperation(
-      store_id, ASCIIToUTF16("store"), IndexedDBKeyPath(),
-      false /*auto_increment*/, transaction_);
+  leveldb::Status s =
+      db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
+                                      /*auto_increment=*/false, transaction_);
   EXPECT_TRUE(s.ok());
   EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
   transaction_->SetCommitFlag();
@@ -625,15 +623,15 @@
 TEST_F(IndexedDBDatabaseOperationAbortTest, CreateIndex) {
   EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
   const int64_t store_id = 1001;
-  leveldb::Status s = db_->CreateObjectStoreOperation(
-      store_id, ASCIIToUTF16("store"), IndexedDBKeyPath(),
-      false /*auto_increment*/, transaction_);
+  leveldb::Status s =
+      db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
+                                      /*auto_increment=*/false, transaction_);
   EXPECT_TRUE(s.ok());
   EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
   const int64_t index_id = 2002;
-  s = db_->CreateIndexOperation(store_id, index_id, ASCIIToUTF16("index"),
-                                IndexedDBKeyPath(), false /*unique*/,
-                                false /*multi_entry*/, transaction_);
+  s = db_->CreateIndexOperation(store_id, index_id, u"index",
+                                IndexedDBKeyPath(), /*unique=*/false,
+                                /*multi_entry=*/false, transaction_);
   EXPECT_TRUE(s.ok());
   EXPECT_EQ(
       1ULL,
@@ -648,9 +646,9 @@
   EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
   const int64_t store_id = 1001;
 
-  leveldb::Status s = db_->CreateObjectStoreOperation(
-      store_id, ASCIIToUTF16("store"), IndexedDBKeyPath(),
-      false /*auto_increment*/, transaction_);
+  leveldb::Status s =
+      db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
+                                      /*auto_increment=*/false, transaction_);
   EXPECT_TRUE(s.ok());
   EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
 
diff --git a/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc b/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
index 4a17337..232c7ff 100644
--- a/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc
@@ -179,7 +179,7 @@
       : special_storage_policy_(
             base::MakeRefCounted<storage::MockSpecialStoragePolicy>()),
         quota_manager_(base::MakeRefCounted<storage::MockQuotaManager>(
-            false /*is_incognito*/,
+            /*is_incognito=*/false,
             CreateAndReturnTempDir(&temp_dir_),
             task_environment_.GetMainThreadTaskRunner(),
             special_storage_policy_)),
@@ -542,8 +542,7 @@
         new_value->external_objects = std::move(external_objects);
 
         connection->version_change_transaction->Put(
-            kObjectStoreId, std::move(new_value),
-            IndexedDBKey(base::UTF8ToUTF16("hello")),
+            kObjectStoreId, std::move(new_value), IndexedDBKey(u"hello"),
             blink::mojom::IDBPutMode::AddOnly,
             std::vector<IndexedDBIndexKeys>(), put_callback.Get());
         connection->version_change_transaction->Commit(0);
@@ -1328,8 +1327,7 @@
         new_value->bits = std::move(value_vector);
 
         connection1->version_change_transaction->Put(
-            kObjectStoreId, std::move(new_value),
-            IndexedDBKey(base::UTF8ToUTF16("key")),
+            kObjectStoreId, std::move(new_value), IndexedDBKey(u"key"),
             blink::mojom::IDBPutMode::AddOnly,
             std::vector<IndexedDBIndexKeys>(), put_callback.Get());
         connection1->version_change_transaction->Commit(0);
@@ -1432,9 +1430,9 @@
 TEST_F(IndexedDBDispatcherHostTest, DatabaseOperationSequencing) {
   const int64_t kDBVersion = 1;
   const int64_t kTransactionId = 1;
-  const std::u16string kObjectStoreName1 = base::ASCIIToUTF16("os1");
-  const std::u16string kObjectStoreName2 = base::ASCIIToUTF16("os2");
-  const std::u16string kObjectStoreName3 = base::ASCIIToUTF16("os3");
+  const std::u16string kObjectStoreName1 = u"os1";
+  const std::u16string kObjectStoreName2 = u"os2";
+  const std::u16string kObjectStoreName3 = u"os3";
 
   std::unique_ptr<TestDatabaseConnection> connection;
   IndexedDBDatabaseMetadata metadata;
diff --git a/content/browser/indexed_db/indexed_db_factory_impl.cc b/content/browser/indexed_db/indexed_db_factory_impl.cc
index 585e7f2..c540c37 100644
--- a/content/browser/indexed_db/indexed_db_factory_impl.cc
+++ b/content/browser/indexed_db/indexed_db_factory_impl.cc
@@ -57,7 +57,6 @@
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
 #include "third_party/leveldatabase/env_chromium.h"
 
-using base::ASCIIToUTF16;
 using url::Origin;
 
 namespace content {
@@ -94,8 +93,7 @@
 IndexedDBDatabaseError CreateDefaultError() {
   return IndexedDBDatabaseError(
       blink::mojom::IDBException::kUnknownError,
-      ASCIIToUTF16("Internal error opening backing store"
-                   " for indexedDB.open."));
+      u"Internal error opening backing store for indexedDB.open.");
 }
 
 // Creates the leveldb and blob storage directories for IndexedDB.
@@ -268,10 +266,9 @@
       std::make_unique<IndexedDBMetadataCoding>(), std::move(unique_identifier),
       factory->lock_manager());
   if (!database.get()) {
-    error = IndexedDBDatabaseError(blink::mojom::IDBException::kUnknownError,
-                                   ASCIIToUTF16("Internal error creating "
-                                                "database backend for "
-                                                "indexedDB.open."));
+    error = IndexedDBDatabaseError(
+        blink::mojom::IDBException::kUnknownError,
+        u"Internal error creating database backend for indexedDB.open.");
     connection->callbacks->OnError(error);
     if (s.IsCorruption())
       HandleBackingStoreCorruption(origin, error);
@@ -358,10 +355,9 @@
       std::make_unique<IndexedDBMetadataCoding>(), unique_identifier,
       factory->lock_manager());
   if (!database.get()) {
-    error = IndexedDBDatabaseError(
-        blink::mojom::IDBException::kUnknownError,
-        ASCIIToUTF16("Internal error creating database backend for "
-                     "indexedDB.deleteDatabase."));
+    error = IndexedDBDatabaseError(blink::mojom::IDBException::kUnknownError,
+                                   u"Internal error creating database backend "
+                                   u"for indexedDB.deleteDatabase.");
     callbacks->OnError(error);
     if (s.IsCorruption())
       HandleBackingStoreCorruption(origin, error);
@@ -721,10 +717,9 @@
     if (disk_full) {
       context_->quota_manager_proxy()->NotifyWriteFailed(origin);
       return {IndexedDBOriginStateHandle(), s,
-              IndexedDBDatabaseError(
-                  blink::mojom::IDBException::kQuotaError,
-                  ASCIIToUTF16("Encountered full disk while opening "
-                               "backing store for indexedDB.open.")),
+              IndexedDBDatabaseError(blink::mojom::IDBException::kQuotaError,
+                                     u"Encountered full disk while opening "
+                                     "backing store for indexedDB.open."),
               data_loss_info, /*was_cold_open=*/true};
 
     } else {
diff --git a/content/browser/indexed_db/indexed_db_factory_unittest.cc b/content/browser/indexed_db/indexed_db_factory_unittest.cc
index 1c45a75..7c363cf5 100644
--- a/content/browser/indexed_db/indexed_db_factory_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_factory_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
@@ -48,7 +47,6 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
-using base::ASCIIToUTF16;
 using blink::IndexedDBDatabaseMetadata;
 using url::Origin;
 
@@ -74,7 +72,7 @@
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     quota_policy_ = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
     quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
-        false /*is_incognito*/, temp_dir_.GetPath(),
+        /*is_incognito=*/false, temp_dir_.GetPath(),
         base::ThreadTaskRunnerHandle::Get().get(), quota_policy_.get());
 
     quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>(
@@ -558,8 +556,7 @@
       callbacks, db_callbacks,
       transaction_id, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
       std::move(create_transaction_callback));
-  factory()->Open(ASCIIToUTF16("db"), std::move(connection), origin,
-                  context()->data_path());
+  factory()->Open(u"db", std::move(connection), origin, context()->data_path());
   RunPostedTasks();
 
   // Now simulate shutdown, which should clear all factories.
@@ -619,8 +616,7 @@
       callbacks, db_callbacks,
       transaction_id, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
       std::move(create_transaction_callback));
-  factory()->Open(ASCIIToUTF16("db"), std::move(connection), origin,
-                  context()->data_path());
+  factory()->Open(u"db", std::move(connection), origin, context()->data_path());
   EXPECT_FALSE(callbacks->connection());
   RunPostedTasks();
   EXPECT_TRUE(callbacks->connection());
@@ -656,7 +652,7 @@
     base::RunLoop loop;
     callbacks->CallOnUpgradeNeeded(
         base::BindLambdaForTesting([&]() { loop.Quit(); }));
-    factory()->Open(ASCIIToUTF16("db"), std::move(connection), origin,
+    factory()->Open(u"db", std::move(connection), origin,
                     context()->data_path());
     loop.Run();
   }
@@ -693,7 +689,7 @@
     base::RunLoop loop;
     callbacks->CallOnUpgradeNeeded(
         base::BindLambdaForTesting([&]() { loop.Quit(); }));
-    factory()->Open(ASCIIToUTF16("db"), std::move(connection), origin,
+    factory()->Open(u"db", std::move(connection), origin,
                     context()->data_path());
     loop.Run();
   }
@@ -717,7 +713,7 @@
   std::unique_ptr<IndexedDBConnection> connection;
   scoped_refptr<MockIndexedDBDatabaseCallbacks> db_callbacks;
   std::tie(connection, db_callbacks) =
-      CreateConnectionForDatatabase(origin, ASCIIToUTF16("db"));
+      CreateConnectionForDatatabase(origin, u"db");
 
   // Force close the database.
   connection->database()->ForceCloseAndRunTasks();
@@ -736,8 +732,7 @@
 
   const Origin origin = Origin::Create(GURL("http://localhost:81"));
 
-  factory()->DeleteDatabase(ASCIIToUTF16("db"), callbacks, origin,
-                            context()->data_path(),
+  factory()->DeleteDatabase(u"db", callbacks, origin, context()->data_path(),
                             /*force_close=*/false);
 
   // Since there are no more references the factory should be closing.
@@ -749,7 +744,7 @@
   SetupContext();
 
   const Origin origin = Origin::Create(GURL("http://localhost:81"));
-  const std::u16string name = ASCIIToUTF16("db");
+  const std::u16string name = u"db";
 
   std::unique_ptr<IndexedDBConnection> connection;
   scoped_refptr<MockIndexedDBDatabaseCallbacks> db_callbacks;
@@ -853,7 +848,7 @@
       base::MakeRefCounted<IndexedDBDatabaseCallbacks>(
           nullptr, mojo::NullAssociatedRemote(), context()->IDBTaskRunner());
   const Origin origin = Origin::Create(GURL("http://localhost:81"));
-  const std::u16string name(ASCIIToUTF16("name"));
+  const std::u16string name(u"name");
   auto create_transaction_callback =
       base::BindOnce(&CreateAndBindTransactionPlaceholder);
   auto connection = std::make_unique<IndexedDBPendingConnection>(
@@ -906,7 +901,7 @@
 TEST_F(IndexedDBFactoryTest, DatabaseFailedOpen) {
   SetupContext();
   const Origin origin = Origin::Create(GURL("http://localhost:81"));
-  const std::u16string db_name(ASCIIToUTF16("db"));
+  const std::u16string db_name(u"db");
   const int64_t transaction_id = 1;
 
   auto callbacks = base::MakeRefCounted<MockIndexedDBCallbacks>();
@@ -1017,8 +1012,7 @@
       callbacks->CallOnDBSuccess(
           base::BindLambdaForTesting([&]() { loop.Quit(); }));
 
-      this->factory()->Open(ASCIIToUTF16("test_db"),
-                            std::move(pending_connection), origin,
+      this->factory()->Open(u"test_db", std::move(pending_connection), origin,
                             context()->data_path());
       loop.Run();
 
diff --git a/content/browser/indexed_db/indexed_db_index_writer.cc b/content/browser/indexed_db/indexed_db_index_writer.cc
index 8d274a5..c6a238e 100644
--- a/content/browser/indexed_db/indexed_db_index_writer.cc
+++ b/content/browser/indexed_db/indexed_db_index_writer.cc
@@ -7,14 +7,12 @@
 #include <stddef.h>
 #include <utility>
 
-#include "base/strings/utf_string_conversions.h"
 #include "content/browser/indexed_db/indexed_db_backing_store.h"
 #include "content/browser/indexed_db/indexed_db_tracing.h"
 #include "content/browser/indexed_db/indexed_db_transaction.h"
 #include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
 
-using base::ASCIIToUTF16;
 using blink::IndexedDBIndexKeys;
 using blink::IndexedDBIndexMetadata;
 using blink::IndexedDBKey;
@@ -50,10 +48,10 @@
       return false;
     if (!*can_add_keys) {
       if (error_message) {
-        *error_message = ASCIIToUTF16("Unable to add key to index '") +
+        *error_message = u"Unable to add key to index '" +
                          index_metadata_.name +
-                         ASCIIToUTF16("': at least one key does not satisfy "
-                                      "the uniqueness requirements.");
+                         u"': at least one key does not satisfy the uniqueness "
+                         u"requirements.";
       }
       return true;
     }
diff --git a/content/browser/indexed_db/indexed_db_leveldb_coding.cc b/content/browser/indexed_db/indexed_db_leveldb_coding.cc
index 150d816..35e3a803 100644
--- a/content/browser/indexed_db/indexed_db_leveldb_coding.cc
+++ b/content/browser/indexed_db/indexed_db_leveldb_coding.cc
@@ -1010,11 +1010,11 @@
 }
 
 int CompareKeys(const StringPiece& a, const StringPiece& b) {
-  return Compare(a, b, false /*index_keys*/);
+  return Compare(a, b, /*index_keys=*/false);
 }
 
 int CompareIndexKeys(const StringPiece& a, const StringPiece& b) {
-  return Compare(a, b, true /*index_keys*/);
+  return Compare(a, b, /*index_keys=*/true);
 }
 
 std::string IndexedDBKeyToDebugString(base::StringPiece key) {
diff --git a/content/browser/indexed_db/indexed_db_leveldb_coding_decodeidbkey_fuzzer.cc b/content/browser/indexed_db/indexed_db_leveldb_coding_decodeidbkey_fuzzer.cc
index f898c5ae..5589aa30 100644
--- a/content/browser/indexed_db/indexed_db_leveldb_coding_decodeidbkey_fuzzer.cc
+++ b/content/browser/indexed_db/indexed_db_leveldb_coding_decodeidbkey_fuzzer.cc
@@ -5,7 +5,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include "base/strings/utf_string_conversions.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
 #include "third_party/blink/public/common/indexeddb/indexeddb_key.h"
 
diff --git a/content/browser/indexed_db/indexed_db_leveldb_coding_decodeidbkeypath_fuzzer.cc b/content/browser/indexed_db/indexed_db_leveldb_coding_decodeidbkeypath_fuzzer.cc
index 340a1152..81fc62c 100644
--- a/content/browser/indexed_db/indexed_db_leveldb_coding_decodeidbkeypath_fuzzer.cc
+++ b/content/browser/indexed_db/indexed_db_leveldb_coding_decodeidbkeypath_fuzzer.cc
@@ -5,7 +5,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include "base/strings/utf_string_conversions.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
 #include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
 
diff --git a/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc b/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc
index 30b1629..7e050d3d 100644
--- a/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_leveldb_coding_unittest.cc
@@ -14,11 +14,9 @@
 
 #include "base/stl_util.h"
 #include "base/strings/string_piece.h"
-#include "base/strings/utf_string_conversions.h"
 #include "components/services/storage/indexed_db/scopes/varint_coding.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using base::ASCIIToUTF16;
 using base::StringPiece;
 using blink::IndexedDBKey;
 using blink::IndexedDBKeyPath;
@@ -129,7 +127,7 @@
   std::string binary_key;
   EncodeIDBKey(IndexedDBKey(std::string("\x00\x01\x02")), &binary_key);
   std::string string_key;
-  EncodeIDBKey(IndexedDBKey(ASCIIToUTF16("Hello world")), &string_key);
+  EncodeIDBKey(IndexedDBKey(u"Hello world"), &string_key);
   std::string number_key;
   EncodeIDBKey(IndexedDBKey(3.14, blink::mojom::IDBKeyType::Number),
                &number_key);
@@ -154,7 +152,7 @@
   std::string binary_key;
   EncodeIDBKey(IndexedDBKey(std::string("\x00\x01\x02")), &binary_key);
   std::string string_key;
-  EncodeIDBKey(IndexedDBKey(ASCIIToUTF16("Hello world")), &string_key);
+  EncodeIDBKey(IndexedDBKey(u"Hello world"), &string_key);
   std::string number_key;
   EncodeIDBKey(IndexedDBKey(3.14, blink::mojom::IDBKeyType::Number),
                &number_key);
@@ -262,9 +260,9 @@
   const char16_t test_string_a[] = {'f', 'o', 'o', '\0'};
   const char16_t test_string_b[] = {0xdead, 0xbeef, '\0'};
 
-  EXPECT_EQ(0u, WrappedEncodeString(ASCIIToUTF16("")).size());
-  EXPECT_EQ(2u, WrappedEncodeString(ASCIIToUTF16("a")).size());
-  EXPECT_EQ(6u, WrappedEncodeString(ASCIIToUTF16("foo")).size());
+  EXPECT_EQ(0u, WrappedEncodeString(u"").size());
+  EXPECT_EQ(2u, WrappedEncodeString(u"a").size());
+  EXPECT_EQ(6u, WrappedEncodeString(u"foo").size());
   EXPECT_EQ(6u, WrappedEncodeString(std::u16string(test_string_a)).size());
   EXPECT_EQ(4u, WrappedEncodeString(std::u16string(test_string_b)).size());
 }
@@ -273,8 +271,7 @@
   const char16_t test_string_a[] = {'f', 'o', 'o', '\0'};
   const char16_t test_string_b[] = {0xdead, 0xbeef, '\0'};
 
-  std::vector<std::u16string> test_cases = {std::u16string(), ASCIIToUTF16("a"),
-                                            ASCIIToUTF16("foo"), test_string_a,
+  std::vector<std::u16string> test_cases = {u"", u"a", u"foo", test_string_a,
                                             test_string_b};
 
   for (size_t i = 0; i < test_cases.size(); ++i) {
@@ -310,8 +307,8 @@
   const char16_t test_string_a[] = {'f', 'o', 'o', '\0'};
   const char16_t test_string_b[] = {0xdead, 0xbeef, '\0'};
 
-  EXPECT_EQ(1u, WrappedEncodeStringWithLength(std::u16string()).size());
-  EXPECT_EQ(3u, WrappedEncodeStringWithLength(ASCIIToUTF16("a")).size());
+  EXPECT_EQ(1u, WrappedEncodeStringWithLength(u"").size());
+  EXPECT_EQ(3u, WrappedEncodeStringWithLength(u"a").size());
   EXPECT_EQ(
       7u, WrappedEncodeStringWithLength(std::u16string(test_string_a)).size());
   EXPECT_EQ(
@@ -328,9 +325,9 @@
     long_string[i] = i;
   long_string[kLongStringLen] = 0;
 
-  std::vector<std::u16string> test_cases = {ASCIIToUTF16(""),
-                                            ASCIIToUTF16("a"),
-                                            ASCIIToUTF16("foo"),
+  std::vector<std::u16string> test_cases = {u"",
+                                            u"a",
+                                            u"foo",
                                             std::u16string(test_string_a),
                                             std::u16string(test_string_b),
                                             std::u16string(long_string)};
@@ -382,12 +379,12 @@
   const char16_t test_string_f[] = {0xfffd, '\0'};
 
   std::vector<std::u16string> test_cases = {
-      ASCIIToUTF16(""),
-      ASCIIToUTF16("a"),
-      ASCIIToUTF16("b"),
-      ASCIIToUTF16("baaa"),
-      ASCIIToUTF16("baab"),
-      ASCIIToUTF16("c"),
+      u"",
+      u"a",
+      u"b",
+      u"baaa",
+      u"baab",
+      u"c",
       std::u16string(test_string_a),
       std::u16string(test_string_b),
       std::u16string(test_string_c),
@@ -517,15 +514,13 @@
   std::vector<IndexedDBKey> test_cases = {
       IndexedDBKey(1234, blink::mojom::IDBKeyType::Number),
       IndexedDBKey(7890, blink::mojom::IDBKeyType::Date),
-      IndexedDBKey(ASCIIToUTF16("Hello World!")),
-      IndexedDBKey(std::string("\x01\x02")),
+      IndexedDBKey(u"Hello World!"), IndexedDBKey(std::string("\x01\x02")),
       IndexedDBKey(IndexedDBKey::KeyArray())};
 
   IndexedDBKey::KeyArray array = {
       IndexedDBKey(1234, blink::mojom::IDBKeyType::Number),
       IndexedDBKey(7890, blink::mojom::IDBKeyType::Date),
-      IndexedDBKey(ASCIIToUTF16("Hello World!")),
-      IndexedDBKey(std::string("\x01\x02")),
+      IndexedDBKey(u"Hello World!"), IndexedDBKey(std::string("\x01\x02")),
       IndexedDBKey(IndexedDBKey::KeyArray())};
   test_cases.push_back(IndexedDBKey(std::move(array)));
 
@@ -566,7 +561,7 @@
   }
 
   {
-    key_paths.push_back(IndexedDBKeyPath(std::u16string()));
+    key_paths.push_back(IndexedDBKeyPath(u""));
     char expected[] = {0, 0,  // Header
                        1,     // Type is string
                        0      // Length is 0
@@ -576,7 +571,7 @@
   }
 
   {
-    key_paths.push_back(IndexedDBKeyPath(ASCIIToUTF16("foo")));
+    key_paths.emplace_back(u"foo");
     char expected[] = {0, 0,                      // Header
                        1,                         // Type is string
                        3, 0, 'f', 0, 'o', 0, 'o'  // String length 3, UTF-16BE
@@ -586,7 +581,7 @@
   }
 
   {
-    key_paths.push_back(IndexedDBKeyPath(ASCIIToUTF16("foo.bar")));
+    key_paths.emplace_back(u"foo.bar");
     char expected[] = {0, 0,  // Header
                        1,     // Type is string
                        7, 0, 'f', 0, 'o', 0, 'o', 0, '.', 0, 'b', 0, 'a', 0,
@@ -597,8 +592,7 @@
   }
 
   {
-    std::vector<std::u16string> array = {std::u16string(), ASCIIToUTF16("foo"),
-                                         ASCIIToUTF16("foo.bar")};
+    std::vector<std::u16string> array = {u"", u"foo", u"foo.bar"};
 
     key_paths.push_back(IndexedDBKeyPath(array));
     char expected[] = {0, 0,                       // Header
@@ -685,16 +679,16 @@
   std::vector<std::string> encoded_paths;
 
   {
-    key_paths.push_back(IndexedDBKeyPath(std::u16string()));
+    key_paths.push_back(IndexedDBKeyPath(u""));
     encoded_paths.push_back(std::string());
   }
   {
-    key_paths.push_back(IndexedDBKeyPath(ASCIIToUTF16("foo")));
+    key_paths.emplace_back(u"foo");
     char expected[] = {0, 'f', 0, 'o', 0, 'o'};
     encoded_paths.push_back(std::string(expected, base::size(expected)));
   }
   {
-    key_paths.push_back(IndexedDBKeyPath(ASCIIToUTF16("foo.bar")));
+    key_paths.emplace_back(u"foo.bar");
     char expected[] = {0, 'f', 0, 'o', 0, 'o', 0, '.', 0, 'b', 0, 'a', 0, 'r'};
     encoded_paths.push_back(std::string(expected, base::size(expected)));
   }
@@ -722,12 +716,12 @@
       IndexedDBKey(100, blink::mojom::IDBKeyType::Date),
       IndexedDBKey(100000, blink::mojom::IDBKeyType::Date),
 
-      IndexedDBKey(ASCIIToUTF16("")),
-      IndexedDBKey(ASCIIToUTF16("a")),
-      IndexedDBKey(ASCIIToUTF16("b")),
-      IndexedDBKey(ASCIIToUTF16("baaa")),
-      IndexedDBKey(ASCIIToUTF16("baab")),
-      IndexedDBKey(ASCIIToUTF16("c")),
+      IndexedDBKey(u""),
+      IndexedDBKey(u"a"),
+      IndexedDBKey(u"b"),
+      IndexedDBKey(u"baaa"),
+      IndexedDBKey(u"baab"),
+      IndexedDBKey(u"c"),
 
       IndexedDBKey(std::string()),
       IndexedDBKey(std::string("\x01")),
@@ -749,9 +743,8 @@
 
       CreateArrayIDBKey(IndexedDBKey(0, blink::mojom::IDBKeyType::Date),
                         IndexedDBKey(0, blink::mojom::IDBKeyType::Date)),
-      CreateArrayIDBKey(IndexedDBKey(ASCIIToUTF16(""))),
-      CreateArrayIDBKey(IndexedDBKey(ASCIIToUTF16("")),
-                        IndexedDBKey(ASCIIToUTF16("a"))),
+      CreateArrayIDBKey(IndexedDBKey(u"")),
+      CreateArrayIDBKey(IndexedDBKey(u""), IndexedDBKey(u"a")),
       CreateArrayIDBKey(CreateArrayIDBKey()),
       CreateArrayIDBKey(CreateArrayIDBKey(), CreateArrayIDBKey()),
       CreateArrayIDBKey(CreateArrayIDBKey(CreateArrayIDBKey())),
@@ -802,9 +795,9 @@
       MaxDatabaseIdKey::Encode(),
       DatabaseFreeListKey::Encode(0),
       DatabaseFreeListKey::EncodeMaxKey(),
-      DatabaseNameKey::Encode("", ASCIIToUTF16("")),
-      DatabaseNameKey::Encode("", ASCIIToUTF16("a")),
-      DatabaseNameKey::Encode("a", ASCIIToUTF16("a")),
+      DatabaseNameKey::Encode("", u""),
+      DatabaseNameKey::Encode("", u"a"),
+      DatabaseNameKey::Encode("a", u"a"),
 
       DatabaseMetaDataKey::Encode(1, DatabaseMetaDataKey::ORIGIN_NAME),
 
@@ -852,11 +845,11 @@
       IndexFreeListKey::EncodeMaxKey(1, 1),
       IndexFreeListKey::Encode(1, 2, kMinimumIndexId),
       IndexFreeListKey::EncodeMaxKey(1, 2),
-      ObjectStoreNamesKey::Encode(1, ASCIIToUTF16("")),
-      ObjectStoreNamesKey::Encode(1, ASCIIToUTF16("a")),
-      IndexNamesKey::Encode(1, 1, ASCIIToUTF16("")),
-      IndexNamesKey::Encode(1, 1, ASCIIToUTF16("a")),
-      IndexNamesKey::Encode(1, 2, ASCIIToUTF16("a")),
+      ObjectStoreNamesKey::Encode(1, u""),
+      ObjectStoreNamesKey::Encode(1, u"a"),
+      IndexNamesKey::Encode(1, 1, u""),
+      IndexNamesKey::Encode(1, 1, u"a"),
+      IndexNamesKey::Encode(1, 2, u"a"),
       ObjectStoreDataKey::Encode(1, 1, std::string()),
       ObjectStoreDataKey::Encode(1, 1, MinIDBKey()),
       ObjectStoreDataKey::Encode(1, 1, MaxIDBKey()),
@@ -892,8 +885,8 @@
       IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 0),
       IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MinIDBKey(), 1),
 
-      IndexDataKey::Encode(1, 1, 30, IndexedDBKey(ASCIIToUTF16("user key")),
-                           IndexedDBKey(ASCIIToUTF16("primary key"))),
+      IndexDataKey::Encode(1, 1, 30, IndexedDBKey(u"user key"),
+                           IndexedDBKey(u"primary key")),
       IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 0),
       IndexDataKey::Encode(1, 1, 30, MinIDBKey(), MaxIDBKey(), 1),
       IndexDataKey::Encode(1, 1, 30, MaxIDBKey(), MinIDBKey(), 0),
diff --git a/content/browser/indexed_db/indexed_db_leveldb_operations.cc b/content/browser/indexed_db/indexed_db_leveldb_operations.cc
index e9c18c7..4b13eec 100644
--- a/content/browser/indexed_db/indexed_db_leveldb_operations.cc
+++ b/content/browser/indexed_db/indexed_db_leveldb_operations.cc
@@ -36,7 +36,7 @@
   int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const override {
     return content::Compare(leveldb_env::MakeStringPiece(a),
                             leveldb_env::MakeStringPiece(b),
-                            false /*index_keys*/);
+                            /*index_keys=*/false);
   }
   const char* Name() const override { return "idb_cmp1"; }
   void FindShortestSeparator(std::string* start,
diff --git a/content/browser/indexed_db/indexed_db_pre_close_task_queue_unittest.cc b/content/browser/indexed_db/indexed_db_pre_close_task_queue_unittest.cc
index f5828cbf..6f46ba52 100644
--- a/content/browser/indexed_db/indexed_db_pre_close_task_queue_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_pre_close_task_queue_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/timer/mock_timer.h"
@@ -29,7 +28,7 @@
 
 namespace {
 constexpr base::TimeDelta kTestMaxRunTime = base::TimeDelta::FromSeconds(30);
-const std::u16string kDBName = base::ASCIIToUTF16("TestDBName");
+const std::u16string kDBName = u"TestDBName";
 constexpr int64_t kDBId = 1;
 constexpr int64_t kDBVersion = 2;
 constexpr int64_t kDBMaxObjectStoreId = 29;
diff --git a/content/browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc b/content/browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc
index dd4e1f7..3624f3c 100644
--- a/content/browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_tombstone_sweeper_unittest.cc
@@ -10,7 +10,6 @@
 
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/time/tick_clock.h"
@@ -95,43 +94,43 @@
     //   os2
     //     index1
     //     index2
-    metadata_.emplace_back(base::ASCIIToUTF16("db1"), kDb1, 1, 29);
+    metadata_.emplace_back(u"db1", kDb1, 1, 29);
     auto& db1 = metadata_.back();
     db1.object_stores[kOs1] = IndexedDBObjectStoreMetadata(
-        base::ASCIIToUTF16("os1"), kOs1, IndexedDBKeyPath(), false, 1000);
+        u"os1", kOs1, IndexedDBKeyPath(), false, 1000);
     db1.object_stores[kOs2] = IndexedDBObjectStoreMetadata(
-        base::ASCIIToUTF16("os2"), kOs2, IndexedDBKeyPath(), false, 1000);
+        u"os2", kOs2, IndexedDBKeyPath(), false, 1000);
     auto& os2 = db1.object_stores[kOs2];
     os2.indexes[kIndex1] = IndexedDBIndexMetadata(
-        base::ASCIIToUTF16("index1"), kIndex1, IndexedDBKeyPath(), true, false);
+        u"index1", kIndex1, IndexedDBKeyPath(), true, false);
     os2.indexes[kIndex2] = IndexedDBIndexMetadata(
-        base::ASCIIToUTF16("index2"), kIndex2, IndexedDBKeyPath(), true, false);
+        u"index2", kIndex2, IndexedDBKeyPath(), true, false);
     // db2
     //   os3
     //     index3
     //   os4
-    metadata_.emplace_back(base::ASCIIToUTF16("db2"), kDb2, 1, 29);
+    metadata_.emplace_back(u"db2", kDb2, 1, 29);
     auto& db2 = metadata_.back();
     db2.object_stores[kOs3] = IndexedDBObjectStoreMetadata(
-        base::ASCIIToUTF16("os3"), kOs3, IndexedDBKeyPath(), false, 1000);
+        u"os3", kOs3, IndexedDBKeyPath(), false, 1000);
     db2.object_stores[kOs4] = IndexedDBObjectStoreMetadata(
-        base::ASCIIToUTF16("os4"), kOs4, IndexedDBKeyPath(), false, 1000);
+        u"os4", kOs4, IndexedDBKeyPath(), false, 1000);
     auto& os3 = db2.object_stores[kOs3];
     os3.indexes[kIndex3] = IndexedDBIndexMetadata(
-        base::ASCIIToUTF16("index3"), kIndex3, IndexedDBKeyPath(), true, false);
+        u"index3", kIndex3, IndexedDBKeyPath(), true, false);
   }
 
   void PopulateSingleIndexDBMetadata() {
     // db1
     //   os1
     //     index1
-    metadata_.emplace_back(base::ASCIIToUTF16("db1"), kDb1, 1, 29);
+    metadata_.emplace_back(u"db1", kDb1, 1, 29);
     auto& db1 = metadata_.back();
     db1.object_stores[kOs1] = IndexedDBObjectStoreMetadata(
-        base::ASCIIToUTF16("os1"), kOs1, IndexedDBKeyPath(), false, 1000);
+        u"os1", kOs1, IndexedDBKeyPath(), false, 1000);
     auto& os2 = db1.object_stores[kOs1];
     os2.indexes[kIndex1] = IndexedDBIndexMetadata(
-        base::ASCIIToUTF16("index1"), kIndex1, IndexedDBKeyPath(), true, false);
+        u"index1", kIndex1, IndexedDBKeyPath(), true, false);
   }
 
   void SetupMockDB() {
diff --git a/content/browser/indexed_db/indexed_db_transaction.cc b/content/browser/indexed_db/indexed_db_transaction.cc
index af93dd6..77e2abc 100644
--- a/content/browser/indexed_db/indexed_db_transaction.cc
+++ b/content/browser/indexed_db/indexed_db_transaction.cc
@@ -547,9 +547,9 @@
 }
 
 void IndexedDBTransaction::Timeout() {
-  leveldb::Status result = Abort(IndexedDBDatabaseError(
-      blink::mojom::IDBException::kTimeoutError,
-      base::ASCIIToUTF16("Transaction timed out due to inactivity.")));
+  leveldb::Status result = Abort(
+      IndexedDBDatabaseError(blink::mojom::IDBException::kTimeoutError,
+                             u"Transaction timed out due to inactivity."));
   if (!result.ok())
     tear_down_callback_.Run(result);
 }
diff --git a/content/browser/indexed_db/indexed_db_transaction_unittest.cc b/content/browser/indexed_db/indexed_db_transaction_unittest.cc
index b4d9fc7..ad28132f 100644
--- a/content/browser/indexed_db/indexed_db_transaction_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_transaction_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "components/services/storage/indexed_db/scopes/disjoint_range_lock_manager.h"
@@ -68,8 +67,7 @@
     // https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md#my-compiler-complains-that-a-constructor-or-destructor-cannot-return-a-value-whats-going-on
     leveldb::Status s;
     std::tie(db_, s) = IndexedDBClassFactory::Get()->CreateIndexedDBDatabase(
-        base::ASCIIToUTF16("db"), backing_store_.get(), factory_.get(),
-        CreateRunTasksCallback(),
+        u"db", backing_store_.get(), factory_.get(), CreateRunTasksCallback(),
         std::make_unique<FakeIndexedDBMetadataCoding>(),
         IndexedDBDatabase::Identifier(), &lock_manager_);
     ASSERT_TRUE(s.ok());
diff --git a/content/browser/indexed_db/indexed_db_unittest.cc b/content/browser/indexed_db/indexed_db_unittest.cc
index cf366aa9..6263958f 100644
--- a/content/browser/indexed_db/indexed_db_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_unittest.cc
@@ -10,7 +10,6 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "base/threading/sequenced_task_runner_handle.h"
@@ -256,21 +255,19 @@
 
   auto create_transaction_callback1 =
       base::BindOnce(&CreateAndBindTransactionPlaceholder);
-  factory->Open(base::ASCIIToUTF16("opendb"),
+  factory->Open(u"opendb",
                 std::make_unique<IndexedDBPendingConnection>(
-                    open_callbacks, open_db_callbacks,
-                    host_transaction_id, version,
-                    std::move(create_transaction_callback1)),
+                    open_callbacks, open_db_callbacks, host_transaction_id,
+                    version, std::move(create_transaction_callback1)),
                 kTestOrigin, context()->data_path());
   EXPECT_TRUE(base::DirectoryExists(test_path));
 
   auto create_transaction_callback2 =
       base::BindOnce(&CreateAndBindTransactionPlaceholder);
-  factory->Open(base::ASCIIToUTF16("closeddb"),
+  factory->Open(u"closeddb",
                 std::make_unique<IndexedDBPendingConnection>(
-                    closed_callbacks, closed_db_callbacks,
-                    host_transaction_id, version,
-                    std::move(create_transaction_callback2)),
+                    closed_callbacks, closed_db_callbacks, host_transaction_id,
+                    version, std::move(create_transaction_callback2)),
                 kTestOrigin, context()->data_path());
   RunPostedTasks();
   ASSERT_TRUE(closed_callbacks->connection());
@@ -332,8 +329,8 @@
       callbacks, db_callbacks,
       transaction_id, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
       std::move(create_transaction_callback1));
-  factory->Open(base::ASCIIToUTF16("db"), std::move(connection),
-                Origin(kTestOrigin), context()->data_path());
+  factory->Open(u"db", std::move(connection), Origin(kTestOrigin),
+                context()->data_path());
   RunPostedTasks();
 
   ASSERT_TRUE(callbacks->connection());
diff --git a/content/browser/media/active_media_session_controller.cc b/content/browser/media/active_media_session_controller.cc
index 7df602a..597cc191 100644
--- a/content/browser/media/active_media_session_controller.cc
+++ b/content/browser/media/active_media_session_controller.cc
@@ -191,6 +191,9 @@
     case MediaSessionAction::kEnterPictureInPicture:
     case MediaSessionAction::kExitPictureInPicture:
     case MediaSessionAction::kSwitchAudioDevice:
+    case MediaSessionAction::kToggleMicrophone:
+    case MediaSessionAction::kToggleCamera:
+    case MediaSessionAction::kHangUp:
       NOTREACHED();
       return;
   }
@@ -239,6 +242,9 @@
     case MediaSessionAction::kEnterPictureInPicture:
     case MediaSessionAction::kExitPictureInPicture:
     case MediaSessionAction::kSwitchAudioDevice:
+    case MediaSessionAction::kToggleMicrophone:
+    case MediaSessionAction::kToggleCamera:
+    case MediaSessionAction::kHangUp:
       return base::nullopt;
   }
 }
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index d30d3e8..ab1a1ba 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -138,6 +138,12 @@
       return MediaSessionUserAction::ExitPictureInPicture;
     case media_session::mojom::MediaSessionAction::kSwitchAudioDevice:
       return MediaSessionUserAction::SwitchAudioDevice;
+    case media_session::mojom::MediaSessionAction::kToggleMicrophone:
+      return MediaSessionUserAction::ToggleMicrophone;
+    case media_session::mojom::MediaSessionAction::kToggleCamera:
+      return MediaSessionUserAction::ToggleCamera;
+    case media_session::mojom::MediaSessionAction::kHangUp:
+      return MediaSessionUserAction::HangUp;
   }
   NOTREACHED();
   return MediaSessionUserAction::Play;
@@ -986,6 +992,14 @@
   if (shared_audio_device_id != media::AudioDeviceDescription::kDefaultDeviceId)
     info->audio_sink_id = shared_audio_device_id;
 
+  if (routed_service_) {
+    info->microphone_state = routed_service_->microphone_state();
+    info->camera_state = routed_service_->camera_state();
+  } else {
+    info->microphone_state = media_session::mojom::MicrophoneState::kUnknown;
+    info->camera_state = media_session::mojom::CameraState::kUnknown;
+  }
+
   return info;
 }
 
@@ -1110,6 +1124,18 @@
   }
 }
 
+void MediaSessionImpl::ToggleMicrophone() {
+  DidReceiveAction(media_session::mojom::MediaSessionAction::kToggleMicrophone);
+}
+
+void MediaSessionImpl::ToggleCamera() {
+  DidReceiveAction(media_session::mojom::MediaSessionAction::kToggleCamera);
+}
+
+void MediaSessionImpl::HangUp() {
+  DidReceiveAction(media_session::mojom::MediaSessionAction::kHangUp);
+}
+
 void MediaSessionImpl::GetMediaImageBitmap(
     const media_session::MediaImage& image,
     int minimum_size_px,
@@ -1284,6 +1310,14 @@
   RebuildAndNotifyActionsChanged();
 }
 
+void MediaSessionImpl::OnMediaSessionInfoChanged(
+    MediaSessionServiceImpl* service) {
+  if (service != routed_service_)
+    return;
+
+  RebuildAndNotifyMediaSessionInfoChanged();
+}
+
 void MediaSessionImpl::DidReceiveAction(
     media_session::mojom::MediaSessionAction action) {
   DidReceiveAction(action, nullptr /* details */);
diff --git a/content/browser/media/session/media_session_impl.h b/content/browser/media/session/media_session_impl.h
index a9afb8d..aa0d3ad 100644
--- a/content/browser/media/session/media_session_impl.h
+++ b/content/browser/media/session/media_session_impl.h
@@ -157,10 +157,15 @@
   // Called when the metadata of a MediaSessionService has changed. Will notify
   // observers if the service is currently routed.
   void OnMediaSessionMetadataChanged(MediaSessionServiceImpl* service);
+
   // Called when the actions of a MediaSessionService has changed. Will notify
   // observers if the service is currently routed.
   void OnMediaSessionActionsChanged(MediaSessionServiceImpl* service);
 
+  // Called when the info of a MediaSessionService has changed. Will notify
+  // observers if the service is currently routed.
+  void OnMediaSessionInfoChanged(MediaSessionServiceImpl* service);
+
   // Requests audio focus to the AudioFocusDelegate.
   // Returns whether the request was granted.
   CONTENT_EXPORT AudioFocusDelegate::AudioFocusResult RequestSystemAudioFocus(
@@ -262,6 +267,15 @@
   // this method is called again.
   void SetAudioSinkId(const base::Optional<std::string>& id) override;
 
+  // Mute/Unmute the microphone for a WebRTC session.
+  void ToggleMicrophone() override;
+
+  // Turn on or off the camera for a WebRTC session.
+  void ToggleCamera() override;
+
+  // Hang up a WebRTC session.
+  void HangUp() override;
+
   // Downloads the bitmap version of a MediaImage at least |minimum_size_px|
   // and closest to |desired_size_px|. If the download failed, was too small or
   // the image did not come from the media session then returns a null image.
diff --git a/content/browser/media/session/media_session_service_impl.cc b/content/browser/media/session/media_session_service_impl.cc
index eb12ef1..a959ab5 100644
--- a/content/browser/media/session/media_session_service_impl.cc
+++ b/content/browser/media/session/media_session_service_impl.cc
@@ -100,6 +100,22 @@
     session->OnMediaSessionMetadataChanged(this);
 }
 
+void MediaSessionServiceImpl::SetMicrophoneState(
+    media_session::mojom::MicrophoneState microphone_state) {
+  microphone_state_ = microphone_state;
+  MediaSessionImpl* session = GetMediaSession();
+  if (session)
+    session->OnMediaSessionInfoChanged(this);
+}
+
+void MediaSessionServiceImpl::SetCameraState(
+    media_session::mojom::CameraState camera_state) {
+  camera_state_ = camera_state;
+  MediaSessionImpl* session = GetMediaSession();
+  if (session)
+    session->OnMediaSessionInfoChanged(this);
+}
+
 void MediaSessionServiceImpl::EnableAction(
     media_session::mojom::MediaSessionAction action) {
   actions_.insert(action);
diff --git a/content/browser/media/session/media_session_service_impl.h b/content/browser/media/session/media_session_service_impl.h
index 5ecb5344..edbb08086 100644
--- a/content/browser/media/session/media_session_service_impl.h
+++ b/content/browser/media/session/media_session_service_impl.h
@@ -45,6 +45,12 @@
   const base::Optional<media_session::MediaPosition>& position() const {
     return position_;
   }
+  media_session::mojom::MicrophoneState microphone_state() const {
+    return microphone_state_;
+  }
+  media_session::mojom::CameraState camera_state() const {
+    return camera_state_;
+  }
 
   void DidFinishNavigation();
   void FlushForTesting();
@@ -57,6 +63,9 @@
   void SetPositionState(
       const base::Optional<media_session::MediaPosition>& position) override;
   void SetMetadata(blink::mojom::SpecMediaMetadataPtr metadata) override;
+  void SetMicrophoneState(
+      media_session::mojom::MicrophoneState microphone_state) override;
+  void SetCameraState(media_session::mojom::CameraState camera_state) override;
 
   void EnableAction(media_session::mojom::MediaSessionAction action) override;
   void DisableAction(media_session::mojom::MediaSessionAction action) override;
@@ -83,6 +92,14 @@
   std::set<media_session::mojom::MediaSessionAction> actions_;
   base::Optional<media_session::MediaPosition> position_;
 
+  // Tracks whether the microphone is muted in a WebRTC session.
+  media_session::mojom::MicrophoneState microphone_state_ =
+      media_session::mojom::MicrophoneState::kUnknown;
+
+  // Tracks whether the camera is turned on in a WebRTC session.
+  media_session::mojom::CameraState camera_state_ =
+      media_session::mojom::CameraState::kUnknown;
+
   DISALLOW_COPY_AND_ASSIGN(MediaSessionServiceImpl);
 };
 
diff --git a/content/browser/media/session/media_session_uma_helper.h b/content/browser/media/session/media_session_uma_helper.h
index 0d1d11e..882d259 100644
--- a/content/browser/media/session/media_session_uma_helper.h
+++ b/content/browser/media/session/media_session_uma_helper.h
@@ -48,7 +48,10 @@
     EnterPictureInPicture = 13,
     ExitPictureInPicture = 14,
     SwitchAudioDevice = 15,
-    kMaxValue = SwitchAudioDevice,
+    ToggleMicrophone = 16,
+    ToggleCamera = 17,
+    HangUp = 18,
+    kMaxValue = HangUp,
   };
 
   MediaSessionUmaHelper();
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index ad91fc7a..6425cd2 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -5429,7 +5429,8 @@
 }
 
 bool NavigationRequest::IsServedFromBackForwardCache() {
-  return rfh_restored_from_back_forward_cache_ != nullptr;
+  const NavigationRequest& request = *this;
+  return request.IsServedFromBackForwardCache();
 }
 
 void NavigationRequest::SetIsOverridingUserAgent(bool override_ua) {
@@ -5811,6 +5812,10 @@
   return prerender_frame_tree_node_id_ != RenderFrameHost::kNoFrameTreeNodeId;
 }
 
+bool NavigationRequest::IsServedFromBackForwardCache() const {
+  return rfh_restored_from_back_forward_cache_ != nullptr;
+}
+
 bool NavigationRequest::IsWaitingForBeforeUnload() {
   return state_ < WILL_START_NAVIGATION;
 }
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 501ffcb..f9428e4 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -825,6 +825,10 @@
   // only meaningful to call this after BeginNavigation().
   bool IsPrerenderedPageActivation() const;
 
+  // This is the same as |NavigationHandle::IsServedFromBackForwardCache|, but
+  // adds a const qualifier.
+  bool IsServedFromBackForwardCache() const;
+
  private:
   friend class NavigationRequestTest;
 
diff --git a/content/browser/renderer_host/pepper/pepper_renderer_connection.cc b/content/browser/renderer_host/pepper/pepper_renderer_connection.cc
index 7bba16a3..9e39496 100644
--- a/content/browser/renderer_host/pepper/pepper_renderer_connection.cc
+++ b/content/browser/renderer_host/pepper/pepper_renderer_connection.cc
@@ -36,9 +36,7 @@
 
 namespace {
 
-const uint32_t kPepperFilteredMessageClasses[] = {
-    PpapiMsgStart, FrameMsgStart,
-};
+const uint32_t kPepperFilteredMessageClasses[] = {PpapiMsgStart};
 
 // Responsible for creating the pending resource hosts, holding their IDs until
 // all of them have been created for a single message, and sending the reply to
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 a2643ee..67c0a3d 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -3967,25 +3967,20 @@
 
     grandchild = child->child_at(0);
 
-    // Now the frames above grandchild differ only in scheme. SiteForCookies
-    // should be the same except that schemefully_same() should be false.
+    // Now the frames above grandchild differ only in scheme. This results in
+    // null SiteForCookies because of the schemefully_same flag, but site should
+    // still not be opaque.
     net::SiteForCookies grandchild_cross_scheme =
         grandchild->current_frame_host()->ComputeSiteForCookies();
-    EXPECT_FALSE(grandchild_cross_scheme.schemefully_same());
-    EXPECT_EQ("a.test", grandchild_cross_scheme.registrable_domain());
+    EXPECT_TRUE(grandchild_cross_scheme.IsNull());
+    EXPECT_FALSE(grandchild_cross_scheme.site().opaque());
 
     net::SiteForCookies grandchild_cross_scheme_navigation =
         grandchild->current_frame_host()
             ->ComputeIsolationInfoForNavigation(other_url)
             .site_for_cookies();
-    EXPECT_FALSE(grandchild_cross_scheme_navigation.schemefully_same());
-    EXPECT_EQ("a.test",
-              grandchild_cross_scheme_navigation.registrable_domain());
-
-    // IsEquivalent() doesn't check schemefully_same.
-    EXPECT_TRUE(grandchild_cross_scheme.IsEquivalent(grandchild_same_scheme));
-    EXPECT_TRUE(grandchild_cross_scheme_navigation.IsEquivalent(
-        grandchild_same_scheme_navigation));
+    EXPECT_TRUE(grandchild_cross_scheme_navigation.IsNull());
+    EXPECT_FALSE(grandchild_cross_scheme_navigation.site().opaque());
   }
 }
 
diff --git a/content/browser/renderer_host/render_message_filter.cc b/content/browser/renderer_host/render_message_filter.cc
index d820891..9bd35be 100644
--- a/content/browser/renderer_host/render_message_filter.cc
+++ b/content/browser/renderer_host/render_message_filter.cc
@@ -76,20 +76,13 @@
 #endif
 
 namespace content {
-namespace {
-
-const uint32_t kRenderFilteredMessageClasses[] = {FrameMsgStart};
-
-}  // namespace
 
 RenderMessageFilter::RenderMessageFilter(
     int render_process_id,
     BrowserContext* browser_context,
     RenderWidgetHelper* render_widget_helper,
     MediaInternals* media_internals)
-    : BrowserMessageFilter(kRenderFilteredMessageClasses,
-                           base::size(kRenderFilteredMessageClasses)),
-      BrowserAssociatedInterface<mojom::RenderMessageFilter>(this),
+    : BrowserAssociatedInterface<mojom::RenderMessageFilter>(this),
       resource_context_(browser_context->GetResourceContext()),
       render_widget_helper_(render_widget_helper),
       render_process_id_(render_process_id),
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 4ad8b786..1b7c03f 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -3184,7 +3184,6 @@
     switches::kDomAutomationController,
     switches::kEnableAccessibilityObjectModel,
     switches::kEnableAutomation,
-    switches::kEnableBackgroundThreadPool,
     switches::kEnableExperimentalAccessibilityLanguageDetection,
     switches::kEnableExperimentalAccessibilityLabelsDebugging,
     switches::kEnableExperimentalWebPlatformFeatures,
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index af318630..61532ad4 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -75,7 +75,6 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/viz/public/mojom/compositing/delegated_ink_point.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h"
@@ -119,6 +118,7 @@
 #include "ui/events/keycodes/keyboard_code_conversion.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/mojom/delegated_ink_point.mojom.h"
 #include "ui/gfx/selection_bound.h"
 #include "ui/wm/core/window_util.h"
 
@@ -6719,14 +6719,14 @@
       mojo::PendingReceiver<viz::mojom::DelegatedInkPointRenderer> receiver)
       : receiver_(this, std::move(receiver)) {}
 
-  void StoreDelegatedInkPoint(const viz::DelegatedInkPoint& point) override {
+  void StoreDelegatedInkPoint(const gfx::DelegatedInkPoint& point) override {
     delegated_ink_point_ = point;
   }
 
   bool HasDelegatedInkPoint() { return delegated_ink_point_.has_value(); }
 
-  viz::DelegatedInkPoint GetDelegatedInkPoint() {
-    viz::DelegatedInkPoint point = delegated_ink_point_.value();
+  gfx::DelegatedInkPoint GetDelegatedInkPoint() {
+    gfx::DelegatedInkPoint point = delegated_ink_point_.value();
     delegated_ink_point_.reset();
     return point;
   }
@@ -6747,7 +6747,7 @@
 
  private:
   mojo::Receiver<viz::mojom::DelegatedInkPointRenderer> receiver_;
-  base::Optional<viz::DelegatedInkPoint> delegated_ink_point_;
+  base::Optional<gfx::DelegatedInkPoint> delegated_ink_point_;
   bool prediction_reset_ = false;
 };
 
@@ -6910,7 +6910,7 @@
   // Then set it to true and confirm that the DelegatedInkPointRenderer is
   // initialized, the connection is made and the point makes it to the renderer.
   SetInkMetadataFlagOnRenderFrameMetadata(true);
-  viz::DelegatedInkPoint expected_point(
+  gfx::DelegatedInkPoint expected_point(
       gfx::PointF(10, 10), base::TimeTicks::Now(), GetExpectedPointerId());
   SendEvent(true, expected_point.point(), expected_point.timestamp());
 
@@ -6919,7 +6919,7 @@
   delegated_ink_point_renderer->FlushForTesting();
 
   EXPECT_TRUE(delegated_ink_point_renderer->HasDelegatedInkPoint());
-  viz::DelegatedInkPoint actual_point =
+  gfx::DelegatedInkPoint actual_point =
       delegated_ink_point_renderer->GetDelegatedInkPoint();
   EXPECT_EQ(expected_point.point(), actual_point.point());
   EXPECT_EQ(expected_point.timestamp(), actual_point.timestamp());
@@ -6936,7 +6936,7 @@
   delegated_ink_point_renderer->FlushForTesting();
 
   unscaled_point.Scale(scale);
-  expected_point = viz::DelegatedInkPoint(unscaled_point, unscaled_time,
+  expected_point = gfx::DelegatedInkPoint(unscaled_point, unscaled_time,
                                           GetExpectedPointerId());
 
   EXPECT_TRUE(delegated_ink_point_renderer->HasDelegatedInkPoint());
diff --git a/content/browser/renderer_host/render_widget_host_view_event_handler.cc b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
index ffa7faf..8c74ec5b 100644
--- a/content/browser/renderer_host/render_widget_host_view_event_handler.cc
+++ b/content/browser/renderer_host/render_widget_host_view_event_handler.cc
@@ -9,7 +9,6 @@
 #include "base/metrics/user_metrics_action.h"
 #include "base/numerics/safe_conversions.h"
 #include "build/build_config.h"
-#include "components/viz/common/delegated_ink_point.h"
 #include "components/viz/common/features.h"
 #include "content/browser/renderer_host/hit_test_debug_key_event_observer.h"
 #include "content/browser/renderer_host/input/touch_selection_controller_client_aura.h"
@@ -35,6 +34,7 @@
 #include "ui/events/blink/blink_event_util.h"
 #include "ui/events/blink/web_input_event.h"
 #include "ui/events/keycodes/dom/dom_code.h"
+#include "ui/gfx/delegated_ink_point.h"
 #include "ui/touch_selection/touch_selection_controller.h"
 
 #if defined(OS_WIN)
@@ -392,7 +392,7 @@
 
     gfx::PointF point = event->root_location_f();
     point.Scale(host_view_->GetDeviceScaleFactor());
-    viz::DelegatedInkPoint delegated_ink_point(point, event->time_stamp(),
+    gfx::DelegatedInkPoint delegated_ink_point(point, event->time_stamp(),
                                                pointer_id);
     TRACE_EVENT_INSTANT1("input",
                          "Forwarding delegated ink point from browser.",
diff --git a/content/browser/renderer_host/render_widget_host_view_event_handler.h b/content/browser/renderer_host/render_widget_host_view_event_handler.h
index b068284..0205058f 100644
--- a/content/browser/renderer_host/render_widget_host_view_event_handler.h
+++ b/content/browser/renderer_host/render_widget_host_view_event_handler.h
@@ -14,7 +14,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/native_web_keyboard_event.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/viz/public/mojom/compositing/delegated_ink_point.mojom.h"
+#include "services/viz/public/mojom/compositing/delegated_ink_point_renderer.mojom.h"
 #include "third_party/blink/public/mojom/input/pointer_lock_result.mojom.h"
 #include "ui/aura/scoped_enable_unadjusted_mouse_events.h"
 #include "ui/aura/scoped_keyboard_hook.h"
diff --git a/content/browser/scheduler/responsiveness/calculator.cc b/content/browser/scheduler/responsiveness/calculator.cc
index 7d1ddb28..d4444892 100644
--- a/content/browser/scheduler/responsiveness/calculator.cc
+++ b/content/browser/scheduler/responsiveness/calculator.cc
@@ -33,14 +33,6 @@
 
 constexpr char kLatencyEventCategory[] = "latency";
 
-// The name emitted for a large UI jank event. Also reused as the scope string
-// for these events, to ensure their ID's don't collide with any other event.
-constexpr char kLargeUIJankEvent[] = "Large UI Jank";
-
-// The name emitted for a large IO jank event. Also reused as the scope string
-// for these events, to ensure their ID's don't collide with any other event.
-constexpr char kLargeIOJankEvent[] = "Large IO Jank";
-
 // The names emitted for JankyInterval measurement events.
 constexpr char kJankyIntervalEvent[] = "JankyInterval";
 constexpr char kJankyIntervalsPerThirtySeconds2Event[] =
@@ -108,16 +100,6 @@
   if (execution_finish_time - queue_time >= kJankThreshold) {
     GetQueueAndExecutionJanksOnUIThread().emplace_back(queue_time,
                                                        execution_finish_time);
-    // Emit a trace event to highlight large janky slices.
-    const auto trace_id = TRACE_ID_WITH_SCOPE(
-        kLargeUIJankEvent, TRACE_ID_LOCAL(g_num_large_ui_janks_));
-    TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
-        kLatencyEventCategory, kLargeUIJankEvent, trace_id, queue_time);
-    TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(kLatencyEventCategory,
-                                                   kLargeUIJankEvent, trace_id,
-                                                   execution_finish_time);
-    g_num_large_ui_janks_++;
-
     if (execution_finish_time - execution_start_time >= kJankThreshold) {
       GetExecutionJanksOnUIThread().emplace_back(execution_start_time,
                                                  execution_finish_time);
@@ -139,16 +121,6 @@
     base::AutoLock lock(io_thread_lock_);
     queue_and_execution_janks_on_io_thread_.emplace_back(queue_time,
                                                          execution_finish_time);
-    // Emit a trace event to highlight large janky slices.
-    const auto trace_id = TRACE_ID_WITH_SCOPE(
-        kLargeIOJankEvent, TRACE_ID_LOCAL(g_num_large_io_janks_));
-    TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
-        kLatencyEventCategory, kLargeIOJankEvent, trace_id, queue_time);
-    TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(kLatencyEventCategory,
-                                                   kLargeIOJankEvent, trace_id,
-                                                   execution_finish_time);
-    g_num_large_io_janks_++;
-
     if (execution_finish_time - execution_start_time >= kJankThreshold) {
       execution_janks_on_io_thread_.emplace_back(execution_start_time,
                                                  execution_finish_time);
@@ -156,17 +128,7 @@
   }
 }
 
-void Calculator::SetProcessSuspended(bool suspended) {
-  // Keep track of the current power state.
-  is_process_suspended_ = suspended;
-  // Regardless of whether the process is entering or exiting suspension, the
-  // current 30-second interval should be flagged as containing suspended state.
-  was_process_suspended_ = true;
-}
-
-void Calculator::EmitResponsiveness(JankType jank_type,
-                                    size_t janky_slices,
-                                    bool was_process_suspended) {
+void Calculator::EmitResponsiveness(JankType jank_type, size_t janky_slices) {
   constexpr size_t kMaxJankySlices = 300;
   DCHECK_LE(janky_slices, kMaxJankySlices);
   switch (jank_type) {
@@ -174,11 +136,6 @@
       UMA_HISTOGRAM_COUNTS_1000(
           "Browser.Responsiveness.JankyIntervalsPerThirtySeconds",
           janky_slices);
-      if (!was_process_suspended) {
-        UMA_HISTOGRAM_COUNTS_1000(
-            "Browser.Responsiveness.JankyIntervalsPerThirtySeconds.NoSuspend",
-            janky_slices);
-      }
       break;
     }
     case JankType::kQueueAndExecution: {
@@ -320,7 +277,6 @@
       last_calculation_time_, new_calculation_time);
 
   last_calculation_time_ = new_calculation_time;
-  was_process_suspended_ = is_process_suspended_;
 }
 
 void Calculator::CalculateResponsiveness(
@@ -349,7 +305,7 @@
       }
     }
 
-    EmitResponsiveness(jank_type, janky_slices.size(), was_process_suspended_);
+    EmitResponsiveness(jank_type, janky_slices.size());
 
     // If the 'latency' tracing category is enabled, emit trace events for the
     // measurement duration and the janky slices.
diff --git a/content/browser/scheduler/responsiveness/calculator.h b/content/browser/scheduler/responsiveness/calculator.h
index b248aad9..3cb7afd 100644
--- a/content/browser/scheduler/responsiveness/calculator.h
+++ b/content/browser/scheduler/responsiveness/calculator.h
@@ -71,9 +71,7 @@
  protected:
   // Emits an UMA metric for responsiveness of a single measurement interval.
   // Exposed for testing.
-  virtual void EmitResponsiveness(JankType jank_type,
-                                  size_t janky_slices,
-                                  bool was_process_suspended);
+  virtual void EmitResponsiveness(JankType jank_type, size_t janky_slices);
 
   // Emits trace events for responsiveness metric. A trace event is emitted for
   // the whole duration of the metric interval and sub events are emitted for
@@ -139,10 +137,6 @@
   //   2) Returns all Janks with Jank.start_time < |end_time|.
   JankList TakeJanksOlderThanTime(JankList* janks, base::TimeTicks end_time);
 
-  // Used to generate a unique id when emitting Large Jank trace events
-  int g_num_large_ui_janks_ = 0;
-  int g_num_large_io_janks_ = 0;
-
   // Janks from tasks/events with a long execution time on the UI thread. Should
   // only be accessed via the accessor, which checks that the caller is on the
   // UI thread.
@@ -159,14 +153,6 @@
   bool is_application_visible_ = false;
 #endif
 
-  // Whether or not the process is suspended (Power management). Accessed only
-  // on the UI thread.
-  bool is_process_suspended_ = false;
-
-  // Stores whether to process was suspended since last metric computation.
-  // Accessed only on the UI thread.
-  bool was_process_suspended_ = false;
-
   // We expect there to be low contention and this lock to cause minimal
   // overhead. If performance of this lock proves to be a problem, we can move
   // to a lock-free data structure.
diff --git a/content/browser/scheduler/responsiveness/calculator_unittest.cc b/content/browser/scheduler/responsiveness/calculator_unittest.cc
index bcbaa55..1c16658 100644
--- a/content/browser/scheduler/responsiveness/calculator_unittest.cc
+++ b/content/browser/scheduler/responsiveness/calculator_unittest.cc
@@ -22,10 +22,8 @@
 
 class FakeCalculator : public Calculator {
  public:
-  MOCK_METHOD3(EmitResponsiveness,
-               void(JankType jank_type,
-                    size_t janky_slices,
-                    bool was_process_suspended));
+  MOCK_METHOD2(EmitResponsiveness,
+               void(JankType jank_type, size_t janky_slices));
 
   MOCK_METHOD3(EmitJankyIntervalsMeasurementTraceEvent,
                void(base::TimeTicks start_time,
@@ -94,10 +92,10 @@
 
 #define EXPECT_EXECUTION_JANKY_SLICES(num_slices) \
   EXPECT_CALL(*calculator_,                       \
-              EmitResponsiveness(JankType::kExecution, num_slices, false));
-#define EXPECT_QUEUE_AND_EXECUTION_JANKY_SLICES(num_slices)                  \
-  EXPECT_CALL(*calculator_, EmitResponsiveness(JankType::kQueueAndExecution, \
-                                               num_slices, false));
+              EmitResponsiveness(JankType::kExecution, num_slices));
+#define EXPECT_QUEUE_AND_EXECUTION_JANKY_SLICES(num_slices) \
+  EXPECT_CALL(*calculator_,                                 \
+              EmitResponsiveness(JankType::kQueueAndExecution, num_slices));
 
 // A single event executing slightly longer than kJankThresholdInMs.
 TEST_F(ResponsivenessCalculatorTest, ShortExecutionJank) {
@@ -355,7 +353,7 @@
   base_time += 10 * kMeasurementIntervalInMs;
   AddEventUI(base_time, base_time, base_time + 1);
 
-  EXPECT_CALL(*calculator_, EmitResponsiveness(_, _, _)).Times(0);
+  EXPECT_CALL(*calculator_, EmitResponsiveness(_, _)).Times(0);
 }
 
 // A long event means that the machine likely went to sleep.
@@ -363,7 +361,7 @@
   int base_time = 105;
   AddEventUI(base_time, base_time, base_time + 10 * kMeasurementIntervalInMs);
 
-  EXPECT_CALL(*calculator_, EmitResponsiveness(_, _, _)).Times(0);
+  EXPECT_CALL(*calculator_, EmitResponsiveness(_, _)).Times(0);
 }
 
 #if defined(OS_ANDROID)
@@ -380,64 +378,11 @@
 
   AddEventUI(kQueueTime, kStartTime + 1, kFinishTime + 1);
 
-  EXPECT_CALL(*calculator_, EmitResponsiveness(_, _, _)).Times(0);
+  EXPECT_CALL(*calculator_, EmitResponsiveness(_, _)).Times(0);
   TriggerCalculation();
 }
 #endif
 
-// The suspended state must be passed to EmitResponsiveness(...).
-// A single event executing slightly longer than 10 * kJankThresholdInMs.
-TEST_F(ResponsivenessCalculatorTest, JankWithPowerSuspend) {
-  constexpr int kQueueTime = 35;
-  constexpr int kStartTime = 40;
-  constexpr int kFinishTime = kStartTime + 10 * kJankThresholdInMs + 5;
-
-  AddEventUI(kQueueTime, kStartTime, kFinishTime);
-  EXPECT_CALL(*calculator_,
-              EmitResponsiveness(JankType::kExecution, 10, false));
-  EXPECT_CALL(*calculator_,
-              EmitResponsiveness(JankType::kQueueAndExecution, 10, false));
-  TriggerCalculation();
-
-  calculator_->SetProcessSuspended(true);
-  AddEventUI(kQueueTime, kStartTime, kFinishTime);
-  EXPECT_CALL(*calculator_, EmitResponsiveness(JankType::kExecution, 10, true));
-  EXPECT_CALL(*calculator_,
-              EmitResponsiveness(JankType::kQueueAndExecution, 10, true));
-  TriggerCalculation();
-
-  calculator_->SetProcessSuspended(false);
-  AddEventUI(kQueueTime, kStartTime, kFinishTime);
-  EXPECT_CALL(*calculator_, EmitResponsiveness(JankType::kExecution, 10, true));
-  EXPECT_CALL(*calculator_,
-              EmitResponsiveness(JankType::kQueueAndExecution, 10, true));
-  TriggerCalculation();
-
-  calculator_->SetProcessSuspended(true);
-  AddEventUI(kQueueTime, kStartTime, kFinishTime);
-  calculator_->SetProcessSuspended(false);
-  EXPECT_CALL(*calculator_, EmitResponsiveness(JankType::kExecution, 10, true));
-  EXPECT_CALL(*calculator_,
-              EmitResponsiveness(JankType::kQueueAndExecution, 10, true));
-  TriggerCalculation();
-
-  // The whole slice must be flagged as containing suspend/resume events.
-  calculator_->SetProcessSuspended(true);
-  calculator_->SetProcessSuspended(false);
-  AddEventUI(kQueueTime, kStartTime, kFinishTime);
-  EXPECT_CALL(*calculator_, EmitResponsiveness(JankType::kExecution, 10, true));
-  EXPECT_CALL(*calculator_,
-              EmitResponsiveness(JankType::kQueueAndExecution, 10, true));
-  TriggerCalculation();
-
-  AddEventUI(kQueueTime, kStartTime, kFinishTime);
-  EXPECT_CALL(*calculator_,
-              EmitResponsiveness(JankType::kExecution, 10, false));
-  EXPECT_CALL(*calculator_,
-              EmitResponsiveness(JankType::kQueueAndExecution, 10, false));
-  TriggerCalculation();
-}
-
 // An event execution that crosses a measurement interval boundary should count
 // towards both measurement intervals.
 TEST_F(ResponsivenessCalculatorTest, ExecutionCrossesBoundary) {
diff --git a/content/browser/scheduler/responsiveness/watcher.cc b/content/browser/scheduler/responsiveness/watcher.cc
index 9ad41b5..b0e7bf8d 100644
--- a/content/browser/scheduler/responsiveness/watcher.cc
+++ b/content/browser/scheduler/responsiveness/watcher.cc
@@ -185,16 +185,6 @@
                                              execution_finish_time);
 }
 
-void Watcher::OnSuspend() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  calculator_->SetProcessSuspended(true);
-}
-
-void Watcher::OnResume() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  calculator_->SetProcessSuspended(false);
-}
-
 Watcher::Watcher() = default;
 Watcher::~Watcher() = default;
 
@@ -208,8 +198,6 @@
 
   metric_source_ = CreateMetricSource();
   metric_source_->SetUp();
-
-  base::PowerMonitor::AddObserver(this);
 }
 
 void Watcher::Destroy() {
@@ -220,8 +208,6 @@
       &Watcher::FinishDestroyMetricSource, base::RetainedRef(this)));
 
   metric_source_->Destroy(std::move(on_destroy_complete));
-
-  base::PowerMonitor::RemoveObserver(this);
 }
 
 void Watcher::SetUpOnIOThread() {
diff --git a/content/browser/scheduler/responsiveness/watcher.h b/content/browser/scheduler/responsiveness/watcher.h
index 92c031a2..94b1da9d 100644
--- a/content/browser/scheduler/responsiveness/watcher.h
+++ b/content/browser/scheduler/responsiveness/watcher.h
@@ -7,7 +7,6 @@
 
 #include <vector>
 
-#include "base/power_monitor/power_observer.h"
 #include "content/browser/scheduler/responsiveness/metric_source.h"
 
 namespace content {
@@ -16,8 +15,7 @@
 class Calculator;
 
 class CONTENT_EXPORT Watcher : public base::RefCounted<Watcher>,
-                               public MetricSource::Delegate,
-                               public base::PowerObserver {
+                               public MetricSource::Delegate {
  public:
   Watcher();
   void SetUp();
@@ -48,13 +46,6 @@
   void WillRunEventOnUIThread(const void* opaque_identifier) override;
   void DidRunEventOnUIThread(const void* opaque_identifier) override;
 
-  // base::PowerObserver interface implementation. The PowerObserver
-  // notifications are asynchronously callbacks on their registration sequence
-  // and may be delayed if there is a long queue of pending tasks to be
-  // executed.
-  void OnSuspend() override;
-  void OnResume() override;
-
  private:
   FRIEND_TEST_ALL_PREFIXES(ResponsivenessWatcherTest, TaskForwarding);
   FRIEND_TEST_ALL_PREFIXES(ResponsivenessWatcherTest, TaskNesting);
diff --git a/content/browser/utility_process_host.cc b/content/browser/utility_process_host.cc
index 940abd5e..e26b312 100644
--- a/content/browser/utility_process_host.cc
+++ b/content/browser/utility_process_host.cc
@@ -240,7 +240,6 @@
       os_crypt::switches::kUseMockKeychain,
 #endif
       switches::kDisableTestCerts,
-      switches::kEnableBackgroundThreadPool,
       switches::kEnableExperimentalCookieFeatures,
       switches::kEnableLogging,
       switches::kForceTextDirection,
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index eb71205..7a35095 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -75,7 +75,6 @@
 #undef IPC_MESSAGE_EXPORT
 #define IPC_MESSAGE_EXPORT CONTENT_EXPORT
 
-#define IPC_MESSAGE_START FrameMsgStart
 IPC_ENUM_TRAITS_MAX_VALUE(blink::mojom::ScrollbarMode,
                           blink::mojom::ScrollbarMode::kMaxValue)
 IPC_ENUM_TRAITS_MAX_VALUE(content::StopFindAction,
diff --git a/content/public/browser/browser_message_filter.cc b/content/public/browser/browser_message_filter.cc
index 4d0eead..f2a3c39 100644
--- a/content/public/browser/browser_message_filter.cc
+++ b/content/public/browser/browser_message_filter.cc
@@ -107,17 +107,15 @@
   DISALLOW_COPY_AND_ASSIGN(Internal);
 };
 
+BrowserMessageFilter::BrowserMessageFilter() = default;
+
 BrowserMessageFilter::BrowserMessageFilter(uint32_t message_class_to_filter)
-    : internal_(nullptr),
-      sender_(nullptr),
-      message_classes_to_filter_(1, message_class_to_filter) {}
+    : message_classes_to_filter_(1, message_class_to_filter) {}
 
 BrowserMessageFilter::BrowserMessageFilter(
     const uint32_t* message_classes_to_filter,
     size_t num_message_classes_to_filter)
-    : internal_(nullptr),
-      sender_(nullptr),
-      message_classes_to_filter_(
+    : message_classes_to_filter_(
           message_classes_to_filter,
           message_classes_to_filter + num_message_classes_to_filter) {
   DCHECK(num_message_classes_to_filter);
diff --git a/content/public/browser/browser_message_filter.h b/content/public/browser/browser_message_filter.h
index e45d046..8637659 100644
--- a/content/public/browser/browser_message_filter.h
+++ b/content/public/browser/browser_message_filter.h
@@ -41,6 +41,7 @@
           BrowserMessageFilter, BrowserMessageFilterTraits>,
       public IPC::Sender {
  public:
+  BrowserMessageFilter();  // For mojo-only BrowserAssociatedInterface.
   explicit BrowserMessageFilter(uint32_t message_class_to_filter);
   BrowserMessageFilter(const uint32_t* message_classes_to_filter,
                        size_t num_message_classes_to_filter);
@@ -140,9 +141,9 @@
   // classes. Internal keeps a reference to this class, which is why there's a
   // weak pointer back. This class could outlive Internal based on what the
   // child class does in its OnDestruct method.
-  Internal* internal_;
+  Internal* internal_ = nullptr;
 
-  IPC::Sender* sender_;
+  IPC::Sender* sender_ = nullptr;
   base::Process peer_process_;
 
   std::vector<uint32_t> message_classes_to_filter_;
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 4516cea..b4e1563a 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -9,7 +9,7 @@
 // declarations instead of including more headers. If that is infeasible, adjust
 // the limit. For more info, see
 // https://chromium.googlesource.com/chromium/src/+/HEAD/docs/wmax_tokens.md
-#pragma clang max_tokens_here 852000
+#pragma clang max_tokens_here 852500
 
 #include <utility>
 
diff --git a/content/renderer/media/renderer_webaudiodevice_impl.cc b/content/renderer/media/renderer_webaudiodevice_impl.cc
index cebf92c..49b95a7 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl.cc
+++ b/content/renderer/media/renderer_webaudiodevice_impl.cc
@@ -16,7 +16,6 @@
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
-#include "build/build_config.h"
 #include "content/renderer/render_frame_impl.h"
 #include "content/renderer/render_thread_impl.h"
 #include "media/base/audio_timestamp_helper.h"
@@ -38,13 +37,6 @@
 
 namespace {
 
-#if defined(OS_ANDROID)
-// AAudio's NONE and POWER_SAVING performance mode makes the underlying
-// buffer capacity quite large (5766) and it breaks the Audio Worklet rendering
-// mode. So we use the nearest higher power of two. See crbug.com/1181434.
-const int kWebAudioHighLatencyBufferSize = 6400;
-#endif  // defined(OS_ANDROID)
-
 blink::WebAudioDeviceSourceType GetLatencyHintSourceType(
     WebAudioLatencyHint::AudioContextLatencyCategory latency_category) {
   switch (latency_category) {
@@ -70,12 +62,6 @@
       hardware_params.hardware_capabilities().value_or(
           media::AudioParameters::HardwareCapabilities());
 
-  TRACE_EVENT2("audio", "GetOutputBufferSize",
-               "HW min frames per buffer",
-               hardware_capabilities.min_frames_per_buffer,
-               "HW max frames per buffer",
-               hardware_capabilities.max_frames_per_buffer);
-
   // Adjust output buffer size according to the latency requirement.
   switch (latency) {
     case media::AudioLatency::LATENCY_INTERACTIVE:
@@ -85,13 +71,8 @@
       return media::AudioLatency::GetRtcBufferSize(
           hardware_params.sample_rate(), hardware_params.frames_per_buffer());
     case media::AudioLatency::LATENCY_PLAYBACK:
-#if defined(OS_ANDROID)
-      return media::AudioLatency::GetHighLatencyBufferSize(
-          hardware_params.sample_rate(), kWebAudioHighLatencyBufferSize);
-#else
       return media::AudioLatency::GetHighLatencyBufferSize(
           hardware_params.sample_rate(), hardware_params.frames_per_buffer());
-#endif  // defined(OS_ANDROID)
     case media::AudioLatency::LATENCY_EXACT_MS:
       return media::AudioLatency::GetExactBufferSize(
           base::TimeDelta::FromSecondsD(latency_hint.Seconds()),
@@ -183,11 +164,6 @@
   // Specify the latency info to be passed to the browser side.
   sink_params_.set_latency_tag(latency);
 
-  TRACE_EVENT2("audio",
-               "RendererWebAudioDeviceImpl::RendererWebAudioDeviceImpl",
-               "sink_params_", sink_params_.AsHumanReadableString(),
-               "output_buffer_size", output_buffer_size);
-
   web_audio_dest_data_ =
       blink::WebVector<float*>(static_cast<size_t>(sink_params_.channels()));
 }
diff --git a/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc b/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
index 8b0c74e..70341fc 100644
--- a/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
+++ b/content/renderer/media/renderer_webaudiodevice_impl_unittest.cc
@@ -26,12 +26,6 @@
 
 const int kHardwareSampleRate = 44100;
 const int kHardwareBufferSize = 128;
-
-#if defined(OS_ANDROID)
-// The nearest higher power of two of 5766. See crbug.com/1181434.
-const int kWebAudioHighLatencyBufferSize = 6400;
-#endif  // defined(OS_ANDROID)
-
 const blink::LocalFrameToken kFrameToken;
 
 blink::LocalFrameToken MockFrameTokenFromCurrentContext() {
@@ -200,14 +194,8 @@
 
   blink::WebAudioLatencyHint playbackLatencyHint(
       blink::WebAudioLatencyHint::kCategoryPlayback);
-  #if defined(OS_ANDROID)
-  int playbackBufferSize = media::AudioLatency::GetHighLatencyBufferSize(
-      kHardwareSampleRate, kWebAudioHighLatencyBufferSize);
-  #else
   int playbackBufferSize = media::AudioLatency::GetHighLatencyBufferSize(
       kHardwareSampleRate, kHardwareBufferSize);
-  #endif  // defined(OS_ANDROID)
-
   SetupDevice(playbackLatencyHint);
 
   EXPECT_EQ(webaudio_device_->SampleRate(), kHardwareSampleRate);
diff --git a/content/test/data/conversions/databases/version_2.sql b/content/test/data/conversions/databases/version_2.sql
index 6508e4b..e0d89d7b 100644
--- a/content/test/data/conversions/databases/version_2.sql
+++ b/content/test/data/conversions/databases/version_2.sql
@@ -4,6 +4,18 @@
 
 CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data TEXT NOT NULL,impression_origin TEXT NOT NULL,conversion_origin TEXT NOT NULL,reporting_origin TEXT NOT NULL,impression_time INTEGER NOT NULL,expiry_time INTEGER NOT NULL,num_conversions INTEGER DEFAULT 0,active INTEGER DEFAULT 1,conversion_destination TEXT NOT NULL);
 
+INSERT INTO impressions
+VALUES(1,
+       '9357e17751666f64',
+       'https://impression.test',
+       'https://sub.conversion.test',
+       'https://report.test',
+       13245278349693988,
+       13247870349693988,
+       0,
+       1,
+       'https://conversion.test/');
+
 CREATE TABLE conversions (conversion_id INTEGER PRIMARY KEY, impression_id INTEGER, conversion_data TEXT NOT NULL, conversion_time INTEGER NOT NULL, report_time INTEGER NOT NULL, attribution_credit INTEGER NOT NULL);
 
 CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
diff --git a/content/test/data/conversions/databases/version_3.sql b/content/test/data/conversions/databases/version_3.sql
new file mode 100644
index 0000000..b4432da
--- /dev/null
+++ b/content/test/data/conversions/databases/version_3.sql
@@ -0,0 +1,25 @@
+PRAGMA foreign_keys=OFF;
+
+BEGIN TRANSACTION;
+
+CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data TEXT NOT NULL,impression_origin TEXT NOT NULL,conversion_origin TEXT NOT NULL,reporting_origin TEXT NOT NULL,impression_time INTEGER NOT NULL,expiry_time INTEGER NOT NULL,num_conversions INTEGER DEFAULT 0,active INTEGER DEFAULT 1,conversion_destination TEXT NOT NULL,source_type INTEGER NOT NULL,attributed_truthfully INTEGER NOT NULL);
+
+CREATE TABLE conversions (conversion_id INTEGER PRIMARY KEY, impression_id INTEGER, conversion_data TEXT NOT NULL, conversion_time INTEGER NOT NULL, report_time INTEGER NOT NULL, attribution_credit INTEGER NOT NULL);
+
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+
+INSERT INTO meta VALUES('mmap_status','-1');
+INSERT INTO meta VALUES('version','3');
+INSERT INTO meta VALUES('last_compatible_version','3');
+
+CREATE INDEX conversion_destination_idx ON impressions(active, conversion_destination, reporting_origin);
+
+CREATE INDEX impression_expiry_idx ON impressions(expiry_time);
+
+CREATE INDEX impression_origin_idx ON impressions(impression_origin);
+
+CREATE INDEX conversion_report_idx ON conversions(report_time);
+
+CREATE INDEX conversion_impression_id_idx ON conversions(impression_id);
+
+COMMIT;
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index 55c484a..2671141fd 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -114,10 +114,6 @@
     }
     const std::string& ext_name = it.first.GetString();
 
-    if (ext_name == kExtensionLargeBlobKey && !request.large_blob_key) {
-      return false;
-    }
-
     if (ext_name == kExtensionHmacSecret) {
       // This extension is checked by |GetAssertionTask| because it needs to be
       // decrypted there.
diff --git a/docs/speed/metrics_changelog/2021_03_cls.md b/docs/speed/metrics_changelog/2021_03_cls.md
index faf9d5b..4119f85f 100644
--- a/docs/speed/metrics_changelog/2021_03_cls.md
+++ b/docs/speed/metrics_changelog/2021_03_cls.md
@@ -15,6 +15,10 @@
 
 [Source code](https://chromium-review.googlesource.com/c/chromium/src/+/2747689)
 
+### Improvement for shift with counterscroll
+
+[Source code](https://chromium-review.googlesource.com/c/chromium/src/+/2741240)
+
 ## When were users affected?
 
 Chrome 91 is currently scheduled to be released the week of May 25, 2021.
diff --git a/docs/speed/metrics_changelog/cls.md b/docs/speed/metrics_changelog/cls.md
index f9647b327..998fe24 100644
--- a/docs/speed/metrics_changelog/cls.md
+++ b/docs/speed/metrics_changelog/cls.md
@@ -5,6 +5,7 @@
 * Chrome 91
   * Metric definition improvement: [Ignore layout shift for more invisible elements](2021_03_cls.md)
   * Metric definition improvement: [Ignore inline direction shift moving from/to out of view](2021_03_cls.md)
+  * Metric definition improvement: [Improvement for shift with counterscroll](2021_03_cls.md)
 * Chrome 90
   * Metric definition improvement: [Bug fixes involving changes to transform, effect, clip or position](2021_02_cls.md)
   * Metric definition improvement: [Consider transform change countering layout shift](2021_02_cls.md)
diff --git a/docs/testing/code_coverage.md b/docs/testing/code_coverage.md
index a97ed21..fa43821 100644
--- a/docs/testing/code_coverage.md
+++ b/docs/testing/code_coverage.md
@@ -160,9 +160,9 @@
 
 ### Step 0 Download Tooling
 Generating code coverage reports requires llvm-profdata and llvm-cov tools.
-Currently, these two tools are not part of Chromium’s Clang bundle,
-[coverage script] downloads and updates them automatically, you can also
-download the tools manually ([tools link]).
+You can get them by adding `"checkout_clang_coverage_tools": True,` to 
+`custom_vars` in the `.gclient` config and run `gclient runhooks`. You can also
+download the tools manually ([tools link])
 
 ### Step 1 Build
 In Chromium, to compile code with coverage enabled, one needs to add
diff --git a/docs/vscode.md b/docs/vscode.md
index e29f5dab..0d73bf5 100644
--- a/docs/vscode.md
+++ b/docs/vscode.md
@@ -383,6 +383,26 @@
 "git.autorefresh": false,
 ```
 
+#### Editing in multiple Git repositories
+If you frequently work in multiple Git repositories that are part of the Chromium repository, you might find that the built-in tooling does not work as expected for files that exist below folders that are part of a `.gitignore` file checked in to Chromium.
+
+To work around this, you can add the directories you edit as separate `folders` entries in your workspace configuration, and ensure that the directories that are ignored in Chromium are listed **before** the Chromium `src` path.
+
+To edit this, go to `Settings` -> Select the `Workspace` tab, and choose to open as JSON (button in the top right), and configure `folders` like this (change paths to match your local setup and usage):
+
+```
+{
+  "folders": [
+    {
+      "path": "chromium/src/third_party/perfetto"
+    },
+    {
+      "path": "chromium/src"
+    }
+  ]
+}
+```
+
 ### Unable to open $File resource is not available when debugging Chromium on Linux
 Chromium [recently changed](https://docs.google.com/document/d/1OX4jY_bOCeNK7PNjVRuBQE9s6BQKS8XRNWGK8FEyh-E/edit?usp=sharing)
 the file path to be relative to the output dir. Check
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 710eb2c7..8bda951 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -751,10 +751,10 @@
               browser_context_));
   WebRequestProxyingURLLoaderFactory::StartProxying(
       browser_context, is_navigation ? -1 : render_process_id,
-      &request_id_generator_, std::move(navigation_ui_data),
-      std::move(navigation_id), ukm_source_id, std::move(proxied_receiver),
-      std::move(target_factory_remote), std::move(header_client_receiver),
-      proxies_.get(), type);
+      frame ? frame->GetRoutingID() : MSG_ROUTING_NONE, &request_id_generator_,
+      std::move(navigation_ui_data), std::move(navigation_id), ukm_source_id,
+      std::move(proxied_receiver), std::move(target_factory_remote),
+      std::move(header_client_receiver), proxies_.get(), type);
   return true;
 }
 
diff --git a/extensions/browser/api/web_request/web_request_info.cc b/extensions/browser/api/web_request/web_request_info.cc
index 16e08f2..e535b22 100644
--- a/extensions/browser/api/web_request/web_request_info.cc
+++ b/extensions/browser/api/web_request/web_request_info.cc
@@ -159,7 +159,7 @@
     int render_process_id,
     int render_frame_id,
     std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
-    int32_t routing_id,
+    int32_t view_routing_id,
     const network::ResourceRequest& request,
     bool is_download,
     bool is_async,
@@ -169,7 +169,7 @@
     : id(request_id),
       url(request.url),
       render_process_id(render_process_id),
-      routing_id(routing_id),
+      view_routing_id(view_routing_id),
       frame_id(render_frame_id),
       method(request.method),
       is_navigation_request(!!navigation_ui_data),
@@ -207,7 +207,7 @@
     // Grab any WebView-related information if relevant.
     WebViewRendererState::WebViewInfo web_view_info;
     if (WebViewRendererState::GetInstance()->GetInfo(
-            render_process_id, routing_id, &web_view_info)) {
+            render_process_id, view_routing_id, &web_view_info)) {
       is_web_view = true;
       web_view_instance_id = web_view_info.instance_id;
       web_view_rules_registry_id = web_view_info.rules_registry_id;
@@ -227,7 +227,7 @@
     : id(params.id),
       url(std::move(params.url)),
       render_process_id(params.render_process_id),
-      routing_id(params.routing_id),
+      view_routing_id(params.view_routing_id),
       frame_id(params.frame_id),
       method(std::move(params.method)),
       is_navigation_request(params.is_navigation_request),
diff --git a/extensions/browser/api/web_request/web_request_info.h b/extensions/browser/api/web_request/web_request_info.h
index 507e307..76a57347 100644
--- a/extensions/browser/api/web_request/web_request_info.h
+++ b/extensions/browser/api/web_request/web_request_info.h
@@ -47,7 +47,7 @@
       int render_process_id,
       int render_frame_id,
       std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
-      int32_t routing_id,
+      int32_t view_routing_id,
       const network::ResourceRequest& request,
       bool is_download,
       bool is_async,
@@ -60,7 +60,7 @@
   uint64_t id = 0;
   GURL url;
   int render_process_id = -1;
-  int routing_id = MSG_ROUTING_NONE;
+  int view_routing_id = MSG_ROUTING_NONE;
   int frame_id = -1;
   std::string method;
   bool is_navigation_request = false;
@@ -108,7 +108,7 @@
   const int render_process_id;
 
   // The routing ID of the object which initiated the request, if applicable.
-  const int routing_id = MSG_ROUTING_NONE;
+  const int view_routing_id = MSG_ROUTING_NONE;
 
   // The render frame ID of the frame which initiated this request, or -1 if
   // the request was not initiated by a frame.
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
index a774f68..aaa4e49 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
@@ -97,7 +97,8 @@
     WebRequestProxyingURLLoaderFactory* factory,
     uint64_t request_id,
     int32_t network_service_request_id,
-    int32_t routing_id,
+    int32_t view_routing_id,
+    int32_t frame_routing_id,
     uint32_t options,
     ukm::SourceIdObj ukm_source_id,
     const network::ResourceRequest& request,
@@ -109,7 +110,8 @@
       original_initiator_(request.request_initiator),
       request_id_(request_id),
       network_service_request_id_(network_service_request_id),
-      routing_id_(routing_id),
+      view_routing_id_(view_routing_id),
+      frame_routing_id_(frame_routing_id),
       options_(options),
       ukm_source_id_(ukm_source_id),
       traffic_annotation_(traffic_annotation),
@@ -134,11 +136,13 @@
 WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
     WebRequestProxyingURLLoaderFactory* factory,
     uint64_t request_id,
+    int32_t frame_routing_id,
     const network::ResourceRequest& request)
     : factory_(factory),
       request_(request),
       original_initiator_(request.request_initiator),
       request_id_(request_id),
+      frame_routing_id_(frame_routing_id),
       ukm_source_id_(ukm::kInvalidSourceIdObj),
       proxied_loader_receiver_(this),
       for_cors_preflight_(true),
@@ -191,10 +195,10 @@
   network::ResourceRequest request_for_info = request_;
   request_for_info.request_initiator = original_initiator_;
   info_.emplace(WebRequestInfoInitParams(
-      request_id_, factory_->render_process_id_, request_.render_frame_id,
+      request_id_, factory_->render_process_id_, frame_routing_id_,
       factory_->navigation_ui_data_ ? factory_->navigation_ui_data_->DeepCopy()
                                     : nullptr,
-      routing_id_, request_for_info, factory_->IsForDownload(),
+      view_routing_id_, request_for_info, factory_->IsForDownload(),
       !(options_ & network::mojom::kURLLoadOptionSynchronous),
       factory_->IsForServiceWorkerScript(), factory_->navigation_id_,
       ukm_source_id_));
@@ -656,7 +660,7 @@
     if (has_any_extra_headers_listeners_)
       options |= network::mojom::kURLLoadOptionUseHeaderClient;
     factory_->target_factory_->CreateLoaderAndStart(
-        target_loader_.BindNewPipeAndPassReceiver(), info_->routing_id,
+        target_loader_.BindNewPipeAndPassReceiver(), info_->view_routing_id,
         network_service_request_id_, options, request_,
         proxied_client_receiver_.BindNewPipeAndPassRemote(),
         traffic_annotation_);
@@ -1050,7 +1054,7 @@
     // be retrieved in the restarted request, which will call
     // RequestIDGenerator::Generate() with the same ID pair.
     factory_->request_id_generator_->SaveID(
-        routing_id_, network_service_request_id_, request_id_);
+        view_routing_id_, network_service_request_id_, request_id_);
 
     state_ = State::kRedirectFollowedByAnotherInProgressRequest;
     // Deletes |this|.
@@ -1097,6 +1101,7 @@
 WebRequestProxyingURLLoaderFactory::WebRequestProxyingURLLoaderFactory(
     content::BrowserContext* browser_context,
     int render_process_id,
+    int frame_routing_id,
     WebRequestAPI::RequestIDGenerator* request_id_generator,
     std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
     base::Optional<int64_t> navigation_id,
@@ -1109,6 +1114,7 @@
     content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type)
     : browser_context_(browser_context),
       render_process_id_(render_process_id),
+      frame_routing_id_(frame_routing_id),
       request_id_generator_(request_id_generator),
       navigation_ui_data_(std::move(navigation_ui_data)),
       navigation_id_(std::move(navigation_id)),
@@ -1141,6 +1147,7 @@
 void WebRequestProxyingURLLoaderFactory::StartProxying(
     content::BrowserContext* browser_context,
     int render_process_id,
+    int frame_routing_id,
     WebRequestAPI::RequestIDGenerator* request_id_generator,
     std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
     base::Optional<int64_t> navigation_id,
@@ -1154,10 +1161,11 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   auto proxy = std::make_unique<WebRequestProxyingURLLoaderFactory>(
-      browser_context, render_process_id, request_id_generator,
-      std::move(navigation_ui_data), std::move(navigation_id), ukm_source_id,
-      std::move(loader_receiver), std::move(target_factory_remote),
-      std::move(header_client_receiver), proxies, loader_factory_type);
+      browser_context, render_process_id, frame_routing_id,
+      request_id_generator, std::move(navigation_ui_data),
+      std::move(navigation_id), ukm_source_id, std::move(loader_receiver),
+      std::move(target_factory_remote), std::move(header_client_receiver),
+      proxies, loader_factory_type);
 
   proxies->AddProxy(std::move(proxy));
 }
@@ -1198,10 +1206,11 @@
   }
 
   auto result = requests_.emplace(
-      web_request_id, std::make_unique<InProgressRequest>(
-                          this, web_request_id, request_id, routing_id, options,
-                          ukm_source_id_, request, traffic_annotation,
-                          std::move(loader_receiver), std::move(client)));
+      web_request_id,
+      std::make_unique<InProgressRequest>(
+          this, web_request_id, request_id, routing_id, frame_routing_id_,
+          options, ukm_source_id_, request, traffic_annotation,
+          std::move(loader_receiver), std::move(client)));
   result.first->second->Restart();
 }
 
@@ -1238,8 +1247,8 @@
       request_id_generator_->Generate(MSG_ROUTING_NONE, 0);
 
   auto result = requests_.insert(std::make_pair(
-      web_request_id,
-      std::make_unique<InProgressRequest>(this, web_request_id, request)));
+      web_request_id, std::make_unique<InProgressRequest>(
+                          this, web_request_id, frame_routing_id_, request)));
 
   result.first->second->OnLoaderCreated(std::move(receiver));
   result.first->second->Restart();
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
index 16b0a836..988a33e2 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
@@ -16,6 +16,7 @@
 #include "base/optional.h"
 #include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
 #include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_frame_host.h"
 #include "extensions/browser/api/web_request/web_request_api.h"
 #include "extensions/browser/api/web_request/web_request_info.h"
 #include "extensions/common/extension_id.h"
@@ -53,7 +54,8 @@
     InProgressRequest(
         WebRequestProxyingURLLoaderFactory* factory,
         uint64_t request_id,
-        int32_t routing_id,
+        int32_t view_routing_id,
+        int32_t frame_routing_id,
         int32_t network_service_request_id,
         uint32_t options,
         ukm::SourceIdObj ukm_source_id,
@@ -64,6 +66,7 @@
     // For CORS preflights
     InProgressRequest(WebRequestProxyingURLLoaderFactory* factory,
                       uint64_t request_id,
+                      int32_t frame_routing_id,
                       const network::ResourceRequest& request);
     ~InProgressRequest() override;
 
@@ -178,7 +181,8 @@
     const base::Optional<url::Origin> original_initiator_;
     const uint64_t request_id_ = 0;
     const int32_t network_service_request_id_ = 0;
-    const int32_t routing_id_ = 0;
+    const int32_t view_routing_id_ = MSG_ROUTING_NONE;
+    const int32_t frame_routing_id_ = MSG_ROUTING_NONE;
     const uint32_t options_ = 0;
     const ukm::SourceIdObj ukm_source_id_;
     const net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
@@ -247,6 +251,7 @@
   WebRequestProxyingURLLoaderFactory(
       content::BrowserContext* browser_context,
       int render_process_id,
+      int frame_routing_id,
       WebRequestAPI::RequestIDGenerator* request_id_generator,
       std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
       base::Optional<int64_t> navigation_id,
@@ -264,6 +269,7 @@
   static void StartProxying(
       content::BrowserContext* browser_context,
       int render_process_id,
+      int frame_routing_id,
       WebRequestAPI::RequestIDGenerator* request_id_generator,
       std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
       base::Optional<int64_t> navigation_id,
@@ -322,6 +328,7 @@
 
   content::BrowserContext* const browser_context_;
   const int render_process_id_;
+  const int frame_routing_id_;
   WebRequestAPI::RequestIDGenerator* const request_id_generator_;
   std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data_;
   base::Optional<int64_t> navigation_id_;
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index ac144de3..44e1cc88 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -220,7 +220,6 @@
         "chrome://extensions/*",
         "chrome://home/*",
         "chrome://chrome-signin/*",
-        "chrome://hats/*",
         "chrome://mobilesetup/*",
         "chrome://oobe/*",
         "chrome://os-settings/*",
@@ -640,7 +639,6 @@
       "chrome://assistant-optin/*",
       "chrome://chrome-signin/*",
       "chrome://discards/*",
-      "chrome://hats/*",
       "chrome://home/*",
       "chrome://mobilesetup/*",
       "chrome://oobe/*",
@@ -662,7 +660,6 @@
       "chrome://assistant-optin/*",
       "chrome://chrome-signin/*",
       "chrome://discards/*",
-      "chrome://hats/*",
       "chrome://home/*",
       "chrome://mobilesetup/*",
       "chrome://oobe/*",
@@ -682,7 +679,6 @@
       "chrome://assistant-optin/*",
       "chrome://chrome-signin/*",
       "chrome://discards/*",
-      "chrome://hats/*",
       "chrome://home/*",
       "chrome://mobilesetup/*",
       "chrome://oobe/*",
diff --git a/extensions/common/extension_l10n_util.cc b/extensions/common/extension_l10n_util.cc
index e54890e..dbbd4c9 100644
--- a/extensions/common/extension_l10n_util.cc
+++ b/extensions/common/extension_l10n_util.cc
@@ -403,7 +403,7 @@
 
 void GetAllLocales(std::set<std::string>* all_locales) {
   const std::vector<std::string>& available_locales =
-      l10n_util::GetAvailableLocales();
+      l10n_util::GetAvailableICULocales();
   // Add all parents of the current locale to the available locales set.
   // I.e. for sr_Cyrl_RS we add sr_Cyrl_RS, sr_Cyrl and sr.
   for (size_t i = 0; i < available_locales.size(); ++i) {
diff --git a/fuchsia/engine/browser/media_player_impl.cc b/fuchsia/engine/browser/media_player_impl.cc
index eb22d8b..ec90a4c 100644
--- a/fuchsia/engine/browser/media_player_impl.cc
+++ b/fuchsia/engine/browser/media_player_impl.cc
@@ -47,6 +47,12 @@
       return {};  // PlayerControl assumes that stop is always supported.
     case MediaSessionAction::kSwitchAudioDevice:
       return {};  // PlayerControl does not support switching audio device.
+    case MediaSessionAction::kToggleMicrophone:
+      return {};  // PlayerControl does not support toggling microphone.
+    case MediaSessionAction::kToggleCamera:
+      return {};  // PlayerControl does not support toggling camera.
+    case MediaSessionAction::kHangUp:
+      return {};  // PlayerControl does not support hanging up.
   }
 }
 
diff --git a/fuchsia/engine/browser/media_player_impl_unittest.cc b/fuchsia/engine/browser/media_player_impl_unittest.cc
index 5eea168d..cf81e99 100644
--- a/fuchsia/engine/browser/media_player_impl_unittest.cc
+++ b/fuchsia/engine/browser/media_player_impl_unittest.cc
@@ -37,6 +37,9 @@
   MOCK_METHOD0(EnterPictureInPicture, void());
   MOCK_METHOD0(ExitPictureInPicture, void());
   MOCK_METHOD1(SetAudioSinkId, void(const base::Optional<std::string>& id));
+  MOCK_METHOD0(ToggleMicrophone, void());
+  MOCK_METHOD0(ToggleCamera, void());
+  MOCK_METHOD0(HangUp, void());
 
   // content::MediaSession APIs faked to implement testing behaviour.
   MOCK_METHOD1(DidReceiveAction,
diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc
index 04eabf35..f8af8d4bf 100644
--- a/gpu/command_buffer/service/feature_info.cc
+++ b/gpu/command_buffer/service/feature_info.cc
@@ -493,7 +493,6 @@
   // changed on another decoder that does expose them.
   ScopedPixelUnpackBufferOverride scoped_pbo_override(has_pixel_buffers, 0);
 
-  AddExtensionString("GL_ANGLE_translated_shader_source");
   AddExtensionString("GL_CHROMIUM_async_pixel_transfers");
   AddExtensionString("GL_CHROMIUM_bind_uniform_location");
   AddExtensionString("GL_CHROMIUM_color_space_metadata");
@@ -526,6 +525,16 @@
     feature_flags_.angle_translated_shader_source = true;
   }
 
+  // The validating command decoder always supports
+  // ANGLE_translated_shader_source regardless of the underlying context. But
+  // passthrough relies on the underlying ANGLE context which can't always
+  // support it (e.g. on Vulkan shaders are translated directly to binary
+  // SPIR-V).
+  if (!is_passthrough_cmd_decoder_ ||
+      feature_flags_.angle_translated_shader_source) {
+    AddExtensionString("GL_ANGLE_translated_shader_source");
+  }
+
   // Check if we should allow GL_EXT_texture_compression_dxt1 and
   // GL_EXT_texture_compression_s3tc.
   bool enable_dxt1 = false;
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg
index 86740c5..0f616146 100644
--- a/infra/config/generated/commit-queue.cfg
+++ b/infra/config/generated/commit-queue.cfg
@@ -90,12 +90,6 @@
         owner_whitelist_group: "project-chromium-robot-committers"
       }
       builders {
-        name: "chrome/try/linux-chrome-beta"
-        includable_only: true
-        owner_whitelist_group: "googlers"
-        owner_whitelist_group: "project-chromium-robot-committers"
-      }
-      builders {
         name: "chrome/try/linux-chrome-stable"
         includable_only: true
         owner_whitelist_group: "googlers"
@@ -114,12 +108,6 @@
         owner_whitelist_group: "project-chromium-robot-committers"
       }
       builders {
-        name: "chrome/try/mac-chrome-beta"
-        includable_only: true
-        owner_whitelist_group: "googlers"
-        owner_whitelist_group: "project-chromium-robot-committers"
-      }
-      builders {
         name: "chrome/try/mac-chrome-stable"
         includable_only: true
         owner_whitelist_group: "googlers"
@@ -132,12 +120,6 @@
         owner_whitelist_group: "project-chromium-robot-committers"
       }
       builders {
-        name: "chrome/try/win-chrome-beta"
-        includable_only: true
-        owner_whitelist_group: "googlers"
-        owner_whitelist_group: "project-chromium-robot-committers"
-      }
-      builders {
         name: "chrome/try/win-chrome-stable"
         includable_only: true
         owner_whitelist_group: "googlers"
@@ -150,12 +132,6 @@
         owner_whitelist_group: "project-chromium-robot-committers"
       }
       builders {
-        name: "chrome/try/win64-chrome-beta"
-        includable_only: true
-        owner_whitelist_group: "googlers"
-        owner_whitelist_group: "project-chromium-robot-committers"
-      }
-      builders {
         name: "chrome/try/win64-chrome-stable"
         includable_only: true
         owner_whitelist_group: "googlers"
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index db765ac9..52e36ec2 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -12924,6 +12924,122 @@
       }
     }
     builders {
+      name: "ToTFuchsia x64"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:32"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        cmd: "recipes"
+      }
+      properties: "{\"$build/goma\":{\"use_luci_auth\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"$recipe_engine/isolated\":{\"server\":\"https://isolateserver.appspot.com\"},\"builder_group\":\"chromium.clang\",\"perf_dashboard_machine_group\":\"ChromiumClang\",\"recipe\":\"chromium\"}"
+      execution_timeout_secs: 50400
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium.resultdb.result_sink"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.junit_tests"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
+      name: "ToTFuchsiaOfficial"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:32"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        cmd: "recipes"
+      }
+      properties: "{\"$build/goma\":{\"use_luci_auth\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"$recipe_engine/isolated\":{\"server\":\"https://isolateserver.appspot.com\"},\"builder_group\":\"chromium.clang\",\"perf_dashboard_machine_group\":\"ChromiumClang\",\"recipe\":\"chromium\"}"
+      execution_timeout_secs: 50400
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium.resultdb.result_sink"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.junit_tests"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "ToTLinux"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -19815,6 +19931,64 @@
       }
     }
     builders {
+      name: "android-web-platform-pie-x86-fyi-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-16.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        cmd: "recipes"
+      }
+      properties: "{\"$build/goma\":{\"enable_ats\":true,\"rpc_extra_params\":\"?prod\",\"server_host\":\"goma.chromium.org\",\"use_luci_auth\":true},\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"$recipe_engine/isolated\":{\"server\":\"https://isolateserver.appspot.com\"},\"builder_group\":\"chromium.android.fyi\",\"recipe\":\"chromium\"}"
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium.resultdb.result_sink"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.junit_tests"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "android-weblayer-10-x86-rel-tests"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index 01b8584..e904bdc 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -2965,6 +2965,11 @@
     short_name: "P-WPT"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/android-web-platform-pie-x86-fyi-rel"
+    category: "builder_tester|web-platform"
+    short_name: "P"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/android-weblayer-pie-x86-wpt-fyi-rel"
     category: "builder_tester|weblayer"
     short_name: "P"
@@ -4317,6 +4322,16 @@
     short_name: "x64"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/ToTFuchsiaOfficial"
+    category: "ToT Fuchsia"
+    short_name: "off"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.ci/ToTFuchsia x64"
+    category: "ToT Fuchsia"
+    short_name: "x64"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/ToTiOS"
     category: "iOS|public"
     short_name: "sim"
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg
index 7803457..29f9679 100644
--- a/infra/config/generated/luci-scheduler.cfg
+++ b/infra/config/generated/luci-scheduler.cfg
@@ -2890,6 +2890,26 @@
   }
 }
 job {
+  id: "ToTFuchsia x64"
+  realm: "ci"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "ToTFuchsia x64"
+  }
+}
+job {
+  id: "ToTFuchsiaOfficial"
+  realm: "ci"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "ToTFuchsiaOfficial"
+  }
+}
+job {
   id: "ToTLinux"
   realm: "ci"
   acl_sets: "ci"
@@ -4641,6 +4661,16 @@
   }
 }
 job {
+  id: "android-web-platform-pie-x86-fyi-rel"
+  realm: "ci"
+  acl_sets: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "android-web-platform-pie-x86-fyi-rel"
+  }
+}
+job {
   id: "android-weblayer-10-x86-rel-tests"
   realm: "ci"
   acls {
@@ -6849,6 +6879,8 @@
   triggers: "ToTAndroidASan"
   triggers: "ToTAndroidCFI"
   triggers: "ToTAndroidOfficial"
+  triggers: "ToTFuchsia x64"
+  triggers: "ToTFuchsiaOfficial"
   triggers: "ToTLinux"
   triggers: "ToTLinux (dbg)"
   triggers: "ToTLinuxASan"
@@ -6928,6 +6960,7 @@
   triggers: "android-pie-arm64-wpt-rel-non-cq"
   triggers: "android-pie-x86-fyi-rel"
   triggers: "android-pie-x86-rel"
+  triggers: "android-web-platform-pie-x86-fyi-rel"
   triggers: "android-weblayer-pie-x86-wpt-fyi-rel"
   triggers: "android-weblayer-with-aosp-webview-x86-fyi-rel"
   triggers: "android-weblayer-x86-rel"
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index 9020d80..3856507c 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -989,6 +989,14 @@
 )
 
 ci.android_fyi_builder(
+    name = "android-web-platform-pie-x86-fyi-rel",
+    console_view_entry = consoles.console_view_entry(
+        category = "builder_tester|web-platform",
+        short_name = "P",
+    ),
+)
+
+ci.android_fyi_builder(
     name = "android-weblayer-pie-x86-wpt-fyi-rel",
     console_view_entry = consoles.console_view_entry(
         category = "builder_tester|weblayer",
@@ -2029,6 +2037,22 @@
     ),
 )
 
+ci.clang_builder(
+    name = "ToTFuchsia x64",
+    console_view_entry = consoles.console_view_entry(
+        category = "ToT Fuchsia",
+        short_name = "x64",
+    ),
+)
+
+ci.clang_builder(
+    name = "ToTFuchsiaOfficial",
+    console_view_entry = consoles.console_view_entry(
+        category = "ToT Fuchsia",
+        short_name = "off",
+    ),
+)
+
 def clang_tot_linux_builder(short_name, category = "ToT Linux", **kwargs):
     ci.clang_builder(
         console_view_entry = consoles.console_view_entry(
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 64a6991..dd89e70 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1864,10 +1864,6 @@
 
 chrome_internal_verifier(
     builder = "linux-chrome",
-)
-
-chrome_internal_verifier(
-    builder = "linux-chrome-beta",
     branch_selector = branches.STANDARD_MILESTONE,
 )
 
@@ -1882,10 +1878,6 @@
 
 chrome_internal_verifier(
     builder = "mac-chrome",
-)
-
-chrome_internal_verifier(
-    builder = "mac-chrome-beta",
     branch_selector = branches.STANDARD_MILESTONE,
 )
 
@@ -1896,10 +1888,6 @@
 
 chrome_internal_verifier(
     builder = "win-chrome",
-)
-
-chrome_internal_verifier(
-    builder = "win-chrome-beta",
     branch_selector = branches.STANDARD_MILESTONE,
 )
 
@@ -1910,10 +1898,6 @@
 
 chrome_internal_verifier(
     builder = "win64-chrome",
-)
-
-chrome_internal_verifier(
-    builder = "win64-chrome-beta",
     branch_selector = branches.STANDARD_MILESTONE,
 )
 
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 4e57f54..b3bef67e 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -425,10 +425,10 @@
      flag_descriptions::kForceStartupSigninPromoName,
      flag_descriptions::kForceStartupSigninPromoDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(signin::kForceStartupSigninPromo)},
-    {"embedder-block-restore-url",
-     flag_descriptions::kEmbedderBlockRestoreUrlName,
-     flag_descriptions::kEmbedderBlockRestoreUrlDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(kEmbedderBlockRestoreUrl)},
+    {"restore-session-from-cache",
+     flag_descriptions::kRestoreSessionFromCacheName,
+     flag_descriptions::kRestoreSessionFromCacheDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(web::features::kRestoreSessionFromCache)},
     {"autofill-save-card-dismiss-on-navigation",
      flag_descriptions::kAutofillSaveCardDismissOnNavigationName,
      flag_descriptions::kAutofillSaveCardDismissOnNavigationDescription,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 232c935..b6f52f5 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -161,10 +161,11 @@
 const char kEditPasswordsInSettingsDescription[] =
     "Enables password editing in settings.";
 
-const char kEmbedderBlockRestoreUrlName[] =
-    "Allow embedders to prevent certain URLs from restoring.";
-const char kEmbedderBlockRestoreUrlDescription[] =
-    "Embedders can prevent URLs from restoring.";
+const char kRestoreSessionFromCacheName[] =
+    "Use iOS_TBA native WKWebView sesion restoration.";
+const char kRestoreSessionFromCacheDescription[] =
+    "Enable iOS_TBA instant session restoration for faster and more "
+    "web session restoration.";
 
 const char kEnableAutofillAddressSavePromptName[] =
     "Autofill Address Save Prompts";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 3733f3f..2393a36 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -145,9 +145,9 @@
 extern const char kEditPasswordsInSettingsName[];
 extern const char kEditPasswordsInSettingsDescription[];
 
-// Title and description for the flag to block restore urls.
-extern const char kEmbedderBlockRestoreUrlName[];
-extern const char kEmbedderBlockRestoreUrlDescription[];
+// Title and description for the flag to native restore web states.
+extern const char kRestoreSessionFromCacheName[];
+extern const char kRestoreSessionFromCacheDescription[];
 
 // Title and description for the flag to enable autofill address save prompts.
 extern const char kEnableAutofillAddressSavePromptName[];
diff --git a/ios/chrome/browser/ui/ui_feature_flags.cc b/ios/chrome/browser/ui/ui_feature_flags.cc
index cb1cae8..4376cebe 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.cc
+++ b/ios/chrome/browser/ui/ui_feature_flags.cc
@@ -16,9 +16,6 @@
 const base::Feature kSettingsRefresh{"SettingsRefresh",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kEmbedderBlockRestoreUrl{"EmbedderBlockRestoreUrl",
-                                             base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kVoiceOverUnstackedTabstrip{
     "VoiceOverUnstackedTabstrip", base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/ios/chrome/browser/ui/ui_feature_flags.h b/ios/chrome/browser/ui/ui_feature_flags.h
index 6f41218..ea2e484 100644
--- a/ios/chrome/browser/ui/ui_feature_flags.h
+++ b/ios/chrome/browser/ui/ui_feature_flags.h
@@ -17,9 +17,6 @@
 // Feature to apply UI Refresh theme to the settings.
 extern const base::Feature kSettingsRefresh;
 
-// Feature flag for embedders to block restore urls.
-extern const base::Feature kEmbedderBlockRestoreUrl;
-
 // Feature flag to use the unstacked tabstrip when voiceover is enabled.
 extern const base::Feature kVoiceOverUnstackedTabstrip;
 
diff --git a/ios/chrome/browser/web/chrome_web_client.h b/ios/chrome/browser/web/chrome_web_client.h
index 97916aea..180f204 100644
--- a/ios/chrome/browser/web/chrome_web_client.h
+++ b/ios/chrome/browser/web/chrome_web_client.h
@@ -24,8 +24,6 @@
   void AddAdditionalSchemes(Schemes* schemes) const override;
   std::string GetApplicationLocale() const override;
   bool IsAppSpecificURL(const GURL& url) const override;
-  bool ShouldBlockUrlDuringRestore(const GURL& url,
-                                   web::WebState* web_state) const override;
   void AddSerializableData(web::SerializableUserDataManager* user_data_manager,
                            web::WebState* web_state) override;
   std::u16string GetPluginNotSupportedText() const override;
@@ -68,7 +66,6 @@
   bool ForceMobileVersionByDefault(const GURL& url) override;
   web::UserAgentType GetDefaultUserAgent(id<UITraitEnvironment> web_view,
                                          const GURL& url) override;
-  bool IsEmbedderBlockRestoreUrlEnabled() override;
 
  private:
   // Reference to a view that is attached to a window.
diff --git a/ios/chrome/browser/web/chrome_web_client.mm b/ios/chrome/browser/web/chrome_web_client.mm
index b438f25..b17d1d6 100644
--- a/ios/chrome/browser/web/chrome_web_client.mm
+++ b/ios/chrome/browser/web/chrome_web_client.mm
@@ -216,13 +216,6 @@
   return url.SchemeIs(kChromeUIScheme);
 }
 
-bool ChromeWebClient::ShouldBlockUrlDuringRestore(
-    const GURL& url,
-    web::WebState* web_state) const {
-  return ios::GetChromeBrowserProvider()->ShouldBlockUrlDuringRestore(
-      url, web_state);
-}
-
 void ChromeWebClient::AddSerializableData(
     web::SerializableUserDataManager* user_data_manager,
     web::WebState* web_state) {
@@ -451,7 +444,3 @@
   return isRegularRegular ? web::UserAgentType::DESKTOP
                           : web::UserAgentType::MOBILE;
 }
-
-bool ChromeWebClient::IsEmbedderBlockRestoreUrlEnabled() {
-  return ios::GetChromeBrowserProvider()->MightBlockUrlDuringRestore();
-}
diff --git a/ios/chrome/test/data/policy/policy_test_cases.json b/ios/chrome/test/data/policy/policy_test_cases.json
index 8ee6cfa..d6ed680 100644
--- a/ios/chrome/test/data/policy/policy_test_cases.json
+++ b/ios/chrome/test/data/policy/policy_test_cases.json
@@ -7,7 +7,7 @@
 
 
     "intro": "The schema below is a subset of the schema in chrome/test/data/policy/policy_test_cases.json",
-    "can_be_recommended": "Whether a recommended value may be set for the policy. Defaults to |false| if not specified.",
+    "can_be_recommended": "Whether a recommended value may be set for the policy. Defaults to |false| if not specified. Use |check_for_mandatory| and |check_for_recommended| if you want to provide separate test cases for mandatory/recommended. Otherwise, the same pref mapping is checked for both mandatory+recommended modes when enabled or only as mandatory if disabled.",
     "note": "If the policy affects any preferences, the following array should be specified with one entry per such preference.",
 
     "policy_pref_mapping_tests": [
@@ -16,8 +16,10 @@
         "prefs": {
           "${pref}": {
             "value": "The value that |pref| should take on. Defaults to the value that the policy was set to.",
-            "expect_default": "Whether or not the pref value should be the default one.",
-            "location": "The location where the pref is registered, possible values are ['user_profile', 'local_state']. Defaults to 'user_profile' if not specified."
+            "expect_default": "Whether or not the pref value should be the default one (i.e. unmanaged and user-modifiable). Defaults to false if not specified.",
+            "location": "The location where the pref is registered, possible values are ['user_profile', 'local_state']. Defaults to 'user_profile' if not specified.",
+            "check_for_mandatory": "Should the preference be tested when a mandatory value is set for the policy? Defaults to |true| if not specified. See |can_be_recommended|.",
+            "check_for_recommended": "Should the preference be tested when a recommended value is set for the policy? Defaults to |true| if not specified. See |can_be_recommended|."
           }
         }
       }
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
index 4d4a50db..0bbcb45 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-a3b3fa3693940c92be6ff50467dddcd2c1d16846
\ No newline at end of file
+f3db8962832ded197d3a06539008bc7f5571a370
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
index 7c15ad8..b10a635 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-f2f2bb65d95564ce349c38b50a5b7d32014a1542
\ No newline at end of file
+2226f1bc4176452c0594931ea38a5faab5ee5fd7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
index 22c695b..875db470 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-c16567eace98174a545f8523db046bfeb1424d43
\ No newline at end of file
+5c9ce4fe564d0914189a126afb4ef1e2253ae969
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
index 5fe65b1..17f34a23 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-64f52cb5b9c990bb331273985105ce3b2768eece
\ No newline at end of file
+74d3717d8c75fba690bdd2ff9d3ea9ca47003261
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
index 94090fd3..fdc49c85 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-fe95444989f195e2cb8fd85c4bb005c0269e7b0c
\ No newline at end of file
+d1a98d6ee56e08e77111f64355f183bfad9c73d0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
index 47b5d8a7..cde02f4 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-ddd6dd9a8117da59f9de5bbf6fa0c8e2687d5940
\ No newline at end of file
+6238bfbef470172c052a405d21c8cfee7f1ff00e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
index 663690c..3961f0e 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-f9607b4d7422c95f5c9060b0a18d86f398a169dd
\ No newline at end of file
+69a8212c46fdbb1401e0629fab033be8d20c9e40
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
index fdd2e2b..0cd33551 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-9ec63dcda53f7998d3bd0696bfe5864bba4b6c6e
\ No newline at end of file
+096e0583e499642ed5cba39ec22b2014ec98a497
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
index 3e35ec1..67537e1 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-65244fe25c609848e293279b53b890a272fb8117
\ No newline at end of file
+dc6d519e7285589d76ca259cf14dce195ef51aac
\ No newline at end of file
diff --git a/ios/public/provider/chrome/browser/chrome_browser_provider.h b/ios/public/provider/chrome/browser/chrome_browser_provider.h
index 7137e10..1129a77c 100644
--- a/ios/public/provider/chrome/browser/chrome_browser_provider.h
+++ b/ios/public/provider/chrome/browser/chrome_browser_provider.h
@@ -39,7 +39,6 @@
 class WebState;
 }
 
-class GURL;
 @protocol LogoVendor;
 @class UITextField;
 @class UIView;
@@ -123,10 +122,6 @@
   // Whether the embedder might block specific URL.
   virtual bool MightBlockUrlDuringRestore();
 
-  // Allow embedders to block a specific URL.
-  virtual bool ShouldBlockUrlDuringRestore(const GURL& url,
-                                           web::WebState* web_state);
-
   // Attaches any embedder-specific tab helpers to the given |web_state|.
   virtual void AttachTabHelpers(web::WebState* web_state) const;
 
diff --git a/ios/public/provider/chrome/browser/chrome_browser_provider.mm b/ios/public/provider/chrome/browser/chrome_browser_provider.mm
index 8066bef..118cfdc 100644
--- a/ios/public/provider/chrome/browser/chrome_browser_provider.mm
+++ b/ios/public/provider/chrome/browser/chrome_browser_provider.mm
@@ -83,12 +83,6 @@
   return false;
 }
 
-bool ChromeBrowserProvider::ShouldBlockUrlDuringRestore(
-    const GURL& url,
-    web::WebState* web_state) {
-  return false;
-}
-
 UITextField* ChromeBrowserProvider::CreateStyledTextField() const {
   return nil;
 }
diff --git a/ios/web/common/features.h b/ios/web/common/features.h
index 0b8e377..2a637e8a 100644
--- a/ios/web/common/features.h
+++ b/ios/web/common/features.h
@@ -65,6 +65,9 @@
 // generate PDF when Page Snapshot is taken just to record PDF size.
 extern const base::Feature kRecordSnapshotSize;
 
+// Feature flag for to use native session restoration.
+extern const base::Feature kRestoreSessionFromCache;
+
 // When enabled, use the native context menu in web content, for the iOS version
 // that supports it.
 extern const base::Feature kWebViewNativeContextMenu;
diff --git a/ios/web/common/features.mm b/ios/web/common/features.mm
index aa19124..626b23c 100644
--- a/ios/web/common/features.mm
+++ b/ios/web/common/features.mm
@@ -49,6 +49,9 @@
 const base::Feature kRecordSnapshotSize{"RecordSnapshotSize",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kRestoreSessionFromCache{"RestoreSessionFromCache",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kWebViewNativeContextMenu{
     "WebViewNativeContextMenu", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/ios/web/navigation/navigation_item_storage_builder.mm b/ios/web/navigation/navigation_item_storage_builder.mm
index 9da16008..a3b78d28 100644
--- a/ios/web/navigation/navigation_item_storage_builder.mm
+++ b/ios/web/navigation/navigation_item_storage_builder.mm
@@ -67,9 +67,7 @@
   // because it is already a session restoration item or because it is an
   // external PDF), don't restore it to avoid issues. See
   // http://crbug.com/1017147 , 1076851 and 1065433.
-  bool should_use_url = navigation_item_storage.URL.SchemeIsHTTPOrHTTPS() ||
-                        web::GetWebClient()->IsEmbedderBlockRestoreUrlEnabled();
-  if (should_use_url) {
+  if (navigation_item_storage.URL.SchemeIsHTTPOrHTTPS()) {
     item->SetURL(navigation_item_storage.URL);
     item->SetVirtualURL(navigation_item_storage.virtualURL);
   } else {
diff --git a/ios/web/navigation/navigation_manager_impl.h b/ios/web/navigation/navigation_manager_impl.h
index 4dc7e55..d4e765c 100644
--- a/ios/web/navigation/navigation_manager_impl.h
+++ b/ios/web/navigation/navigation_manager_impl.h
@@ -198,8 +198,8 @@
   // matches |url|.  Applies the workaround for crbug.com/997182
   void SetWKWebViewNextPendingUrlNotSerializable(const GURL& url);
 
-  // Returns true if specific URL is blocked from session restore.
-  bool ShouldBlockUrlDuringRestore(const GURL& url);
+  // Returns true if URL was restored via session restoration cache.
+  bool RestoreSessionFromCache(const GURL& url);
 
   // Resets the transient url rewriter list.
   void RemoveTransientURLRewriters();
diff --git a/ios/web/navigation/navigation_manager_impl.mm b/ios/web/navigation/navigation_manager_impl.mm
index 83e4fc6b..8c457f1 100644
--- a/ios/web/navigation/navigation_manager_impl.mm
+++ b/ios/web/navigation/navigation_manager_impl.mm
@@ -489,24 +489,11 @@
   next_pending_url_should_skip_serialization_ = url;
 }
 
-bool NavigationManagerImpl::ShouldBlockUrlDuringRestore(const GURL& url) {
+bool NavigationManagerImpl::RestoreSessionFromCache(const GURL& url) {
   DCHECK(is_restore_session_in_progress_);
-  if (!web::GetWebClient()->ShouldBlockUrlDuringRestore(url, GetWebState()))
-    return false;
 
-  // Abort restore.
-  DiscardNonCommittedItems();
-  last_committed_item_index_ = web_view_cache_.GetCurrentItemIndex();
-  if (restored_visible_item_ &&
-      restored_visible_item_->GetUserAgentType() != UserAgentType::NONE) {
-    NavigationItem* last_committed_item =
-        GetLastCommittedItemInCurrentOrRestoredSession();
-    last_committed_item->SetUserAgentType(
-        restored_visible_item_->GetUserAgentType());
-  }
-  restored_visible_item_.reset();
-  FinalizeSessionRestore();
-  return true;
+  // TODO(crbug.com/1174560): Bring up native session restoration.
+  return false;
 }
 
 void NavigationManagerImpl::RemoveTransientURLRewriters() {
diff --git a/ios/web/public/test/fakes/fake_web_client.h b/ios/web/public/test/fakes/fake_web_client.h
index 0e8add5..3164ac3d 100644
--- a/ios/web/public/test/fakes/fake_web_client.h
+++ b/ios/web/public/test/fakes/fake_web_client.h
@@ -28,9 +28,6 @@
   // Returns true for kTestWebUIScheme URL.
   bool IsAppSpecificURL(const GURL& url) const override;
 
-  bool ShouldBlockUrlDuringRestore(const GURL& url,
-                                   WebState* web_state) const override;
-
   void AddSerializableData(web::SerializableUserDataManager* user_data_manager,
                            web::WebState* web_state) override;
 
diff --git a/ios/web/public/test/fakes/fake_web_client.mm b/ios/web/public/test/fakes/fake_web_client.mm
index b255759..4b0b0ea 100644
--- a/ios/web/public/test/fakes/fake_web_client.mm
+++ b/ios/web/public/test/fakes/fake_web_client.mm
@@ -36,11 +36,6 @@
   return url.SchemeIs(kTestWebUIScheme) || url.SchemeIs(kTestAppSpecificScheme);
 }
 
-bool FakeWebClient::ShouldBlockUrlDuringRestore(const GURL& url,
-                                                WebState* web_state) const {
-  return false;
-}
-
 void FakeWebClient::AddSerializableData(
     web::SerializableUserDataManager* user_data_manager,
     web::WebState* web_state) {}
diff --git a/ios/web/public/web_client.h b/ios/web/public/web_client.h
index 59243066..0e6de9b 100644
--- a/ios/web/public/web_client.h
+++ b/ios/web/public/web_client.h
@@ -85,10 +85,6 @@
   // browser would return true for "chrome://about" URL.
   virtual bool IsAppSpecificURL(const GURL& url) const;
 
-  // Returns true if URL should not be restored.
-  virtual bool ShouldBlockUrlDuringRestore(const GURL& url,
-                                           WebState* web_state) const;
-
   // Allow embedder to inject data.
   virtual void AddSerializableData(
       web::SerializableUserDataManager* user_data_manager,
@@ -211,9 +207,6 @@
   // content, based on the size class of |web_view| and the |url|.
   virtual UserAgentType GetDefaultUserAgent(id<UITraitEnvironment> web_view,
                                             const GURL& url);
-
-  // Returns whether the embedders could block restore urls.
-  virtual bool IsEmbedderBlockRestoreUrlEnabled();
 };
 
 }  // namespace web
diff --git a/ios/web/web_client.mm b/ios/web/web_client.mm
index 97031a5e..6ef0859 100644
--- a/ios/web/web_client.mm
+++ b/ios/web/web_client.mm
@@ -44,11 +44,6 @@
   return false;
 }
 
-bool WebClient::ShouldBlockUrlDuringRestore(const GURL& url,
-                                            WebState* web_state) const {
-  return false;
-}
-
 void WebClient::AddSerializableData(
     web::SerializableUserDataManager* user_data_manager,
     web::WebState* web_state) {}
@@ -138,8 +133,4 @@
   return UserAgentType::MOBILE;
 }
 
-bool WebClient::IsEmbedderBlockRestoreUrlEnabled() {
-  return false;
-}
-
 }  // namespace web
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index e6e9648..468b098 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -1115,20 +1115,6 @@
                            loadSuccess:loadSuccess
                                context:context];
 
-  if (web::GetWebClient()->IsEmbedderBlockRestoreUrlEnabled()) {
-    if (@available(iOS 14, *)) {
-    } else {
-      if (@available(iOS 13.5, *)) {
-        // In some cases on iOS 13.5, when restoring about: URL, the load might
-        // never ends. Make sure to mark the load as done here. This is fixed in
-        // iOS 14. See crbug.com/1099235.
-        if (currentURL.SchemeIs(url::kAboutScheme)) {
-          self.webStateImpl->SetIsLoading(false);
-        }
-      }
-    }
-  }
-
   // Execute the pending LoadCompleteActions.
   for (ProceduralBlock action in _pendingLoadCompleteActions) {
     action();
diff --git a/ios/web/web_state/ui/crw_web_request_controller.mm b/ios/web/web_state/ui/crw_web_request_controller.mm
index 20f465b..3a4059b4 100644
--- a/ios/web/web_state/ui/crw_web_request_controller.mm
+++ b/ios/web/web_state/ui/crw_web_request_controller.mm
@@ -591,8 +591,8 @@
                   placeholderNavigation:isPlaceholderURL];
 
     if (self.navigationManagerImpl->IsRestoreSessionInProgress()) {
-      if (self.navigationManagerImpl->ShouldBlockUrlDuringRestore(
-              navigationURL)) {
+      if (self.navigationManagerImpl->RestoreSessionFromCache(navigationURL)) {
+        // Return early if the session was restored from cache.
         return;
       }
       [_delegate
diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h
index 8a44ae6..e9414b1 100644
--- a/ipc/ipc_message_start.h
+++ b/ipc/ipc_message_start.h
@@ -13,7 +13,6 @@
 // for all new work.
 enum IPCMessageStart {
   AutomationMsgStart = 0,
-  FrameMsgStart,
   TestMsgStart,
   WorkerMsgStart,
   NaClMsgStart,
diff --git a/ipc/message_filter_router.cc b/ipc/message_filter_router.cc
index 344b83ca..685443b 100644
--- a/ipc/message_filter_router.cc
+++ b/ipc/message_filter_router.cc
@@ -51,7 +51,6 @@
   // messages of a certain class.
   std::vector<uint32_t> supported_message_classes;
   if (filter->GetSupportedMessageClasses(&supported_message_classes)) {
-    DCHECK(!supported_message_classes.empty());
     for (size_t i = 0; i < supported_message_classes.size(); ++i) {
       const int message_class = supported_message_classes[i];
       DCHECK(ValidMessageClass(message_class));
diff --git a/ipc/trace_ipc_message.cc b/ipc/trace_ipc_message.cc
index ffeb3879..eecc3bd 100644
--- a/ipc/trace_ipc_message.cc
+++ b/ipc/trace_ipc_message.cc
@@ -22,9 +22,6 @@
     case AutomationMsgStart:
       message_class = ChromeLegacyIpc::CLASS_AUTOMATION;
       break;
-    case FrameMsgStart:
-      message_class = ChromeLegacyIpc::CLASS_FRAME;
-      break;
     case TestMsgStart:
       message_class = ChromeLegacyIpc::CLASS_TEST;
       break;
diff --git a/media/audio/android/aaudio.sigs b/media/audio/android/aaudio.sigs
index 95cbbb4..1d89b56d 100644
--- a/media/audio/android/aaudio.sigs
+++ b/media/audio/android/aaudio.sigs
@@ -27,8 +27,6 @@
 aaudio_result_t AAudioStream_requestStop(AAudioStream* stream);
 aaudio_result_t AAudioStream_getTimestamp(AAudioStream* stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds);
 aaudio_result_t AAudioStream_setBufferSizeInFrames(AAudioStream* stream, int32_t numFrames);
-int32_t AAudioStream_getBufferCapacityInFrames(AAudioStream* stream);
-int32_t AAudioStream_getBufferSizeInFrames(AAudioStream* stream);
 int32_t AAudioStream_getFramesPerBurst(AAudioStream* stream);
 int64_t AAudioStream_getFramesWritten(AAudioStream* stream);
 aaudio_result_t AAudioStream_waitForStateChange(AAudioStream* stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds);
\ No newline at end of file
diff --git a/media/audio/android/aaudio_output.cc b/media/audio/android/aaudio_output.cc
index 74e5812..9e9b53ab 100644
--- a/media/audio/android/aaudio_output.cc
+++ b/media/audio/android/aaudio_output.cc
@@ -100,37 +100,18 @@
   if (AAUDIO_OK != result)
     return false;
 
-  switch (performance_mode_) {
-    case AAUDIO_PERFORMANCE_MODE_NONE:
-    case AAUDIO_PERFORMANCE_MODE_POWER_SAVING: {
-      // For the high-latency modes, it is better to match the requested
-      // buffer size and the AAudio's internal buffer capacity to reduce the
-      // irregulairty of the callback interval. See crbug.com/1181434.
-      int32_t buffer_capacity =
-          AAudioStream_getBufferCapacityInFrames(aaudio_stream_);
-      AAudioStream_setBufferSizeInFrames(aaudio_stream_, buffer_capacity);
-      break;
-    }
-    case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY: {
-      // For the low latency mode, set the effective buffer size to 3X the burst
-      // size to prevent glitching when the burst is small (e.g. < 128). On some
-      // devices you can get by with 1X or 2X, but 3X is safer.
-      int32_t frames_per_burst = AAudioStream_getFramesPerBurst(aaudio_stream_);
-      int32_t buffer_size_requested =
-          frames_per_burst * (frames_per_burst < 128 ? 3 : 2);
-      AAudioStream_setBufferSizeInFrames(aaudio_stream_, buffer_size_requested);
-      break;
-    }
-    default:
-      NOTREACHED();
-  }
+  // After opening the stream, sets the effective buffer size to 3X the burst
+  // size to prevent glitching if the burst is small (e.g. < 128). On some
+  // devices you can get by with 1X or 2X, but 3X is safer.
+  int32_t framesPerBurst = AAudioStream_getFramesPerBurst(aaudio_stream_);
+  int32_t sizeRequested = framesPerBurst * (framesPerBurst < 128 ? 3 : 2);
+  AAudioStream_setBufferSizeInFrames(aaudio_stream_, sizeRequested);
 
   audio_bus_ = AudioBus::Create(params_);
 
   TRACE_EVENT2("audio", "AAudioOutputStream::Open",
                "params_", params_.AsHumanReadableString(),
-               "AAudio BufferSizeInFrames",
-               AAudioStream_getBufferSizeInFrames(aaudio_stream_));
+               "requested BufferSizeInFrames", sizeRequested);
 
   return true;
 }
diff --git a/media/base/win/dxgi_device_scope_handle_unittest.cc b/media/base/win/dxgi_device_scope_handle_unittest.cc
index be16636..6e98c00 100644
--- a/media/base/win/dxgi_device_scope_handle_unittest.cc
+++ b/media/base/win/dxgi_device_scope_handle_unittest.cc
@@ -25,7 +25,7 @@
     if (!test_supported_)
       return;
 
-    ASSERT_NE(nullptr, session_ = InitializeMediaFoundation());
+    ASSERT_TRUE(InitializeMediaFoundation());
 
     // Get a shared DXGI Device Manager from Media Foundation.
     ASSERT_HRESULT_SUCCEEDED(
@@ -60,7 +60,6 @@
     }
   }
 
-  MFSessionLifetime session_;
   Microsoft::WRL::ComPtr<IMFDXGIDeviceManager> dxgi_device_man_ = nullptr;
   UINT device_reset_token_ = 0;
   const bool test_supported_;
diff --git a/media/base/win/mf_initializer.cc b/media/base/win/mf_initializer.cc
index a19f79f6..89b0644 100644
--- a/media/base/win/mf_initializer.cc
+++ b/media/base/win/mf_initializer.cc
@@ -7,20 +7,59 @@
 #include <mfapi.h>
 
 #include "base/logging.h"
+#include "base/memory/singleton.h"
 
-#include "base/optional.h"
+namespace {
+
+// MFShutdown() is sometimes very expensive if it's the last instance and
+// shouldn't result in excessive memory usage to leave around, so only start it
+// once and only shut it down at process exit. See https://crbug.com/1069603#c90
+// for details.
+//
+// Note: Most Chrome process exits will not invoke the AtExit handler, so
+// MFShutdown() will generally not be called. However, we use the default
+// singleton traits (which register an AtExit handler) for tests and remoting.
+class MediaFoundationSession {
+ public:
+  static MediaFoundationSession* GetInstance() {
+    return base::Singleton<MediaFoundationSession>::get();
+  }
+
+  ~MediaFoundationSession() {
+    // The public documentation stating that it needs to have a corresponding
+    // shutdown for all startups (even failed ones) is wrong.
+    if (has_media_foundation_)
+      MFShutdown();
+  }
+
+  bool has_media_foundation() const { return has_media_foundation_; }
+
+ private:
+  friend struct base::DefaultSingletonTraits<MediaFoundationSession>;
+
+  MediaFoundationSession() {
+    const auto hr = MFStartup(MF_VERSION, MFSTARTUP_LITE);
+    has_media_foundation_ = hr == S_OK;
+
+    LOG_IF(ERROR, !has_media_foundation_)
+        << "Failed to start Media Foundation, accelerated media functionality "
+           "may be disabled. If you're using Windows N, see "
+           "https://support.microsoft.com/en-us/topic/"
+           "media-feature-pack-for-windows-10-n-may-2020-ebbdf559-b84c-0fc2-"
+           "bd51-e23c9f6a4439 for information on how to install the Media "
+           "Feature Pack. Error: "
+        << logging::SystemErrorCodeToString(hr);
+  }
+
+  bool has_media_foundation_ = false;
+};
+
+}  // namespace
 
 namespace media {
 
-MFSessionLifetime InitializeMediaFoundation() {
-  if (MFStartup(MF_VERSION, MFSTARTUP_LITE) == S_OK)
-    return std::make_unique<MFSession>();
-  DVLOG(1) << "Media Foundation unavailable or it failed to initialize";
-  return nullptr;
-}
-
-MFSession::~MFSession() {
-  MFShutdown();
+bool InitializeMediaFoundation() {
+  return MediaFoundationSession::GetInstance()->has_media_foundation();
 }
 
 }  // namespace media
diff --git a/media/base/win/mf_initializer.h b/media/base/win/mf_initializer.h
index 714cf3c..2d48415c 100644
--- a/media/base/win/mf_initializer.h
+++ b/media/base/win/mf_initializer.h
@@ -5,27 +5,13 @@
 #ifndef MEDIA_BASE_WIN_MF_INITIALIZER_H_
 #define MEDIA_BASE_WIN_MF_INITIALIZER_H_
 
-#include <mfapi.h>
-
-#include <memory>
-
 #include "base/compiler_specific.h"
 #include "media/base/win/mf_initializer_export.h"
 
 namespace media {
 
-// Handy-dandy wrapper struct that kills MediaFoundation on destruction.
-struct MF_INITIALIZER_EXPORT MFSession {
-  ~MFSession();
-};
-
-using MFSessionLifetime = std::unique_ptr<MFSession>;
-
-// Make sure that MFShutdown is called for each MFStartup that is successful.
-// The public documentation stating that it needs to have a corresponding
-// shutdown for all startups (even failed ones) is wrong.
-MF_INITIALIZER_EXPORT MFSessionLifetime InitializeMediaFoundation()
-    WARN_UNUSED_RESULT;
+// Must be called before any code that needs MediaFoundation.
+MF_INITIALIZER_EXPORT bool InitializeMediaFoundation() WARN_UNUSED_RESULT;
 
 }  // namespace media
 
diff --git a/media/capture/video/win/video_capture_device_factory_win.cc b/media/capture/video/win/video_capture_device_factory_win.cc
index c82499b8..ddfa7b0 100644
--- a/media/capture/video/win/video_capture_device_factory_win.cc
+++ b/media/capture/video/win/video_capture_device_factory_win.cc
@@ -317,8 +317,8 @@
 // distributions such as Windows 7 N and Windows 7 KN.
 // static
 bool VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation() {
-  static bool g_dlls_available = LoadMediaFoundationDlls();
-  return g_dlls_available;
+  static const bool g_dlls_available = LoadMediaFoundationDlls();
+  return g_dlls_available && InitializeMediaFoundation();
 }
 
 VideoCaptureDeviceFactoryWin::VideoCaptureDeviceFactoryWin()
@@ -332,7 +332,6 @@
     LogVideoCaptureWinBackendUsed(
         VideoCaptureWinBackendUsed::kUsingDirectShowAsFallback);
   } else if (use_media_foundation_) {
-    session_ = InitializeMediaFoundation();
     LogVideoCaptureWinBackendUsed(
         VideoCaptureWinBackendUsed::kUsingMediaFoundationAsDefault);
   } else {
@@ -539,7 +538,7 @@
 
   std::vector<VideoCaptureDeviceInfo> devices_info;
 
-  if (use_media_foundation_ && session_) {
+  if (use_media_foundation_) {
     DCHECK(PlatformSupportsMediaFoundation());
     devices_info = GetDevicesInfoMediaFoundation();
     AugmentDevicesListWithDirectShowOnlyDevices(&devices_info);
diff --git a/media/capture/video/win/video_capture_device_factory_win.h b/media/capture/video/win/video_capture_device_factory_win.h
index 43047d7b..cf61573 100644
--- a/media/capture/video/win/video_capture_device_factory_win.h
+++ b/media/capture/video/win/video_capture_device_factory_win.h
@@ -18,7 +18,6 @@
 #include "base/macros.h"
 #include "base/threading/thread.h"
 #include "media/base/win/dxgi_device_manager.h"
-#include "media/base/win/mf_initializer.h"
 #include "media/capture/video/video_capture_device_factory.h"
 
 namespace media {
@@ -97,7 +96,6 @@
 
   bool use_media_foundation_;
   bool use_d3d11_with_media_foundation_;
-  MFSessionLifetime session_;
 
   // For calling WinRT methods on a COM initiated thread.
   base::Thread com_thread_;
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.cc b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
index 7e1dece2..55c4d93 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.cc
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.cc
@@ -50,6 +50,7 @@
 #include "media/base/media_switches.h"
 #include "media/base/video_frame.h"
 #include "media/base/win/mf_helpers.h"
+#include "media/base/win/mf_initializer.h"
 #include "media/filters/vp9_parser.h"
 #include "media/gpu/command_buffer_helper.h"
 #include "media/gpu/windows/d3d11_video_device_format_support.h"
@@ -784,8 +785,8 @@
   RETURN_ON_FAILURE((state == kUninitialized),
                     "Initialize: invalid state: " << state, false);
 
-  RETURN_ON_FAILURE(session_ = InitializeMediaFoundation(),
-                    "Could not initialize Media Foundartion", false);
+  RETURN_ON_FAILURE(InitializeMediaFoundation(),
+                    "Could not initialize Media Foundation", false);
 
   config_ = config;
 
diff --git a/media/gpu/windows/dxva_video_decode_accelerator_win.h b/media/gpu/windows/dxva_video_decode_accelerator_win.h
index 034abba..b7fc1b95 100644
--- a/media/gpu/windows/dxva_video_decode_accelerator_win.h
+++ b/media/gpu/windows/dxva_video_decode_accelerator_win.h
@@ -25,7 +25,6 @@
 #include "base/threading/thread.h"
 #include "gpu/config/gpu_preferences.h"
 #include "media/base/video_color_space.h"
-#include "media/base/win/mf_initializer.h"
 #include "media/gpu/gpu_video_decode_accelerator_helpers.h"
 #include "media/gpu/media_gpu_export.h"
 #include "media/gpu/windows/d3d11_com_defs.h"
@@ -419,9 +418,6 @@
   // To expose client callbacks from VideoDecodeAccelerator.
   VideoDecodeAccelerator::Client* client_;
 
-  // MediaFoundation session, calls MFShutdown on deletion.
-  MFSessionLifetime session_;
-
   Microsoft::WRL::ComPtr<IMFTransform> decoder_;
 
   Microsoft::WRL::ComPtr<IDirect3D9Ex> d3d9_;
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
index 849fec1d..4dabdb1 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
@@ -432,9 +432,8 @@
     }
   }
 
-  if (!(session_ = InitializeMediaFoundation())) {
+  if (!InitializeMediaFoundation())
     return 0;
-  }
 
   uint32_t flags = MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER;
   MFT_REGISTER_TYPE_INFO input_info;
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.h b/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
index 31a4d4d..f2e7df8 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
@@ -19,7 +19,6 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread.h"
 #include "media/base/win/dxgi_device_manager.h"
-#include "media/base/win/mf_initializer.h"
 #include "media/gpu/media_gpu_export.h"
 #include "media/video/video_encode_accelerator.h"
 
@@ -153,9 +152,6 @@
   Microsoft::WRL::ComPtr<IMFSample> input_sample_;
   Microsoft::WRL::ComPtr<IMFSample> output_sample_;
 
-  // MediaFoundation session.
-  MFSessionLifetime session_;
-
   // To expose client callbacks from VideoEncodeAccelerator.
   // NOTE: all calls to this object *MUST* be executed on
   // |main_client_task_runner_|.
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc
index 8701b81b..b69263d 100644
--- a/media/renderers/win/media_foundation_renderer.cc
+++ b/media/renderers/win/media_foundation_renderer.cc
@@ -22,6 +22,7 @@
 #include "media/base/cdm_context.h"
 #include "media/base/timestamp_constants.h"
 #include "media/base/win/mf_helpers.h"
+#include "media/base/win/mf_initializer.h"
 
 namespace media {
 
@@ -125,8 +126,7 @@
     MediaResource* media_resource) {
   DVLOG_FUNC(1);
 
-  mf_session_life_time_ = InitializeMediaFoundation();
-  if (!mf_session_life_time_)
+  if (!InitializeMediaFoundation())
     return E_FAIL;
 
   // TODO(frankli): Only call the followings when there is a video stream.
diff --git a/media/renderers/win/media_foundation_renderer.h b/media/renderers/win/media_foundation_renderer.h
index f6b9ba6..70a209f 100644
--- a/media/renderers/win/media_foundation_renderer.h
+++ b/media/renderers/win/media_foundation_renderer.h
@@ -23,7 +23,6 @@
 #include "media/base/pipeline_status.h"
 #include "media/base/renderer.h"
 #include "media/base/renderer_client.h"
-#include "media/base/win/mf_initializer.h"
 #include "media/renderers/win/media_engine_extension.h"
 #include "media/renderers/win/media_engine_notify_impl.h"
 #include "media/renderers/win/media_foundation_protection_manager.h"
@@ -105,9 +104,6 @@
   // This is used for testing.
   const bool force_dcomp_mode_for_testing_;
 
-  // Keep this here so it's destroyed after all Media Foundation members below.
-  MFSessionLifetime mf_session_life_time_;
-
   RendererClient* renderer_client_;
 
   Microsoft::WRL::ComPtr<IMFMediaEngine> mf_media_engine_;
diff --git a/net/base/features.cc b/net/base/features.cc
index 186e8f20..e8942723 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -150,7 +150,7 @@
                                        base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kSchemefulSameSite{"SchemefulSameSite",
-                                       base::FEATURE_DISABLED_BY_DEFAULT};
+                                       base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kTLSLegacyCryptoFallbackForMetrics{
     "TLSLegacyCryptoFallbackForMetrics", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/net/cookies/site_for_cookies.cc b/net/cookies/site_for_cookies.cc
index 71e58a04..977a0b7c 100644
--- a/net/cookies/site_for_cookies.cc
+++ b/net/cookies/site_for_cookies.cc
@@ -73,8 +73,11 @@
 }
 
 bool SiteForCookies::IsEquivalent(const SiteForCookies& other) const {
-  if (IsNull())
-    return other.IsNull();
+  if (IsNull() || other.IsNull()) {
+    // We need to check if `other.IsNull()` explicitly in order to catch if
+    // `other.schemefully_same_` is false when "Schemeful Same-Site" is enabled.
+    return IsNull() && other.IsNull();
+  }
 
   // In the case where the site has no registrable domain or host, the scheme
   // cannot be ws(s) or http(s), so equality of sites implies actual equality of
diff --git a/net/cookies/site_for_cookies.h b/net/cookies/site_for_cookies.h
index 031e765..d0379b23 100644
--- a/net/cookies/site_for_cookies.h
+++ b/net/cookies/site_for_cookies.h
@@ -19,17 +19,21 @@
 // Represents which origins are to be considered same-site for a given
 // context (e.g. frame). There may be none.
 //
-// The currently implemented policy ("schemeless") is that two valid URLs would
-// be considered the same site if either:
-// 1) They both have non-empty and equal registrable domains or hostnames/IPs.
-// 2) They both have empty hostnames and equal schemes.
-//
-// With the SchemefulSameSite feature enabled the policy ("schemeful") is that
+// The currently implemented policy ("schemeful") is that
 // two valid URLs would be considered same-site if either:
 // 1) They both have compatible schemes along with non-empty and equal
 //    registrable domains or hostnames/IPs. Two schemes are compatible if they
 //    are either equal, or are both in {http, ws}, or are both in {https, wss}.
-// 2) They both have empty hostnames and exactly equal schemes.
+//    E.x. "https://example.com" and "wss://example.com"
+// 2) They both have empty hostnames and exactly equal schemes. E.x. "file://"
+// and "file://"
+//
+// With the SchemefulSameSite feature disabled the policy ("schemeless") is that
+// two valid URLs would be considered the same site if either: 1) They both have
+// non-empty and equal registrable domains or hostnames/IPs. E.x. "example.com"
+// and "example.com" 2) They both have empty hostnames and equal schemes. E.x.
+// "file://" and "file://"
+//
 //
 // Invalid URLs are never same-site to anything.
 class NET_EXPORT SiteForCookies {
@@ -59,8 +63,8 @@
 
   // If the origin is opaque, returns SiteForCookies that matches nothing.
   //
-  // If it's not, returns one that matches URLs which are considered to be
-  // same-party as URLs from `origin`.
+  // If it's not opaque, returns one that matches URLs which are considered to
+  // be same-party as URLs from `origin`.
   static SiteForCookies FromOrigin(const url::Origin& origin);
 
   // Equivalent to FromOrigin(url::Origin::Create(url)).
@@ -90,7 +94,9 @@
                                      bool compute_schemefully) const;
 
   // Returns true if `other.IsFirstParty()` is true for exactly the same URLs
-  // as `this->IsFirstParty` (potentially none).
+  // as `this->IsFirstParty` (potentially none). Two SFCs are also considered
+  // equivalent if neither ever returns true for `IsFirstParty()`. I.e., both
+  // are null.
   bool IsEquivalent(const SiteForCookies& other) const;
 
   // Compares a "candidate" SFC, `this`, with an origin (represented as a
@@ -123,7 +129,7 @@
   // Same-Site" is disabled.)
   bool CompareWithFrameTreeSiteAndRevise(const SchemefulSite& other);
 
-  // Overload which converts an Origin into a SchemefulSite and then calls
+  // Converts an Origin into a SchemefulSite and then calls
   //`CompareWithFrameTreeSiteAndRevise(SchemefulSite)`
   //
   // If possible, prefer `CompareWithFrameTreeSiteAndRevise(SchemefulSite)`
@@ -151,7 +157,7 @@
   }
 
   // Used for serialization/deserialization. This value is irrelevant if
-  // IsNull() is true.
+  // site().opaque() is true.
   bool schemefully_same() const { return schemefully_same_; }
 
   void SetSchemefullySameForTesting(bool schemefully_same) {
diff --git a/net/cookies/site_for_cookies_unittest.cc b/net/cookies/site_for_cookies_unittest.cc
index 5468133..69526f6d 100644
--- a/net/cookies/site_for_cookies_unittest.cc
+++ b/net/cookies/site_for_cookies_unittest.cc
@@ -18,10 +18,10 @@
 namespace net {
 namespace {
 
-class SchemefulSiteForCookiesTest : public ::testing::Test {
+class SchemelessSiteForCookiesTest : public ::testing::Test {
  public:
-  SchemefulSiteForCookiesTest() {
-    scope_feature_list_.InitAndEnableFeature(features::kSchemefulSameSite);
+  SchemelessSiteForCookiesTest() {
+    scope_feature_list_.InitAndDisableFeature(features::kSchemefulSameSite);
   }
 
  protected:
@@ -93,7 +93,7 @@
             should_match_none.ToDebugString());
 }
 
-TEST(SiteForCookiesTest, Basic) {
+TEST_F(SchemelessSiteForCookiesTest, Basic) {
   std::vector<GURL> equivalent = {
       GURL("https://example.com"),
       GURL("http://sub1.example.com:42/something"),
@@ -108,8 +108,9 @@
   TestEquivalentAndDistinct(equivalent, distinct, "example.com");
 }
 
-// Similar to SiteForCookiesTest_Basic with a focus on testing secure SFCs.
-TEST_F(SchemefulSiteForCookiesTest, BasicSecure) {
+// Similar to SchemelessSiteForCookiesTest_Basic with a focus on testing secure
+// SFCs.
+TEST(SiteForCookiesTest, BasicSecure) {
   std::vector<GURL> equivalent = {GURL("https://example.com"),
                                   GURL("wss://example.com"),
                                   GURL("https://sub1.example.com:42/something"),
@@ -124,8 +125,9 @@
   TestEquivalentAndDistinct(equivalent, distinct, "example.com");
 }
 
-// Similar to SiteForCookiesTest_Basic with a focus on testing insecure SFCs.
-TEST_F(SchemefulSiteForCookiesTest, BasicInsecure) {
+// Similar to SchemelessSiteForCookiesTest_Basic with a focus on testing
+// insecure SFCs.
+TEST(SiteForCookiesTest, BasicInsecure) {
   std::vector<GURL> equivalent = {GURL("http://example.com"),
                                   GURL("ws://example.com"),
                                   GURL("http://sub1.example.com:42/something"),
@@ -149,7 +151,7 @@
   TestEquivalentAndDistinct(equivalent, distinct, "");
 }
 
-TEST(SiteForCookiesTest, Extension) {
+TEST_F(SchemelessSiteForCookiesTest, Extension) {
   url::ScopedSchemeRegistryForTests scoped_registry;
   url::AddStandardScheme("chrome-extension", url::SCHEME_WITH_HOST);
   std::vector<GURL> equivalent = {GURL("chrome-extension://abc/"),
@@ -163,9 +165,9 @@
   TestEquivalentAndDistinct(equivalent, distinct, "abc");
 }
 
-// Similar to SiteForCookiesTest_Extension with a focus on ensuring that http(s)
-// schemes are distinct.
-TEST_F(SchemefulSiteForCookiesTest, Extension) {
+// Similar to SchemelessSiteForCookiesTest_Extension with a focus on ensuring
+// that http(s) schemes are distinct.
+TEST(SiteForCookiesTest, Extension) {
   url::ScopedSchemeRegistryForTests scoped_registry;
   url::AddStandardScheme("chrome-extension", url::SCHEME_WITH_HOST);
   std::vector<GURL> equivalent = {
@@ -192,7 +194,7 @@
   TestEquivalentAndDistinct(equivalent, distinct, "");
 }
 
-TEST(SiteForCookiesTest, Blob) {
+TEST_F(SchemelessSiteForCookiesTest, Blob) {
   // This case isn't really well-specified and is inconsistent between
   // different user agents; the behavior chosen here was to be more
   // consistent between url and origin handling.
@@ -210,8 +212,8 @@
       SiteForCookies::FromUrl(GURL("http://www.example.org:631"))));
 }
 
-// Similar to SiteForCookiesTest_Blob with a focus on a secure blob.
-TEST_F(SchemefulSiteForCookiesTest, SecureBlob) {
+// Similar to SchemelessSiteForCookiesTest_Blob with a focus on a secure blob.
+TEST(SiteForCookiesTest, SecureBlob) {
   SiteForCookies from_blob = SiteForCookies::FromUrl(
       GURL("blob:https://example.org/9115d58c-bcda-ff47-86e5-083e9a2153041"));
 
@@ -227,8 +229,9 @@
       SiteForCookies::FromUrl(GURL("http://www.example.org:631"))));
 }
 
-// Similar to SiteForCookiesTest_Blob with a focus on an insecure blob.
-TEST_F(SchemefulSiteForCookiesTest, InsecureBlob) {
+// Similar to SchemelessSiteForCookiesTest_Blob with a focus on an insecure
+// blob.
+TEST(SiteForCookiesTest, InsecureBlob) {
   SiteForCookies from_blob = SiteForCookies::FromUrl(
       GURL("blob:http://example.org/9115d58c-bcda-ff47-86e5-083e9a2153041"));
 
@@ -245,7 +248,7 @@
       SiteForCookies::FromUrl(GURL("https://www.example.org:631"))));
 }
 
-TEST(SiteForCookiesTest, Wire) {
+TEST_F(SchemelessSiteForCookiesTest, Wire) {
   SiteForCookies out;
 
   // Empty one.
@@ -295,9 +298,9 @@
             out.ToDebugString());
 }
 
-// Similar to SiteForCookiesTest_Wire except that schemefully_same has an
-// effect (makes IsNull() return true if schemefully_same is false).
-TEST_F(SchemefulSiteForCookiesTest, Wire) {
+// Similar to SchemelessSiteForCookiesTest_Wire except that schemefully_same has
+// an effect (makes IsNull() return true if schemefully_same is false).
+TEST(SiteForCookiesTest, Wire) {
   SiteForCookies out;
 
   // Empty one.
@@ -356,7 +359,7 @@
       out.ToDebugString());
 }
 
-TEST_F(SchemefulSiteForCookiesTest, SchemefulSite) {
+TEST(SiteForCookiesTest, SchemefulSite) {
   const char* kTestCases[] = {"opaque.com",
                               "http://a.com",
                               "https://sub1.example.com:42/something",
@@ -499,6 +502,29 @@
   EXPECT_EQ(candidate4.site(), opaque_site1);
 }
 
+TEST(SiteForCookiesTest, NotSchemefullySameEquivalent) {
+  SiteForCookies first =
+      SiteForCookies::FromUrl(GURL("https://www.example.com"));
+  SiteForCookies second =
+      SiteForCookies::FromUrl(GURL("https://www.example.com"));
+  // Smoke check that two SFCs should match when they're the same.
+  EXPECT_TRUE(first.IsEquivalent(second));
+  EXPECT_TRUE(second.IsEquivalent(first));
+
+  // Two SFC should not be equivalent to each other when one of their
+  // schemefully_same_ flags is false, even if they're otherwise the same, when
+  // Schemeful Same-Site is enabled.
+  second.SetSchemefullySameForTesting(false);
+  EXPECT_FALSE(first.IsEquivalent(second));
+  EXPECT_FALSE(second.IsEquivalent(first));
+
+  // However, they should match if both their schemefully_same_ flags are false.
+  // Because they're both considered null at that point.
+  first.SetSchemefullySameForTesting(false);
+  EXPECT_TRUE(first.IsEquivalent(second));
+  EXPECT_TRUE(second.IsEquivalent(first));
+}
+
 }  // namespace
 
 TEST(SiteForCookiesTest, SameScheme) {
diff --git a/net/cookies/static_cookie_policy_unittest.cc b/net/cookies/static_cookie_policy_unittest.cc
index 958de26..3c57a57 100644
--- a/net/cookies/static_cookie_policy_unittest.cc
+++ b/net/cookies/static_cookie_policy_unittest.cc
@@ -57,8 +57,9 @@
   SetPolicyType(StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES);
 
   EXPECT_THAT(CanAccessCookies(url_google_, url_google_), IsOk());
-  EXPECT_THAT(CanAccessCookies(url_google_, url_google_secure_), IsOk());
   EXPECT_THAT(CanAccessCookies(url_google_, url_google_mail_), IsOk());
+  EXPECT_NE(OK, CanAccessCookies(url_google_, url_google_secure_));
+  EXPECT_NE(OK, CanAccessCookies(url_google_secure_, url_google_));
   EXPECT_NE(OK, CanAccessCookies(url_google_, url_google_analytics_));
   EXPECT_NE(OK, CanAccessCookies(url_google_, GURL()));
 }
diff --git a/net/quic/platform/impl/quic_bug_tracker_impl.h b/net/quic/platform/impl/quic_bug_tracker_impl.h
index 3ca8461..ac6f5a02 100644
--- a/net/quic/platform/impl/quic_bug_tracker_impl.h
+++ b/net/quic/platform/impl/quic_bug_tracker_impl.h
@@ -11,4 +11,10 @@
 #define QUIC_PEER_BUG_IMPL QUIC_LOG(ERROR)
 #define QUIC_PEER_BUG_IF_IMPL(condition) QUIC_LOG_IF(ERROR, condition)
 
+#define QUIC_BUG_V2_IMPL(bug_id) QUIC_LOG(DFATAL)
+#define QUIC_BUG_IF_V2_IMPL(bug_id, condition) QUIC_LOG_IF(DFATAL, condition)
+#define QUIC_PEER_BUG_V2_IMPL(bug_id) QUIC_LOG(ERROR)
+#define QUIC_PEER_BUG_IF_V2_IMPL(bug_id, condition) \
+  QUIC_LOG_IF(ERROR, condition)
+
 #endif  // NET_QUIC_PLATFORM_IMPL_QUIC_BUG_TRACKER_IMPL_H_
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index ff2565a..c67d05e1 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -257,7 +257,7 @@
     quic_params_->allow_port_migration = false;
     socket_factory_ = std::make_unique<TestConnectionMigrationSocketFactory>();
     FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-    FLAGS_quic_reloadable_flag_quic_send_path_response = true;
+    FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
     FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = true;
     Initialize();
   }
@@ -4750,7 +4750,7 @@
   }
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = true;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = true;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
@@ -4763,7 +4763,7 @@
 TEST_P(QuicStreamFactoryTest, MigratePortOnPathDegrading_WithoutNetworkHandle) {
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = false;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = false;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = false;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = false;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
@@ -4780,7 +4780,7 @@
   }
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = false;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = false;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = false;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = false;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
@@ -4919,7 +4919,7 @@
   }
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = true;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = true;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
@@ -5047,7 +5047,7 @@
   mock_ncn->SetConnectedNetworksList({kDefaultNetworkForTests});
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = true;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = true;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
@@ -5069,7 +5069,7 @@
   mock_ncn->SetConnectedNetworksList({kDefaultNetworkForTests});
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = false;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = false;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = false;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = false;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
@@ -5095,7 +5095,7 @@
   quic_params_->migrate_sessions_on_network_change_v2 = true;
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = false;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = false;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = false;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = false;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
@@ -5122,7 +5122,7 @@
   quic_params_->migrate_sessions_on_network_change_v2 = true;
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = true;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = true;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
@@ -5326,7 +5326,7 @@
 TEST_P(QuicStreamFactoryTest, MultiplePortMigrationsExceedsMaxLimit) {
   quic_params_->allow_port_migration = true;
   FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = false;
-  FLAGS_quic_reloadable_flag_quic_send_path_response = false;
+  FLAGS_quic_reloadable_flag_quic_send_path_response2 = false;
   FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier = false;
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
diff --git a/net/quic/quic_transport_client.h b/net/quic/quic_transport_client.h
index 34cf0c8..82a1dac 100644
--- a/net/quic/quic_transport_client.h
+++ b/net/quic/quic_transport_client.h
@@ -32,7 +32,7 @@
 
 // QuicTransportClient is the top-level API for QuicTransport in //net.
 class NET_EXPORT QuicTransportClient
-    : public quic::QuicTransportClientSession::ClientVisitor,
+    : public quic::WebTransportVisitor,
       public QuicChromiumPacketReader::Visitor,
       public QuicChromiumPacketWriter::Delegate,
       public quic::QuicSession::Visitor {
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index 90667df..3f84328 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -428,6 +428,8 @@
       "src/quic/core/quic_connection.h",
       "src/quic/core/quic_connection_id.cc",
       "src/quic/core/quic_connection_id.h",
+      "src/quic/core/quic_connection_id_manager.cc",
+      "src/quic/core/quic_connection_id_manager.h",
       "src/quic/core/quic_connection_stats.cc",
       "src/quic/core/quic_connection_stats.h",
       "src/quic/core/quic_constants.cc",
@@ -1346,6 +1348,7 @@
     "src/quic/core/quic_circular_deque_test.cc",
     "src/quic/core/quic_coalesced_packet_test.cc",
     "src/quic/core/quic_config_test.cc",
+    "src/quic/core/quic_connection_id_manager_test.cc",
     "src/quic/core/quic_connection_id_test.cc",
     "src/quic/core/quic_connection_test.cc",
     "src/quic/core/quic_control_frame_manager_test.cc",
diff --git a/net/websockets/websocket_stream_cookie_test.cc b/net/websockets/websocket_stream_cookie_test.cc
index e8a218e..9eb2e16a 100644
--- a/net/websockets/websocket_stream_cookie_test.cc
+++ b/net/websockets/websocket_stream_cookie_test.cc
@@ -140,8 +140,7 @@
 
   const GURL url(GetParam().url);
   const GURL cookie_url(GetParam().cookie_url);
-  const url::Origin origin =
-      url::Origin::Create(GURL("http://www.example.com"));
+  const url::Origin origin = url::Origin::Create(GURL(GetParam().url));
   const SiteForCookies site_for_cookies = SiteForCookies::FromOrigin(origin);
   const IsolationInfo isolation_info =
       IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin, origin,
@@ -180,8 +179,7 @@
 
   const GURL url(GetParam().url);
   const GURL cookie_url(GetParam().cookie_url);
-  const url::Origin origin =
-      url::Origin::Create(GURL("http://www.example.com"));
+  const url::Origin origin = url::Origin::Create(GURL(GetParam().url));
   const SiteForCookies site_for_cookies = SiteForCookies::FromOrigin(origin);
   const IsolationInfo isolation_info =
       IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin, origin,
diff --git a/pdf/post_message_receiver.cc b/pdf/post_message_receiver.cc
index 1e978f81e..a3d477e 100644
--- a/pdf/post_message_receiver.cc
+++ b/pdf/post_message_receiver.cc
@@ -49,8 +49,19 @@
 
 gin::ObjectTemplateBuilder PostMessageReceiver::GetObjectTemplateBuilder(
     v8::Isolate* isolate) {
+  // The function template needs to be created with a repeating callback instead
+  // of a member function pointer (MFP). Gin expects the first parameter for a
+  // callback to a MFP to be the JavaScript `this` object corresponding to this
+  // scriptable object exposed through Blink. However, the actual receiving
+  // object for a plugins is a HTMLEmbedElement and Blink internally forwards
+  // the parameters to this scriptable object.
+  //
+  // `base::Unretained(this)` is safe to use because the callback will only be
+  // called within the lifetime of the wrapped PostMessageReceiver object.
   return gin::Wrappable<PostMessageReceiver>::GetObjectTemplateBuilder(isolate)
-      .SetMethod("postMessage", &PostMessageReceiver::PostMessage);
+      .SetMethod("postMessage",
+                 base::BindRepeating(&PostMessageReceiver::PostMessage,
+                                     base::Unretained(this)));
 }
 
 const char* PostMessageReceiver::GetTypeName() {
diff --git a/services/device/device_service.cc b/services/device/device_service.cc
index 38af98e..cae7456f 100644
--- a/services/device/device_service.cc
+++ b/services/device/device_service.cc
@@ -85,84 +85,39 @@
 
 namespace device {
 
-#if defined(OS_ANDROID)
+DeviceServiceParams::DeviceServiceParams() = default;
+
+DeviceServiceParams::~DeviceServiceParams() = default;
+
 std::unique_ptr<DeviceService> CreateDeviceService(
-    scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
-    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    network::NetworkConnectionTracker* network_connection_tracker,
-    const std::string& geolocation_api_key,
-    bool use_gms_core_location_provider,
-    const WakeLockContextCallback& wake_lock_context_callback,
-    const CustomLocationProviderCallback& custom_location_provider_callback,
-    const base::android::JavaRef<jobject>& java_nfc_delegate,
+    std::unique_ptr<DeviceServiceParams> params,
     mojo::PendingReceiver<mojom::DeviceService> receiver) {
   GeolocationProviderImpl::SetGeolocationConfiguration(
-      url_loader_factory, geolocation_api_key,
-      custom_location_provider_callback,
-      /*system_permission_manager=*/nullptr, use_gms_core_location_provider);
-  return std::make_unique<DeviceService>(
-      std::move(file_task_runner), std::move(io_task_runner),
-      std::move(url_loader_factory), network_connection_tracker,
-      geolocation_api_key, wake_lock_context_callback, java_nfc_delegate,
-      std::move(receiver));
+      params->url_loader_factory, params->geolocation_api_key,
+      params->custom_location_provider_callback,
+      params->location_permission_manager,
+      params->use_gms_core_location_provider);
+  return std::make_unique<DeviceService>(std::move(params),
+                                         std::move(receiver));
 }
-#else
-std::unique_ptr<DeviceService> CreateDeviceService(
-    scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
-    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    network::NetworkConnectionTracker* network_connection_tracker,
-    const std::string& geolocation_api_key,
-    GeolocationSystemPermissionManager* location_permission_manager,
-    const CustomLocationProviderCallback& custom_location_provider_callback,
-    mojo::PendingReceiver<mojom::DeviceService> receiver) {
-  GeolocationProviderImpl::SetGeolocationConfiguration(
-      url_loader_factory, geolocation_api_key,
-      custom_location_provider_callback, location_permission_manager);
-  return std::make_unique<DeviceService>(
-      std::move(file_task_runner), std::move(io_task_runner),
-      std::move(url_loader_factory), network_connection_tracker,
-      geolocation_api_key, std::move(receiver));
-}
-#endif
+
+DeviceService::DeviceService(
+    std::unique_ptr<DeviceServiceParams> params,
+    mojo::PendingReceiver<mojom::DeviceService> receiver)
+    : file_task_runner_(std::move(params->file_task_runner)),
+      io_task_runner_(std::move(params->io_task_runner)),
+      url_loader_factory_(std::move(params->url_loader_factory)),
+      network_connection_tracker_(params->network_connection_tracker),
+      geolocation_api_key_(params->geolocation_api_key),
+      wake_lock_context_callback_(params->wake_lock_context_callback),
+      wake_lock_provider_(file_task_runner_,
+                          params->wake_lock_context_callback) {
+  receivers_.Add(this, std::move(receiver));
 
 #if defined(OS_ANDROID)
-DeviceService::DeviceService(
-    scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
-    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    network::NetworkConnectionTracker* network_connection_tracker,
-    const std::string& geolocation_api_key,
-    const WakeLockContextCallback& wake_lock_context_callback,
-    const base::android::JavaRef<jobject>& java_nfc_delegate,
-    mojo::PendingReceiver<mojom::DeviceService> receiver)
-    : file_task_runner_(std::move(file_task_runner)),
-      io_task_runner_(std::move(io_task_runner)),
-      url_loader_factory_(std::move(url_loader_factory)),
-      network_connection_tracker_(network_connection_tracker),
-      geolocation_api_key_(geolocation_api_key),
-      wake_lock_context_callback_(wake_lock_context_callback),
-      wake_lock_provider_(file_task_runner_, wake_lock_context_callback_),
-      java_interface_provider_initialized_(false) {
-  receivers_.Add(this, std::move(receiver));
-  java_nfc_delegate_.Reset(java_nfc_delegate);
-}
-#else
-DeviceService::DeviceService(
-    scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
-    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    network::NetworkConnectionTracker* network_connection_tracker,
-    const std::string& geolocation_api_key,
-    mojo::PendingReceiver<mojom::DeviceService> receiver)
-    : file_task_runner_(std::move(file_task_runner)),
-      io_task_runner_(std::move(io_task_runner)),
-      url_loader_factory_(std::move(url_loader_factory)),
-      network_connection_tracker_(network_connection_tracker),
-      geolocation_api_key_(geolocation_api_key),
-      wake_lock_provider_(file_task_runner_, wake_lock_context_callback_) {
-  receivers_.Add(this, std::move(receiver));
+  java_nfc_delegate_.Reset(params->java_nfc_delegate);
+#endif
+
 #if ((defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(USE_UDEV)) || \
     defined(OS_WIN) || defined(OS_MAC)
   serial_port_manager_ = std::make_unique<SerialPortManagerImpl>(
@@ -178,12 +133,14 @@
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
 #endif
 #endif
+
+#if !defined(OS_ANDROID)
   // Ensure that the battery backend is initialized now; otherwise it may end up
   // getting initialized on access during destruction, when it's no longer safe
   // to initialize.
   device::BatteryStatusService::GetInstance();
+#endif  // !defined(OS_ANDROID)
 }
-#endif
 
 DeviceService::~DeviceService() {
 #if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/services/device/device_service.h b/services/device/device_service.h
index c1882e9..03b45f9 100644
--- a/services/device/device_service.h
+++ b/services/device/device_service.h
@@ -79,54 +79,36 @@
 class TimeZoneMonitor;
 class GeolocationSystemPermissionManager;
 
-#if defined(OS_ANDROID)
 // NOTE: See the comments on the definitions of PublicIpAddressLocationNotifier,
 // |WakeLockContextCallback|, |CustomLocationProviderCallback| and
 // NFCDelegate.java to understand the semantics and usage of these parameters.
+struct DeviceServiceParams {
+  DeviceServiceParams();
+  ~DeviceServiceParams();
+
+  scoped_refptr<base::SingleThreadTaskRunner> file_task_runner;
+  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner;
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
+  network::NetworkConnectionTracker* network_connection_tracker = nullptr;
+  std::string geolocation_api_key;
+  CustomLocationProviderCallback custom_location_provider_callback;
+  bool use_gms_core_location_provider = false;
+  GeolocationSystemPermissionManager* location_permission_manager = nullptr;
+  WakeLockContextCallback wake_lock_context_callback;
+
+#if defined(OS_ANDROID)
+  base::android::ScopedJavaGlobalRef<jobject> java_nfc_delegate;
+#endif  // defined(OS_ANDROID)
+};
+
 std::unique_ptr<DeviceService> CreateDeviceService(
-    scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
-    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    network::NetworkConnectionTracker* network_connection_tracker,
-    const std::string& geolocation_api_key,
-    bool use_gms_core_location_provider,
-    const WakeLockContextCallback& wake_lock_context_callback,
-    const CustomLocationProviderCallback& custom_location_provider_callback,
-    const base::android::JavaRef<jobject>& java_nfc_delegate,
+    std::unique_ptr<DeviceServiceParams> params,
     mojo::PendingReceiver<mojom::DeviceService> receiver);
-#else
-std::unique_ptr<DeviceService> CreateDeviceService(
-    scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
-    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    network::NetworkConnectionTracker* network_connection_tracker,
-    const std::string& geolocation_api_key,
-    GeolocationSystemPermissionManager* location_permission_manager,
-    const CustomLocationProviderCallback& custom_location_provider_callback,
-    mojo::PendingReceiver<mojom::DeviceService> receiver);
-#endif
 
 class DeviceService : public mojom::DeviceService {
  public:
-#if defined(OS_ANDROID)
-  DeviceService(
-      scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
-      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
-      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      network::NetworkConnectionTracker* network_connection_tracker,
-      const std::string& geolocation_api_key,
-      const WakeLockContextCallback& wake_lock_context_callback,
-      const base::android::JavaRef<jobject>& java_nfc_delegate,
-      mojo::PendingReceiver<mojom::DeviceService> receiver);
-#else
-  DeviceService(
-      scoped_refptr<base::SingleThreadTaskRunner> file_task_runner,
-      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
-      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      network::NetworkConnectionTracker* network_connection_tracker,
-      const std::string& geolocation_api_key,
-      mojo::PendingReceiver<mojom::DeviceService> receiver);
-#endif
+  DeviceService(std::unique_ptr<DeviceServiceParams> params,
+                mojo::PendingReceiver<mojom::DeviceService> receiver);
   ~DeviceService() override;
 
   void AddReceiver(mojo::PendingReceiver<mojom::DeviceService> receiver);
@@ -235,7 +217,7 @@
   service_manager::InterfaceProvider java_interface_provider_{
       base::ThreadTaskRunnerHandle::Get()};
 
-  bool java_interface_provider_initialized_;
+  bool java_interface_provider_initialized_ = false;
 
   base::android::ScopedJavaGlobalRef<jobject> java_nfc_delegate_;
 #else
diff --git a/services/device/device_service_test_base.cc b/services/device/device_service_test_base.cc
index 8ae0974..4852193 100644
--- a/services/device/device_service_test_base.cc
+++ b/services/device/device_service_test_base.cc
@@ -36,21 +36,18 @@
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     mojo::PendingReceiver<mojom::DeviceService> receiver,
     GeolocationSystemPermissionManager* location_permission_manager) {
-#if defined(OS_ANDROID)
-  return CreateDeviceService(
-      file_task_runner, io_task_runner, url_loader_factory,
-      network::TestNetworkConnectionTracker::GetInstance(),
-      kTestGeolocationApiKey, false, WakeLockContextCallback(),
-      base::BindRepeating(&GetCustomLocationProviderForTest), nullptr,
-      std::move(receiver));
-#else
-  return CreateDeviceService(
-      file_task_runner, io_task_runner, url_loader_factory,
-      network::TestNetworkConnectionTracker::GetInstance(),
-      kTestGeolocationApiKey, location_permission_manager,
-      base::BindRepeating(&GetCustomLocationProviderForTest),
-      std::move(receiver));
-#endif
+  auto params = std::make_unique<DeviceServiceParams>();
+  params->file_task_runner = std::move(file_task_runner);
+  params->io_task_runner = std::move(io_task_runner);
+  params->url_loader_factory = std::move(url_loader_factory);
+  params->network_connection_tracker =
+      network::TestNetworkConnectionTracker::GetInstance();
+  params->geolocation_api_key = kTestGeolocationApiKey;
+  params->custom_location_provider_callback =
+      base::BindRepeating(&GetCustomLocationProviderForTest);
+  params->location_permission_manager = location_permission_manager;
+
+  return CreateDeviceService(std::move(params), std::move(receiver));
 }
 
 }  // namespace
diff --git a/services/device/geolocation/geolocation_provider_impl.h b/services/device/geolocation/geolocation_provider_impl.h
index 9f556f30..72280cf6 100644
--- a/services/device/geolocation/geolocation_provider_impl.h
+++ b/services/device/geolocation/geolocation_provider_impl.h
@@ -75,7 +75,7 @@
       const std::string& api_key,
       const CustomLocationProviderCallback& custom_location_provider_getter,
       GeolocationSystemPermissionManager* system_permission_manager,
-      bool use_gms_core_location_provider = false);
+      bool use_gms_core_location_provider);
 
   void BindGeolocationControlReceiver(
       mojo::PendingReceiver<mojom::GeolocationControl> receiver);
diff --git a/services/device/geolocation/network_location_provider.cc b/services/device/geolocation/network_location_provider.cc
index 842d53a..d1a5eb5f 100644
--- a/services/device/geolocation/network_location_provider.cc
+++ b/services/device/geolocation/network_location_provider.cc
@@ -96,9 +96,19 @@
 
 void NetworkLocationProvider::OnSystemPermissionUpdate(
     LocationSystemPermissionStatus new_status) {
+  is_awaiting_initial_permission_status_ = false;
   const bool was_permission_granted = is_system_permission_granted_;
   is_system_permission_granted_ =
       (new_status == LocationSystemPermissionStatus::kAllowed);
+
+  if (!is_system_permission_granted_ && location_provider_update_callback_) {
+    mojom::Geoposition error_position;
+    error_position.error_code =
+        mojom::Geoposition::ErrorCode::PERMISSION_DENIED;
+    error_position.error_message =
+        "User has not allowed access to system location.";
+    location_provider_update_callback_.Run(this, error_position);
+  }
   if (!was_permission_granted && is_system_permission_granted_ && IsStarted()) {
     wifi_data_provider_manager_->ForceRescan();
     OnWifiDataUpdate();
@@ -111,6 +121,14 @@
 #if defined(OS_MAC)
   if (!is_system_permission_granted_ &&
       base::FeatureList::IsEnabled(features::kMacCoreLocationImplementation)) {
+    if (!is_awaiting_initial_permission_status_) {
+      mojom::Geoposition error_position;
+      error_position.error_code =
+          mojom::Geoposition::ErrorCode::PERMISSION_DENIED;
+      error_position.error_message =
+          "User has not allowed access to system location.";
+      location_provider_update_callback_.Run(this, error_position);
+    }
     return;
   }
 #endif
diff --git a/services/device/geolocation/network_location_provider.h b/services/device/geolocation/network_location_provider.h
index 7dc744e..762858a 100644
--- a/services/device/geolocation/network_location_provider.h
+++ b/services/device/geolocation/network_location_provider.h
@@ -102,6 +102,8 @@
 
   bool is_system_permission_granted_ = false;
 
+  bool is_awaiting_initial_permission_status_ = true;
+
   base::WeakPtrFactory<NetworkLocationProvider> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(NetworkLocationProvider);
diff --git a/services/device/geolocation/network_location_provider_unittest.cc b/services/device/geolocation/network_location_provider_unittest.cc
index e8c43c2..7f933d3 100644
--- a/services/device/geolocation/network_location_provider_unittest.cc
+++ b/services/device/geolocation/network_location_provider_unittest.cc
@@ -51,11 +51,14 @@
                         const mojom::Geoposition& position) {
     last_position = position;
     update_count++;
+    if (position.error_code != mojom::Geoposition::ErrorCode::NONE)
+      error_count++;
   }
 
   const LocationProvider::LocationProviderUpdateCallback callback;
   mojom::Geoposition last_position;
   int update_count = 0;
+  int error_count = 0;
 };
 
 // A mock implementation of WifiDataProvider for testing. Adapted from
@@ -598,9 +601,9 @@
   // be called immediately.
   provider->OnPermissionGranted();
 
-  // Ensure the callback was never called.
+  // Ensure there was an error callback.
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(0, listener.update_count);
+  EXPECT_EQ(1, listener.error_count);
 
   // Now try to make a request for new wifi data.
   wifi_data_provider_->set_got_data(true);
diff --git a/services/media_session/media_controller.cc b/services/media_session/media_controller.cc
index 483b20b..fb95b30 100644
--- a/services/media_session/media_controller.cc
+++ b/services/media_session/media_controller.cc
@@ -298,6 +298,27 @@
     session_->ipc()->SetAudioSinkId(id);
 }
 
+void MediaController::ToggleMicrophone() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (session_)
+    session_->ipc()->ToggleMicrophone();
+}
+
+void MediaController::ToggleCamera() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (session_)
+    session_->ipc()->ToggleCamera();
+}
+
+void MediaController::HangUp() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (session_)
+    session_->ipc()->HangUp();
+}
+
 void MediaController::SetMediaSession(AudioFocusRequest* session) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
diff --git a/services/media_session/media_controller.h b/services/media_session/media_controller.h
index 6f946c5b..57b1e78 100644
--- a/services/media_session/media_controller.h
+++ b/services/media_session/media_controller.h
@@ -53,6 +53,9 @@
   void EnterPictureInPicture() override;
   void ExitPictureInPicture() override;
   void SetAudioSinkId(const base::Optional<std::string>& id) override;
+  void ToggleMicrophone() override;
+  void ToggleCamera() override;
+  void HangUp() override;
 
   // mojom::MediaSessionObserver overrides.
   void MediaSessionInfoChanged(
diff --git a/services/media_session/public/cpp/test/mock_media_session.h b/services/media_session/public/cpp/test/mock_media_session.h
index f441196..a81e339e 100644
--- a/services/media_session/public/cpp/test/mock_media_session.h
+++ b/services/media_session/public/cpp/test/mock_media_session.h
@@ -160,6 +160,9 @@
   void EnterPictureInPicture() override;
   void ExitPictureInPicture() override;
   void SetAudioSinkId(const base::Optional<std::string>& id) override {}
+  void ToggleMicrophone() override {}
+  void ToggleCamera() override {}
+  void HangUp() override {}
 
   void SetIsControllable(bool value);
   void SetPreferStop(bool value) { prefer_stop_ = value; }
diff --git a/services/media_session/public/cpp/test/test_media_controller.h b/services/media_session/public/cpp/test/test_media_controller.h
index e780e6f3..655812c2 100644
--- a/services/media_session/public/cpp/test/test_media_controller.h
+++ b/services/media_session/public/cpp/test/test_media_controller.h
@@ -156,6 +156,9 @@
   void EnterPictureInPicture() override;
   void ExitPictureInPicture() override;
   void SetAudioSinkId(const base::Optional<std::string>& id) override {}
+  void ToggleMicrophone() override {}
+  void ToggleCamera() override {}
+  void HangUp() override {}
 
   int toggle_suspend_resume_count() const {
     return toggle_suspend_resume_count_;
diff --git a/services/media_session/public/cpp/util.cc b/services/media_session/public/cpp/util.cc
index f4b2a1b..c06e1d8 100644
--- a/services/media_session/public/cpp/util.cc
+++ b/services/media_session/public/cpp/util.cc
@@ -46,6 +46,15 @@
     case mojom::MediaSessionAction::kExitPictureInPicture:
       media_controller_remote->ExitPictureInPicture();
       break;
+    case mojom::MediaSessionAction::kToggleMicrophone:
+      media_controller_remote->ToggleMicrophone();
+      break;
+    case mojom::MediaSessionAction::kToggleCamera:
+      media_controller_remote->ToggleCamera();
+      break;
+    case mojom::MediaSessionAction::kHangUp:
+      media_controller_remote->HangUp();
+      break;
     case mojom::MediaSessionAction::kSkipAd:
     case mojom::MediaSessionAction::kSeekTo:
     case mojom::MediaSessionAction::kScrubTo:
diff --git a/services/media_session/public/mojom/media_controller.mojom b/services/media_session/public/mojom/media_controller.mojom
index abe811c6..1fca24ae9 100644
--- a/services/media_session/public/mojom/media_controller.mojom
+++ b/services/media_session/public/mojom/media_controller.mojom
@@ -8,6 +8,8 @@
 import "mojo/public/mojom/base/unguessable_token.mojom";
 import "services/media_session/public/mojom/media_session.mojom";
 
+// Next MinVersion: 2
+
 // Next Method ID: 3
 [Stable, Uuid="1ed3225e-33c8-487b-b79f-4c3ec13ad623"]
 interface MediaControllerManager {
@@ -26,9 +28,12 @@
   SuspendAllSessions@2();
 };
 
-// Controls a MediaSession. If the media session is not controllable then the
-// commands will be no-ops.
-// Next Method ID: 14
+// Controls the associated MediaSession. An active media controller will be
+// automatically associated with the most recently interacted with media
+// session, otherwise media sessions will be associated with a particular media
+// session provided to the service when creating the media controller. If the
+// media session is not controllable then the commands will be no-ops.
+// Next Method ID: 17
 [Stable]
 interface MediaController {
   // Suspend the media session.
@@ -91,6 +96,15 @@
   // Routes the audio from this Media Session to the given output device. If
   // |id| is null, we will route to the default output device.
   SetAudioSinkId@13(string? id);
+
+  // Mute or unmute the microphone for a WebRTC session.
+  [MinVersion=1] ToggleMicrophone@14();
+
+  // Turn on or off the camera for a WebRTC session.
+  [MinVersion=1] ToggleCamera@15();
+
+  // Hang up a WebRTC session.
+  [MinVersion=1] HangUp@16();
 };
 
 // The observer for observing media controller events. This is different to a
diff --git a/services/media_session/public/mojom/media_session.mojom b/services/media_session/public/mojom/media_session.mojom
index f3a833a..c082cd17 100644
--- a/services/media_session/public/mojom/media_session.mojom
+++ b/services/media_session/public/mojom/media_session.mojom
@@ -9,7 +9,7 @@
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "url/mojom/url.mojom";
 
-// Next MinVersion: 11
+// Next MinVersion: 12
 
 [Stable, Extensible]
 enum MediaPlaybackState {
@@ -32,6 +32,9 @@
   kEnterPictureInPicture,
   kExitPictureInPicture,
   kSwitchAudioDevice,
+  [MinVersion=11] kToggleMicrophone,
+  [MinVersion=11] kToggleCamera,
+  [MinVersion=11] kHangUp,
 };
 
 [Stable, Extensible]
@@ -108,6 +111,20 @@
   mojo_base.mojom.TimeTicks last_updated_time;
 };
 
+[Stable, Extensible]
+enum MicrophoneState {
+  kUnknown,
+  kMuted,
+  kUnmuted,
+};
+
+[Stable, Extensible]
+enum CameraState {
+  kUnknown,
+  kTurnedOn,
+  kTurnedOff,
+};
+
 // Contains state information about a MediaSession.
 [Stable]
 struct MediaSessionInfo {
@@ -164,6 +181,12 @@
 
   // The audio/video states of all the players in the Media Session (if known).
   [MinVersion=10] array<MediaAudioVideoState>? audio_video_states;
+
+  // Tracks whether the microphone is muted in WebRTC sessions.
+  [MinVersion=11] MicrophoneState microphone_state;
+
+  // Tracks whether the camera is turned on in WebRTC sessions.
+  [MinVersion=11] CameraState camera_state;
 };
 
 // Contains debugging information about a MediaSession. This will be displayed
@@ -206,7 +229,7 @@
 
 // A MediaSession manages the media session and audio focus for a given
 // WebContents or ARC app.
-// Next Method ID: 18
+// Next Method ID: 21
 [Stable]
 interface MediaSession {
   [Stable, Extensible]
@@ -293,4 +316,13 @@
   // Routes the audio from this Media Session to the given output device. If
   // |id| is null, we will route to the default output device.
   SetAudioSinkId@17(string? id);
+
+  // Mute or unmute the microphone for a WebRTC session.
+  [MinVersion=11] ToggleMicrophone@18();
+
+  // Turn on or off the camera for a WebRTC session.
+  [MinVersion=11] ToggleCamera@19();
+
+  // Hang up a WebRTC session.
+  [MinVersion=11] HangUp@20();
 };
diff --git a/services/network/quic_transport.cc b/services/network/quic_transport.cc
index ee8f9a4..0e81d530 100644
--- a/services/network/quic_transport.cc
+++ b/services/network/quic_transport.cc
@@ -40,7 +40,7 @@
 
 class QuicTransport::Stream final {
  public:
-  class StreamVisitor final : public quic::QuicTransportStream::Visitor {
+  class StreamVisitor final : public quic::WebTransportStreamVisitor {
    public:
     explicit StreamVisitor(Stream* stream)
         : stream_(stream->weak_factory_.GetWeakPtr()) {}
diff --git a/services/viz/privileged/mojom/compositing/display_private.mojom b/services/viz/privileged/mojom/compositing/display_private.mojom
index 0c9686f..56268cc 100644
--- a/services/viz/privileged/mojom/compositing/display_private.mojom
+++ b/services/viz/privileged/mojom/compositing/display_private.mojom
@@ -14,7 +14,7 @@
 import "ui/latency/mojom/latency_info.mojom";
 import "services/viz/privileged/mojom/compositing/layered_window_updater.mojom";
 import "services/viz/privileged/mojom/compositing/vsync_parameter_observer.mojom";
-import "services/viz/public/mojom/compositing/delegated_ink_point.mojom";
+import "services/viz/public/mojom/compositing/delegated_ink_point_renderer.mojom";
 
 // The DisplayPrivate is used by privileged clients to talk to Display.
 interface DisplayPrivate {
diff --git a/services/viz/public/cpp/compositing/compositor_frame_metadata_mojom_traits.h b/services/viz/public/cpp/compositing/compositor_frame_metadata_mojom_traits.h
index 3e97e46..6a4eec9 100644
--- a/services/viz/public/cpp/compositing/compositor_frame_metadata_mojom_traits.h
+++ b/services/viz/public/cpp/compositing/compositor_frame_metadata_mojom_traits.h
@@ -12,10 +12,10 @@
 #include "components/viz/common/quads/compositor_frame_metadata.h"
 #include "services/viz/public/cpp/compositing/begin_frame_args_mojom_traits.h"
 #include "services/viz/public/cpp/compositing/compositor_frame_transition_directive_mojom_traits.h"
-#include "services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.h"
 #include "services/viz/public/cpp/compositing/frame_deadline_mojom_traits.h"
 #include "services/viz/public/cpp/compositing/surface_range_mojom_traits.h"
 #include "services/viz/public/mojom/compositing/compositor_frame_metadata.mojom-shared.h"
+#include "ui/gfx/mojom/delegated_ink_metadata_mojom_traits.h"
 #include "ui/gfx/mojom/display_color_spaces_mojom_traits.h"
 #include "ui/gfx/mojom/overlay_transform_mojom_traits.h"
 
@@ -123,7 +123,7 @@
     return metadata.display_transform_hint;
   }
 
-  static const std::unique_ptr<viz::DelegatedInkMetadata>&
+  static const std::unique_ptr<gfx::DelegatedInkMetadata>&
   delegated_ink_metadata(const viz::CompositorFrameMetadata& metadata) {
     return metadata.delegated_ink_metadata;
   }
diff --git a/services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.h b/services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.h
deleted file mode 100644
index 1b7d841..0000000
--- a/services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_
-#define SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_
-
-#include <memory>
-
-#include "components/viz/common/delegated_ink_metadata.h"
-#include "mojo/public/cpp/base/time_mojom_traits.h"
-#include "services/viz/public/mojom/compositing/delegated_ink_metadata.mojom-shared.h"
-#include "skia/public/mojom/skcolor_mojom_traits.h"
-#include "ui/gfx/mojom/rrect_f_mojom_traits.h"
-
-namespace mojo {
-
-template <>
-struct StructTraits<viz::mojom::DelegatedInkMetadataDataView,
-                    std::unique_ptr<viz::DelegatedInkMetadata>> {
-  static bool IsNull(const std::unique_ptr<viz::DelegatedInkMetadata>& input) {
-    return !input;
-  }
-
-  static void SetToNull(std::unique_ptr<viz::DelegatedInkMetadata>* input) {
-    input->reset();
-  }
-
-  static const gfx::PointF& point(
-      const std::unique_ptr<viz::DelegatedInkMetadata>& input) {
-    return input->point();
-  }
-
-  static double diameter(
-      const std::unique_ptr<viz::DelegatedInkMetadata>& input) {
-    return input->diameter();
-  }
-
-  static SkColor color(
-      const std::unique_ptr<viz::DelegatedInkMetadata>& input) {
-    return input->color();
-  }
-
-  static base::TimeTicks timestamp(
-      const std::unique_ptr<viz::DelegatedInkMetadata>& input) {
-    return input->timestamp();
-  }
-
-  static const gfx::RectF& presentation_area(
-      const std::unique_ptr<viz::DelegatedInkMetadata>& input) {
-    return input->presentation_area();
-  }
-
-  static base::TimeTicks frame_time(
-      const std::unique_ptr<viz::DelegatedInkMetadata>& input) {
-    return input->frame_time();
-  }
-
-  static bool is_hovering(
-      const std::unique_ptr<viz::DelegatedInkMetadata>& input) {
-    return input->is_hovering();
-  }
-
-  static bool Read(viz::mojom::DelegatedInkMetadataDataView data,
-                   std::unique_ptr<viz::DelegatedInkMetadata>* out);
-};
-
-}  // namespace mojo
-
-#endif  // SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_
diff --git a/services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.cc b/services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.cc
deleted file mode 100644
index 1e68e4b..0000000
--- a/services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.cc
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.h"
-
-namespace mojo {
-
-// static
-bool StructTraits<
-    viz::mojom::DelegatedInkPointDataView,
-    viz::DelegatedInkPoint>::Read(viz::mojom::DelegatedInkPointDataView data,
-                                  viz::DelegatedInkPoint* out) {
-  out->pointer_id_ = data.pointer_id();
-  return data.ReadPoint(&out->point_) && data.ReadTimestamp(&out->timestamp_);
-}
-
-}  // namespace mojo
diff --git a/services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.h b/services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.h
deleted file mode 100644
index cb592bb..0000000
--- a/services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.h
+++ /dev/null
@@ -1,36 +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 SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_DELEGATED_INK_POINT_MOJOM_TRAITS_H_
-#define SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_DELEGATED_INK_POINT_MOJOM_TRAITS_H_
-
-#include "components/viz/common/delegated_ink_point.h"
-#include "mojo/public/cpp/base/time_mojom_traits.h"
-#include "services/viz/public/mojom/compositing/delegated_ink_point.mojom-shared.h"
-#include "ui/gfx/geometry/mojom/geometry_mojom_traits.h"
-
-namespace mojo {
-
-template <>
-struct StructTraits<viz::mojom::DelegatedInkPointDataView,
-                    viz::DelegatedInkPoint> {
-  static const gfx::PointF& point(const viz::DelegatedInkPoint& input) {
-    return input.point();
-  }
-
-  static base::TimeTicks timestamp(const viz::DelegatedInkPoint& input) {
-    return input.timestamp();
-  }
-
-  static int32_t pointer_id(const viz::DelegatedInkPoint& input) {
-    return input.pointer_id();
-  }
-
-  static bool Read(viz::mojom::DelegatedInkPointDataView data,
-                   viz::DelegatedInkPoint* out);
-};
-
-}  // namespace mojo
-
-#endif  // SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_DELEGATED_INK_POINT_MOJOM_TRAITS_H_
diff --git a/services/viz/public/mojom/BUILD.gn b/services/viz/public/mojom/BUILD.gn
index 580b25fe..a27ab5f 100644
--- a/services/viz/public/mojom/BUILD.gn
+++ b/services/viz/public/mojom/BUILD.gn
@@ -19,8 +19,7 @@
     "compositing/compositor_render_pass_id.mojom",
     "compositing/copy_output_request.mojom",
     "compositing/copy_output_result.mojom",
-    "compositing/delegated_ink_metadata.mojom",
-    "compositing/delegated_ink_point.mojom",
+    "compositing/delegated_ink_point_renderer.mojom",
     "compositing/filter_operation.mojom",
     "compositing/filter_operations.mojom",
     "compositing/frame_deadline.mojom",
@@ -275,30 +274,6 @@
     {
       types = [
         {
-          mojom = "viz.mojom.DelegatedInkMetadata"
-          cpp = "::std::unique_ptr<::viz::DelegatedInkMetadata>"
-          move_only = true
-          nullable_is_same_type = true
-        },
-      ]
-      traits_sources = [ "//services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.cc" ]
-      traits_headers = [ "//services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.h" ]
-      traits_public_deps = [ "//components/viz/common" ]
-    },
-    {
-      types = [
-        {
-          mojom = "viz.mojom.DelegatedInkPoint"
-          cpp = "::viz::DelegatedInkPoint"
-        },
-      ]
-      traits_sources = [ "//services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.cc" ]
-      traits_headers = [ "//services/viz/public/cpp/compositing/delegated_ink_point_mojom_traits.h" ]
-      traits_public_deps = [ "//components/viz/common" ]
-    },
-    {
-      types = [
-        {
           mojom = "viz.mojom.FilterOperation"
           cpp = "::cc::FilterOperation"
         },
@@ -582,17 +557,5 @@
         "//ui/latency/mojom",
       ]
     },
-    {
-      types = [
-        {
-          mojom = "viz.mojom.DelegatedInkMetadata"
-          cpp = "::std::unique_ptr<::viz::DelegatedInkMetadata>"
-          move_only = true
-          nullable_is_same_type = true
-        },
-      ]
-      traits_headers = [ "//services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.h" ]
-      traits_public_deps = [ "//components/viz/common" ]
-    },
   ]
 }
diff --git a/services/viz/public/mojom/compositing/compositor_frame_metadata.mojom b/services/viz/public/mojom/compositing/compositor_frame_metadata.mojom
index 33ac54b..9d8899c5 100644
--- a/services/viz/public/mojom/compositing/compositor_frame_metadata.mojom
+++ b/services/viz/public/mojom/compositing/compositor_frame_metadata.mojom
@@ -7,7 +7,7 @@
 import "mojo/public/mojom/base/time.mojom";
 import "services/viz/public/mojom/compositing/begin_frame_args.mojom";
 import "services/viz/public/mojom/compositing/compositor_frame_transition_directive.mojom";
-import "services/viz/public/mojom/compositing/delegated_ink_metadata.mojom";
+import "ui/gfx/mojom/delegated_ink_metadata.mojom";
 import "services/viz/public/mojom/compositing/frame_deadline.mojom";
 import "services/viz/public/mojom/compositing/selection.mojom";
 import "services/viz/public/mojom/compositing/surface_id.mojom";
@@ -61,7 +61,7 @@
   // The ink trail created with this metadata will only last for a single frame
   // before it disappears, regardless of whether or not the next frame contains
   // delegated ink metadata.
-  DelegatedInkMetadata? delegated_ink_metadata;
+  gfx.mojom.DelegatedInkMetadata? delegated_ink_metadata;
 
   // Transition directives represent a list of directives for animating a
   // transition between different compositor frames / render passes.
diff --git a/services/viz/public/mojom/compositing/delegated_ink_point.mojom b/services/viz/public/mojom/compositing/delegated_ink_point_renderer.mojom
similarity index 72%
rename from services/viz/public/mojom/compositing/delegated_ink_point.mojom
rename to services/viz/public/mojom/compositing/delegated_ink_point_renderer.mojom
index 49a45939..4fbda94 100644
--- a/services/viz/public/mojom/compositing/delegated_ink_point.mojom
+++ b/services/viz/public/mojom/compositing/delegated_ink_point_renderer.mojom
@@ -1,18 +1,10 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
+// 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.
 
 module viz.mojom;
 
-import "mojo/public/mojom/base/time.mojom";
-import "ui/gfx/geometry/mojom/geometry.mojom";
-
-// See components/viz/common/delegated_ink_point.h.
-struct DelegatedInkPoint {
-  gfx.mojom.PointF point;
-  mojo_base.mojom.TimeTicks timestamp;
-  int32 pointer_id;
-};
+import "ui/gfx/mojom/delegated_ink_point.mojom";
 
 // This interface is used to connect the browser process to viz to support
 // delegated ink trails. A delegated ink point will be produced in the
@@ -24,10 +16,10 @@
 interface DelegatedInkPointRenderer {
   // Used to send the DelegatedInkPoint that was created in the browser process
   // to viz in order to be drawn as part of the delegated ink trail.
-  StoreDelegatedInkPoint(DelegatedInkPoint point);
+  StoreDelegatedInkPoint(gfx.mojom.DelegatedInkPoint point);
 
   // Used to reset prediction and prediction metrics that have been generated
   // by previously received points. Used by the browser process when a delegated
   // ink trail should end.
   ResetPrediction();
-};
+};
\ No newline at end of file
diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn
index bb9f5ff..56f3b131 100644
--- a/storage/browser/BUILD.gn
+++ b/storage/browser/BUILD.gn
@@ -59,6 +59,8 @@
     "blob/view_blob_internals_job.h",
     "blob/write_blob_to_file.cc",
     "blob/write_blob_to_file.h",
+    "database/database_connections.cc",
+    "database/database_connections.h",
     "database/database_quota_client.cc",
     "database/database_quota_client.h",
     "database/database_tracker.cc",
@@ -277,6 +279,7 @@
     "blob/blob_transport_strategy_unittest.cc",
     "blob/blob_url_registry_unittest.cc",
     "blob/blob_url_store_impl_unittest.cc",
+    "database/database_connections_unittest.cc",
     "database/database_quota_client_unittest.cc",
     "database/database_tracker_unittest.cc",
     "database/database_util_unittest.cc",
diff --git a/storage/common/database/database_connections.cc b/storage/browser/database/database_connections.cc
similarity index 98%
rename from storage/common/database/database_connections.cc
rename to storage/browser/database/database_connections.cc
index 988b3f6..22cd1e4 100644
--- a/storage/common/database/database_connections.cc
+++ b/storage/browser/database/database_connections.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 "storage/common/database/database_connections.h"
+#include "storage/browser/database/database_connections.h"
 
 #include <ostream>
 
diff --git a/storage/common/database/database_connections.h b/storage/browser/database/database_connections.h
similarity index 89%
rename from storage/common/database/database_connections.h
rename to storage/browser/database/database_connections.h
index a8e23e5b..cdca7fc 100644
--- a/storage/common/database/database_connections.h
+++ b/storage/browser/database/database_connections.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 STORAGE_COMMON_DATABASE_DATABASE_CONNECTIONS_H_
-#define STORAGE_COMMON_DATABASE_DATABASE_CONNECTIONS_H_
+#ifndef STORAGE_BROWSER_DATABASE_DATABASE_CONNECTIONS_H_
+#define STORAGE_BROWSER_DATABASE_DATABASE_CONNECTIONS_H_
 
 #include <stdint.h>
 
@@ -15,7 +15,7 @@
 
 namespace storage {
 
-class COMPONENT_EXPORT(STORAGE_COMMON) DatabaseConnections {
+class COMPONENT_EXPORT(STORAGE_BROWSER) DatabaseConnections {
  public:
   DatabaseConnections();
   ~DatabaseConnections();
@@ -61,4 +61,4 @@
 
 }  // namespace storage
 
-#endif  // STORAGE_COMMON_DATABASE_DATABASE_CONNECTIONS_H_
+#endif  // STORAGE_BROWSER_DATABASE_DATABASE_CONNECTIONS_H_
diff --git a/storage/common/database/database_connections_unittest.cc b/storage/browser/database/database_connections_unittest.cc
similarity index 97%
rename from storage/common/database/database_connections_unittest.cc
rename to storage/browser/database/database_connections_unittest.cc
index fe023f8..9dc2f25 100644
--- a/storage/common/database/database_connections_unittest.cc
+++ b/storage/browser/database/database_connections_unittest.cc
@@ -4,7 +4,7 @@
 
 #include <stdint.h>
 
-#include "storage/common/database/database_connections.h"
+#include "storage/browser/database/database_connections.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace storage {
diff --git a/storage/browser/database/database_tracker.h b/storage/browser/database/database_tracker.h
index e4ce095..a46bc25 100644
--- a/storage/browser/database/database_tracker.h
+++ b/storage/browser/database/database_tracker.h
@@ -24,7 +24,7 @@
 #include "base/strings/string_util.h"
 #include "base/time/time.h"
 #include "net/base/completion_once_callback.h"
-#include "storage/common/database/database_connections.h"
+#include "storage/browser/database/database_connections.h"
 #include "url/origin.h"
 
 namespace sql {
diff --git a/storage/common/BUILD.gn b/storage/common/BUILD.gn
index 8fdf0dc0..26e2ec3 100644
--- a/storage/common/BUILD.gn
+++ b/storage/common/BUILD.gn
@@ -7,8 +7,6 @@
 component("common") {
   output_name = "storage_common"
   sources = [
-    "database/database_connections.cc",
-    "database/database_connections.h",
     "database/database_identifier.cc",
     "database/database_identifier.h",
     "file_system/file_system_info.cc",
@@ -47,7 +45,6 @@
   testonly = true
 
   sources = [
-    "database/database_connections_unittest.cc",
     "database/database_identifier_unittest.cc",
     "file_system/file_system_util_unittest.cc",
     "run_all_unittests.cc",
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index ec53f8d..32e0b59 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -250,65 +250,6 @@
       },
       {
         "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "android_webview_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4",
-              "os": "Ubuntu-16.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "android_webview_unittests",
-        "test_id_prefix": "ninja://android_webview/test:android_webview_unittests/"
-      },
-      {
-        "args": [
           "angle_unittests",
           "-v",
           "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
@@ -4157,44 +4098,6 @@
   "android-pie-arm64-wpt-rel-non-cq": {
     "isolated_scripts": [
       {
-        "isolate_name": "chrome_public_wpt",
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "chrome_public_wpt",
-        "resultdb": {
-          "enable": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "device_os": "PQ3A.190801.002",
-              "device_os_flavor": "google",
-              "device_os_type": "userdebug",
-              "device_type": "walleye",
-              "os": "Android"
-            }
-          ],
-          "expiration": 18000,
-          "hard_timeout": 14400,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 15
-        },
-        "test_id_prefix": "ninja://chrome/android:chrome_public_wpt/"
-      },
-      {
         "isolate_name": "system_webview_wpt",
         "merge": {
           "args": [
@@ -4272,6 +4175,62 @@
       }
     ]
   },
+  "android-web-platform-pie-x86-fyi-rel": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "--avd-config=../../tools/android/avd/proto/generic_android28.textpb"
+        ],
+        "isolate_name": "chrome_public_wpt",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "chrome_public_wpt",
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-8",
+              "os": "Ubuntu-16.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "expiration": 18000,
+          "hard_timeout": 14400,
+          "named_caches": [
+            {
+              "name": "avd_generic_android28",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_28_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 18
+        },
+        "test_id_prefix": "ninja://chrome/android:chrome_public_wpt/"
+      }
+    ]
+  },
   "android-weblayer-10-x86-rel-tests": {
     "gtest_tests": [
       {
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index 06c66ca..e23d7f2c 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -11859,6 +11859,2823 @@
       }
     ]
   },
+  "ToTFuchsia x64": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "gtest_tests": [
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "accessibility_unittests",
+        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_util_unittests",
+        "test_id_prefix": "ninja://base/util:base_util_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.blink_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_browsertests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_unittests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.components_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "compositor_unittests",
+        "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cr_fuchsia_base_unittests",
+        "test_id_prefix": "ninja://fuchsia/base:cr_fuchsia_base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_tests",
+        "test_id_prefix": "ninja://components/cronet:cronet_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_unittests",
+        "test_id_prefix": "ninja://components/cronet:cronet_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.headless_browsertests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_browsertests",
+        "test_id_prefix": "ninja://headless:headless_browsertests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_unittests",
+        "test_id_prefix": "ninja://headless:headless_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "http_service_tests",
+        "test_id_prefix": "ninja://fuchsia/http:http_service_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_blink_unittests",
+        "test_id_prefix": "ninja://media/blink:media_blink_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "native_theme_unittests",
+        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.net_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "service_manager_unittests",
+        "test_id_prefix": "ninja://services/service_manager/tests:service_manager_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "snapshot_unittests",
+        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.storage_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.ui_base_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.viz_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_browsertests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_browsertests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_unittests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "args": [
+          "bin/run_angle_unittests",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "isolate_name": "angle_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "angle_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/"
+      },
+      {
+        "args": [
+          "--num-retries=3"
+        ],
+        "isolate_name": "blink_web_tests",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "blink_web_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 12
+        },
+        "test_id_prefix": "ninja://:blink_web_tests/"
+      },
+      {
+        "args": [
+          "context_lost",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "context_lost_validating_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "depth_capture",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "depth_capture_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "gpu_process",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gpu_process_launch_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "hardware_accelerated_feature",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "hardware_accelerated_feature_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "info_collection",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--expected-vendor-id",
+          "0",
+          "--expected-device-id",
+          "0"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "info_collection_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "maps_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "pixel_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "screenshot_sync_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "trace_test",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "trace_test",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "kvm": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      }
+    ]
+  },
+  "ToTFuchsiaOfficial": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "gtest_tests": [
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "accessibility_unittests",
+        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_util_unittests",
+        "test_id_prefix": "ninja://base/util:base_util_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.blink_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_browsertests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_browsertests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_runner_unittests",
+        "test_id_prefix": "ninja://fuchsia/runners:cast_runner_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.components_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "compositor_unittests",
+        "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cr_fuchsia_base_unittests",
+        "test_id_prefix": "ninja://fuchsia/base:cr_fuchsia_base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_tests",
+        "test_id_prefix": "ninja://components/cronet:cronet_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cronet_unittests",
+        "test_id_prefix": "ninja://components/cronet:cronet_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.headless_browsertests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_browsertests",
+        "test_id_prefix": "ninja://headless:headless_browsertests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "headless_unittests",
+        "test_id_prefix": "ninja://headless:headless_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "http_service_tests",
+        "test_id_prefix": "ninja://fuchsia/http:http_service_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_blink_unittests",
+        "test_id_prefix": "ninja://media/blink:media_blink_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "native_theme_unittests",
+        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.net_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "service_manager_unittests",
+        "test_id_prefix": "ninja://services/service_manager/tests:service_manager_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "snapshot_unittests",
+        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.storage_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.ui_base_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.viz_unittests.filter",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_browsertests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_browsertests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_engine_unittests",
+        "test_id_prefix": "ninja://fuchsia/engine:web_engine_unittests/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "web_runner_integration_tests",
+        "test_id_prefix": "ninja://fuchsia/runners:web_runner_integration_tests/"
+      },
+      {
+        "args": [
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "args": [
+          "bin/run_angle_unittests",
+          "--runner-logs-dir=${ISOLATED_OUTDIR}/runner_logs"
+        ],
+        "isolate_name": "angle_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "angle_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/"
+      },
+      {
+        "args": [
+          "--sizes-path",
+          "fuchsia/release/size_tests/fyi_sizes.json"
+        ],
+        "isolate_name": "fuchsia_sizes",
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "fuchsia_sizes",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://fuchsia/release:fuchsia_sizes/"
+      }
+    ]
+  },
   "ToTLinux": {
     "additional_compile_targets": [
       "all"
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index bedbca64..9caaa76f 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -58,6 +58,7 @@
     'remove_from': [
       # On chromium.android, these do not need to run prior to M.
       'android-lollipop-arm-rel',
+      'android-11-x86-fyi-rel', # crbug.com/1188282: keeps crashing
     ],
   },
   'angle_end2end_tests': {
@@ -923,6 +924,20 @@
       }
     }
   },
+  'chrome_public_wpt': {
+    'modifications': {
+      'android-web-platform-pie-x86-fyi-rel': {
+        'swarming': {
+          'dimension_sets': [
+            {
+              'machine_type': 'n1-standard-8',
+            },
+          ],
+          'shards': 18,
+        },
+      },
+    },
+  },
   'chrome_sizes': {
     'modifications': {
       'lacros-amd64-generic-chrome': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 30541622..20f92ba4 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -5722,7 +5722,6 @@
     ],
 
     'android_wpt_scripts': [
-      'chrome_public_wpt',
       'system_webview_wpt',
       'weblayer_shell_wpt',
     ],
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 002e9434..eff17dc 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1228,6 +1228,20 @@
         'use_swarming': True,
         'os_type': 'android',
       },
+      'android-web-platform-pie-x86-fyi-rel': {
+        'mixins': [
+          'enable_resultdb',
+          'pie-x86-emulator',
+          'emulator-4-cores',
+          'linux-xenial',
+          'x86-64',
+        ],
+        'test_suites': {
+          'isolated_scripts': 'chrome_public_wpt',
+        },
+        'use_swarming': True,
+        'os_type': 'android',
+      },
       'android-weblayer-10-x86-rel-tests': {
         'mixins': [
           'enable_resultdb',
@@ -1851,6 +1865,42 @@
           'isolated_scripts': 'chrome_sizes_android',
         },
       },
+      'ToTFuchsia x64': {
+        'additional_compile_targets': [
+          'all',
+        ],
+        'browser_config': 'web-engine-shell',
+        'os_type': 'linux',
+        'mixins': [
+          'linux-xenial',
+        ],
+        'swarming': {
+          'dimension_sets': [
+            {
+              'kvm': '1',
+            },
+          ],
+        },
+        'test_suites': {
+          'gtest_tests': 'fuchsia_gtests',
+          'isolated_scripts': 'fuchsia_x64_isolated_scripts',
+          'gpu_telemetry_tests': 'fuchsia_gpu_telemetry_tests',
+        },
+      },
+      'ToTFuchsiaOfficial': {
+        'additional_compile_targets': [
+          'all',
+        ],
+        'test_suites': {
+          'gtest_tests': 'fuchsia_gtests',
+          'isolated_scripts': 'fuchsia_arm64_isolated_scripts',
+        },
+        'mixins': [
+          'arm64',
+          'docker',
+          'linux-xenial',
+        ],
+      },
       'ToTLinux': {
         'mixins': [
           'linux-xenial',
diff --git a/testing/trigger_scripts/base_test_triggerer.py b/testing/trigger_scripts/base_test_triggerer.py
index 71eb621..ddd4c79 100755
--- a/testing/trigger_scripts/base_test_triggerer.py
+++ b/testing/trigger_scripts/base_test_triggerer.py
@@ -107,6 +107,8 @@
     else:
       additional_args = all_args + bot_args
     additional_args = self.append_additional_args(additional_args, shard_index)
+    # crbug/1140389: debug print outs
+    logging.info('DEBUG: Before adding shardmap args: %s', additional_args)
     if shard_map:
       shard_map_str = json.dumps(shard_map, separators=(',', ':'))
       shard_map_args = ['--use-dynamic-shards']
@@ -279,6 +281,8 @@
     Returns:
       Exit code for the script.
     """
+    # crbug/1140389: debug print outs
+    logging.info('DEBUG: init: %s', remaining)
     verbose = args.multiple_dimension_script_verbose
     self.parse_bot_configs(args)
     # Prunes config list to the exact set of configurations to trigger jobs on.
@@ -295,6 +299,8 @@
       for k in config.iterkeys():
         filtered_remaining_args = self.remove_swarming_dimension(
           filtered_remaining_args, k)
+    # crbug/1140389: debug print outs
+    logging.info('DEBUG: After filtered: %s', filtered_remaining_args)
 
     merged_json = {}
     selected_config = self.select_config_indices(args, verbose)
@@ -314,9 +320,13 @@
       try:
         json_temp = self.make_temp_file(prefix='base_trigger_dimensions',
                                         suffix='.json')
+        # crbug/1140389: debug print outs
+        logging.info('DEBUG: Before modify args: %s', filtered_remaining_args)
         args_to_pass = self.modify_args(
             filtered_remaining_args, bot_index, shard_index, args.shards,
             json_temp, shard_map)
+        # crbug/1140389: debug print outs
+        logging.info('DEBUG: Before calling swarming: %s', args_to_pass)
         ret = self.run_swarming_go(
           args_to_pass, verbose, json_temp, shard_index, args.shards,
           merged_json)
diff --git a/testing/trigger_scripts/perf_device_trigger.py b/testing/trigger_scripts/perf_device_trigger.py
index 011b8b2..d09bbe27 100755
--- a/testing/trigger_scripts/perf_device_trigger.py
+++ b/testing/trigger_scripts/perf_device_trigger.py
@@ -355,7 +355,9 @@
   # Setup args for common contract of base class
   parser = base_test_triggerer.BaseTestTriggerer.setup_parser_contract(
       argparse.ArgumentParser(description=__doc__))
-  parser.add_argument('--use-dynamic-shards', type=bool, default=None,
+  parser.add_argument('--use-dynamic-shards',
+                      action='store_true',
+                      required=False,
                       help='Ignore --shards and the existing shard map. Will '
                            'generate a shard map at run time and use as much '
                            'device as possible.')
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 4408b0d5..c012f96 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -5535,29 +5535,6 @@
             ]
         }
     ],
-    "QuickAnswersLiveExperiment": [
-        {
-            "platforms": [
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "selected_text_confidence_threshold": "0.8",
-                        "surrounding_text_confidence_threshold": "0.9"
-                    },
-                    "enable_features": [
-                        "QuickAnswers",
-                        "QuickAnswersRichUi",
-                        "QuickAnswersTextAnnotator",
-                        "QuickAnswersTranslation",
-                        "QuickAnswersTranslationCloudAPI"
-                    ]
-                }
-            ]
-        }
-    ],
     "QuietNotificationPrompts": [
         {
             "platforms": [
@@ -6001,27 +5978,6 @@
             ]
         }
     ],
-    "SchemefulSameSiteAllOS": [
-        {
-            "platforms": [
-                "android",
-                "android_weblayer",
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "SchemefulSameSite"
-                    ]
-                }
-            ]
-        }
-    ],
     "ScrollResamplingRollout": [
         {
             "platforms": [
@@ -6913,21 +6869,6 @@
             ]
         }
     ],
-    "UMABackgroundSessions": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "UMABackgroundSessions"
-                    ]
-                }
-            ]
-        }
-    ],
     "UnidoPreferSync": [
         {
             "platforms": [
diff --git a/testing/xvfb.py b/testing/xvfb.py
index 5e093d9d..c03568a 100755
--- a/testing/xvfb.py
+++ b/testing/xvfb.py
@@ -209,7 +209,7 @@
       # been terminated. In that case, it's safe to read the return value.
       if wait_for_openbox:
         xwmstartupcheck_proc.wait()
-        if xwmstartupcheck_proc.returncode is not 0:
+        if xwmstartupcheck_proc.returncode != 0:
           raise _XvfbProcessError('Failed to get OpenBox up.')
 
     if use_xcompmgr:
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 348214a6..c693a3b 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -868,5 +868,13 @@
 const base::Feature kEnablePenetratingImageSelection{
     "EnablePenetratingImageSelection", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Used to configure a per-origin allowlist of performance.mark events that are
+// permitted to be included in slow reports traces. See crbug.com/1181774.
+const base::Feature kBackgroundTracingPerformanceMark{
+    "BackgroundTracingPerformanceMark", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::FeatureParam<std::string>
+    kBackgroundTracingPerformanceMark_AllowList{
+        &kBackgroundTracingPerformanceMark, "allow_list", ""};
+
 }  // namespace features
 }  // namespace blink
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 8757272..d1333f2 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -358,6 +358,13 @@
 
 BLINK_COMMON_EXPORT extern const base::Feature kEnablePenetratingImageSelection;
 
+// Used to configure a per-origin allowlist of performance.mark events that are
+// permitted to be included in slow reports traces. See crbug.com/1181774.
+BLINK_COMMON_EXPORT extern const base::Feature
+    kBackgroundTracingPerformanceMark;
+BLINK_COMMON_EXPORT extern const base::FeatureParam<std::string>
+    kBackgroundTracingPerformanceMark_AllowList;
+
 }  // namespace features
 }  // namespace blink
 
diff --git a/third_party/blink/public/mojom/buckets/bucket_manager_host.mojom b/third_party/blink/public/mojom/buckets/bucket_manager_host.mojom
index b169054..5e0d2909 100644
--- a/third_party/blink/public/mojom/buckets/bucket_manager_host.mojom
+++ b/third_party/blink/public/mojom/buckets/bucket_manager_host.mojom
@@ -20,7 +20,6 @@
 
 // The policies applied to a StorageBucket upon its creation.
 struct BucketPolicies {
-  string title;
   bool persisted;
   BucketDurability durability;
   int64 quota;
diff --git a/third_party/blink/public/mojom/handwriting/handwriting.mojom b/third_party/blink/public/mojom/handwriting/handwriting.mojom
index 3a788d7..efd639e 100644
--- a/third_party/blink/public/mojom/handwriting/handwriting.mojom
+++ b/third_party/blink/public/mojom/handwriting/handwriting.mojom
@@ -18,7 +18,7 @@
   gfx.mojom.PointF location;
   // The time elapsed since the starting time (e.g. when the first ink point
   // of the drawing is captured).
-  mojo_base.mojom.TimeDelta t;
+  mojo_base.mojom.TimeDelta? t;
 };
 
 // Represents a stroke which is just a series of points.
@@ -125,7 +125,7 @@
   array<string> languages;
 };
 
-// Interface for a renderer to use a specific speech recognition backend.
+// Interface for a renderer to use a specific handwriting recognition backend.
 // The browser handles the requests and forwards them to the appropriate
 // backend.
 interface HandwritingRecognizer {
@@ -141,7 +141,7 @@
 };
 
 // Per-document interface that allows the renderer to ask the browser for
-// a specific speech recognizer backend, query capabilities, et cetera.
+// a specific handwriting recognizer backend, query capabilities, et cetera.
 // Corresponds to `navigator_handwriting_recognition_service.idl`.
 interface HandwritingRecognitionService {
   // Creates a recognizer.
diff --git a/third_party/blink/public/mojom/mediasession/media_session.mojom b/third_party/blink/public/mojom/mediasession/media_session.mojom
index a5e407e..6b29a3a 100644
--- a/third_party/blink/public/mojom/mediasession/media_session.mojom
+++ b/third_party/blink/public/mojom/mediasession/media_session.mojom
@@ -59,6 +59,12 @@
   // on the UI.
   SetMetadata(SpecMediaMetadata? metadata);
 
+  // Notifies the browser that the page specified its current microphone state.
+  SetMicrophoneState(media_session.mojom.MicrophoneState microphone_state);
+
+  // Notifies the browser that the page specified its current camera state.
+  SetCameraState(media_session.mojom.CameraState camera_state);
+
   // Notifies the browser that the event handler for |action| has been set,
   // browser needs to show a media button in the UI or register listeners to the
   // platform.
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 9452d2f..e4048506 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -2683,7 +2683,7 @@
   kV8RTCRtpTransceiver_Stopped_AttributeGetter = 3374,
   kV8RTCRtpTransceiver_Stop_Method = 3375,
   kSecurePaymentConfirmation = 3376,
-  kOBSOLETE_CSSInvalidVariableUnset = 3377,
+  kCSSInvalidVariableUnset = 3377,
   kElementInternalsShadowRoot = 3378,
   kAnyPiiFieldDetected_PredictedTypeMatch = 3379,
   kEmailFieldDetected_PredictedTypeMatch = 3380,
diff --git a/third_party/blink/renderer/bindings/core/v8/module_record.cc b/third_party/blink/renderer/bindings/core/v8/module_record.cc
index 6dd4e48..eb1c91ad 100644
--- a/third_party/blink/renderer/bindings/core/v8/module_record.cc
+++ b/third_party/blink/renderer/bindings/core/v8/module_record.cc
@@ -188,7 +188,7 @@
   DCHECK(modulator);
 
   ModuleRequest module_request(
-      ToCoreStringWithNullCheck(specifier), TextPosition(),
+      ToCoreStringWithNullCheck(specifier), TextPosition::MinimumPosition(),
       ModuleRecord::ToBlinkImportAssertions(
           context, referrer, import_assertions,
           /*v8_import_assertions_has_positions=*/true));
@@ -227,7 +227,7 @@
     v8::Local<v8::String> v8_assertion_value =
         v8_import_assertions->Get(context, (i * kV8AssertionEntrySize) + 1)
             .As<v8::String>();
-    TextPosition assertion_position;
+    TextPosition assertion_position = TextPosition::MinimumPosition();
     if (v8_import_assertions_has_positions) {
       int32_t v8_assertion_source_offset =
           v8_import_assertions->Get(context, (i * kV8AssertionEntrySize) + 2)
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc
index 21ce076ff..d4b5fc7 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc
@@ -352,7 +352,7 @@
   TRACE_EVENT_END1(
       kTraceEventCategoryGroup, "v8.compile", "data",
       inspector_compile_script_event::Data(
-          file_name, TextPosition(),
+          file_name, TextPosition::MinimumPosition(),
           inspector_compile_script_event::V8CacheResult(
               inspector_compile_script_event::V8CacheResult::ProduceResult(
                   cached_data ? cached_data->length : 0),
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
index b7509d4f..7f88acc 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -595,7 +595,7 @@
   }
 
   ModuleRequest module_request(
-      specifier, TextPosition(),
+      specifier, TextPosition::MinimumPosition(),
       ModuleRecord::ToBlinkImportAssertions(
           script_state->GetContext(), v8::Local<v8::Module>(),
           v8_import_assertions, /*v8_import_assertions_has_positions=*/false));
diff --git a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.cc b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.cc
index 67c85ab..4d23935 100644
--- a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.cc
+++ b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.cc
@@ -28,6 +28,7 @@
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_copier.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 using performance_manager::mojom::blink::IframeAttributionData;
 using performance_manager::mojom::blink::IframeAttributionDataPtr;
@@ -248,6 +249,11 @@
       frame.GetFrameToken().GetAs<RemoteFrameToken>());
 }
 
+void RendererResourceCoordinatorImpl::FireBackgroundTracingTrigger(
+    const String& trigger_name) {
+  // TODO(crbug.com/1181774): Implement this.
+}
+
 RendererResourceCoordinatorImpl::RendererResourceCoordinatorImpl(
     mojo::PendingRemote<ProcessCoordinationUnit> remote) {
   service_.Bind(std::move(remote));
diff --git a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h
index a908f80..583b9abd 100644
--- a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h
+++ b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl.h
@@ -41,6 +41,7 @@
                                     const HTMLFrameOwnerElement& owner) final;
   void OnBeforeContentFrameDetached(const Frame& frame,
                                     const HTMLFrameOwnerElement& owner) final;
+  void FireBackgroundTracingTrigger(const String& trigger_name) final;
 
  private:
   friend class RendererResourceCoordinatorImplTest;
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 8055f4f..98e2a10 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1595,6 +1595,7 @@
     "svg/svg_text_content_element_test.cc",
     "svg/svg_use_element_test.cc",
     "svg/unsafe_svg_attribute_sanitization_test.cc",
+    "timing/background_tracing_helper_test.cc",
     "timing/memory_info_test.cc",
     "timing/performance_mark_test.cc",
     "timing/performance_navigation_timing_test.cc",
diff --git a/third_party/blink/renderer/core/DEPS b/third_party/blink/renderer/core/DEPS
index ee1f20f..97c5c7a 100644
--- a/third_party/blink/renderer/core/DEPS
+++ b/third_party/blink/renderer/core/DEPS
@@ -148,7 +148,7 @@
     ],
     "data_object_item.cc" : [ "+ui/gfx/codec" ],
     "chrome_client.h" : [
-        "+components/viz/common/delegated_ink_metadata.h",
+        "+ui/gfx/delegated_ink_metadata.h",
         "+components/viz/common/surfaces/frame_sink_id.h",
     ],
     "clipboard_utilities.cc" : [ "+net/base/escape.h" ],
diff --git a/third_party/blink/renderer/core/clipboard/data_object.cc b/third_party/blink/renderer/core/clipboard/data_object.cc
index 58d1c47d..d10402a7 100644
--- a/third_party/blink/renderer/core/clipboard/data_object.cc
+++ b/third_party/blink/renderer/core/clipboard/data_object.cc
@@ -57,10 +57,9 @@
   for (const String& type : system_clipboard->ReadAvailableTypes()) {
     if (paste_mode == PasteMode::kPlainTextOnly && type != kMimeTypeTextPlain)
       continue;
-    mojom::blink::ClipboardFilesPtr files;
     if (type == kMimeTypeTextURIList &&
         base::FeatureList::IsEnabled(features::kClipboardFilenames)) {
-      files = system_clipboard->ReadFiles();
+      mojom::blink::ClipboardFilesPtr files = system_clipboard->ReadFiles();
       // Ignore ReadFiles() result if clipboard sequence number has changed.
       if (system_clipboard->SequenceNumber() != sequence_number) {
         files->files.clear();
@@ -72,9 +71,9 @@
             base::MakeRefCounted<FileSystemAccessDropData>(
                 std::move(file->file_system_access_token)));
       }
-    }
-    if (files && !files->files.IsEmpty()) {
-      DraggedIsolatedFileSystem::PrepareForDataObject(data_object);
+      if (files && !files->files.IsEmpty()) {
+        DraggedIsolatedFileSystem::PrepareForDataObject(data_object);
+      }
     } else {
       data_object->item_list_.push_back(DataObjectItem::CreateFromClipboard(
           system_clipboard, type, sequence_number));
diff --git a/third_party/blink/renderer/core/css/css_test_helpers.cc b/third_party/blink/renderer/core/css/css_test_helpers.cc
index 46d5bba0..f766d54 100644
--- a/third_party/blink/renderer/core/css/css_test_helpers.cc
+++ b/third_party/blink/renderer/core/css/css_test_helpers.cc
@@ -56,9 +56,8 @@
 }
 
 void TestStyleSheet::AddCSSRules(const String& css_text, bool is_empty_sheet) {
-  TextPosition position;
   unsigned sheet_length = style_sheet_->length();
-  style_sheet_->Contents()->ParseStringAtPosition(css_text, position);
+  style_sheet_->Contents()->ParseString(css_text);
   if (!is_empty_sheet)
     ASSERT_GT(style_sheet_->length(), sheet_length);
   else
@@ -66,9 +65,8 @@
 }
 
 CSSStyleSheet* CreateStyleSheet(Document& document) {
-  TextPosition position;
-  return CSSStyleSheet::CreateInline(document, NullURL(), position,
-                                     UTF8Encoding());
+  return CSSStyleSheet::CreateInline(
+      document, NullURL(), TextPosition::MinimumPosition(), UTF8Encoding());
 }
 
 PropertyRegistration* CreatePropertyRegistration(const String& name) {
@@ -146,9 +144,8 @@
 }
 
 StyleRuleBase* ParseRule(Document& document, String text) {
-  TextPosition position;
-  auto* sheet = CSSStyleSheet::CreateInline(document, NullURL(), position,
-                                            UTF8Encoding());
+  auto* sheet = CSSStyleSheet::CreateInline(
+      document, NullURL(), TextPosition::MinimumPosition(), UTF8Encoding());
   const auto* context = MakeGarbageCollected<CSSParserContext>(document);
   return CSSParser::ParseRule(context, sheet->Contents(), text);
 }
diff --git a/third_party/blink/renderer/core/css/properties/longhand.h b/third_party/blink/renderer/core/css/properties/longhand.h
index 5d4aa0d..cb69308 100644
--- a/third_party/blink/renderer/core/css/properties/longhand.h
+++ b/third_party/blink/renderer/core/css/properties/longhand.h
@@ -31,7 +31,6 @@
   }
   virtual void ApplyInitial(StyleResolverState&) const { NOTREACHED(); }
   virtual void ApplyInherit(StyleResolverState&) const { NOTREACHED(); }
-
   // Properties which take tree-scoped references should override this method to
   // handle the TreeScope during application.
   virtual void ApplyValue(StyleResolverState& state,
diff --git a/third_party/blink/renderer/core/css/properties/longhands/custom_property.cc b/third_party/blink/renderer/core/css/properties/longhands/custom_property.cc
index 935ab26..8baa977 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/custom_property.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/custom_property.cc
@@ -81,7 +81,6 @@
 void CustomProperty::ApplyValue(StyleResolverState& state,
                                 const CSSValue& value) const {
   if (value.IsInvalidVariableValue()) {
-    DCHECK(SupportsGuaranteedInvalid());
     state.Style()->SetVariableData(name_, nullptr, IsInherited());
     if (registration_)
       state.Style()->SetVariableValue(name_, nullptr, IsInherited());
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index 041a2b0..9dbf951f 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -639,10 +639,12 @@
 
   state_.Style()->SetHasVariableDeclaration();
 
-  if (!data || resolver.InCycle()) {
-    if (!To<CustomProperty>(property).SupportsGuaranteedInvalid())
-      return cssvalue::CSSUnsetValue::Create();
+  if (resolver.InCycle())
     return CSSInvalidVariableValue::Create();
+
+  if (!data) {
+    MaybeUseCountInvalidVariableUnset(To<CustomProperty>(property));
+    return cssvalue::CSSUnsetValue::Create();
   }
 
   if (data == decl.Value())
@@ -979,4 +981,18 @@
     CountUse(WebFeature::kCSSKeywordRevert);
 }
 
+void StyleCascade::MaybeUseCountInvalidVariableUnset(
+    const CustomProperty& property) {
+  if (!property.SupportsGuaranteedInvalid())
+    return;
+  if (!property.IsInherited() && !property.HasInitialValue())
+    return;
+  const AtomicString& name = property.GetPropertyNameAtomicString();
+  const ComputedStyle* parent_style = state_.ParentStyle();
+  if (parent_style &&
+      parent_style->GetVariableData(name, property.IsInherited())) {
+    CountUse(WebFeature::kCSSInvalidVariableUnset);
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.h b/third_party/blink/renderer/core/css/resolver/style_cascade.h
index 2affe8fd..40422a1 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.h
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.h
@@ -340,6 +340,7 @@
   void CountUse(WebFeature);
   void MaybeUseCountRevert(const CSSValue&);
   void MaybeUseCountSummaryDisplayBlock();
+  void MaybeUseCountInvalidVariableUnset(const CustomProperty&);
 
   StyleResolverState& state_;
   MatchResult match_result_;
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
index eb2909f..0879021d9 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
@@ -1060,19 +1060,6 @@
   cascade.Add("--b", "var(--a)");
   cascade.Apply();
 
-  EXPECT_EQ("0px", cascade.ComputedValue("--a"));
-  EXPECT_EQ("0px", cascade.ComputedValue("--b"));
-}
-
-TEST_F(StyleCascadeTest, UniversalSyntaxCycle) {
-  RegisterProperty(GetDocument(), "--a", "*", "foo", false);
-  RegisterProperty(GetDocument(), "--b", "*", "bar", false);
-
-  TestCascade cascade(GetDocument());
-  cascade.Add("--a", "var(--b)");
-  cascade.Add("--b", "var(--a)");
-  cascade.Apply();
-
   EXPECT_FALSE(cascade.ComputedValue("--a"));
   EXPECT_FALSE(cascade.ComputedValue("--b"));
 }
@@ -1085,11 +1072,11 @@
   cascade.Add("--b", "var(--a)");
   cascade.Apply();
 
-  EXPECT_EQ("0px", cascade.ComputedValue("--a"));
+  EXPECT_FALSE(cascade.ComputedValue("--a"));
   EXPECT_FALSE(cascade.ComputedValue("--b"));
 }
 
-TEST_F(StyleCascadeTest, ReferencedRegisteredCycle) {
+TEST_F(StyleCascadeTest, FallbackTriggeredByRegisteredCycle) {
   RegisterProperty(GetDocument(), "--a", "<length>", "0px", false);
   RegisterProperty(GetDocument(), "--b", "<length>", "0px", false);
 
@@ -1102,10 +1089,10 @@
   cascade.Add("--d", "var(--b,2px)");
   cascade.Apply();
 
-  EXPECT_EQ("0px", cascade.ComputedValue("--a"));
-  EXPECT_EQ("0px", cascade.ComputedValue("--b"));
-  EXPECT_EQ("0px", cascade.ComputedValue("--c"));
-  EXPECT_EQ("0px", cascade.ComputedValue("--d"));
+  EXPECT_FALSE(cascade.ComputedValue("--a"));
+  EXPECT_FALSE(cascade.ComputedValue("--b"));
+  EXPECT_EQ("1px", cascade.ComputedValue("--c"));
+  EXPECT_EQ("2px", cascade.ComputedValue("--d"));
 }
 
 TEST_F(StyleCascadeTest, CycleStillInvalidWithFallback) {
@@ -1268,7 +1255,7 @@
   cascade.Add("--x", "10em");
   cascade.Apply();
 
-  EXPECT_EQ("0px", cascade.ComputedValue("--x"));
+  EXPECT_FALSE(cascade.ComputedValue("--x"));
 }
 
 TEST_F(StyleCascadeTest, SubstitutingEmCycles) {
@@ -1281,8 +1268,8 @@
   cascade.Add("--z", "var(--x,1px)");
   cascade.Apply();
 
-  EXPECT_EQ("0px", cascade.ComputedValue("--y"));
-  EXPECT_EQ("0px", cascade.ComputedValue("--z"));
+  EXPECT_FALSE(cascade.ComputedValue("--y"));
+  EXPECT_EQ("1px", cascade.ComputedValue("--z"));
 }
 
 TEST_F(StyleCascadeTest, RemUnit) {
@@ -1331,7 +1318,7 @@
   cascade.Add("--x", "1rem");
   cascade.Apply();
 
-  EXPECT_EQ("0px", cascade.ComputedValue("--x"));
+  EXPECT_FALSE(cascade.ComputedValue("--x"));
 }
 
 TEST_F(StyleCascadeTest, RemUnitInRootFontSizeNonCycle) {
@@ -1838,8 +1825,7 @@
   EXPECT_EQ("40px", cascade.ComputedValue("margin-left"));
 }
 
-// TODO(crbug.com/1185745): Temporarily disabled
-TEST_F(StyleCascadeTest, DISABLED_RevertToCycleInKeyframe) {
+TEST_F(StyleCascadeTest, RevertToCycleInKeyframe) {
   RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
 
   AppendSheet(R"HTML(
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index e4ec512..ee7c2f9 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -818,7 +818,7 @@
   CSSStyleSheet* style_sheet = nullptr;
   style_sheet = CSSStyleSheet::CreateInline(element, NullURL(), start_position,
                                             GetDocument().Encoding());
-  style_sheet->Contents()->ParseStringAtPosition(text, start_position);
+  style_sheet->Contents()->ParseString(text);
   return style_sheet;
 }
 
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 59e63fedd..8da208f 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -3182,6 +3182,239 @@
       GetDocument().IsUseCounted(WebFeature::kCSSSystemColorComputeToSelf));
 }
 
+TEST_F(StyleEngineTest, InvalidVariableUnsetUseCount) {
+  // Do not count for basic variable usage.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      #outer { --x: foo; }
+      #inner { --x: bar; }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Do not count when a fallback handles the unknown variable.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      #outer { --x: foo; }
+      #inner { --x: var(--unknown,bar); }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Do not count for explicit 'unset'.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      #outer { --x: foo; }
+      #inner { --x: unset; }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Do not count when we anyway end up with the guaranteed-invalid value.
+  // (Applies to registered properties as well).
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      @property --y {
+        syntax: "*";
+        inherits: true;
+      }
+      @property --z {
+        syntax: "*";
+        inherits: false;
+      }
+      #inner {
+        --x: var(--unknown);
+        --y: var(--unknown);
+        --z: var(--unknown);
+      }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Count when 'unset' inherits something that not guaranteed-invalid.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      #outer { --x: foo; }
+      #inner { --x: var(--unknown); }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_TRUE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Do not count for non-universal registered custom properties.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      @property --x {
+        syntax: "<length>";
+        inherits: true;
+        initial-value: 0px;
+      }
+      #outer { --x: 1px; }
+      #inner { --x: var(--unknown); }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Count for universal registered custom properties.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      @property --x {
+        syntax: "*";
+        inherits: true;
+      }
+      #outer { --x: bar; }
+      #inner { --x: var(--unknown); }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_TRUE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Do not count for non-inherited universal registered custom properties
+  // without initial value.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      @property --x {
+        syntax: "*";
+        inherits: false;
+      }
+      #outer { --x: bar; }
+      #inner { --x: var(--unknown); }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Count for universal registered custom properties even with an
+  // initial-value defined.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      @property --x {
+        syntax: "*";
+        inherits: true;
+        initial-value: foo;
+      }
+      #outer { --x: bar; }
+      #inner { --x: var(--unknown); }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_TRUE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Do not count for cycles.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      @property --a {
+        syntax: "*";
+        inherits: true;
+      }
+      @property --b {
+        syntax: "*";
+        inherits: true;
+      }
+      #outer {
+        --a: foo;
+        --b: foo;
+        --c: foo;
+        --d: foo;
+      }
+      #inner {
+        --a: var(--b);
+        --b: var(--a);
+        --c: var(--d);
+        --d: var(--c);
+      }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Count for @keyframes
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      @keyframes anim {
+        from { --x: var(--unknown); }
+        to { --x: var(--unknown); }
+      }
+      #outer {
+        --x: foo;
+      }
+      #inner {
+        animation: anim 10s;
+      }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_TRUE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+
+  // Don't count for @keyframes if there's nothing to inherit.
+  GetDocument().body()->setInnerHTML(R"HTML(
+    <style>
+      @keyframes anim {
+        from { --x: var(--unknown); }
+        to { --x: var(--unknown); }
+      }
+      #inner {
+        animation: anim 10s;
+      }
+    </style>
+    <div id=outer>
+      <div id=inner></div>
+    <div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(IsUseCounted(WebFeature::kCSSInvalidVariableUnset));
+  ClearUseCounter(WebFeature::kCSSInvalidVariableUnset);
+}
+
 // https://crbug.com/1050564
 TEST_F(StyleEngineTest, MediaAttributeChangeUpdatesFontCacheVersion) {
   GetDocument().body()->setInnerHTML(R"HTML(
diff --git a/third_party/blink/renderer/core/css/style_sheet_contents.cc b/third_party/blink/renderer/core/css/style_sheet_contents.cc
index 0d116d83..ab7e0485 100644
--- a/third_party/blink/renderer/core/css/style_sheet_contents.cc
+++ b/third_party/blink/renderer/core/css/style_sheet_contents.cc
@@ -348,14 +348,6 @@
 
 ParseSheetResult StyleSheetContents::ParseString(const String& sheet_text,
                                                  bool allow_import_rules) {
-  return ParseStringAtPosition(sheet_text, TextPosition::MinimumPosition(),
-                               allow_import_rules);
-}
-
-ParseSheetResult StyleSheetContents::ParseStringAtPosition(
-    const String& sheet_text,
-    const TextPosition& start_position,
-    bool allow_import_rules) {
   const auto* context =
       MakeGarbageCollected<CSSParserContext>(ParserContext(), this);
   return CSSParser::ParseSheet(context, this, sheet_text,
diff --git a/third_party/blink/renderer/core/css/style_sheet_contents.h b/third_party/blink/renderer/core/css/style_sheet_contents.h
index c1b8345..ecf0698d 100644
--- a/third_party/blink/renderer/core/css/style_sheet_contents.h
+++ b/third_party/blink/renderer/core/css/style_sheet_contents.h
@@ -68,9 +68,6 @@
 
   void ParseAuthorStyleSheet(const CSSStyleSheetResource*);
   ParseSheetResult ParseString(const String&, bool allow_import_rules = true);
-  ParseSheetResult ParseStringAtPosition(const String&,
-                                         const TextPosition&,
-                                         bool allow_import_rules = true);
 
   bool IsCacheableForResource() const;
   bool IsCacheableForStyleElement() const;
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index d767bdfc..290eedd6 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -6266,12 +6266,11 @@
               ScriptState* state = resolver->GetScriptState();
               ScriptState::Scope scope(state);
 
-              // TODO(yaoxia): Distinguish between the different causes.
               resolver->Reject(V8ThrowDOMException::CreateOrEmpty(
                   state->GetIsolate(), DOMExceptionCode::kDataError,
                   "Failed to get the interest cohort: either it is "
                   "unavailable, or the preferences or content settings has "
-                  "denined the access."));
+                  "denied access."));
             } else {
               InterestCohort* result = InterestCohort::Create();
               result->setId(interest_cohort->id);
diff --git a/third_party/blink/renderer/core/exported/web_document_test.cc b/third_party/blink/renderer/core/exported/web_document_test.cc
index d11deaa..9e4267a4 100644
--- a/third_party/blink/renderer/core/exported/web_document_test.cc
+++ b/third_party/blink/renderer/core/exported/web_document_test.cc
@@ -382,8 +382,12 @@
 
   ASSERT_TRUE(SiteForCookiesEqual(g_nested_origin_secure_a,
                                   TopDocument()->SiteForCookies()));
-  ASSERT_TRUE(SiteForCookiesEqual(g_nested_origin_secure_a,
-                                  NestedDocument()->SiteForCookies()));
+  // Since NestedDocument is secure, and the parent is insecure, its
+  // SiteForCookies will be null and therefore will not match.
+  ASSERT_FALSE(SiteForCookiesEqual(g_nested_origin_secure_a,
+                                   NestedDocument()->SiteForCookies()));
+  // However its site shouldn't be opaque
+  ASSERT_FALSE(NestedDocument()->SiteForCookies().site().opaque());
 
   ASSERT_TRUE(
       OriginsEqual(g_nested_origin_secure_a, TopDocument()->TopFrameOrigin()));
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 57d18d8..222c8cd 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -2359,6 +2359,10 @@
     DCHECK(dispatching_pageshow);
     DCHECK(page_restore_params);
     Scheduler()->SetPageBackForwardCached(new_state->is_in_back_forward_cache);
+    if (MainFrame()->IsWebLocalFrame()) {
+      LocalFrame* local_frame = To<LocalFrame>(page->MainFrame());
+      probe::DidRestoreFromBackForwardCache(local_frame);
+    }
   }
 
   // Make sure no TrackedFeaturesUpdate message is sent after the ACK
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy_test.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy_test.cc
index 07e0f80..b4f36193 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy_test.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy_test.cc
@@ -339,7 +339,7 @@
 
   String context_url;
   String content;
-  WTF::OrdinalNumber context_line;
+  OrdinalNumber context_line = OrdinalNumber::First();
 
   // We need document for HTMLScriptElement tests.
   auto dummy = std::make_unique<DummyPageHolder>();
@@ -1154,7 +1154,7 @@
   String source;
   String context_url;
   String nonce;
-  OrdinalNumber ordinal_number;
+  OrdinalNumber ordinal_number = OrdinalNumber::First();
   auto* element =
       MakeGarbageCollected<HTMLScriptElement>(*document, CreateElementFlags());
 
diff --git a/third_party/blink/renderer/core/frame/location.cc b/third_party/blink/renderer/core/frame/location.cc
index 4b3442954..b28346b7 100644
--- a/third_party/blink/renderer/core/frame/location.cc
+++ b/third_party/blink/renderer/core/frame/location.cc
@@ -280,7 +280,7 @@
              ->AllowInline(ContentSecurityPolicy::InlineType::kNavigation,
                            nullptr /* element */, script_source,
                            String() /* nonce */, incumbent_window->Url(),
-                           OrdinalNumber())) {
+                           OrdinalNumber::First())) {
       return;
     }
   }
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index a82097b..ff4df30 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -2749,7 +2749,7 @@
 }
 
 void WebFrameWidgetImpl::SetDelegatedInkMetadata(
-    std::unique_ptr<viz::DelegatedInkMetadata> metadata) {
+    std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
   widget_base_->LayerTreeHost()->SetDelegatedInkMetadata(std::move(metadata));
 }
 
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index 55c23ec..59d85d7 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -204,7 +204,7 @@
   mojom::blink::DisplayMode DisplayMode() const override;
   const WebVector<gfx::Rect>& WindowSegments() const override;
   void SetDelegatedInkMetadata(
-      std::unique_ptr<viz::DelegatedInkMetadata> metadata) final;
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata) final;
   void DidOverscroll(const gfx::Vector2dF& overscroll_delta,
                      const gfx::Vector2dF& accumulated_overscroll,
                      const gfx::PointF& position,
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index 1b180b9..88312a9 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -2042,12 +2042,6 @@
   // We must call init() after frame_ is assigned because it is referenced
   // during init().
   frame_->Init(opener_frame, std::move(policy_container));
-  CHECK(frame_);
-  CHECK(frame_->GetDocument()->IsInitialEmptyDocument());
-  if (!Parent() && !Opener() &&
-      frame_->GetSettings()->GetShouldReuseGlobalForUnownedMainFrame()) {
-    frame_->DomWindow()->GetMutableSecurityOrigin()->GrantUniversalAccess();
-  }
 
   if (!owner) {
     // This trace event is needed to detect the main frame of the
diff --git a/third_party/blink/renderer/core/html/parser/atomic_html_token_test.cc b/third_party/blink/renderer/core/html/parser/atomic_html_token_test.cc
index ea3389d..7a1dae6e 100644
--- a/third_party/blink/renderer/core/html/parser/atomic_html_token_test.cc
+++ b/third_party/blink/renderer/core/html/parser/atomic_html_token_test.cc
@@ -55,7 +55,8 @@
   token.BeginAttributeValue(8);
   token.EndAttributeValue(8);
 
-  AtomicHTMLToken atoken(CompactHTMLToken(&token, TextPosition()));
+  AtomicHTMLToken atoken(
+      CompactHTMLToken(&token, TextPosition::MinimumPosition()));
 
   const blink::Attribute* attribute_b = atoken.GetAttributeItem(
       QualifiedName(AtomicString(), "b", AtomicString()));
diff --git a/third_party/blink/renderer/core/html/parser/compact_html_token_test.cc b/third_party/blink/renderer/core/html/parser/compact_html_token_test.cc
index f71e5cc..4b49831 100644
--- a/third_party/blink/renderer/core/html/parser/compact_html_token_test.cc
+++ b/third_party/blink/renderer/core/html/parser/compact_html_token_test.cc
@@ -22,7 +22,7 @@
   token.BeginAttributeValue(8);
   token.EndAttributeValue(8);
 
-  CompactHTMLToken ctoken(&token, TextPosition());
+  CompactHTMLToken ctoken(&token, TextPosition::MinimumPosition());
 
   const CompactHTMLToken::Attribute* attribute_b = ctoken.GetAttributeItem(
       QualifiedName(AtomicString(), "b", AtomicString()));
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.cc b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
index 0f82a03..edb6f48 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser.cc
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
@@ -405,6 +405,7 @@
                                   this,
                                   loading_task_runner_.get())
                             : nullptr),
+      text_position_(TextPosition::MinimumPosition()),
       task_runner_state_(
           MakeGarbageCollected<HTMLDocumentParserState>(sync_policy)),
       pending_csp_meta_token_(nullptr),
diff --git a/third_party/blink/renderer/core/html/parser/html_input_stream.h b/third_party/blink/renderer/core/html/parser/html_input_stream.h
index 9539cce..61791f4f 100644
--- a/third_party/blink/renderer/core/html/parser/html_input_stream.h
+++ b/third_party/blink/renderer/core/html/parser/html_input_stream.h
@@ -113,9 +113,9 @@
 
  public:
   explicit InsertionPointRecord(HTMLInputStream& input_stream)
-      : input_stream_(&input_stream) {
-    line_ = input_stream_->Current().CurrentLine();
-    column_ = input_stream_->Current().CurrentColumn();
+      : input_stream_(&input_stream),
+        line_(input_stream_->Current().CurrentLine()),
+        column_(input_stream_->Current().CurrentColumn()) {
     input_stream_->SplitInto(next_);
     // We 'fork' current position and use it for the generated script part. This
     // is a bit weird, because generated part does not have positions within an
diff --git a/third_party/blink/renderer/core/html/parser/html_resource_preloader_test.cc b/third_party/blink/renderer/core/html/parser/html_resource_preloader_test.cc
index afb03ba1..9dbe2ef 100644
--- a/third_party/blink/renderer/core/html/parser/html_resource_preloader_test.cc
+++ b/third_party/blink/renderer/core/html/parser/html_resource_preloader_test.cc
@@ -56,11 +56,12 @@
     // TODO(yoav): Need a mock loader here to verify things are happenning
     // beyond preconnect.
     auto preload_request = PreloadRequest::CreateIfNeeded(
-        String(), TextPosition(), test_case.url, KURL(test_case.base_url),
-        ResourceType::kImage, network::mojom::ReferrerPolicy(),
-        PreloadRequest::kDocumentIsReferrer, ResourceFetcher::kImageNotImageSet,
-        nullptr /* exclusion_info */, FetchParameters::ResourceWidth(),
-        ClientHintsPreferences(), PreloadRequest::kRequestTypePreconnect);
+        String(), TextPosition::MinimumPosition(), test_case.url,
+        KURL(test_case.base_url), ResourceType::kImage,
+        network::mojom::ReferrerPolicy(), PreloadRequest::kDocumentIsReferrer,
+        ResourceFetcher::kImageNotImageSet, nullptr /* exclusion_info */,
+        FetchParameters::ResourceWidth(), ClientHintsPreferences(),
+        PreloadRequest::kRequestTypePreconnect);
     DCHECK(preload_request);
     if (test_case.is_cors)
       preload_request->SetCrossOrigin(kCrossOriginAttributeAnonymous);
diff --git a/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator_test.cc b/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator_test.cc
index d3ebc641..e98c703 100644
--- a/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator_test.cc
+++ b/third_party/blink/renderer/core/html/parser/html_tree_builder_simulator_test.cc
@@ -20,22 +20,25 @@
   HTMLToken token;
   EXPECT_TRUE(tokenizer->NextToken(input, token));
   EXPECT_EQ(HTMLTreeBuilderSimulator::kOtherToken,
-            simulator.Simulate(CompactHTMLToken(&token, TextPosition()),
-                               tokenizer.get()));
+            simulator.Simulate(
+                CompactHTMLToken(&token, TextPosition::MinimumPosition()),
+                tokenizer.get()));
 
   token.Clear();
   EXPECT_TRUE(tokenizer->NextToken(input, token));
   EXPECT_EQ(HTMLTreeBuilderSimulator::kValidScriptStart,
-            simulator.Simulate(CompactHTMLToken(&token, TextPosition()),
-                               tokenizer.get()));
+            simulator.Simulate(
+                CompactHTMLToken(&token, TextPosition::MinimumPosition()),
+                tokenizer.get()));
 
   EXPECT_EQ(HTMLTokenizer::kScriptDataState, tokenizer->GetState());
 
   token.Clear();
   EXPECT_TRUE(tokenizer->NextToken(input, token));
   EXPECT_EQ(HTMLTreeBuilderSimulator::kScriptEnd,
-            simulator.Simulate(CompactHTMLToken(&token, TextPosition()),
-                               tokenizer.get()));
+            simulator.Simulate(
+                CompactHTMLToken(&token, TextPosition::MinimumPosition()),
+                tokenizer.get()));
 }
 
 TEST(HTMLTreeBuilderSimulatorTest, SelfClosingMathFollowedByScript) {
@@ -47,22 +50,25 @@
   HTMLToken token;
   EXPECT_TRUE(tokenizer->NextToken(input, token));
   EXPECT_EQ(HTMLTreeBuilderSimulator::kOtherToken,
-            simulator.Simulate(CompactHTMLToken(&token, TextPosition()),
-                               tokenizer.get()));
+            simulator.Simulate(
+                CompactHTMLToken(&token, TextPosition::MinimumPosition()),
+                tokenizer.get()));
 
   token.Clear();
   EXPECT_TRUE(tokenizer->NextToken(input, token));
   EXPECT_EQ(HTMLTreeBuilderSimulator::kValidScriptStart,
-            simulator.Simulate(CompactHTMLToken(&token, TextPosition()),
-                               tokenizer.get()));
+            simulator.Simulate(
+                CompactHTMLToken(&token, TextPosition::MinimumPosition()),
+                tokenizer.get()));
 
   EXPECT_EQ(HTMLTokenizer::kScriptDataState, tokenizer->GetState());
 
   token.Clear();
   EXPECT_TRUE(tokenizer->NextToken(input, token));
   EXPECT_EQ(HTMLTreeBuilderSimulator::kScriptEnd,
-            simulator.Simulate(CompactHTMLToken(&token, TextPosition()),
-                               tokenizer.get()));
+            simulator.Simulate(
+                CompactHTMLToken(&token, TextPosition::MinimumPosition()),
+                tokenizer.get()));
 }
 
 TEST(HTMLTreeBuilderSimulatorTest, DetectInvalidScriptType) {
@@ -74,14 +80,16 @@
   HTMLToken token;
   EXPECT_TRUE(tokenizer->NextToken(input, token));
   EXPECT_NE(HTMLTreeBuilderSimulator::kValidScriptStart,
-            simulator.Simulate(CompactHTMLToken(&token, TextPosition()),
-                               tokenizer.get()));
+            simulator.Simulate(
+                CompactHTMLToken(&token, TextPosition::MinimumPosition()),
+                tokenizer.get()));
 
   token.Clear();
   EXPECT_TRUE(tokenizer->NextToken(input, token));
   EXPECT_EQ(HTMLTreeBuilderSimulator::kScriptEnd,
-            simulator.Simulate(CompactHTMLToken(&token, TextPosition()),
-                               tokenizer.get()));
+            simulator.Simulate(
+                CompactHTMLToken(&token, TextPosition::MinimumPosition()),
+                tokenizer.get()));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/dev_tools_host.cc b/third_party/blink/renderer/core/inspector/dev_tools_host.cc
index eff71ef..9e27ef0e 100644
--- a/third_party/blink/renderer/core/inspector/dev_tools_host.cc
+++ b/third_party/blink/renderer/core/inspector/dev_tools_host.cc
@@ -128,7 +128,8 @@
   v8::MicrotasksScope microtasks(script_state->GetIsolate(),
                                  v8::MicrotasksScope::kRunMicrotasks);
   ScriptSourceCode source_code(expression, ScriptSourceLocationType::kInternal,
-                               nullptr, KURL(), TextPosition());
+                               nullptr, KURL(),
+                               TextPosition::MinimumPosition());
   V8ScriptRunner::CompileAndRunInternalScript(script_state->GetIsolate(),
                                               script_state, source_code);
 }
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
index 6dc1674b..bace807 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
@@ -1020,6 +1020,10 @@
   GetFrontend()->frameNavigated(BuildObjectForFrame(loader->GetFrame()));
 }
 
+void InspectorPageAgent::DidRestoreFromBackForwardCache(LocalFrame* frame) {
+  GetFrontend()->frameNavigated(BuildObjectForFrame(frame));
+}
+
 void InspectorPageAgent::DidOpenDocument(LocalFrame* frame,
                                          DocumentLoader* loader) {
   GetFrontend()->documentOpened(BuildObjectForFrame(loader->GetFrame()));
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.h b/third_party/blink/renderer/core/inspector/inspector_page_agent.h
index 84c741de..5da178a9 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.h
@@ -188,6 +188,7 @@
   void DomContentLoadedEventFired(LocalFrame*);
   void LoadEventFired(LocalFrame*);
   void WillCommitLoad(LocalFrame*, DocumentLoader*);
+  void DidRestoreFromBackForwardCache(LocalFrame*);
   void DidOpenDocument(LocalFrame*, DocumentLoader*);
   void FrameAttachedToParent(LocalFrame*);
   void FrameDetachedFromParent(LocalFrame*, FrameDetachType);
diff --git a/third_party/blink/renderer/core/inspector/thread_debugger.cc b/third_party/blink/renderer/core/inspector/thread_debugger.cc
index bb4c3af..2f64037f 100644
--- a/third_party/blink/renderer/core/inspector/thread_debugger.cc
+++ b/third_party/blink/renderer/core/inspector/thread_debugger.cc
@@ -353,7 +353,7 @@
           isolate_, ScriptState::From(context),
           ScriptSourceCode("(function(e) { console.log(e.type, e); })",
                            ScriptSourceLocationType::kInternal, nullptr, KURL(),
-                           TextPosition()))
+                           TextPosition::MinimumPosition()))
           .ToLocal(&function_value) &&
       function_value->IsFunction();
   DCHECK(success);
diff --git a/third_party/blink/renderer/core/layout/layout-shift-tracker-old-paint-offset.md b/third_party/blink/renderer/core/layout/layout-shift-tracker-old-paint-offset.md
new file mode 100644
index 0000000..69a8fa38
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/layout-shift-tracker-old-paint-offset.md
@@ -0,0 +1,76 @@
+# Explanation of `old_paint_offset` adjustment for
+`LayoutShiftTracker::NotifyBoxPrePaint()`
+
+Suppose from the layout shift root (see
+[PaintPropertyTreeBuilder](../paint/paint_property_tree_builder.h) for the
+definition) to a LayoutObject, there are transform nodes:
+```
+  {Troot, T1, T2, ... Tn}
+```
+where T1, T2, ... Tn are all 2d translations including
+* `PaintOffsetTranslation`s
+* `Transform`s with 2d translation matrixes
+* `ScrollTranslation`
+
+The location of the LayoutObject in the layout shift root can be calculated
+from:
+* the paint offset,
+* `acc_2d = sum(2d-offset(Ti), i=1..n)`
+
+We can calculate the old location and the new location in the layout shift
+root:
+```
+  old_location = old_paint_offset + old_acc_2d
+  new_location = new_paint_offset + new_acc_2d
+```
+
+`LayoutShiftTracker` then could use `old_location`, `new_location` to check
+if the object has shifted within the layout shift root. Then it could map
+`old_location` and `new_location` from the layout shift root's property tree
+state to the viewport's property tree state to get the old and new location
+in viewport, then check if the object has shifted in viewport. This would
+need `PaintInvalidator` to pass the following parameters to
+`LayoutShiftTracker`:
+* `old_location`
+* `new_location`
+* property tree state of the layout shift root [^1]
+
+[^1] Changes of paint properties above the layout shift root are ignored
+     intentionally because
+     * It's hard to track such changes.
+     * Some of such changes (e.g. 3d transform) should be ignored according to
+       the spec.
+     * Layout shift of the root itself should have already been reported, so
+       the descendants just need to report their shift relative to the layout
+       shift root.
+
+However, the above set of parameters requires `PaintInvalidator` to track
+all of them. To reduce the amount of data to track, `PaintInvalidator` passes
+the following parameters instead [2]:
+* `adjusted_old_paint_offset = old_paint_offset - acc_2d_delta`
+* `new_paint_offset`
+* property tree state of the current object
+most of which can be gotten from the current context except `acc_2d_delta`.
+Then `LayoutShiftTracker` can use `adjusted_old_paint_offset` and
+`new_paint_offset` instead of `old_location` and `new_location` to check if
+the object has shifted within the layout shift root because
+```
+  new_location - old_location == new_paint_offset - adjusted_old_paint_offset
+```
+and it can map `adjusted_old_paint_offset` and `new_paint_offset` from the
+current paint property tree state of the object to the viewport, as if there
+were old paint property tree state below the layout shift root because the
+changes of paint properties below the layout shift root is accounted in
+`adjusted_old_paint_offset`.
+
+[^2] Actually `PaintInvalidator` also passes the following parameters:
+     * `translation_delta`
+     * `scroll_delta`
+     so that `LayoutShiftTracker` can check shift by ignoring (or not) 2d
+     translation and scroll changes below the layout shift root.
+     See [the explainer](https://github.com/WICG/layout-instability#transform-changes)
+     for why we ignore transform and scroll changes by default. However,
+     in case that a layout shift is countered by a transform change and/or a
+     scroll change making the element not visually move, we should ignore the
+     shift. These situations require `LayoutShiftTracker` to determine shift
+     by both including and not including the transform/scroll changes.
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 874a9ed..6e79450 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -1709,7 +1709,6 @@
   for (const auto* effect = &paint_properties.Effect().Unalias(); effect;
        effect = effect->UnaliasedParent()) {
     if (!effect->Filter().IsEmpty() || !effect->BackdropFilter().IsEmpty() ||
-        effect->GetColorFilter() != kColorFilterNone ||
         effect->BlendMode() != SkBlendMode::kSrcOver ||
         effect->Opacity() != 1.0) {
       return true;
diff --git a/third_party/blink/renderer/core/layout/layout_shift_tracker.cc b/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
index 6ab060fb..99f3220 100644
--- a/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
+++ b/third_party/blink/renderer/core/layout/layout_shift_tracker.cc
@@ -231,7 +231,8 @@
     const PhysicalRect& old_rect,
     const PhysicalRect& new_rect,
     const FloatPoint& old_starting_point,
-    const FloatPoint& old_transform_indifferent_starting_point,
+    const FloatSize& translation_delta,
+    const FloatSize& scroll_delta,
     const FloatPoint& new_starting_point) {
   // The caller should ensure these conditions.
   DCHECK(!old_rect.IsEmpty());
@@ -240,15 +241,31 @@
   float threshold_physical_px =
       kMovementThreshold * object.StyleRef().EffectiveZoom();
 
+  // Check shift of starting point, including 2d-translation and scroll deltas.
   if (EqualWithinMovementThreshold(old_starting_point, new_starting_point,
                                    threshold_physical_px))
     return;
 
-  if (old_transform_indifferent_starting_point != old_starting_point &&
-      EqualWithinMovementThreshold(old_transform_indifferent_starting_point,
+  // Check shift of 2d-translation-indifferent starting point.
+  if (!translation_delta.IsZero() &&
+      EqualWithinMovementThreshold(old_starting_point + translation_delta,
                                    new_starting_point, threshold_physical_px))
     return;
 
+  // Check shift of scroll-indifferent starting point.
+  if (!scroll_delta.IsZero() &&
+      EqualWithinMovementThreshold(old_starting_point + scroll_delta,
+                                   new_starting_point, threshold_physical_px))
+    return;
+
+  // Check shift of 2d-translation-and-scroll-indifferent starting point.
+  FloatSize translation_and_scroll_delta = scroll_delta + translation_delta;
+  if (!translation_and_scroll_delta.IsZero() &&
+      EqualWithinMovementThreshold(
+          old_starting_point + translation_and_scroll_delta, new_starting_point,
+          threshold_physical_px))
+    return;
+
   const auto& root_state =
       object.View()->FirstFragment().LocalBorderBoxProperties();
   FloatClipRect clip_rect =
@@ -266,8 +283,10 @@
 
   auto transform = GeometryMapper::SourceToDestinationProjection(
       property_tree_state.Transform(), root_state.Transform());
+  // TODO(crbug.com/1187979): Shift by |scroll_delta| to keep backward
+  // compatibility in https://crrev.com/c/2754969. See the bug for details.
   FloatPoint old_starting_point_in_root =
-      transform.MapPoint(old_starting_point);
+      transform.MapPoint(old_starting_point + scroll_delta);
   FloatPoint new_starting_point_in_root =
       transform.MapPoint(new_starting_point);
 
@@ -276,30 +295,10 @@
                                    threshold_physical_px))
     return;
 
-  if (EqualWithinMovementThreshold(
-          old_starting_point_in_root + frame_scroll_delta_,
-          new_starting_point_in_root, threshold_physical_px)) {
-    // TODO(skobes): Checking frame_scroll_delta_ is an imperfect solution to
-    // allowing counterscrolled layout shifts. Ideally, we would map old_rect
-    // to viewport coordinates using the previous frame's scroll tree.
-    return;
-  }
-
-  if (old_transform_indifferent_starting_point != old_starting_point) {
-    FloatPoint old_transform_indifferent_starting_point_in_root =
-        transform.MapPoint(old_transform_indifferent_starting_point);
-    if (EqualWithinMovementThreshold(
-            old_transform_indifferent_starting_point_in_root,
-            new_starting_point_in_root, threshold_physical_px))
-      return;
-    if (EqualWithinMovementThreshold(
-            old_transform_indifferent_starting_point_in_root +
-                frame_scroll_delta_,
-            new_starting_point_in_root, threshold_physical_px))
-      return;
-  }
-
   FloatRect old_rect_in_root(old_rect);
+  // TODO(crbug.com/1187979): Shift by |scroll_delta| to keep backward
+  // compatibility in https://crrev.com/c/2754969. See the bug for details.
+  old_rect_in_root.Move(scroll_delta);
   transform.MapRect(old_rect_in_root);
   FloatRect new_rect_in_root(new_rect);
   transform.MapRect(new_rect_in_root);
@@ -344,7 +343,8 @@
             << " (visible from " << visible_old_rect << " to "
             << visible_new_rect << ")";
     if (old_starting_point_in_root != old_rect_in_root.Location() ||
-        new_starting_point_in_root != new_rect_in_root.Location()) {
+        new_starting_point_in_root != new_rect_in_root.Location() ||
+        !translation_delta.IsZero() || !scroll_delta.IsZero()) {
       VLOG(1) << " (starting point from " << old_starting_point_in_root
               << " to " << new_starting_point_in_root << ")";
     }
@@ -414,13 +414,13 @@
     const PhysicalRect& old_rect,
     const PhysicalRect& new_rect,
     const PhysicalOffset& old_paint_offset,
-    const PhysicalOffset& old_transform_indifferent_paint_offset,
+    const FloatSize& translation_delta,
+    const FloatSize& scroll_delta,
     const PhysicalOffset& new_paint_offset) {
   DCHECK(NeedsToTrack(box));
   ObjectShifted(box, property_tree_state, old_rect, new_rect,
                 StartingPoint(old_paint_offset, box, box.PreviousSize()),
-                StartingPoint(old_transform_indifferent_paint_offset, box,
-                              box.PreviousSize()),
+                translation_delta, scroll_delta,
                 StartingPoint(new_paint_offset, box, box.Size()));
 }
 
@@ -430,7 +430,8 @@
     const LogicalOffset& old_starting_point,
     const LogicalOffset& new_starting_point,
     const PhysicalOffset& old_paint_offset,
-    const PhysicalOffset& old_transform_indifferent_paint_offset,
+    const FloatSize& translation_delta,
+    const FloatSize& scroll_delta,
     const PhysicalOffset& new_paint_offset,
     LayoutUnit logical_height) {
   DCHECK(NeedsToTrack(text));
@@ -442,9 +443,6 @@
       old_paint_offset + old_starting_point.ConvertToPhysical(writing_direction,
                                                               block->old_size_,
                                                               PhysicalSize());
-  PhysicalOffset old_transform_indifferent_physical_starting_point =
-      old_physical_starting_point + old_transform_indifferent_paint_offset -
-      old_paint_offset;
   PhysicalOffset new_physical_starting_point =
       new_paint_offset + new_starting_point.ConvertToPhysical(writing_direction,
                                                               block->new_size_,
@@ -462,9 +460,8 @@
     return;
 
   ObjectShifted(text, property_tree_state, old_rect, new_rect,
-                FloatPoint(old_physical_starting_point),
-                FloatPoint(old_transform_indifferent_physical_starting_point),
-                FloatPoint(new_physical_starting_point));
+                FloatPoint(old_physical_starting_point), translation_delta,
+                scroll_delta, FloatPoint(new_physical_starting_point));
 }
 
 double LayoutShiftTracker::SubframeWeightingFactor() const {
@@ -542,7 +539,6 @@
   // Reset accumulated state.
   region_.Reset();
   frame_max_distance_ = 0.0;
-  frame_scroll_delta_ = ScrollOffset();
   attributions_.fill(Attribution());
 }
 
@@ -658,10 +654,7 @@
   frame.Client()->DidObserveInputForLayoutShiftTracking(timestamp);
 }
 
-void LayoutShiftTracker::NotifyScroll(mojom::blink::ScrollType scroll_type,
-                                      ScrollOffset delta) {
-  frame_scroll_delta_ += delta;
-
+void LayoutShiftTracker::NotifyScroll(mojom::blink::ScrollType scroll_type) {
   // Only set observed_input_or_scroll_ for user-initiated scrolls, and not
   // other scrolls such as hash fragment navigations.
   if (scroll_type == mojom::blink::ScrollType::kUser ||
diff --git a/third_party/blink/renderer/core/layout/layout_shift_tracker.h b/third_party/blink/renderer/core/layout/layout_shift_tracker.h
index 3d40d54..cf164d8 100644
--- a/third_party/blink/renderer/core/layout/layout_shift_tracker.h
+++ b/third_party/blink/renderer/core/layout/layout_shift_tracker.h
@@ -46,30 +46,34 @@
   // |old_rect| and |old_paint_offset| so that we can calculate the correct old
   // visual representation and old starting point in the initial containing
   // block and the viewport with the new property tree state in most cases.
-  // |old_transform_indifferent_paint_offset| is the adjusted old paint offset
-  // with transform changes excluded.
-  void NotifyBoxPrePaint(
-      const LayoutBox& box,
-      const PropertyTreeStateOrAlias& property_tree_state,
-      const PhysicalRect& old_rect,
-      const PhysicalRect& new_rect,
-      const PhysicalOffset& old_paint_offset,
-      const PhysicalOffset& old_transform_indifferent_paint_offset,
-      const PhysicalOffset& new_paint_offset);
+  // The adjustment should include the deltas of 2d translations and scrolls,
+  // and LayoutShiftTracker can determine stability by including (by default)
+  // or excluding |translation_delta| and/or |scroll_delta|.
+  //
+  // See renderer/core/layout/layout-shift-tracker-old-paint-offset.md for
+  // more details about |old_paint_offset|.
+  void NotifyBoxPrePaint(const LayoutBox& box,
+                         const PropertyTreeStateOrAlias& property_tree_state,
+                         const PhysicalRect& old_rect,
+                         const PhysicalRect& new_rect,
+                         const PhysicalOffset& old_paint_offset,
+                         const FloatSize& translation_delta,
+                         const FloatSize& scroll_delta,
+                         const PhysicalOffset& new_paint_offset);
 
-  void NotifyTextPrePaint(
-      const LayoutText& text,
-      const PropertyTreeStateOrAlias& property_tree_state,
-      const LogicalOffset& old_starting_point,
-      const LogicalOffset& new_starting_point,
-      const PhysicalOffset& old_paint_offset,
-      const PhysicalOffset& old_transform_indifferent_paint_offset,
-      const PhysicalOffset& new_paint_offset,
-      const LayoutUnit logical_height);
+  void NotifyTextPrePaint(const LayoutText& text,
+                          const PropertyTreeStateOrAlias& property_tree_state,
+                          const LogicalOffset& old_starting_point,
+                          const LogicalOffset& new_starting_point,
+                          const PhysicalOffset& old_paint_offset,
+                          const FloatSize& translation_delta,
+                          const FloatSize& scroll_delta,
+                          const PhysicalOffset& new_paint_offset,
+                          const LayoutUnit logical_height);
 
   void NotifyPrePaintFinished();
   void NotifyInput(const WebInputEvent&);
-  void NotifyScroll(mojom::blink::ScrollType, ScrollOffset delta);
+  void NotifyScroll(mojom::blink::ScrollType);
   void NotifyViewportSizeChanged();
   void NotifyFindInPageInput();
   void NotifyChangeEvent();
@@ -153,7 +157,8 @@
                      const PhysicalRect& old_rect,
                      const PhysicalRect& new_rect,
                      const FloatPoint& old_starting_point,
-                     const FloatPoint& old_transform_indifferent_starting_point,
+                     const FloatSize& translation_delta,
+                     const FloatSize& scroll_offset_delta,
                      const FloatPoint& new_starting_point);
 
   void ReportShift(double score_delta, double weighted_score_delta);
@@ -221,9 +226,6 @@
   // frames.
   float overall_max_distance_;
 
-  // Sum of all scroll deltas that occurred in the current animation frame.
-  ScrollOffset frame_scroll_delta_;
-
   // Whether either a user input or document scroll have been observed during
   // the session. (This is only tracked so UkmPageLoadMetricsObserver to report
   // LayoutInstability.CumulativeShiftScore.MainFrame.BeforeInputOrScroll. It's
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
index 7f834b1..cf6f7d87 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
@@ -78,12 +78,13 @@
   Vector<GridItemData> out_of_flow_items;
   ConstructAndAppendGridItems(&grid_items, &out_of_flow_items);
 
-  NGGridLayoutAlgorithmTrackCollection column_track_collection;
-  NGGridLayoutAlgorithmTrackCollection row_track_collection;
-  NGGridPlacement grid_placement(Style(),
+  const auto& container_style = Style();
+  NGGridPlacement grid_placement(container_style,
                                  ComputeAutomaticRepetitions(kForColumns),
                                  ComputeAutomaticRepetitions(kForRows));
 
+  NGGridLayoutAlgorithmTrackCollection column_track_collection;
+  NGGridLayoutAlgorithmTrackCollection row_track_collection;
   BuildAlgorithmTrackCollections(&grid_items, &column_track_collection,
                                  &row_track_collection, &grid_placement);
 
@@ -95,15 +96,20 @@
   // |InitializeTrackSizes|, which we need to get an initial column and row set
   // geometry. Then |ComputeUsedTrackSizes|, to finalize the sizing algorithm
   // for both dimensions.
-  GridGeometry grid_geometry = {InitializeTrackSizes(&column_track_collection),
-                                InitializeTrackSizes(&row_track_collection)};
+  GridGeometry grid_geometry(InitializeTrackSizes(&column_track_collection),
+                             InitializeTrackSizes(&row_track_collection));
 
-  // Cache set indices for grid items.
+  // Cache set indices and alignment fallbacks for grid items.
   for (auto& grid_item : grid_items.item_data) {
     grid_item.SetIndices(column_track_collection);
     grid_item.SetIndices(row_track_collection);
+    grid_item.SetAlignmentFallback(column_track_collection, container_style);
+    grid_item.SetAlignmentFallback(row_track_collection, container_style);
   }
 
+  // Store column baselines, as these contributions can influence column sizing.
+  CalculateAlignmentBaselines(grid_items, grid_geometry, kForColumns);
+
   // Resolve inline size.
   ComputeUsedTrackSizes(SizingConstraint::kLayout, grid_geometry,
                         &column_track_collection, &grid_items);
@@ -112,6 +118,9 @@
   grid_geometry.column_geometry = ComputeSetGeometry(
       column_track_collection, grid_available_size_.inline_size);
 
+  // Store row baselines now that column sizing is computed.
+  CalculateAlignmentBaselines(grid_items, grid_geometry, kForRows);
+
   // Resolve block size.
   ComputeUsedTrackSizes(SizingConstraint::kLayout, grid_geometry,
                         &row_track_collection, &grid_items);
@@ -120,6 +129,9 @@
   grid_geometry.row_geometry =
       ComputeSetGeometry(row_track_collection, grid_available_size_.block_size);
 
+  // Recompute column baselines now that the row sizing is determined.
+  CalculateAlignmentBaselines(grid_items, grid_geometry, kForColumns);
+
   // Intrinsic block size is based on the final row offset. Because gutters are
   // included in row offsets, subtract out the final gutter (if there is one).
   DCHECK_GT(grid_geometry.row_geometry.sets.size(), 0u);
@@ -136,7 +148,7 @@
                               BorderScrollbarPadding(), intrinsic_block_size);
 
   LayoutUnit block_size = ComputeBlockSizeForFragment(
-      ConstraintSpace(), Style(), BorderPadding(), intrinsic_block_size,
+      ConstraintSpace(), container_style, BorderPadding(), intrinsic_block_size,
       border_box_size_.inline_size);
 
   // If we had an indefinite available block-size, we now need to re-calculate
@@ -234,12 +246,13 @@
   GridItems grid_items;
   ConstructAndAppendGridItems(&grid_items);
 
-  NGGridLayoutAlgorithmTrackCollection column_track_collection_for_min_size;
-  NGGridLayoutAlgorithmTrackCollection row_track_collection;
-  NGGridPlacement grid_placement(Style(),
+  const auto& container_style = Style();
+  NGGridPlacement grid_placement(container_style,
                                  ComputeAutomaticRepetitions(kForColumns),
                                  ComputeAutomaticRepetitions(kForRows));
 
+  NGGridLayoutAlgorithmTrackCollection column_track_collection_for_min_size;
+  NGGridLayoutAlgorithmTrackCollection row_track_collection;
   BuildAlgorithmTrackCollections(&grid_items,
                                  &column_track_collection_for_min_size,
                                  &row_track_collection, &grid_placement);
@@ -248,15 +261,21 @@
   CacheGridItemsTrackSpanProperties(column_track_collection_for_min_size,
                                     &grid_items);
 
-  // Cache set indices for grid items.
+  // Cache set indices and alignment fallbacks for grid items.
   for (auto& grid_item : grid_items) {
     grid_item.SetIndices(column_track_collection_for_min_size);
     grid_item.SetIndices(row_track_collection);
+    grid_item.SetAlignmentFallback(column_track_collection_for_min_size,
+                                   container_style);
+    grid_item.SetAlignmentFallback(row_track_collection, container_style);
   }
 
-  GridGeometry grid_geometry = {
+  GridGeometry grid_geometry(
       InitializeTrackSizes(&column_track_collection_for_min_size),
-      InitializeTrackSizes(&row_track_collection)};
+      InitializeTrackSizes(&row_track_collection));
+
+  // Store column baselines.
+  CalculateAlignmentBaselines(grid_items, grid_geometry, kForColumns);
 
   // Before the track sizing algorithm, create a copy of the column collection;
   // one will be used to compute the min size and the other for the max size.
@@ -366,6 +385,79 @@
       .HasProperty(TrackSpanProperties::kHasIntrinsicTrack);
 }
 
+bool NGGridLayoutAlgorithm::GridItemData::IsBaselineAlignedForDirection(
+    GridTrackSizingDirection track_direction) const {
+  return (track_direction == kForColumns)
+             ? inline_axis_alignment == AxisEdge::kBaseline
+             : block_axis_alignment == AxisEdge::kBaseline;
+}
+
+void NGGridLayoutAlgorithm::GridItemData::SetAlignmentFallback(
+    const NGGridLayoutAlgorithmTrackCollection& track_collection,
+    const ComputedStyle& container_style) {
+  if (inline_axis_alignment == AxisEdge::kBaseline ||
+      block_axis_alignment == AxisEdge::kBaseline) {
+    auto CanParticipateInBaselineAlignment =
+        [&](const ComputedStyle& container_style,
+            const NGGridLayoutAlgorithmTrackCollection& track_collection)
+        -> bool {
+      const auto track_direction = track_collection.Direction();
+
+      if (!IsBaselineAlignedForDirection(track_direction))
+        return false;
+
+      // If the grid item and grid container have parallel writing directions,
+      // then the baseline is calculated as an offset in the opposite direction
+      // of the container. Otherwise, it's an orthogonal grid item and it's
+      // calculated as an offset in the parallel direction of the container.
+      if ((track_direction == kForRows) ==
+          IsParallelWritingMode(container_style.GetWritingMode(),
+                                node.Style().GetWritingMode())) {
+        return true;
+      }
+
+      // "If a box spans multiple shared alignment contexts, then it
+      // participates in first/last baseline alignment within its start-most/
+      // end-most shared alignment context along that axis", so we only need to
+      // look at the start index for baseline/first-baseline support.
+      // https://www.w3.org/TR/css-align-3/#baseline-sharing-group
+      const auto& track_size = track_collection
+                                   .SetAt((track_direction == kForColumns)
+                                              ? column_set_indices->begin
+                                              : row_set_indices->begin)
+                                   .TrackSize();
+
+      // "If baseline alignment is specified on a grid item whose size in that
+      // axis depends on the size of an intrinsically-sized track (whose size is
+      // therefore dependent on both the item’s size and baseline alignment,
+      // creating a cyclic dependency), that item does not participate in
+      // baseline alignment, and instead uses its fallback alignment as if that
+      // were originally specified. For this purpose, <flex> track sizes count
+      // as “intrinsically-sized” when the grid container has an indefinite size
+      // in the relevant axis."
+      // https://drafts.csswg.org/css-grid-2/#row-align
+      //
+      // TODO(kschmi) - this is not complete, as flex items should also count if
+      // the container has a definite size. However, this will require some
+      // additional refactoring, as we hit later asserts when we try to
+      // calculate the baseline for these items. It will fix
+      // grid-self-baseline-not-applied-if-sizing-cyclic-dependency related
+      // tests.
+      return (track_size.HasFixedMinTrackBreadth() &&
+              track_size.HasFixedMaxTrackBreadth());
+    };
+
+    // Revert to start edges if an item requests baseline alignment but does not
+    // meet requirements for baseline alignment.
+    if (!CanParticipateInBaselineAlignment(container_style, track_collection)) {
+      if (track_collection.IsForColumns())
+        inline_axis_alignment = AxisEdge::kStart;
+      else
+        block_axis_alignment = AxisEdge::kStart;
+    }
+  }
+}
+
 NGGridLayoutAlgorithm::ItemSetIndices
 NGGridLayoutAlgorithm::GridItemData::SetIndices(
     const NGGridLayoutAlgorithmTrackCollection& track_collection,
@@ -473,6 +565,57 @@
 
 namespace {
 
+using BaselineType = NGGridLayoutAlgorithm::BaselineType;
+
+}  // namespace
+
+void NGGridLayoutAlgorithm::GridGeometry::UpdateBaseline(
+    const GridItemData& grid_item,
+    LayoutUnit candidate_baseline,
+    GridTrackSizingDirection track_direction) {
+  LayoutUnit* track_baseline;
+  if (track_direction == kForColumns) {
+    // "If a box spans multiple shared alignment contexts, then it participates
+    // in first/last baseline alignment within its start-most/end-most shared
+    // alignment context along that axis", so we only need to look at the first
+    // index for baseline/first-baseline support.
+    // https://www.w3.org/TR/css-align-3/#baseline-sharing-group
+    const wtf_size_t set_index = grid_item.column_set_indices->begin;
+    track_baseline = (grid_item.column_baseline_type == BaselineType::kMajor)
+                         ? &major_inline_baselines[set_index]
+                         : &minor_inline_baselines[set_index];
+  } else {
+    const wtf_size_t set_index = grid_item.row_set_indices->begin;
+    track_baseline = (grid_item.row_baseline_type == BaselineType::kMajor)
+                         ? &major_block_baselines[set_index]
+                         : &minor_block_baselines[set_index];
+  }
+  *track_baseline = std::max(*track_baseline, candidate_baseline);
+}
+
+LayoutUnit NGGridLayoutAlgorithm::GridGeometry::Baseline(
+    const GridItemData& grid_item,
+    GridTrackSizingDirection track_direction) const {
+  if (track_direction == kForColumns) {
+    // "If a box spans multiple shared alignment contexts, then it participates
+    // in first/last baseline alignment within its start-most/end-most shared
+    // alignment context along that axis", so we only need to look at the first
+    // index for baseline/first-baseline support.
+    // https://www.w3.org/TR/css-align-3/#baseline-sharing-group
+    const wtf_size_t set_index = grid_item.column_set_indices->begin;
+    return (grid_item.column_baseline_type == BaselineType::kMajor)
+               ? major_inline_baselines[set_index]
+               : minor_inline_baselines[set_index];
+  } else {
+    const wtf_size_t set_index = grid_item.row_set_indices->begin;
+    return (grid_item.row_baseline_type == BaselineType::kMajor)
+               ? major_block_baselines[set_index]
+               : minor_block_baselines[set_index];
+  }
+}
+
+namespace {
+
 using SetIterator = NGGridLayoutAlgorithmTrackCollection::SetIterator;
 
 SetIterator GetSetIteratorForItem(
@@ -483,6 +626,70 @@
   return track_collection.GetSetIterator(set_indices.begin, set_indices.end);
 }
 
+LayoutUnit GetLogicalBaseline(const GridTrackSizingDirection track_direction,
+                              const NGBoxFragment& fragment,
+                              const WritingMode writing_mode) {
+  const auto child_writing_mode =
+      fragment.GetWritingDirection().GetWritingMode();
+  const bool is_for_columns = (track_direction == kForColumns);
+
+  // TODO(kschmi): Reconcile this with layout experts to see if this makes
+  // sense. Some of the entries here are non-intuitive.
+  switch (writing_mode) {
+    case WritingMode::kHorizontalTb:
+      switch (child_writing_mode) {
+        case WritingMode::kHorizontalTb:
+          return is_for_columns ? LayoutUnit()
+                                : fragment.BaselineOrSynthesize();
+        case WritingMode::kVerticalLr:
+          return is_for_columns ? fragment.Baseline().value_or(LayoutUnit())
+                                : fragment.InlineSize();
+        case WritingMode::kVerticalRl:
+          return is_for_columns ? (fragment.BlockSize() -
+                                   fragment.Baseline().value_or(LayoutUnit()))
+                                : fragment.InlineSize();
+        default:
+          NOTREACHED();
+          return LayoutUnit();
+      }
+    case WritingMode::kVerticalLr:
+      switch (child_writing_mode) {
+        case WritingMode::kHorizontalTb:
+          return is_for_columns ? fragment.BaselineOrSynthesize()
+                                : LayoutUnit();
+        case WritingMode::kVerticalLr:
+          return is_for_columns ? fragment.InlineSize()
+                                : fragment.Baseline().value_or(LayoutUnit());
+        case WritingMode::kVerticalRl:
+          return is_for_columns ? fragment.InlineSize()
+                                : (fragment.BlockSize() -
+                                   fragment.Baseline().value_or(LayoutUnit()));
+        default:
+          NOTREACHED();
+          return LayoutUnit();
+      }
+    case WritingMode::kVerticalRl:
+      switch (child_writing_mode) {
+        case WritingMode::kHorizontalTb:
+          return is_for_columns ? fragment.BaselineOrSynthesize()
+                                : fragment.InlineSize();
+        case WritingMode::kVerticalLr:
+          return is_for_columns
+                     ? fragment.InlineSize()
+                     : (fragment.BlockSize() - fragment.BaselineOrSynthesize());
+        case WritingMode::kVerticalRl:
+          return is_for_columns ? fragment.InlineSize()
+                                : fragment.BaselineOrSynthesize();
+        default:
+          NOTREACHED();
+          return LayoutUnit();
+      }
+    default:
+      NOTREACHED();
+      return LayoutUnit();
+  }
+}
+
 }  // namespace
 
 // TODO(ethavar): Current implementation of this method simply returns the
@@ -496,14 +703,6 @@
     GridItemContributionType contribution_type) const {
   const NGBlockNode& node = grid_item.node;
   const ComputedStyle& item_style = node.Style();
-
-  // TODO(ikilpatrick): We'll need to record if any child used an indefinite
-  // size for its contribution, such that we can then do the 2nd pass on the
-  // track-sizing algorithm.
-  LogicalRect unused;
-  const NGConstraintSpace space = CreateConstraintSpace(
-      grid_geometry, grid_item, NGCacheSlot::kMeasure, &unused);
-
   bool is_parallel_with_track_direction =
       (track_direction == kForColumns) ==
       IsParallelWritingMode(Style().GetWritingMode(),
@@ -519,18 +718,45 @@
     return ComputeMinAndMaxContentContributionForSelf(node, input).sizes;
   };
 
+  // TODO(ikilpatrick): We'll need to record if any child used an indefinite
+  // size for its contribution, such that we can then do the 2nd pass on the
+  // track-sizing algorithm.
+  LogicalRect unused;
+  const NGConstraintSpace space = CreateConstraintSpace(
+      grid_geometry, grid_item, NGCacheSlot::kMeasure, &unused);
+  const auto margins =
+      ComputeMarginsFor(space, node.Style(), ConstraintSpace());
+
   // This function will determine the correct block-size of a grid-item.
   // TODO(ikilpatrick): This should try and skip layout when possible. Notes:
   //  - We'll need to do a full layout for tables.
   //  - We'll need special logic for replaced elements.
   //  - We'll need to respect the aspect-ratio when appropriate.
-  auto BlockSize = [&]() -> LayoutUnit {
+  auto BlockContributionSize = [&]() -> LayoutUnit {
     DCHECK(!is_parallel_with_track_direction);
     scoped_refptr<const NGLayoutResult> result = node.Layout(space);
-    // We want to return the block-size in the *child's* writing-mode.
-    return NGFragment(item_style.GetWritingDirection(),
-                      result->PhysicalFragment())
-        .BlockSize();
+    NGBoxFragment fragment(
+        item_style.GetWritingDirection(),
+        To<NGPhysicalBoxFragment>(result->PhysicalFragment()));
+
+    LayoutUnit contribution = fragment.BlockSize();
+    if (grid_item.IsBaselineAlignedForDirection(track_direction)) {
+      const LayoutUnit baseline = GetLogicalBaseline(
+          track_direction, fragment,
+          ConstraintSpace().GetWritingDirection().GetWritingMode());
+
+      // The item's baseline alignment impacts the item's contribution as the
+      // difference between the track's baseline and the item's baseline.
+      contribution +=
+          (grid_geometry.Baseline(grid_item, track_direction) - baseline);
+
+      // Subtract out margins so they don't get added a second time at the end
+      // of NGGridLayoutAlgorithm::ContributionSizeForGridItem.
+      contribution -= (track_direction == kForRows) ? margins.block_start
+                                                    : margins.inline_start;
+    }
+
+    return contribution;
   };
 
   LayoutUnit contribution;
@@ -540,7 +766,7 @@
       if (is_parallel_with_track_direction)
         contribution = MinMaxContentSizes().min_size;
       else
-        contribution = BlockSize();
+        contribution = BlockContributionSize();
       break;
     case GridItemContributionType::kForIntrinsicMinimums: {
       // TODO(ikilpatrick): All of the below is incorrect for replaced elements.
@@ -595,7 +821,7 @@
           if (is_parallel_with_track_direction)
             contribution = MinMaxContentSizes().min_size;
           else
-            contribution = BlockSize();
+            contribution = BlockContributionSize();
           break;
         }
         case Length::kMinContent:
@@ -611,7 +837,7 @@
             contribution =
                 ComputeInlineSizeForFragment(space, node, border_padding);
           } else {
-            contribution = BlockSize();
+            contribution = BlockContributionSize();
           }
           break;
         }
@@ -630,7 +856,7 @@
       if (is_parallel_with_track_direction)
         contribution = MinMaxContentSizes().max_size;
       else
-        contribution = BlockSize();
+        contribution = BlockContributionSize();
       break;
     case GridItemContributionType::kForFreeSpace:
       NOTREACHED() << "|kForFreeSpace| should only be used to distribute extra "
@@ -638,9 +864,6 @@
       break;
   }
 
-  const auto margins =
-      ComputeMarginsFor(space, node.Style(), ConstraintSpace());
-
   DCHECK_NE(contribution, kIndefiniteSize);
   return contribution + ((track_direction == kForColumns) ? margins.InlineSum()
                                                           : margins.BlockSum());
@@ -869,6 +1092,47 @@
   return AxisEdge::kStart;
 }
 
+// Determines whether the track direction, grid container writing mode, and
+// grid item writing mode are part of the same alignment context as specified in
+// https://www.w3.org/TR/css-align-3/#baseline-sharing-group
+// In particular, 'Boxes share an alignment context, along a particular axis,
+// and established by a particular box, when they are grid items in the same
+// row, along the grid’s row (inline) axis, established by the grid container.'
+//
+// TODO(kschmi): Some of these conditions are non-intuitive, so investigate
+// whether these conditions are correct or if the test expectations are off.
+NGGridLayoutAlgorithm::BaselineType DetermineBaselineType(
+    const GridTrackSizingDirection track_direction,
+    const WritingMode container_writing_mode,
+    const WritingMode child_writing_mode) {
+  bool is_major = false;
+  switch (container_writing_mode) {
+    case WritingMode::kHorizontalTb:
+      is_major = (track_direction == kForRows)
+                     ? true
+                     : (child_writing_mode == WritingMode::kVerticalLr ||
+                        child_writing_mode == WritingMode::kHorizontalTb);
+      break;
+    case WritingMode::kVerticalLr:
+      is_major = (track_direction == kForRows)
+                     ? (child_writing_mode == WritingMode::kVerticalLr ||
+                        child_writing_mode == WritingMode::kHorizontalTb)
+                     : true;
+      break;
+    case WritingMode::kVerticalRl:
+      is_major = (track_direction == kForRows)
+                     ? (child_writing_mode == WritingMode::kVerticalRl ||
+                        child_writing_mode == WritingMode::kHorizontalTb)
+                     : true;
+      break;
+    default:
+      is_major = true;
+      break;
+  }
+
+  return is_major ? BaselineType::kMajor : BaselineType::kMinor;
+}
+
 }  // namespace
 
 NGGridLayoutAlgorithm::GridItemData NGGridLayoutAlgorithm::MeasureGridItem(
@@ -898,6 +1162,23 @@
           .GetPosition(),
       /* is_inline_axis */ false, &grid_item.is_block_axis_stretched);
 
+  const auto item_writing_direction =
+      grid_item.node.Style().GetWritingDirection().GetWritingMode();
+  const auto container_writing_direction = ConstraintSpace().GetWritingMode();
+  grid_item.row_baseline_type = DetermineBaselineType(
+      kForRows, container_writing_direction, item_writing_direction);
+  grid_item.column_baseline_type = DetermineBaselineType(
+      kForColumns, container_writing_direction, item_writing_direction);
+
+  // This bit reflects whether an item is eligible to be the grid container's
+  // baseline. It needs to be pre-computed, as the grid item's alignment can
+  // fall back to 'start' for alignment purposes, but in that case, the element
+  // should still be considered for the container's alignment baseline. As per
+  // spec, only the inline axis is considered for the container's baseline, so
+  // 'justify' values are not considered even in vertical writing modes.
+  grid_item.has_baseline_alignment =
+      (grid_item.block_axis_alignment == AxisEdge::kBaseline);
+
   grid_item.item_type = node.IsOutOfFlowPositioned() ? ItemType::kOutOfFlow
                                                      : ItemType::kInGridFlow;
 
@@ -1047,6 +1328,49 @@
       TrackSpanProperties::kHasIntrinsicTrack);
 }
 
+void NGGridLayoutAlgorithm::CalculateAlignmentBaselines(
+    GridItems& grid_items,
+    GridGeometry& grid_geometry,
+    GridTrackSizingDirection direction) const {
+  // Reset existing baselines from geometry so they are clean with each call to
+  // this method. Use 'WTF::Vector::Fill()' over 'WTF::Vector::clear()', as
+  // 'clear' will reset the capacity to zero and require re-allocations.
+  if (direction == kForColumns) {
+    grid_geometry.major_inline_baselines.Fill(LayoutUnit::Min());
+    grid_geometry.minor_inline_baselines.Fill(LayoutUnit::Min());
+  } else {
+    grid_geometry.major_block_baselines.Fill(LayoutUnit::Min());
+    grid_geometry.minor_block_baselines.Fill(LayoutUnit::Min());
+  }
+
+  // TODO(kschmi): Skip this loop (or method) entirely if we don't have any
+  // baseline-aligned grid-items.
+  for (auto& grid_item : grid_items.item_data) {
+    if (!grid_item.IsBaselineAlignedForDirection(direction))
+      continue;
+
+    LogicalRect unused;
+    const NGConstraintSpace space = CreateConstraintSpace(
+        grid_geometry, grid_item, NGCacheSlot::kMeasure, &unused);
+    scoped_refptr<const NGLayoutResult> result = grid_item.node.Layout(space);
+
+    NGBoxFragment fragment(
+        grid_item.node.Style().GetWritingDirection(),
+        To<NGPhysicalBoxFragment>(result->PhysicalFragment()));
+
+    const auto margins =
+        ComputeMarginsFor(space, grid_item.node.Style(), ConstraintSpace());
+    LayoutUnit margin =
+        (direction == kForColumns) ? margins.inline_start : margins.block_start;
+    LayoutUnit baseline =
+        margin + GetLogicalBaseline(
+                     direction, fragment,
+                     ConstraintSpace().GetWritingDirection().GetWritingMode());
+
+    grid_geometry.UpdateBaseline(grid_item, baseline, direction);
+  }
+}
+
 // https://drafts.csswg.org/css-grid-2/#algo-init
 NGGridLayoutAlgorithm::SetGeometry NGGridLayoutAlgorithm::InitializeTrackSizes(
     NGGridLayoutAlgorithmTrackCollection* track_collection) const {
@@ -2267,6 +2591,7 @@
                            LayoutUnit size,
                            LayoutUnit margin_start,
                            LayoutUnit margin_end,
+                           LayoutUnit baseline_offset,
                            AxisEdge axis_edge) {
   switch (axis_edge) {
     case AxisEdge::kStart:
@@ -2276,8 +2601,7 @@
     case AxisEdge::kEnd:
       return container_size - margin_end - size;
     case AxisEdge::kBaseline:
-      // TODO(kschmi): Implement baseline alignment.
-      return margin_start;
+      return baseline_offset;
   }
   NOTREACHED();
   return LayoutUnit();
@@ -2381,10 +2705,32 @@
     scoped_refptr<const NGLayoutResult> result = grid_item.node.Layout(space);
     const auto& physical_fragment =
         To<NGPhysicalBoxFragment>(result->PhysicalFragment());
+    NGBoxFragment logical_fragment(grid_item.node.Style().GetWritingDirection(),
+                                   physical_fragment);
+
+    auto BaselineOffset =
+        [&](GridTrackSizingDirection direction) -> LayoutUnit {
+      const auto margins =
+          ComputeMarginsFor(space, grid_item.node.Style(), ConstraintSpace());
+      LayoutUnit baseline_offset =
+          direction == kForRows ? margins.block_start : margins.inline_start;
+      if (grid_item.IsBaselineAlignedForDirection(direction)) {
+        // The baseline offset is the difference between the grid item's
+        // baseline and its track baseline.
+        const LayoutUnit item_baseline = GetLogicalBaseline(
+            direction, logical_fragment,
+            ConstraintSpace().GetWritingDirection().GetWritingMode());
+        baseline_offset =
+            grid_geometry.Baseline(grid_item, direction) - item_baseline;
+      }
+      return baseline_offset;
+    };
 
     const auto& item_style = grid_item.node.Style();
     const auto margins =
         ComputeMarginsFor(space, item_style, ConstraintSpace());
+    LayoutUnit inline_baseline_offset = BaselineOffset(kForColumns);
+    LayoutUnit block_baseline_offset = BaselineOffset(kForRows);
 
     // Apply the grid-item's alignment (if any).
     NGBoxFragment fragment(ConstraintSpace().GetWritingDirection(),
@@ -2392,10 +2738,12 @@
     containing_grid_area.offset += LogicalOffset(
         AlignmentOffset(containing_grid_area.size.inline_size,
                         fragment.InlineSize(), margins.inline_start,
-                        margins.inline_end, grid_item.inline_axis_alignment),
+                        margins.inline_end, inline_baseline_offset,
+                        grid_item.inline_axis_alignment),
         AlignmentOffset(containing_grid_area.size.block_size,
                         fragment.BlockSize(), margins.block_start,
-                        margins.block_end, grid_item.block_axis_alignment));
+                        margins.block_end, block_baseline_offset,
+                        grid_item.block_axis_alignment));
 
     // Grid is special in that %-based offsets resolve against the grid-area.
     // Adjust the offset here (instead of in the builder). This is safe as grid
@@ -2420,7 +2768,7 @@
 
     LayoutUnit baseline = fragment.BaselineOrSynthesize() +
                           containing_grid_area.offset.block_offset;
-    if (grid_item.block_axis_alignment == AxisEdge::kBaseline) {
+    if (grid_item.has_baseline_alignment) {
       if (!alignment_baseline ||
           IsBeforeInGridOrder(grid_item.resolved_position,
                               alignment_baseline->resolved_position)) {
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
index 07444544..0bd8eeb 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
@@ -34,7 +34,12 @@
     kForMaxContentMinimums,
     kForIntrinsicMaximums,
     kForMaxContentMaximums,
-    kForFreeSpace
+    kForFreeSpace,
+  };
+
+  enum class BaselineType {
+    kMajor,
+    kMinor,
   };
 
   struct ItemSetIndices {
@@ -65,6 +70,12 @@
     bool IsSpanningIntrinsicTrack(
         GridTrackSizingDirection track_direction) const;
 
+    bool IsBaselineAlignedForDirection(
+        GridTrackSizingDirection track_direction) const;
+    void SetAlignmentFallback(
+        const NGGridLayoutAlgorithmTrackCollection& track_collection,
+        const ComputedStyle& container_style);
+
     // For this item and track direction, computes and stores the pair of
     // indices "begin" and "end" such that the item spans every set from the
     // respective collection's |sets_| with an index in the range [begin, end).
@@ -85,6 +96,10 @@
     bool is_inline_axis_stretched;
     bool is_block_axis_stretched;
 
+    bool has_baseline_alignment;
+    BaselineType row_baseline_type;
+    BaselineType column_baseline_type;
+
     TrackSpanProperties column_span_properties;
     TrackSpanProperties row_span_properties;
 
@@ -185,10 +200,36 @@
 
   // Typically we pass around both the column, and row geometry together.
   struct GridGeometry {
+    GridGeometry(SetGeometry&& column_geometry, SetGeometry&& row_geometry)
+        : column_geometry(column_geometry),
+          row_geometry(row_geometry),
+          major_inline_baselines(column_geometry.sets.size(),
+                                 LayoutUnit::Min()),
+          minor_inline_baselines(column_geometry.sets.size(),
+                                 LayoutUnit::Min()),
+          major_block_baselines(row_geometry.sets.size(), LayoutUnit::Min()),
+          minor_block_baselines(row_geometry.sets.size(), LayoutUnit::Min()) {}
+
+    GridGeometry() = default;
+
     const SetGeometry& Geometry(GridTrackSizingDirection track_direction) const;
 
+    // Updates stored major/minor baseline value.
+    void UpdateBaseline(const GridItemData& grid_item,
+                        LayoutUnit candidate_baseline,
+                        GridTrackSizingDirection track_direction);
+
+    // Retrieves major/minor baseline.
+    LayoutUnit Baseline(const GridItemData& grid_item,
+                        GridTrackSizingDirection track_direction) const;
+
     SetGeometry column_geometry;
     SetGeometry row_geometry;
+
+    Vector<LayoutUnit> major_inline_baselines;
+    Vector<LayoutUnit> minor_inline_baselines;
+    Vector<LayoutUnit> major_block_baselines;
+    Vector<LayoutUnit> minor_block_baselines;
   };
 
   explicit NGGridLayoutAlgorithm(const NGLayoutAlgorithmParams& params);
@@ -240,6 +281,12 @@
       const NGGridLayoutAlgorithmTrackCollection& track_collection,
       GridItems* grid_items) const;
 
+  // Determines the major/minor alignment baselines for each row/column based on
+  // each item in |grid_items|, and stores the results in |grid_geometry|.
+  void CalculateAlignmentBaselines(GridItems& grid_items,
+                                   GridGeometry& grid_geometry,
+                                   GridTrackSizingDirection direction) const;
+
   // Initializes the given track collection, and returns the base set geometry.
   SetGeometry InitializeTrackSizes(
       NGGridLayoutAlgorithmTrackCollection* track_collection) const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index d096fe0..f84d106 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -218,7 +218,13 @@
       is_line_clamp_context_(params.space.IsLineClampContext()),
       lines_until_clamp_(params.space.LinesUntilClamp()),
       exclusion_space_(params.space.ExclusionSpace()),
-      early_break_(params.early_break) {}
+      early_break_(params.early_break) {
+  child_percentage_size_ = CalculateChildPercentageSize(
+      ConstraintSpace(), Node(), ChildAvailableSize());
+  replaced_child_percentage_size_ = CalculateReplacedChildPercentageSize(
+      ConstraintSpace(), Node(), ChildAvailableSize(), BorderScrollbarPadding(),
+      BorderPadding());
+}
 
 // Define the destructor here, so that we can forward-declare more in the
 // header.
@@ -289,8 +295,8 @@
       // an anonymous box that contains all line boxes.
       // |NextSibling| returns the next block sibling, or nullptr, skipping all
       // following inline siblings and descendants.
-      child_result =
-          child.ComputeMinMaxSizes(Style().GetWritingMode(), child_input);
+      child_result = To<NGInlineNode>(child).ComputeMinMaxSizes(
+          Style().GetWritingMode(), child_input);
     } else {
       child_result = ComputeMinAndMaxContentContribution(
           Style(), To<NGBlockNode>(child), child_input);
@@ -480,12 +486,6 @@
 
 inline scoped_refptr<const NGLayoutResult> NGBlockLayoutAlgorithm::Layout(
     NGInlineChildLayoutContext* inline_child_layout_context) {
-  child_percentage_size_ = CalculateChildPercentageSize(
-      ConstraintSpace(), Node(), ChildAvailableSize());
-  replaced_child_percentage_size_ = CalculateReplacedChildPercentageSize(
-      ConstraintSpace(), Node(), ChildAvailableSize(), BorderScrollbarPadding(),
-      BorderPadding());
-
   if (ConstraintSpace().IsLegacyTableCell())
     container_builder_.AdjustBorderScrollbarPaddingForTableCell();
 
@@ -2532,11 +2532,11 @@
     builder.SetStretchInlineSizeIfAuto(true);
     NGConstraintSpace space = builder.ToConstraintSpace();
 
+    const auto block_child = To<NGBlockNode>(child);
     NGBoxStrut child_border_padding =
-        ComputeBorders(space, To<NGBlockNode>(child)) +
-        ComputePadding(space, child_style);
+        ComputeBorders(space, block_child) + ComputePadding(space, child_style);
     LayoutUnit child_inline_size =
-        ComputeInlineSizeForFragment(space, child, child_border_padding);
+        ComputeInlineSizeForFragment(space, block_child, child_border_padding);
 
     ResolveInlineMargins(child_style, Style(),
                          space.AvailableSize().inline_size, child_inline_size,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
index 0c4d8f11..70f9a9b6 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
@@ -164,7 +164,8 @@
 
 void NGBoxFragmentBuilder::AddResult(const NGLayoutResult& child_layout_result,
                                      const LogicalOffset offset,
-                                     bool offset_includes_relative_position) {
+                                     bool offset_includes_relative_position,
+                                     bool propagate_oof_descendants) {
   const auto& fragment = child_layout_result.PhysicalFragment();
   if (items_builder_) {
     if (const NGPhysicalLineBoxFragment* line =
@@ -181,7 +182,7 @@
 
   AddChild(fragment, offset, /* inline_container */ nullptr, &end_margin_strut,
            child_layout_result.IsSelfCollapsing(),
-           offset_includes_relative_position);
+           offset_includes_relative_position, propagate_oof_descendants);
   if (fragment.IsBox())
     PropagateBreak(child_layout_result);
 }
@@ -191,7 +192,8 @@
                                     const LayoutInline* inline_container,
                                     const NGMarginStrut* margin_strut,
                                     bool is_self_collapsing,
-                                    bool offset_includes_relative_position) {
+                                    bool offset_includes_relative_position,
+                                    bool propagate_oof_descendants) {
   LogicalOffset adjusted_offset = child_offset;
 
   if (box_type_ != NGPhysicalBoxFragment::NGBoxType::kInlineBox &&
@@ -310,7 +312,8 @@
       offset_includes_relative_position;
 #endif
 
-  PropagateChildData(child, adjusted_offset, inline_container);
+  PropagateChildData(child, adjusted_offset, inline_container,
+                     propagate_oof_descendants);
   AddChildInternal(&child, adjusted_offset);
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
index d5d4a64..dc2c97c9 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
@@ -203,14 +203,16 @@
   // descendants, propagating fragmentainer breaks, and more.
   void AddResult(const NGLayoutResult&,
                  const LogicalOffset,
-                 bool offset_includes_relative_position = false);
+                 bool offset_includes_relative_position = false,
+                 bool propagate_oof_descendants = true);
 
   void AddChild(const NGPhysicalContainerFragment&,
                 const LogicalOffset&,
                 const LayoutInline* inline_container = nullptr,
                 const NGMarginStrut* margin_strut = nullptr,
                 bool is_self_collapsing = false,
-                bool offset_includes_relative_position = false);
+                bool offset_includes_relative_position = false,
+                bool propagate_oof_descendants = true);
 
   // Manually add a break token to the builder. Note that we're assuming that
   // this break token is for content in the same flow as this parent.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
index 103cd65..0276a8a 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
@@ -38,32 +38,14 @@
 void NGContainerFragmentBuilder::PropagateChildData(
     const NGPhysicalContainerFragment& child,
     const LogicalOffset& child_offset,
-    const LayoutInline* inline_container) {
-  // Collect the child's out of flow descendants.
-  const WritingModeConverter converter(GetWritingDirection(), child.Size());
-  for (const auto& descendant : child.OutOfFlowPositionedDescendants()) {
-    NGLogicalStaticPosition static_position =
-        descendant.static_position.ConvertToLogical(converter);
-    static_position.offset += child_offset;
-
-    const LayoutInline* new_inline_container = descendant.inline_container;
-    if (!new_inline_container &&
-        IsInlineContainerForNode(descendant.node, inline_container))
-      new_inline_container = inline_container;
-
-    // |oof_positioned_candidates_| should not have duplicated entries.
-    DCHECK(std::none_of(
-        oof_positioned_candidates_.begin(), oof_positioned_candidates_.end(),
-        [&descendant](const NGLogicalOutOfFlowPositionedNode& node) {
-          return node.node == descendant.node;
-        }));
-    oof_positioned_candidates_.emplace_back(descendant.node, static_position,
-                                            new_inline_container);
+    const LayoutInline* inline_container,
+    bool propagate_oof_descendants) {
+  if (propagate_oof_descendants) {
+    PropagateOOFPositionedInfo(child, child_offset,
+                               fragmentainer_consumed_block_size_,
+                               inline_container);
   }
 
-  PropagateOOFPositionedInfo(child, child_offset,
-                             fragmentainer_consumed_block_size_);
-
   // We only need to report if inflow or floating elements depend on the
   // percentage resolution block-size. OOF-positioned children resolve their
   // percentages against the "final" size of their parent.
@@ -260,12 +242,6 @@
   std::swap(oof_positioned_fragmentainer_descendants_, *descendants);
 }
 
-void NGContainerFragmentBuilder::ClearOutOfFlowFragmentainerDescendants() {
-  if (!HasOutOfFlowFragmentainerDescendants())
-    return;
-  oof_positioned_fragmentainer_descendants_.clear();
-}
-
 void NGContainerFragmentBuilder::
     MoveOutOfFlowDescendantCandidatesToDescendants() {
   DCHECK(oof_positioned_descendants_.IsEmpty());
@@ -296,7 +272,33 @@
 void NGContainerFragmentBuilder::PropagateOOFPositionedInfo(
     const NGPhysicalContainerFragment& fragment,
     LogicalOffset offset,
-    LayoutUnit fragmentainer_consumed_block_size) {
+    LayoutUnit fragmentainer_consumed_block_size,
+    const LayoutInline* inline_container) {
+  // TODO(almaher): Determine if this needs updating once nested fixedpos
+  // elements are properly handled in a multicol.
+
+  // Collect the child's out of flow descendants.
+  const WritingModeConverter converter(GetWritingDirection(), fragment.Size());
+  for (const auto& descendant : fragment.OutOfFlowPositionedDescendants()) {
+    NGLogicalStaticPosition static_position =
+        descendant.static_position.ConvertToLogical(converter);
+    static_position.offset += offset;
+
+    const LayoutInline* new_inline_container = descendant.inline_container;
+    if (!new_inline_container &&
+        IsInlineContainerForNode(descendant.node, inline_container))
+      new_inline_container = inline_container;
+
+    // |oof_positioned_candidates_| should not have duplicated entries.
+    DCHECK(std::none_of(
+        oof_positioned_candidates_.begin(), oof_positioned_candidates_.end(),
+        [&descendant](const NGLogicalOutOfFlowPositionedNode& node) {
+          return node.node == descendant.node;
+        }));
+    oof_positioned_candidates_.emplace_back(descendant.node, static_position,
+                                            new_inline_container);
+  }
+
   const NGPhysicalBoxFragment* box_fragment =
       DynamicTo<NGPhysicalBoxFragment>(&fragment);
   if (!box_fragment)
@@ -319,7 +321,6 @@
     return;
   }
 
-  const WritingModeConverter converter(GetWritingDirection(), fragment.Size());
   const auto& out_of_flow_fragmentainer_descendants =
       box_fragment->OutOfFlowPositionedFragmentainerDescendants();
   for (const auto& descendant : out_of_flow_fragmentainer_descendants) {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
index 03d1b455..72a6fbc 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
@@ -145,8 +145,6 @@
   void SwapMulticolsWithPendingOOFs(
       MulticolCollection* multicols_with_pending_oofs);
 
-  void ClearOutOfFlowFragmentainerDescendants();
-
   bool HasOutOfFlowPositionedCandidates() const {
     return !oof_positioned_candidates_.IsEmpty();
   }
@@ -178,9 +176,11 @@
   // first have to create NGLogicalOutOfFlowPositionedNodes copies before
   // appending them to our list of descendants.
   // In addition, propagate any inner multicols with pending OOF descendants.
-  void PropagateOOFPositionedInfo(const NGPhysicalContainerFragment& fragment,
-                                  LogicalOffset offset,
-                                  LayoutUnit fragmentainer_consumed_block_size);
+  void PropagateOOFPositionedInfo(
+      const NGPhysicalContainerFragment& fragment,
+      LogicalOffset offset,
+      LayoutUnit fragmentainer_consumed_block_size,
+      const LayoutInline* inline_container = nullptr);
 
   void SetIsSelfCollapsing() { is_self_collapsing_ = true; }
 
@@ -251,7 +251,8 @@
 
   void PropagateChildData(const NGPhysicalContainerFragment& child,
                           const LogicalOffset& child_offset,
-                          const LayoutInline* inline_container = nullptr);
+                          const LayoutInline* inline_container = nullptr,
+                          bool propagate_oof_descendants = true);
 
   void AddChildInternal(scoped_refptr<const NGPhysicalFragment>,
                         const LogicalOffset&);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_fragment.h
index 0e66ebd..3b3c0b27 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragment.h
@@ -36,6 +36,9 @@
     return physical_fragment_.Size().ConvertToLogical(
         writing_direction_.GetWritingMode());
   }
+  WritingDirectionMode GetWritingDirection() const {
+    return writing_direction_;
+  }
 
  protected:
   const NGPhysicalFragment& physical_fragment_;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc
index 0d82846..181fcba 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.cc
@@ -99,15 +99,6 @@
   return IsBlock() && blink::IsTextControlPlaceholder(GetDOMNode());
 }
 
-MinMaxSizesResult NGLayoutInputNode::ComputeMinMaxSizes(
-    WritingMode writing_mode,
-    const MinMaxSizesInput& input,
-    const NGConstraintSpace* space) const {
-  if (auto* inline_node = DynamicTo<NGInlineNode>(this))
-    return inline_node->ComputeMinMaxSizes(writing_mode, input, space);
-  return To<NGBlockNode>(*this).ComputeMinMaxSizes(writing_mode, input, space);
-}
-
 void NGLayoutInputNode::IntrinsicSize(
     base::Optional<LayoutUnit>* computed_inline_size,
     base::Optional<LayoutUnit>* computed_block_size) const {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
index 1df8c5b..4e5c15b 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_input_node.h
@@ -23,7 +23,6 @@
 class Document;
 class LayoutObject;
 class LayoutBox;
-class NGConstraintSpace;
 struct MinMaxSizes;
 struct PhysicalSize;
 
@@ -213,12 +212,6 @@
     return false;
   }
 
-  // Returns the border-box min/max content sizes for the node.
-  MinMaxSizesResult ComputeMinMaxSizes(
-      WritingMode,
-      const MinMaxSizesInput&,
-      const NGConstraintSpace* = nullptr) const;
-
   // Returns intrinsic sizing information for replaced elements.
   // ComputeReplacedSize can use it to compute actual replaced size.
   // Corresponds to Legacy's LayoutReplaced::IntrinsicSizingInfo.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
index 9cf8b4af..31ac83d 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
@@ -490,7 +490,7 @@
 
 LayoutUnit ComputeInlineSizeForFragmentInternal(
     const NGConstraintSpace& space,
-    NGLayoutInputNode node,
+    const NGBlockNode& node,
     const NGBoxStrut& border_padding,
     const MinMaxSizes* override_min_max_sizes) {
   auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult {
@@ -544,7 +544,7 @@
 
 LayoutUnit ComputeInlineSizeForFragment(
     const NGConstraintSpace& space,
-    NGLayoutInputNode node,
+    const NGBlockNode& node,
     const NGBoxStrut& border_padding,
     const MinMaxSizes* override_min_max_sizes_for_test) {
   if (space.IsFixedInlineSize() || space.IsAnonymous())
@@ -559,7 +559,7 @@
 
 LayoutUnit ComputeUsedInlineSizeForTableFragment(
     const NGConstraintSpace& space,
-    NGLayoutInputNode node,
+    const NGBlockNode& node,
     const NGBoxStrut& border_padding,
     const MinMaxSizes& table_grid_min_max_sizes) {
   DCHECK(!space.IsFixedInlineSize());
diff --git a/third_party/blink/renderer/core/layout/ng/ng_length_utils.h b/third_party/blink/renderer/core/layout/ng/ng_length_utils.h
index 2e69dd6..f2dec7b 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_length_utils.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_length_utils.h
@@ -400,7 +400,7 @@
 // |override_min_max_sizes_for_test| is provided *solely* for use by unit tests.
 CORE_EXPORT LayoutUnit ComputeInlineSizeForFragment(
     const NGConstraintSpace&,
-    NGLayoutInputNode,
+    const NGBlockNode& node,
     const NGBoxStrut& border_padding,
     const MinMaxSizes* override_min_max_sizes_for_test = nullptr);
 
@@ -409,7 +409,7 @@
 // https://drafts.csswg.org/css-tables-3/#used-width-of-table
 CORE_EXPORT LayoutUnit ComputeUsedInlineSizeForTableFragment(
     const NGConstraintSpace& space,
-    NGLayoutInputNode node,
+    const NGBlockNode& node,
     const NGBoxStrut& border_padding,
     const MinMaxSizes& table_grid_min_max_sizes);
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index e430ce1..b2c3bba 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -149,16 +149,7 @@
       LayoutFragmentainerDescendants(&fragmentainer_descendants,
                                      column_inline_progression);
     }
-
-    if (container_builder_->HasMulticolsWithPendingOOFs()) {
-      NGContainerFragmentBuilder::MulticolCollection
-          multicols_with_pending_oofs;
-      container_builder_->SwapMulticolsWithPendingOOFs(
-          &multicols_with_pending_oofs);
-      DCHECK(!multicols_with_pending_oofs.IsEmpty());
-      for (LayoutBox* multicol : multicols_with_pending_oofs)
-        LayoutOOFsInMulticol(NGBlockNode(multicol));
-    }
+    HandleMulticolsWithPendingOOFs(container_builder_);
   }
 
   const LayoutObject* current_container = container_builder_->GetLayoutObject();
@@ -561,7 +552,24 @@
   }
 }
 
-// TODO(almaher): Look into moving this to NGColumnLayoutAlgorithm instead.
+void NGOutOfFlowLayoutPart::HandleMulticolsWithPendingOOFs(
+    NGBoxFragmentBuilder* container_builder) {
+  if (!container_builder->HasMulticolsWithPendingOOFs())
+    return;
+
+  NGContainerFragmentBuilder::MulticolCollection multicols_with_pending_oofs;
+  container_builder->SwapMulticolsWithPendingOOFs(&multicols_with_pending_oofs);
+  DCHECK(!multicols_with_pending_oofs.IsEmpty());
+
+  while (!multicols_with_pending_oofs.IsEmpty()) {
+    for (LayoutBox* multicol : multicols_with_pending_oofs)
+      LayoutOOFsInMulticol(NGBlockNode(multicol));
+    multicols_with_pending_oofs.clear();
+    container_builder->SwapMulticolsWithPendingOOFs(
+        &multicols_with_pending_oofs);
+  }
+}
+
 void NGOutOfFlowLayoutPart::LayoutOOFsInMulticol(const NGBlockNode& multicol) {
   Vector<NGLogicalOutOfFlowPositionedNode> oof_nodes_to_layout;
   Vector<MulticolChildInfo> multicol_children;
@@ -616,7 +624,11 @@
         current_column_index = multicol_children.size();
       }
 
-      multicol_container_builder.AddChild(*fragment, offset);
+      multicol_container_builder.AddChild(
+          *fragment, offset, /* inline_container */ nullptr,
+          /* margin_strut */ nullptr, /* is_self_collapsing */ false,
+          /* offset_includes_relative_position */ false,
+          /* propagate_oof_descendants */ false);
       multicol_children.emplace_back(MulticolChildInfo(&child));
     }
 
@@ -681,17 +693,17 @@
     previous_column_break_token = current_column_break_token;
   }
   DCHECK(!oof_nodes_to_layout.IsEmpty());
-
-  // Clear out any OOF fragmentainer descendants that had been re-propagated
-  // when setting up |multicol_container_builder|.
-  // TODO(almaher): Avoid adding the descendants again to begin with.
-  multicol_container_builder.ClearOutOfFlowFragmentainerDescendants();
+  DCHECK(!multicol_container_builder.HasOutOfFlowFragmentainerDescendants());
 
   // Layout the OOF positioned elements inside the inner multicol.
   NGOutOfFlowLayoutPart(multicol, multicol_constraint_space,
                         &multicol_container_builder)
       .LayoutFragmentainerDescendants(
           &oof_nodes_to_layout, column_inline_progression, &multicol_children);
+
+  // Handle any inner multicols with OOF descendants that may have propagated up
+  // while laying out the direct OOF descendants of the current multicol.
+  HandleMulticolsWithPendingOOFs(&multicol_container_builder);
 }
 
 void NGOutOfFlowLayoutPart::LayoutFragmentainerDescendants(
@@ -1317,7 +1329,6 @@
     result->GetMutableForOutOfFlow().SetOutOfFlowPositionedOffset(
         oof_offset, allow_first_tier_oof_cache_);
   }
-  // TODO(almaher): Handle nested inner multicols with pending OOFs.
   container_builder_->PropagateOOFPositionedInfo(
       result->PhysicalFragment(), result->OutOfFlowPositionedOffset(),
       fragmentainer_consumed_block_size_);
@@ -1369,7 +1380,11 @@
     }
     scoped_refptr<const NGLayoutResult> new_result = algorithm->Layout();
     node.AddColumnResult(new_result);
-    container_builder_->AddChild(new_result->PhysicalFragment(), offset);
+    container_builder_->AddChild(
+        new_result->PhysicalFragment(), offset, /* inline_container */ nullptr,
+        /* margin_strut */ nullptr, /* is_self_collapsing */ false,
+        /* offset_includes_relative_position */ false,
+        /* propagate_oof_descendants */ false);
   } else {
     scoped_refptr<const NGLayoutResult> new_result = algorithm->Layout();
     node.ReplaceColumnResult(new_result, fragment);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
index 2848374..947e54f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
@@ -85,7 +85,6 @@
     LogicalRect rect;
   };
 
-  // TODO(almaher): Move this to the multicol algorithm in upcoming refactor.
   // This stores the information needed to update a multicol child inside an
   // existing multicol fragment. This is used during nested fragmentation of an
   // OOF positioned element.
@@ -169,6 +168,7 @@
                         const LayoutBox* only_layout,
                         HashSet<const LayoutObject*>* placed_objects);
 
+  void HandleMulticolsWithPendingOOFs(NGBoxFragmentBuilder* container_builder);
   void LayoutOOFsInMulticol(const NGBlockNode& multicol);
 
   // Layout the OOF nodes that are descendants of a fragmentation context root.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
index c2c5fff..f6568bdb 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_simplified_oof_layout_algorithm.cc
@@ -65,7 +65,9 @@
 
 void NGSimplifiedOOFLayoutAlgorithm::AppendOutOfFlowResult(
     scoped_refptr<const NGLayoutResult> result) {
-  container_builder_.AddResult(*result, result->OutOfFlowPositionedOffset());
+  container_builder_.AddResult(*result, result->OutOfFlowPositionedOffset(),
+                               /* offset_includes_relative_position */ false,
+                               /* propagate_oof_descendants */ false);
 
   // If there is an incoming child break token, make sure that it matches
   // the OOF child that was just added.
@@ -94,7 +96,8 @@
   container_builder_.AddChild(
       *fragment, child_offset, /* inline_container */ nullptr,
       /* margin_strut */ nullptr, /* is_self_collapsing */ false,
-      /* offset_includes_relative_position */ true);
+      /* offset_includes_relative_position */ true,
+      /* propagate_oof_descendants */ false);
 }
 
 void NGSimplifiedOOFLayoutAlgorithm::AdvanceChildIterator() {
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 45c3e1cc..ea436a6a 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -496,6 +496,10 @@
   // All the security properties of the document must be preserved. Note that
   // sandbox flags and various policies are copied separately during commit in
   // CommitNavigation() and CalculateSandboxFlags().
+  // TODO(dcheng): Is it a problem that origin_to_commit_ is copied with
+  // isolated copy? This probably has observable side effects (e.g. executing a
+  // javascript: URL in an about:blank frame that inherited an origin will cause
+  // the origin to no longer be aliased).
   params->origin_to_commit = window->GetSecurityOrigin();
   params->sandbox_flags = sandbox_flags_;
   params->origin_agent_cluster = origin_agent_cluster_;
@@ -1726,6 +1730,15 @@
     // preserve `domain_` via `url::Origin` and B) doesn't alias the origin /
     // `domain_` - changes in the "about:blank" document do not affect the
     // initiator document).
+    //
+    // TODO(dcheng): if we're aliasing an origin, do we need to go through any
+    // of the other checks below? This seems like it could have potentially
+    // surprising side effects: for example, if the web security setting toggle
+    // is disabled, this will affect the owner document's origin too...
+    //
+    // TODO(dcheng): maybe FrameLoader::Init() should specify origin_to_commit_?
+    // But origin_to_commit_ is currently cloned with IsolatedCopy() which
+    // breaks aliasing...
     origin = owner_document->domWindow()->GetMutableSecurityOrigin();
   } else {
     // Otherwise, create an origin that propagates precursor information
@@ -1762,7 +1775,16 @@
     origin = sandbox_origin;
   }
 
-  if (!frame_->GetSettings()->GetWebSecurityEnabled()) {
+  if (commit_reason_ == CommitReason::kInitialization &&
+      frame_->GetSettings()->GetShouldReuseGlobalForUnownedMainFrame() &&
+      !frame_->Parent() && !frame_->Opener()) {
+    // For legacy reasons, grant universal access to a top-level initial empty
+    // Document in Android WebView. This allows the WebView embedder to inject
+    // arbitrary script into about:blank and have it persist when the frame is
+    // navigated.
+    CHECK(origin->IsOpaque());
+    origin->GrantUniversalAccess();
+  } else if (!frame_->GetSettings()->GetWebSecurityEnabled()) {
     // Web security is turned off. We should let this document access
     // every other document. This is used primary by testing harnesses for
     // web sites.
diff --git a/third_party/blink/renderer/core/loader/http_equiv.cc b/third_party/blink/renderer/core/loader/http_equiv.cc
index 0660fe6..5675b9d 100644
--- a/third_party/blink/renderer/core/loader/http_equiv.cc
+++ b/third_party/blink/renderer/core/loader/http_equiv.cc
@@ -178,7 +178,7 @@
   UseCounter::Count(window, WebFeature::kMetaRefresh);
   if (!window->GetContentSecurityPolicy()->AllowInline(
           ContentSecurityPolicy::InlineType::kScript, element, "" /* content */,
-          "" /* nonce */, NullURL(), OrdinalNumber(),
+          "" /* nonce */, NullURL(), OrdinalNumber::First(),
           ReportingDisposition::kSuppressReporting)) {
     UseCounter::Count(window,
                       WebFeature::kMetaRefreshWhenCSPBlocksInlineScript);
diff --git a/third_party/blink/renderer/core/page/chrome_client.h b/third_party/blink/renderer/core/page/chrome_client.h
index a92adf5..80fcd89 100644
--- a/third_party/blink/renderer/core/page/chrome_client.h
+++ b/third_party/blink/renderer/core/page/chrome_client.h
@@ -32,7 +32,6 @@
 #include "cc/input/overscroll_behavior.h"
 #include "cc/paint/paint_image.h"
 #include "cc/trees/paint_holding_commit_trigger.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "third_party/blink/public/common/dom_storage/session_storage_namespace_id.h"
 #include "third_party/blink/public/common/page/drag_operation.h"
@@ -57,6 +56,7 @@
 #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 
 // To avoid conflicts with the CreateWindow macro from the Windows SDK...
 #undef CreateWindow
@@ -527,7 +527,7 @@
 
   virtual void SetDelegatedInkMetadata(
       LocalFrame* frame,
-      std::unique_ptr<viz::DelegatedInkMetadata> metadata) {}
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {}
 
   virtual void BatterySavingsChanged(LocalFrame& main_frame,
                                      BatterySavingsFlags savings) = 0;
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.cc b/third_party/blink/renderer/core/page/chrome_client_impl.cc
index ccecd90..64297db 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.cc
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.cc
@@ -1181,7 +1181,7 @@
 
 void ChromeClientImpl::SetDelegatedInkMetadata(
     LocalFrame* frame,
-    std::unique_ptr<viz::DelegatedInkMetadata> metadata) {
+    std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
   frame->GetWidgetForLocalRoot()->SetDelegatedInkMetadata(std::move(metadata));
 }
 
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.h b/third_party/blink/renderer/core/page/chrome_client_impl.h
index efe53ab21..7cf65f17 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.h
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.h
@@ -210,7 +210,7 @@
   void SetCursorForPlugin(const ui::Cursor&, LocalFrame*) override;
   void SetDelegatedInkMetadata(
       LocalFrame* frame,
-      std::unique_ptr<viz::DelegatedInkMetadata> metadata) override;
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata) override;
 
   // ChromeClientImpl:
   void SetNewWindowNavigationPolicy(WebNavigationPolicy);
diff --git a/third_party/blink/renderer/core/page/create_window.cc b/third_party/blink/renderer/core/page/create_window.cc
index 112f32a..36019d9 100644
--- a/third_party/blink/renderer/core/page/create_window.cc
+++ b/third_party/blink/renderer/core/page/create_window.cc
@@ -247,7 +247,7 @@
     if (!csp_for_world->AllowInline(
             ContentSecurityPolicy::InlineType::kNavigation,
             nullptr /* element */, script_source, String() /* nonce */,
-            opener_window.Url(), OrdinalNumber())) {
+            opener_window.Url(), OrdinalNumber::First())) {
       return nullptr;
     }
   }
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index 238a6a3..972b9935 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -1914,6 +1914,9 @@
     const GraphicsLayer* graphics_layer,
     GraphicsContext& context,
     const IntRect& interest_rect) const {
+  if (GetLayoutObject().StyleRef().Visibility() != EVisibility::kVisible)
+    return;
+
   // cull_rect is in the space of the containing scrollable area in which
   // Scrollbar::Paint() will paint the scrollbar.
   CullRect cull_rect(interest_rect);
diff --git a/third_party/blink/renderer/core/paint/css_mask_painter.cc b/third_party/blink/renderer/core/paint/css_mask_painter.cc
index d9c097b2..8ccde44 100644
--- a/third_party/blink/renderer/core/paint/css_mask_painter.cc
+++ b/third_party/blink/renderer/core/paint/css_mask_painter.cc
@@ -58,19 +58,4 @@
   return PixelSnappedIntRect(maximum_mask_region);
 }
 
-ColorFilter CSSMaskPainter::MaskColorFilter(const LayoutObject& object) {
-  if (!object.IsSVGChild())
-    return kColorFilterNone;
-  SVGResourceClient* client = SVGResources::GetClient(object);
-  if (!client)
-    return kColorFilterNone;
-  auto* masker = GetSVGResourceAsType<LayoutSVGResourceMasker>(
-      *client, object.StyleRef().MaskerResource());
-  if (!masker)
-    return kColorFilterNone;
-  return masker->StyleRef().MaskType() == EMaskType::kLuminance
-             ? kColorFilterLuminanceToAlpha
-             : kColorFilterNone;
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/css_mask_painter.h b/third_party/blink/renderer/core/paint/css_mask_painter.h
index cb2e0e82..cb44671 100644
--- a/third_party/blink/renderer/core/paint/css_mask_painter.h
+++ b/third_party/blink/renderer/core/paint/css_mask_painter.h
@@ -8,7 +8,6 @@
 #include "base/optional.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
-#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
@@ -26,10 +25,6 @@
   static base::Optional<IntRect> MaskBoundingBox(
       const LayoutObject&,
       const PhysicalOffset& paint_offset);
-
-  // Returns the color filter used to interpret mask pixel values as opaqueness.
-  // The return value is undefined if there is no mask or the mask is invalid.
-  static ColorFilter MaskColorFilter(const LayoutObject&);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_invalidator.cc b/third_party/blink/renderer/core/paint/paint_invalidator.cc
index 67ce4e2..ade4ec95 100644
--- a/third_party/blink/renderer/core/paint/paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/paint_invalidator.cc
@@ -200,14 +200,17 @@
       *tree_builder_context.current.clip, *tree_builder_context.current_effect);
 
   // Adjust old_paint_offset so that LayoutShiftTracker will see the change of
-  // offset caused by change of paint offset translations below the layout shift
-  // root.
-  PhysicalOffset adjusted_old_transform_indifferent_paint_offset =
-      context.old_paint_offset -
-      tree_builder_context.current.additional_offset_to_layout_shift_root_delta;
+  // offset caused by change of paint offset translations and scroll offset
+  // below the layout shift root. For more details, see
+  // renderer/core/layout/layout-shift-tracker-old-paint-offset.md.
   PhysicalOffset adjusted_old_paint_offset =
-      adjusted_old_transform_indifferent_paint_offset -
-      tree_builder_context.translation_2d_to_layout_shift_root_delta;
+      context.old_paint_offset -
+      tree_builder_context.current
+          .additional_offset_to_layout_shift_root_delta -
+      PhysicalOffset::FromFloatSizeRound(
+          tree_builder_context.translation_2d_to_layout_shift_root_delta +
+          tree_builder_context.current
+              .scroll_offset_to_layout_shift_root_delta);
   PhysicalOffset new_paint_offset = tree_builder_context.current.paint_offset;
 
   if (object.IsText()) {
@@ -229,8 +232,9 @@
     layout_shift_tracker.NotifyTextPrePaint(
         text, property_tree_state, old_starting_point, new_starting_point,
         adjusted_old_paint_offset,
-        adjusted_old_transform_indifferent_paint_offset, new_paint_offset,
-        logical_height);
+        tree_builder_context.translation_2d_to_layout_shift_root_delta,
+        tree_builder_context.current.scroll_offset_to_layout_shift_root_delta,
+        new_paint_offset, logical_height);
     return;
   }
 
@@ -290,7 +294,9 @@
   if (should_report_layout_shift) {
     layout_shift_tracker.NotifyBoxPrePaint(
         box, property_tree_state, old_rect, new_rect, adjusted_old_paint_offset,
-        adjusted_old_transform_indifferent_paint_offset, new_paint_offset);
+        tree_builder_context.translation_2d_to_layout_shift_root_delta,
+        tree_builder_context.current.scroll_offset_to_layout_shift_root_delta,
+        new_paint_offset);
   }
 }
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 2f6d3a0..3261db2e 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -1045,7 +1045,7 @@
     }
     if (transform->IsIdentityOr2DTranslation()) {
       context_.translation_2d_to_layout_shift_root_delta +=
-          PhysicalOffset::FromFloatSizeRound(transform->Translation2D());
+          transform->Translation2D();
     }
   } else if (RuntimeEnabledFeatures::TransformInteropEnabled() &&
              !object_.IsAnonymous()) {
@@ -1327,7 +1327,6 @@
         EffectPaintPropertyNode::State mask_state;
         mask_state.local_transform_space = context_.current.transform;
         mask_state.output_clip = output_clip;
-        mask_state.color_filter = CSSMaskPainter::MaskColorFilter(object_);
         mask_state.blend_mode = SkBlendMode::kDstIn;
         mask_state.compositor_element_id = mask_compositor_element_id;
         mask_state.direct_compositing_reasons = mask_direct_compositing_reasons;
@@ -2173,6 +2172,11 @@
         frame_view->SetPaintArtifactCompositorNeedsUpdate();
       }
     }
+
+    // A scroller creates a layout shift root, so we just calculate one scroll
+    // offset delta without accumulation.
+    context_.current.scroll_offset_to_layout_shift_root_delta =
+        scroll_translation->Translation2D() - old_scroll_offset;
   }
 }
 
@@ -2731,10 +2735,13 @@
   // For LayoutView, additional_offset_to_layout_shift_root_delta applies to
   // neither itself nor descendants. For other layout shift roots, we clear the
   // delta at the end of UpdateForChildren() because the delta still applies to
-  // the object itself.
+  // the object itself. Same for translation_2d_to_layout_shift_delta and
+  // scroll_offset_to_layout_shift_root_delta.
   if (IsA<LayoutView>(object_)) {
     context_.current.additional_offset_to_layout_shift_root_delta =
-        context_.translation_2d_to_layout_shift_root_delta = PhysicalOffset();
+        PhysicalOffset();
+    context_.translation_2d_to_layout_shift_root_delta = FloatSize();
+    context_.current.scroll_offset_to_layout_shift_root_delta = FloatSize();
   }
 }
 
@@ -2769,7 +2776,11 @@
     // additional_offset_to_layout_shift_root_delta.
     context_.current.additional_offset_to_layout_shift_root_delta =
         context_.old_paint_offset - fragment_data_.PaintOffset();
-    context_.translation_2d_to_layout_shift_root_delta = PhysicalOffset();
+    context_.translation_2d_to_layout_shift_root_delta = FloatSize();
+    // Don't reset scroll_offset_to_layout_shift_root_delta if this object has
+    // scroll translation because we need to propagate the delta to descendants.
+    if (!properties_ || !properties_->ScrollTranslation())
+      context_.current.scroll_offset_to_layout_shift_root_delta = FloatSize();
   }
 
 #if DCHECK_IS_ON()
@@ -2808,7 +2819,7 @@
     if (const auto* transform = properties->Transform()) {
       if (transform->IsIdentityOr2DTranslation()) {
         context.translation_2d_to_layout_shift_root_delta -=
-            PhysicalOffset::FromFloatSizeRound(transform->Translation2D());
+            transform->Translation2D();
       }
     }
   }
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
index 863c116b..510f53a0 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
@@ -65,6 +65,10 @@
     // layout shift root.
     PhysicalOffset additional_offset_to_layout_shift_root_delta;
 
+    // Similar to additional_offset_to_layout_shift_root_delta but for scroll
+    // offsets.
+    FloatSize scroll_offset_to_layout_shift_root_delta;
+
     // For paint invalidation optimization for subpixel movement under
     // composited layer. It's reset to zero if subpixel can't be propagated
     // thus the optimization is not applicable (e.g. when crossing a
@@ -154,7 +158,7 @@
 
   // The delta between the old and new accumulated offsets of 2d translation
   // transforms to the layout shift root.
-  PhysicalOffset translation_2d_to_layout_shift_root_delta;
+  FloatSize translation_2d_to_layout_shift_root_delta;
 };
 
 struct PaintPropertyTreeBuilderContext {
diff --git a/third_party/blink/renderer/core/paint/svg_mask_painter.cc b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
index fb46328c..72f23f4 100644
--- a/third_party/blink/renderer/core/paint/svg_mask_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
@@ -65,7 +65,15 @@
 
   context.Save();
   context.ConcatCTM(content_transformation);
+  bool needs_luminance_layer =
+      masker->StyleRef().MaskType() == EMaskType::kLuminance;
+  if (needs_luminance_layer) {
+    context.BeginLayer(1.0f, SkBlendMode::kSrcOver, nullptr,
+                       kColorFilterLuminanceToAlpha);
+  }
   context.DrawRecord(std::move(record));
+  if (needs_luminance_layer)
+    context.EndLayer();
   context.Restore();
 }
 
diff --git a/third_party/blink/renderer/core/probe/core_probes.json5 b/third_party/blink/renderer/core/probe/core_probes.json5
index 6990979..f3ec953 100644
--- a/third_party/blink/renderer/core/probe/core_probes.json5
+++ b/third_party/blink/renderer/core/probe/core_probes.json5
@@ -191,6 +191,7 @@
         "DidClearDocumentOfWindowObject",
         "DidNavigateWithinDocument",
         "WillCommitLoad",
+        "DidRestoreFromBackForwardCache",
         "DidResizeMainFrame",
         "DidRunJavaScriptDialog",
         "DomContentLoadedEventFired",
diff --git a/third_party/blink/renderer/core/probe/core_probes.pidl b/third_party/blink/renderer/core/probe/core_probes.pidl
index 79c87ad..964803c 100644
--- a/third_party/blink/renderer/core/probe/core_probes.pidl
+++ b/third_party/blink/renderer/core/probe/core_probes.pidl
@@ -118,6 +118,7 @@
   void WillCommitLoad([Keep] LocalFrame*, DocumentLoader*);
   void DidCommitLoad([Keep] LocalFrame*, DocumentLoader*);
   void DidNavigateWithinDocument([Keep] LocalFrame*);
+  void DidRestoreFromBackForwardCache([Keep] LocalFrame*);
   void DidOpenDocument([Keep] LocalFrame*, DocumentLoader*);
   void FrameDocumentUpdated([Keep] LocalFrame*);
   void FrameOwnerContentUpdated([Keep] LocalFrame*, HTMLFrameOwnerElement*);
diff --git a/third_party/blink/renderer/core/script/classic_pending_script.cc b/third_party/blink/renderer/core/script/classic_pending_script.cc
index 460936df..5e2f27a 100644
--- a/third_party/blink/renderer/core/script/classic_pending_script.cc
+++ b/third_party/blink/renderer/core/script/classic_pending_script.cc
@@ -50,7 +50,7 @@
 
   ClassicPendingScript* pending_script =
       MakeGarbageCollected<ClassicPendingScript>(
-          element, TextPosition(), KURL(), String(),
+          element, TextPosition::MinimumPosition(), KURL(), String(),
           ScriptSourceLocationType::kExternalFile, options,
           true /* is_external */);
 
diff --git a/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc b/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc
index 802fe840..ffd4f6ca 100644
--- a/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc
+++ b/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc
@@ -238,7 +238,8 @@
                    scope.GetScriptState()));
 
   auto* resolver = MakeGarbageCollected<DynamicModuleResolver>(modulator);
-  ModuleRequest module_request("./dependency.js", TextPosition(),
+  ModuleRequest module_request("./dependency.js",
+                               TextPosition::MinimumPosition(),
                                Vector<ImportAssertion>());
   resolver->ResolveDynamically(module_request, TestReferrerURL(),
                                ReferrerScriptInfo(), promise_resolver);
@@ -273,9 +274,9 @@
 
   auto* resolver = MakeGarbageCollected<DynamicModuleResolver>(modulator);
   Vector<ImportAssertion> import_assertions{
-      ImportAssertion("type", "json", TextPosition())};
-  ModuleRequest module_request("./dependency.json", TextPosition(),
-                               import_assertions);
+      ImportAssertion("type", "json", TextPosition::MinimumPosition())};
+  ModuleRequest module_request(
+      "./dependency.json", TextPosition::MinimumPosition(), import_assertions);
   resolver->ResolveDynamically(module_request, TestReferrerURL(),
                                ReferrerScriptInfo(), promise_resolver);
 
@@ -304,7 +305,8 @@
                capture->Bind());
 
   auto* resolver = MakeGarbageCollected<DynamicModuleResolver>(modulator);
-  ModuleRequest module_request("invalid-specifier", TextPosition(),
+  ModuleRequest module_request("invalid-specifier",
+                               TextPosition::MinimumPosition(),
                                Vector<ImportAssertion>());
   resolver->ResolveDynamically(module_request, TestReferrerURL(),
                                ReferrerScriptInfo(), promise_resolver);
@@ -333,9 +335,9 @@
 
   auto* resolver = MakeGarbageCollected<DynamicModuleResolver>(modulator);
   Vector<ImportAssertion> import_assertions{
-      ImportAssertion("type", "notARealType", TextPosition())};
-  ModuleRequest module_request("./dependency.js", TextPosition(),
-                               import_assertions);
+      ImportAssertion("type", "notARealType", TextPosition::MinimumPosition())};
+  ModuleRequest module_request(
+      "./dependency.js", TextPosition::MinimumPosition(), import_assertions);
   resolver->ResolveDynamically(module_request, TestReferrerURL(),
                                ReferrerScriptInfo(), promise_resolver);
 
@@ -362,7 +364,8 @@
                capture->Bind());
 
   auto* resolver = MakeGarbageCollected<DynamicModuleResolver>(modulator);
-  ModuleRequest module_request("./dependency.js", TextPosition(),
+  ModuleRequest module_request("./dependency.js",
+                               TextPosition::MinimumPosition(),
                                Vector<ImportAssertion>());
   resolver->ResolveDynamically(module_request, TestReferrerURL(),
                                ReferrerScriptInfo(), promise_resolver);
@@ -394,7 +397,8 @@
                capture->Bind());
 
   auto* resolver = MakeGarbageCollected<DynamicModuleResolver>(modulator);
-  ModuleRequest module_request("./dependency.js", TextPosition(),
+  ModuleRequest module_request("./dependency.js",
+                               TextPosition::MinimumPosition(),
                                Vector<ImportAssertion>());
   resolver->ResolveDynamically(module_request, TestReferrerURL(),
                                ReferrerScriptInfo(), promise_resolver);
@@ -435,7 +439,8 @@
                    scope.GetScriptState()));
 
   auto* resolver = MakeGarbageCollected<DynamicModuleResolver>(modulator);
-  ModuleRequest module_request("./dependency.js", TextPosition(),
+  ModuleRequest module_request("./dependency.js",
+                               TextPosition::MinimumPosition(),
                                Vector<ImportAssertion>());
   resolver->ResolveDynamically(module_request, /* null referrer */ KURL(),
                                ReferrerScriptInfo(), promise_resolver);
@@ -471,7 +476,8 @@
   auto* resolver = MakeGarbageCollected<DynamicModuleResolver>(modulator);
   KURL wrong_base_url("https://example.com/wrong/bar.js");
   KURL correct_base_url("https://example.com/correct/baz.js");
-  ModuleRequest module_request("./dependency.js", TextPosition(),
+  ModuleRequest module_request("./dependency.js",
+                               TextPosition::MinimumPosition(),
                                Vector<ImportAssertion>());
   resolver->ResolveDynamically(
       module_request, wrong_base_url,
diff --git a/third_party/blink/renderer/core/script/module_pending_script.cc b/third_party/blink/renderer/core/script/module_pending_script.cc
index 7686ac5..bd44696 100644
--- a/third_party/blink/renderer/core/script/module_pending_script.cc
+++ b/third_party/blink/renderer/core/script/module_pending_script.cc
@@ -40,7 +40,7 @@
 ModulePendingScript::ModulePendingScript(ScriptElementBase* element,
                                          ModulePendingScriptTreeClient* client,
                                          bool is_external)
-    : PendingScript(element, TextPosition()),
+    : PendingScript(element, TextPosition::MinimumPosition()),
       module_tree_client_(client),
       is_external_(is_external) {
   CHECK(GetElement());
diff --git a/third_party/blink/renderer/core/script/module_record_resolver_impl_test.cc b/third_party/blink/renderer/core/script/module_record_resolver_impl_test.cc
index 000a915..75050795 100644
--- a/third_party/blink/renderer/core/script/module_record_resolver_impl_test.cc
+++ b/third_party/blink/renderer/core/script/module_record_resolver_impl_test.cc
@@ -146,7 +146,8 @@
   Modulator()->SetModuleScript(target_module_script);
 
   v8::Local<v8::Module> resolved = resolver->Resolve(
-      ModuleRequest("./target.js", TextPosition(), Vector<ImportAssertion>()),
+      ModuleRequest("./target.js", TextPosition::MinimumPosition(),
+                    Vector<ImportAssertion>()),
       referrer_module_script->V8Module(), scope.GetExceptionState());
   EXPECT_FALSE(scope.GetExceptionState().HadException());
   EXPECT_EQ(resolved, target_module_script->V8Module());
diff --git a/third_party/blink/renderer/core/script/script_loader.cc b/third_party/blink/renderer/core/script/script_loader.cc
index 3adbaf48..e1a50a2 100644
--- a/third_party/blink/renderer/core/script/script_loader.cc
+++ b/third_party/blink/renderer/core/script/script_loader.cc
@@ -410,8 +410,8 @@
   const bool is_in_document_write = element_document.IsInDocumentWrite();
 
   // Reset line numbering for nested writes.
-  TextPosition position =
-      is_in_document_write ? TextPosition() : script_start_position;
+  TextPosition position = is_in_document_write ? TextPosition::MinimumPosition()
+                                               : script_start_position;
 
   // <spec step="13">If the script element does not have a src content
   // attribute, and the Should element's inline behavior be blocked by Content
diff --git a/third_party/blink/renderer/core/script/script_runner_test.cc b/third_party/blink/renderer/core/script/script_runner_test.cc
index dafe113..7325a426 100644
--- a/third_party/blink/renderer/core/script/script_runner_test.cc
+++ b/third_party/blink/renderer/core/script/script_runner_test.cc
@@ -36,7 +36,7 @@
 
   MockPendingScript(ScriptElementBase* element,
                     ScriptSchedulingType scheduling_type)
-      : PendingScript(element, TextPosition()) {
+      : PendingScript(element, TextPosition::MinimumPosition()) {
     SetSchedulingType(scheduling_type);
   }
   ~MockPendingScript() override {}
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc
index 6576988..c9914bbf 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -436,7 +436,7 @@
 
   if (offset_changed && GetLayoutBox() && GetLayoutBox()->GetFrameView()) {
     GetLayoutBox()->GetFrameView()->GetLayoutShiftTracker().NotifyScroll(
-        scroll_type, delta);
+        scroll_type);
   }
 
   GetScrollAnimator().SetCurrentOffset(offset);
diff --git a/third_party/blink/renderer/core/timing/DEPS b/third_party/blink/renderer/core/timing/DEPS
new file mode 100644
index 0000000..1440897
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/DEPS
@@ -0,0 +1,13 @@
+specific_include_rules = {
+    "background_tracing_helper.cc": [
+        "+base/hash/md5.h",
+        "+base/sys_byteorder.h",
+        "+url/url_constants.h",
+    ],
+    "background_tracing_helper.h": [
+        "+base/strings/string_piece.h",
+    ],
+    "background_tracing_helper_test.cc": [
+        "+base/hash/md5_constexpr.h",
+    ],
+}
diff --git a/third_party/blink/renderer/core/timing/background_tracing_helper.cc b/third_party/blink/renderer/core/timing/background_tracing_helper.cc
new file mode 100644
index 0000000..f9c414b
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/background_tracing_helper.cc
@@ -0,0 +1,290 @@
+// 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 "third_party/blink/renderer/core/timing/background_tracing_helper.h"
+
+#include "base/feature_list.h"
+#include "base/hash/md5.h"
+#include "base/stl_util.h"
+#include "base/sys_byteorder.h"
+#include "base/trace_event/typed_macros.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/timing/performance_mark.h"
+#include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.h"
+#include "third_party/blink/renderer/platform/network/network_utils.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/wtf/text/ascii_ctype.h"
+#include "third_party/blink/renderer/platform/wtf/text/number_parsing_options.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_operators.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_to_number.h"
+#include "url/url_constants.h"
+
+namespace blink {
+
+namespace {
+
+enum TerminationCondition {
+  kMustHaveTerminator,
+  kMayHaveTerminator,
+};
+
+// Consumes a 1-8 byte hash from the given string, and the terminator if one is
+// encountered. On success, the parsed hash is output via the optional |hash|
+// and the new position of the cursor is returned. On failure, nullptr is
+// returned.
+const char* ParseHash(const char* begin,
+                      const char* end,
+                      uint32_t& hash,
+                      TerminationCondition termination,
+                      char valid_terminator0,
+                      char valid_terminator1 = 0,
+                      char valid_terminator2 = 0) {
+  DCHECK(begin);
+  DCHECK(end);
+  DCHECK_LE(begin, end);
+  DCHECK_NE(valid_terminator0, 0);
+
+  const char* cur = begin;
+  while (cur < end) {
+    // Stop when a terminator is encountered.
+    if (*cur == valid_terminator0)
+      break;
+    if (valid_terminator1 != 0 && *cur == valid_terminator1)
+      break;
+    if (valid_terminator2 != 0 && *cur == valid_terminator2)
+      break;
+    // Stop if any invalid characters are encountered.
+    if (!IsASCIIHexDigit(*cur))
+      return nullptr;
+    ++cur;
+    // Stop if the hash string is too long.
+    if (cur - begin > 8)
+      return nullptr;
+  }
+
+  // Stop if the hash is empty.
+  if (cur == begin)
+    return nullptr;
+
+  // Enforce mandatory terminator characters.
+  if (termination == kMustHaveTerminator && cur == end)
+    return nullptr;
+
+  // At this point we've successfully consumed a hash, so parse it.
+  bool parsed = false;
+  hash = WTF::HexCharactersToUInt(reinterpret_cast<const unsigned char*>(begin),
+                                  cur - begin, WTF::NumberParsingOptions::kNone,
+                                  &parsed);
+  DCHECK(parsed);
+
+  // If there's a terminator, advance past it.
+  if (cur < end)
+    ++cur;
+
+  // Finally, return the advanced cursor.
+  return cur;
+}
+
+static constexpr char kTriggerPrefix[] = "trigger:";
+
+bool MarkNameIsTrigger(const String& mark_name) {
+  return mark_name.StartsWith(kTriggerPrefix);
+}
+
+String GenerateFullTrigger(const String& site, const String& mark_name) {
+  DCHECK(MarkNameIsTrigger(mark_name));
+  return site + "-" + mark_name.Substring(base::size(kTriggerPrefix) - 1);
+}
+
+}  // namespace
+
+// A thin wrapper around a SiteMarkHashMap that populates it at construction by
+// parsing the relevant Finch parameters.
+struct BackgroundTracingHelper::SiteMarkHashMapContainer {
+  SiteMarkHashMapContainer();
+  ~SiteMarkHashMapContainer() = default;
+
+  SiteMarkHashMap site_mark_hash_map;
+};
+
+BackgroundTracingHelper::SiteMarkHashMapContainer::SiteMarkHashMapContainer() {
+  // Do nothing if the feature is not enabled.
+  if (!base::FeatureList::IsEnabled(
+          features::kBackgroundTracingPerformanceMark))
+    return;
+
+  // Get the allow-list from the Finch configuration.
+  std::string allow_list =
+      features::kBackgroundTracingPerformanceMark_AllowList.Get();
+
+  // Parse the allow-list. Silently ignoring malformed configuration data simply
+  // means the feature will be disabled when this occurs.
+  BackgroundTracingHelper::ParseBackgroundTracingPerformanceMarkHashes(
+      allow_list, site_mark_hash_map);
+}
+
+BackgroundTracingHelper::BackgroundTracingHelper(ExecutionContext* context) {
+  // Used to configure a per-origin allowlist of performance.mark events that
+  // are permitted to be included in background traces. See crbug.com/1181774.
+
+  // If there's no allow-list, then bail early.
+  if (GetSiteMarkHashMap().IsEmpty())
+    return;
+
+  // Only support http and https origins to actual remote servers.
+  auto* origin = context->GetSecurityOrigin();
+  if (origin->IsLocal() || origin->IsOpaque() || origin->IsLocalhost())
+    return;
+  if (origin->Protocol() != url::kHttpScheme &&
+      origin->Protocol() != url::kHttpsScheme) {
+    return;
+  }
+
+  // Get the hash of the site (eTLD+1) in an encoded format (friendly for
+  // converting to ASCII, and matching the format in which URLs will be encoded
+  // prior to hashing in the Finch list).
+  String this_site =
+      EncodeWithURLEscapeSequences(network_utils::GetDomainAndRegistry(
+          origin->Host(), network_utils::kIncludePrivateRegistries));
+  uint32_t this_site_hash = MD5Hash32(this_site.Ascii());
+
+  // Get the allow-list for this site, if there is one.
+  mark_hashes_ = GetMarkHashSetForSiteHash(this_site_hash);
+
+  // We only need the site information if there's actually a set of mark hashes.
+  if (mark_hashes_) {
+    site_ = this_site;
+    site_hash_ = this_site_hash;
+  }
+}
+
+BackgroundTracingHelper::~BackgroundTracingHelper() = default;
+
+void BackgroundTracingHelper::MaybeEmitBackgroundTracingPerformanceMarkEvent(
+    const PerformanceMark& mark) {
+  if (!mark_hashes_)
+    return;
+
+  // Get the hashed mark name.
+  const String& mark_name = mark.name();
+  std::string mark_name_ascii = mark_name.Ascii();
+  uint32_t mark_hash = MD5Hash32(mark_name_ascii);
+
+  // See if the mark hash is in the permitted list.
+  if (!mark_hashes_->Contains(mark_hash))
+    return;
+
+  // Emit the trace event. We emit hashes and strings to facilitate local trace
+  // consumption. However, the strings will be stripped and only the hashes
+  // shipped externally.
+  TRACE_EVENT("blink", "performance.mark", [&](perfetto::EventContext ctx) {
+    auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
+    auto* data = event->set_chrome_hashed_performance_mark();
+    data->set_site_hash(site_hash_);
+    data->set_site(site_.Ascii());
+    data->set_mark_hash(mark_hash);
+    data->set_mark(mark_name_ascii);
+  });
+
+  // If this is a slow-reports trigger then fire it.
+  if (MarkNameIsTrigger(mark_name)) {
+    RendererResourceCoordinator::Get()->FireBackgroundTracingTrigger(
+        GenerateFullTrigger(site_, mark_name));
+  }
+}
+
+void BackgroundTracingHelper::Trace(Visitor*) const {}
+
+// static
+const BackgroundTracingHelper::SiteMarkHashMap&
+BackgroundTracingHelper::GetSiteMarkHashMap() {
+  // This needs to be thread-safe because performance.mark is supported by both
+  // windows and workers.
+  DEFINE_THREAD_SAFE_STATIC_LOCAL(SiteMarkHashMapContainer,
+                                  site_mark_hash_map_container, ());
+  return site_mark_hash_map_container.site_mark_hash_map;
+}
+
+// static
+const BackgroundTracingHelper::MarkHashSet*
+BackgroundTracingHelper::GetMarkHashSetForSiteHash(uint32_t site_hash) {
+  const SiteMarkHashMap& site_mark_hash_map = GetSiteMarkHashMap();
+  auto it = site_mark_hash_map.find(site_hash);
+  if (it == site_mark_hash_map.end())
+    return nullptr;
+  return &(it->value);
+}
+
+// static
+uint32_t BackgroundTracingHelper::MD5Hash32(base::StringPiece string) {
+  base::MD5Digest digest;
+  base::MD5Sum(string.data(), string.size(), &digest);
+  uint32_t value;
+  DCHECK_GE(sizeof(digest.a), sizeof(value));
+  memcpy(&value, digest.a, sizeof(value));
+  return base::NetToHost32(value);
+}
+
+// static
+bool BackgroundTracingHelper::ParseBackgroundTracingPerformanceMarkHashes(
+    base::StringPiece allow_list,
+    SiteMarkHashMap& allow_listed_hashes) {
+  // We parse into this temporary structure, and move into the output on
+  // success.
+  SiteMarkHashMap parsed_allow_listed_hashes;
+
+  // The format is:
+  //
+  //   sitehash0=markhash0,...,markhashn;sitehash1=markhash0,...,markhashn
+  //
+  // where each hash is a 32-bit hex hash. We also allow commas to be replaced
+  // with underscores so that they can be easily specified via the
+  // --enable-features command-line.
+  const char* cur = allow_list.data();
+  const char* end = allow_list.data() + allow_list.size();
+  while (cur < end) {
+    // Parse a site hash.
+    uint32_t site_hash = 0;
+    cur = ParseHash(cur, end, site_hash, kMustHaveTerminator, '=');
+    if (!cur)
+      return false;
+
+    // The site hash must be unique.
+    if (parsed_allow_listed_hashes.Contains(site_hash))
+      return false;
+
+    // Parse the mark hashes.
+    MarkHashSet parsed_mark_hashes;
+    while (true) {
+      // At least a single mark hash entry is expected per site hash.
+      uint32_t mark_hash = 0;
+      cur = ParseHash(cur, end, mark_hash, kMayHaveTerminator, ',', ';', '_');
+      if (!cur)
+        return false;
+
+      // Duplicate entries are an error.
+      auto result = parsed_mark_hashes.insert(mark_hash);
+      if (!result.is_new_entry)
+        return false;
+
+      // We're done processing the current list of mark hashes if there's no
+      // data left to consume, or if the terminator was a ';'.
+      if (cur == end || cur[-1] == ';')
+        break;
+    }
+
+    auto result = parsed_allow_listed_hashes.insert(
+        site_hash, std::move(parsed_mark_hashes));
+    // We guaranteed uniqueness of insertion by checking for the |site_hash|
+    // before parsing the mark hashes.
+    DCHECK(result.is_new_entry);
+  }
+
+  // Getting here means we successfully parsed the whole list.
+  allow_listed_hashes = std::move(parsed_allow_listed_hashes);
+  return true;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/background_tracing_helper.h b/third_party/blink/renderer/core/timing/background_tracing_helper.h
new file mode 100644
index 0000000..ff2f345
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/background_tracing_helper.h
@@ -0,0 +1,94 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_BACKGROUND_TRACING_HELPER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_BACKGROUND_TRACING_HELPER_H_
+
+#include <cstdint>
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/impl/heap.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+class ExecutionContext;
+class PerformanceMark;
+
+// The following class is a helper for implementing the background-tracing
+// performance.mark integration. See crbug.com/1181774 for details, and refer
+// to integration with performance.cc.
+
+// This is CORE_EXPORT for component builds of blink_unittests.
+class CORE_EXPORT BackgroundTracingHelper final
+    : public GarbageCollected<BackgroundTracingHelper> {
+ public:
+  using MarkHashSet = HashSet<uint32_t>;
+  using SiteMarkHashMap = HashMap<uint32_t, MarkHashSet>;
+
+  explicit BackgroundTracingHelper(ExecutionContext* context);
+  ~BackgroundTracingHelper();
+
+  void MaybeEmitBackgroundTracingPerformanceMarkEvent(
+      const PerformanceMark& mark);
+
+  // Implements GarbageCollected:
+  void Trace(Visitor*) const;
+
+ protected:
+  struct SiteMarkHashMapContainer;
+  friend class BackgroundTracingHelperTest;
+
+  // Returns a reference to a thread-safe static singleton `SiteMarkHashMap`,
+  // with all of the configured allow-listed sites and marks. This object is
+  // populated with parsed data upon first access.
+  static const SiteMarkHashMap& GetSiteMarkHashMap();
+
+  // Returns a pointer to the `MarkHashSet` contain allow-listed hashes for the
+  // provided |site_hash|. This will be nullptr if there are no allow-listed
+  // mark hashes for the given |site_hash|. This is threadsafe.
+  static const MarkHashSet* GetMarkHashSetForSiteHash(uint32_t site_hash);
+
+  // Generates a 32-bit MD5 hash of the given string piece. This will return a
+  // value that is equivalent to the first 8 bytes of a full MD5 hash. In bash
+  // parlance, the returned 32-bit integer expressed in hex format:
+  //
+  //   printf "%08x" <hashed_value>
+  //
+  // will have the same value as
+  //
+  //   echo -n <string_value> | md5sum | cut -b 1-8
+  //
+  // This will return the same result as MD5Hash32Constexpr as defined in
+  // base/hash/md5_constexpr.h. This uses base::StringPiece because it is
+  // interacting with Finch code, which doesn't use WTF primitives.
+  static uint32_t MD5Hash32(base::StringPiece string);
+
+  // For the given |target_site_hash| (`MD5Hash32()` of eTLD+1 represented in
+  // ASCII), and the provided background-tracing performance.mark |allow_list|
+  // (also represented as an ASCII std::string), populates vector
+  // |allow_listed_mark_hashes| of allowed performance.mark event names, as
+  // 32-bit hashes. Returns true on success, or false if any errors were
+  // observed in the input data. If this returns false the
+  // |allow_listed_mark_hashes| will be returned empty. This uses
+  // std::string because it is interacting with Finch code, which doesn't use
+  // WTF primitives.
+  static bool ParseBackgroundTracingPerformanceMarkHashes(
+      base::StringPiece allow_list,
+      SiteMarkHashMap& allow_listed_hashes);
+
+ private:
+  String site_;
+  uint32_t site_hash_ = 0;
+  // This points to a thread-safe global singleton.
+  const MarkHashSet* mark_hashes_ = nullptr;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_BACKGROUND_TRACING_HELPER_H_
diff --git a/third_party/blink/renderer/core/timing/background_tracing_helper_test.cc b/third_party/blink/renderer/core/timing/background_tracing_helper_test.cc
new file mode 100644
index 0000000..697f99b
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/background_tracing_helper_test.cc
@@ -0,0 +1,161 @@
+// 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 "third_party/blink/renderer/core/timing/background_tracing_helper.h"
+
+#include "base/hash/md5_constexpr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+class BackgroundTracingHelperTest : public testing::Test {
+ public:
+  using SiteMarkHashMap = BackgroundTracingHelper::SiteMarkHashMap;
+  using MarkHashSet = BackgroundTracingHelper::MarkHashSet;
+
+  BackgroundTracingHelperTest() = default;
+  ~BackgroundTracingHelperTest() override = default;
+
+  static uint32_t MD5Hash32(base::StringPiece string) {
+    return BackgroundTracingHelper::MD5Hash32(string);
+  }
+
+  static bool ParseBackgroundTracingPerformanceMarkHashes(
+      const std::string& allow_list,
+      SiteMarkHashMap& allow_listed_hashes) {
+    return BackgroundTracingHelper::ParseBackgroundTracingPerformanceMarkHashes(
+        allow_list, allow_listed_hashes);
+  }
+};
+
+TEST_F(BackgroundTracingHelperTest, MD5Hash32) {
+  static constexpr char kFoo[] = "foo";
+  static constexpr uint32_t kFooHash = 0xacbd18db;
+  static_assert(kFooHash == base::MD5Hash32Constexpr(kFoo), "unexpected hash");
+  EXPECT_EQ(kFooHash, MD5Hash32(kFoo));
+
+  static constexpr char kQuickFox[] =
+      "the quick fox jumps over the lazy brown dog";
+  static constexpr uint32_t kQuickFoxHash = 0x01275c33;
+  static_assert(kQuickFoxHash == base::MD5Hash32Constexpr(kQuickFox),
+                "unexpected hash");
+  EXPECT_EQ(kQuickFoxHash, MD5Hash32(kQuickFox));
+}
+
+TEST_F(BackgroundTracingHelperTest,
+       ParseBackgroundTracingPerformanceMarkHashes) {
+  SiteMarkHashMap hashes;
+  constexpr uint32_t kSiteHash1 = 0xdeadc0de;
+
+  // A list with a valid site hash not followed by an '=' is invalid.
+  EXPECT_FALSE(ParseBackgroundTracingPerformanceMarkHashes("deadc0de", hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // A list with invalid characters in the site hash is invalid.
+  EXPECT_FALSE(
+      ParseBackgroundTracingPerformanceMarkHashes("nothex=aabbccdd", hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // A list with an.IsEmpty site hash is invalid.
+  EXPECT_FALSE(
+      ParseBackgroundTracingPerformanceMarkHashes("=aabbccdd", hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // A list with an too long site hash is invalid.
+  EXPECT_FALSE(ParseBackgroundTracingPerformanceMarkHashes(
+      "00deadc0de=aabbccdd", hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // A list with no mark hashes is invalid.
+  EXPECT_FALSE(
+      ParseBackgroundTracingPerformanceMarkHashes("deadc0de=", hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // A list with an.IsEmpty mark hash is invalid.
+  EXPECT_FALSE(ParseBackgroundTracingPerformanceMarkHashes("deadc0de=,aabbccdd",
+                                                           hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // A list with a too long mark hash is invalid.
+  EXPECT_FALSE(ParseBackgroundTracingPerformanceMarkHashes(
+      "deadc0de=aabbccddee", hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // A list with a non-hex mark hash is invalid.
+  EXPECT_FALSE(
+      ParseBackgroundTracingPerformanceMarkHashes("deadc0de=nothex", hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // Parsing an empty list is valid, but the return should be empty as well.
+  EXPECT_TRUE(ParseBackgroundTracingPerformanceMarkHashes("", hashes));
+  EXPECT_TRUE(hashes.IsEmpty());
+
+  // Expect a single mark hash to be parsed.
+  EXPECT_TRUE(
+      ParseBackgroundTracingPerformanceMarkHashes("dEADc0de=aabbccdd", hashes));
+  EXPECT_EQ(1u, hashes.size());
+  EXPECT_TRUE(hashes.Contains(kSiteHash1));
+  {
+    const auto& mark_hashes = hashes.find(kSiteHash1)->value;
+    EXPECT_EQ(1u, mark_hashes.size());
+    EXPECT_TRUE(mark_hashes.Contains(0xaabbccdd));
+  }
+
+  // Expect multiple mark hashes to be parsed.
+  EXPECT_TRUE(ParseBackgroundTracingPerformanceMarkHashes(
+      "dEADc0de=aabbccdd,bcd", hashes));
+  EXPECT_EQ(1u, hashes.size());
+  EXPECT_TRUE(hashes.Contains(kSiteHash1));
+  {
+    const auto& mark_hashes = hashes.find(kSiteHash1)->value;
+    EXPECT_EQ(2u, mark_hashes.size());
+    EXPECT_TRUE(mark_hashes.Contains(0x00000bcd));
+    EXPECT_TRUE(mark_hashes.Contains(0xaabbccdd));
+  }
+
+  // Expect even more mark hashes to be parsed, and allow a trailing ';' to be
+  // ignored.
+  EXPECT_TRUE(ParseBackgroundTracingPerformanceMarkHashes(
+      "dEADc0de=aabbccdd,bcd,bbCCddee;", hashes));
+  EXPECT_EQ(1u, hashes.size());
+  EXPECT_TRUE(hashes.Contains(kSiteHash1));
+  {
+    const auto& mark_hashes = hashes.find(kSiteHash1)->value;
+    EXPECT_EQ(3u, mark_hashes.size());
+    EXPECT_TRUE(mark_hashes.Contains(0x00000bcd));
+    EXPECT_TRUE(mark_hashes.Contains(0xaabbccdd));
+    EXPECT_TRUE(mark_hashes.Contains(0xbbccddee));
+  }
+
+  // Expect a list with multiple sites to be parsed.
+  constexpr uint32_t kSiteHash2 = 0xa0b0c0d0;
+  constexpr uint32_t kSiteHash3 = 0xb0b0b0b0;
+  EXPECT_TRUE(ParseBackgroundTracingPerformanceMarkHashes(
+      "a0b0c0d0=aabbccdd;dEADc0de=aabbccdd,00000bcd,bbCCddee;b0b0b0b0=abc,b0e",
+      hashes));
+  EXPECT_EQ(3u, hashes.size());
+  EXPECT_TRUE(hashes.Contains(kSiteHash1));
+  EXPECT_TRUE(hashes.Contains(kSiteHash2));
+  EXPECT_TRUE(hashes.Contains(kSiteHash3));
+  {
+    const auto& mark_hashes = hashes.find(kSiteHash1)->value;
+    EXPECT_EQ(3u, mark_hashes.size());
+    EXPECT_TRUE(mark_hashes.Contains(0x00000bcd));
+    EXPECT_TRUE(mark_hashes.Contains(0xaabbccdd));
+    EXPECT_TRUE(mark_hashes.Contains(0xbbccddee));
+  }
+  {
+    const auto& mark_hashes = hashes.find(kSiteHash2)->value;
+    EXPECT_EQ(1u, mark_hashes.size());
+    EXPECT_TRUE(mark_hashes.Contains(0xaabbccdd));
+  }
+  {
+    const auto& mark_hashes = hashes.find(kSiteHash3)->value;
+    EXPECT_EQ(2u, mark_hashes.size());
+    EXPECT_TRUE(mark_hashes.Contains(0x00000abc));
+    EXPECT_TRUE(mark_hashes.Contains(0x00000b0e));
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/build.gni b/third_party/blink/renderer/core/timing/build.gni
index 016200a..5d43f873 100644
--- a/third_party/blink/renderer/core/timing/build.gni
+++ b/third_party/blink/renderer/core/timing/build.gni
@@ -55,6 +55,8 @@
   "profiler.h",
   "profiler_group.cc",
   "profiler_group.h",
+  "background_tracing_helper.cc",
+  "background_tracing_helper.h",
   "task_attribution_timing.cc",
   "task_attribution_timing.h",
   "time_clamper.cc",
diff --git a/third_party/blink/renderer/core/timing/performance.cc b/third_party/blink/renderer/core/timing/performance.cc
index 55d68a3..576911c 100644
--- a/third_party/blink/renderer/core/timing/performance.cc
+++ b/third_party/blink/renderer/core/timing/performance.cc
@@ -58,6 +58,7 @@
 #include "third_party/blink/renderer/core/loader/document_load_timing.h"
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/timing/background_tracing_helper.h"
 #include "third_party/blink/renderer/core/timing/largest_contentful_paint.h"
 #include "third_party/blink/renderer/core/timing/layout_shift.h"
 #include "third_party/blink/renderer/core/timing/measure_memory/measure_memory_controller.h"
@@ -110,7 +111,8 @@
 
 Performance::Performance(
     base::TimeTicks time_origin,
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    ExecutionContext* context)
     : resource_timing_buffer_size_limit_(kDefaultResourceTimingBufferSize),
       event_timing_buffer_max_size_(kDefaultEventTimingBufferSize),
       element_timing_buffer_max_size_(kDefaultElementTimingBufferSize),
@@ -129,6 +131,11 @@
   unix_at_zero_monotonic_ = ConvertSecondsToDOMHighResTimeStamp(
       base::DefaultClock::GetInstance()->Now().ToDoubleT() -
       tick_clock_->NowTicks().since_origin().InSecondsF());
+  // |context| may be null in tests.
+  if (context) {
+    background_tracing_helper_ =
+        MakeGarbageCollected<BackgroundTracingHelper>(context);
+  }
 }
 
 Performance::~Performance() = default;
@@ -715,6 +722,8 @@
   PerformanceMark* performance_mark = PerformanceMark::Create(
       script_state, mark_name, mark_options, exception_state);
   if (performance_mark) {
+    background_tracing_helper_->MaybeEmitBackgroundTracingPerformanceMarkEvent(
+        *performance_mark);
     GetUserTiming().AddMarkToPerformanceTimeline(*performance_mark);
     NotifyObserversOfEntry(*performance_mark);
   }
@@ -1061,6 +1070,7 @@
   visitor->Trace(suspended_observers_);
   visitor->Trace(deliver_observations_timer_);
   visitor->Trace(resource_timing_buffer_full_timer_);
+  visitor->Trace(background_tracing_helper_);
   EventTargetWithInlineData::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/core/timing/performance.h b/third_party/blink/renderer/core/timing/performance.h
index 65898ea8..3be592ed 100644
--- a/third_party/blink/renderer/core/timing/performance.h
+++ b/third_party/blink/renderer/core/timing/performance.h
@@ -57,9 +57,10 @@
 
 namespace blink {
 
-class PerformanceMarkOptions;
+class BackgroundTracingHelper;
 class EventCounts;
 class ExceptionState;
+class ExecutionContext;
 class LargestContentfulPaint;
 class LayoutShift;
 class MemoryInfo;
@@ -67,6 +68,7 @@
 class PerformanceElementTiming;
 class PerformanceEventTiming;
 class PerformanceMark;
+class PerformanceMarkOptions;
 class PerformanceMeasure;
 class PerformanceNavigation;
 class PerformanceObserver;
@@ -341,7 +343,8 @@
 
  protected:
   Performance(base::TimeTicks time_origin,
-              scoped_refptr<base::SingleThreadTaskRunner>);
+              scoped_refptr<base::SingleThreadTaskRunner>,
+              ExecutionContext* context = nullptr);
 
   // Expect WindowPerformance to override this method,
   // WorkerPerformance doesn't have to override this.
@@ -391,6 +394,9 @@
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   HeapTaskRunnerTimer<Performance> deliver_observations_timer_;
   HeapTaskRunnerTimer<Performance> resource_timing_buffer_full_timer_;
+
+  // See crbug.com/1181774.
+  Member<BackgroundTracingHelper> background_tracing_helper_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/window_performance.cc b/third_party/blink/renderer/core/timing/window_performance.cc
index 8f068c7..f139d6f 100644
--- a/third_party/blink/renderer/core/timing/window_performance.cc
+++ b/third_party/blink/renderer/core/timing/window_performance.cc
@@ -153,7 +153,8 @@
 
 WindowPerformance::WindowPerformance(LocalDOMWindow* window)
     : Performance(ToTimeOrigin(window),
-                  window->GetTaskRunner(TaskType::kPerformanceTimeline)),
+                  window->GetTaskRunner(TaskType::kPerformanceTimeline),
+                  window),
       ExecutionContextClient(window),
       PageVisibilityObserver(window->GetFrame()->GetPage()) {
   DCHECK(window);
diff --git a/third_party/blink/renderer/core/timing/worker_performance.cc b/third_party/blink/renderer/core/timing/worker_performance.cc
index 835c295..ee03112 100644
--- a/third_party/blink/renderer/core/timing/worker_performance.cc
+++ b/third_party/blink/renderer/core/timing/worker_performance.cc
@@ -41,7 +41,8 @@
 
 WorkerPerformance::WorkerPerformance(WorkerGlobalScope* context)
     : Performance(context->TimeOrigin(),
-                  context->GetTaskRunner(TaskType::kPerformanceTimeline)),
+                  context->GetTaskRunner(TaskType::kPerformanceTimeline),
+                  context),
       execution_context_(context) {}
 
 void WorkerPerformance::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 9dd6ce9..7a0d03c1 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -1228,9 +1228,11 @@
       base::Optional<base::UnguessableToken> child_token =
           child_frame->GetEmbeddingToken();
       if (child_token && !(IsDetached() || ChildCountIncludingIgnored())) {
+        ui::AXTreeID child_tree_id =
+            ui::AXTreeID::FromToken(child_token.value());
         node_data->AddStringAttribute(
             ax::mojom::blink::StringAttribute::kChildTreeId,
-            child_token->ToString());
+            child_tree_id.ToString());
       }
     }
   }
diff --git a/third_party/blink/renderer/modules/buckets/storage_bucket_manager.cc b/third_party/blink/renderer/modules/buckets/storage_bucket_manager.cc
index 3fe968f..9ff653a 100644
--- a/third_party/blink/renderer/modules/buckets/storage_bucket_manager.cc
+++ b/third_party/blink/renderer/modules/buckets/storage_bucket_manager.cc
@@ -12,18 +12,37 @@
 #include "third_party/blink/renderer/modules/buckets/storage_bucket.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "third_party/blink/renderer/platform/wtf/text/ascii_ctype.h"
 
 namespace blink {
 
 namespace {
 
+bool IsValidName(const String& name) {
+  if (!name.IsLowerASCII())
+    return false;
+
+  if (!name.ContainsOnlyASCIIOrEmpty())
+    return false;
+
+  if (name.IsEmpty() || name.length() >= 64)
+    return false;
+
+  // | name | must only contain lowercase latin letters, digits 0-9, or special
+  // characters '-' & '_' in the middle of the name, but not at the beginning.
+  for (wtf_size_t i = 0; i < name.length(); i++) {
+    if (!IsASCIIAlphanumeric(name[i]) &&
+        (i == 0 || (name[i] != '_' && name[i] != '-'))) {
+      return false;
+    }
+  }
+  return true;
+}
+
 mojom::blink::BucketPoliciesPtr ToMojoBucketPolicies(
     const StorageBucketOptions* options) {
   auto policies = mojom::blink::BucketPolicies::New();
   policies->persisted = options->persisted();
-  policies->title = (options->hasTitle() && !options->title().IsEmpty())
-                        ? options->title()
-                        : "";
   policies->quota = options->hasQuotaNonNull()
                         ? options->quotaNonNull()
                         : mojom::blink::kNoQuotaPolicyValue;
@@ -71,6 +90,13 @@
   mojom::blink::BucketPoliciesPtr bucket_policies =
       ToMojoBucketPolicies(options);
 
+  if (!IsValidName(name)) {
+    resolver->Reject(MakeGarbageCollected<DOMException>(
+        DOMExceptionCode::kInvalidCharacterError,
+        "The bucket name '" + name + "' is not a valid name."));
+    return promise;
+  }
+
   GetBucketManager(script_state)
       ->OpenBucket(name, std::move(bucket_policies),
                    WTF::Bind(&StorageBucketManager::DidOpen,
@@ -95,6 +121,13 @@
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
   ScriptPromise promise = resolver->Promise();
 
+  if (!IsValidName(name)) {
+    resolver->Reject(MakeGarbageCollected<DOMException>(
+        DOMExceptionCode::kInvalidCharacterError,
+        "The bucket name " + name + " is not a valid name."));
+    return promise;
+  }
+
   GetBucketManager(script_state)
       ->DeleteBucket(name,
                      WTF::Bind(&StorageBucketManager::DidDelete,
diff --git a/third_party/blink/renderer/modules/buckets/storage_bucket_options.idl b/third_party/blink/renderer/modules/buckets/storage_bucket_options.idl
index a6c3f5ed..3f79c4c 100644
--- a/third_party/blink/renderer/modules/buckets/storage_bucket_options.idl
+++ b/third_party/blink/renderer/modules/buckets/storage_bucket_options.idl
@@ -10,7 +10,6 @@
 };
 
 dictionary StorageBucketOptions {
-  DOMString? title = null;
   boolean persisted = false;
   StorageBucketDurability durability = "relaxed";
   unsigned long long? quota = null;
diff --git a/third_party/blink/renderer/modules/delegated_ink/DEPS b/third_party/blink/renderer/modules/delegated_ink/DEPS
index 830557a2..b6fd9b3 100644
--- a/third_party/blink/renderer/modules/delegated_ink/DEPS
+++ b/third_party/blink/renderer/modules/delegated_ink/DEPS
@@ -1,5 +1,5 @@
 specific_include_rules = {
   "delegated_ink_trail_presenter.*.cc" : [
-    "+components/viz/common/delegated_ink_metadata.h",
+    "+ui/gfx/delegated_ink_metadata.h",
   ]
 }
diff --git a/third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter.cc b/third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter.cc
index b574e03..1ebbd11 100644
--- a/third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter.cc
+++ b/third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter.cc
@@ -4,7 +4,6 @@
 
 #include "third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter.h"
 
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ink_trail_style.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser.h"
 #include "third_party/blink/renderer/core/dom/element.h"
@@ -18,6 +17,7 @@
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 
 namespace blink {
 
@@ -148,8 +148,8 @@
       !(evt->GetModifiers() & WebInputEvent::Modifiers::kLeftButtonDown);
 
   const double diameter_in_physical_pixels = style->diameter() * effective_zoom;
-  std::unique_ptr<viz::DelegatedInkMetadata> metadata =
-      std::make_unique<viz::DelegatedInkMetadata>(
+  std::unique_ptr<gfx::DelegatedInkMetadata> metadata =
+      std::make_unique<gfx::DelegatedInkMetadata>(
           point, diameter_in_physical_pixels, color.Rgb(),
           evt->PlatformTimeStamp(), area, is_hovering);
 
diff --git a/third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter_unittest.cc b/third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter_unittest.cc
index 6faf067..1e7c3d5 100644
--- a/third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter_unittest.cc
+++ b/third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter_unittest.cc
@@ -4,7 +4,6 @@
 
 #include "third_party/blink/renderer/modules/delegated_ink/delegated_ink_trail_presenter.h"
 
-#include "components/viz/common/delegated_ink_metadata.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_pointer_event_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ink_trail_style.h"
 #include "third_party/blink/renderer/core/dom/element.h"
@@ -14,13 +13,14 @@
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
+#include "ui/gfx/delegated_ink_metadata.h"
 
 namespace blink {
 namespace {
 
 class TestDelegatedInkMetadata {
  public:
-  explicit TestDelegatedInkMetadata(viz::DelegatedInkMetadata* metadata)
+  explicit TestDelegatedInkMetadata(gfx::DelegatedInkMetadata* metadata)
       : point_(metadata->point()),
         color_(metadata->color()),
         diameter_(metadata->diameter()),
diff --git a/third_party/blink/renderer/modules/geolocation/geolocation.cc b/third_party/blink/renderer/modules/geolocation/geolocation.cc
index 027f759c..e8c071e0c 100644
--- a/third_party/blink/renderer/modules/geolocation/geolocation.cc
+++ b/third_party/blink/renderer/modules/geolocation/geolocation.cc
@@ -501,8 +501,12 @@
     last_position_ = CreateGeoposition(*position);
     PositionChanged();
   } else {
-    HandleError(
-        CreatePositionError(position->error_code, position->error_message));
+    GeolocationPositionError* position_error =
+        CreatePositionError(position->error_code, position->error_message);
+    if (position_error->code() == GeolocationPositionError::kPermissionDenied) {
+      position_error->SetIsFatal(true);
+    }
+    HandleError(position_error);
   }
   if (!disconnected_geolocation_)
     QueryNextPosition();
diff --git a/third_party/blink/renderer/modules/handwriting/handwriting_type_converters.cc b/third_party/blink/renderer/modules/handwriting/handwriting_type_converters.cc
index 71771bc..7e66b26 100644
--- a/third_party/blink/renderer/modules/handwriting/handwriting_type_converters.cc
+++ b/third_party/blink/renderer/modules/handwriting/handwriting_type_converters.cc
@@ -38,7 +38,9 @@
   }
   auto output = handwriting::mojom::blink::HandwritingPoint::New();
   output->location = gfx::PointF(input->x(), input->y());
-  output->t = base::TimeDelta::FromMilliseconds(input->t());
+  if (input->hasT()) {
+    output->t = base::TimeDelta::FromMilliseconds(input->t());
+  }
   return output;
 }
 
@@ -102,7 +104,9 @@
   auto* output = blink::HandwritingPoint::Create();
   output->setX(input->location.x());
   output->setY(input->location.y());
-  output->setT(input->t.InMilliseconds());
+  if (input->t.has_value()) {
+    output->setT(input->t->InMilliseconds());
+  }
   return output;
 }
 
diff --git a/third_party/blink/renderer/modules/handwriting/handwriting_type_converters_unittest.cc b/third_party/blink/renderer/modules/handwriting/handwriting_type_converters_unittest.cc
index edaffc9..489f961 100644
--- a/third_party/blink/renderer/modules/handwriting/handwriting_type_converters_unittest.cc
+++ b/third_party/blink/renderer/modules/handwriting/handwriting_type_converters_unittest.cc
@@ -42,7 +42,21 @@
   ASSERT_FALSE(mojo_point.is_null());
   EXPECT_NEAR(mojo_point->location.x(), 1.1, 1e-5);
   EXPECT_NEAR(mojo_point->location.y(), 2.3, 1e-5);
-  EXPECT_EQ(mojo_point->t.InMilliseconds(), 345);
+  ASSERT_TRUE(mojo_point->t.has_value());
+  EXPECT_EQ(mojo_point->t->InMilliseconds(), 345);
+}
+
+TEST(HandwritingTypeConvertersTest, IdlHandwritingPointToMojoWithoutT) {
+  auto* idl_point = blink::HandwritingPoint::Create();
+
+  idl_point->setX(3.1);
+  idl_point->setY(4.3);
+
+  auto mojo_point = mojo::ConvertTo<HandwritingPointPtr>(idl_point);
+  ASSERT_FALSE(mojo_point.is_null());
+  EXPECT_NEAR(mojo_point->location.x(), 3.1, 1e-5);
+  EXPECT_NEAR(mojo_point->location.y(), 4.3, 1e-5);
+  ASSERT_FALSE(mojo_point->t.has_value());
 }
 
 TEST(HandwritingTypeConvertersTest, IdlHandwritingStrokeToMojo) {
@@ -55,7 +69,6 @@
   idl_stroke->addPoint(idl_point1);
   idl_point2->setX(1.1);
   idl_point2->setY(1.2);
-  idl_point2->setT(456);
   idl_stroke->addPoint(idl_point2);
 
   auto mojo_stroke = mojo::ConvertTo<HandwritingStrokePtr>(idl_stroke);
@@ -63,10 +76,11 @@
   ASSERT_EQ(mojo_stroke->points.size(), 2u);
   EXPECT_NEAR(mojo_stroke->points[0]->location.x(), 0.1, 1e-5);
   EXPECT_NEAR(mojo_stroke->points[0]->location.y(), 0.2, 1e-5);
-  EXPECT_EQ(mojo_stroke->points[0]->t.InMilliseconds(), 123);
+  ASSERT_TRUE(mojo_stroke->points[0]->t.has_value());
+  EXPECT_EQ(mojo_stroke->points[0]->t->InMilliseconds(), 123);
   EXPECT_NEAR(mojo_stroke->points[1]->location.x(), 1.1, 1e-5);
   EXPECT_NEAR(mojo_stroke->points[1]->location.y(), 1.2, 1e-5);
-  EXPECT_EQ(mojo_stroke->points[1]->t.InMilliseconds(), 456);
+  ASSERT_FALSE(mojo_stroke->points[1]->t.has_value());
 }
 
 TEST(HandwritingTypeConvertersTest, IdlHandwritingHintsToMojo) {
@@ -126,9 +140,21 @@
   ASSERT_NE(idl_point, nullptr);
   EXPECT_NEAR(idl_point->x(), 0.3, 1e-5);
   EXPECT_NEAR(idl_point->y(), 0.4, 1e-5);
+  ASSERT_TRUE(idl_point->hasT());
   EXPECT_EQ(idl_point->t(), 123u);
 }
 
+TEST(HandwritingTypeConvertersTest, MojoHandwritingPointToIdlWithoutT) {
+  auto mojo_point = handwriting::mojom::blink::HandwritingPoint::New();
+  mojo_point->location = gfx::PointF(0.3, 0.4);
+
+  auto* idl_point = mojo::ConvertTo<blink::HandwritingPoint*>(mojo_point);
+  ASSERT_NE(idl_point, nullptr);
+  EXPECT_NEAR(idl_point->x(), 0.3, 1e-5);
+  EXPECT_NEAR(idl_point->y(), 0.4, 1e-5);
+  ASSERT_FALSE(idl_point->hasT());
+}
+
 TEST(HandwritingTypeConvertersTest, MojoHandwritingStrokeToIdl) {
   auto mojo_stroke = handwriting::mojom::blink::HandwritingStroke::New();
   auto mojo_point1 = handwriting::mojom::blink::HandwritingPoint::New();
@@ -137,7 +163,6 @@
   mojo_stroke->points.push_back(std::move(mojo_point1));
   auto mojo_point2 = handwriting::mojom::blink::HandwritingPoint::New();
   mojo_point2->location = gfx::PointF(3.1, 3.2);
-  mojo_point2->t = base::TimeDelta::FromMilliseconds(456);
   mojo_stroke->points.push_back(std::move(mojo_point2));
 
   auto* idl_stroke = mojo::ConvertTo<blink::HandwritingStroke*>(mojo_stroke);
@@ -145,10 +170,11 @@
   ASSERT_EQ(idl_stroke->getPoints().size(), 2u);
   EXPECT_NEAR(idl_stroke->getPoints()[0]->x(), 2.1, 1e-5);
   EXPECT_NEAR(idl_stroke->getPoints()[0]->y(), 2.2, 1e-5);
+  ASSERT_TRUE(idl_stroke->getPoints()[0]->hasT());
   EXPECT_EQ(idl_stroke->getPoints()[0]->t(), 321u);
   EXPECT_NEAR(idl_stroke->getPoints()[1]->x(), 3.1, 1e-5);
   EXPECT_NEAR(idl_stroke->getPoints()[1]->y(), 3.2, 1e-5);
-  EXPECT_EQ(idl_stroke->getPoints()[1]->t(), 456u);
+  ASSERT_FALSE(idl_stroke->getPoints()[1]->hasT());
 }
 
 TEST(HandwritingTypeConvertersTest, MojoHandwritingFeatureQueryResultIdl) {
diff --git a/third_party/blink/renderer/modules/mediasession/media_session_test.cc b/third_party/blink/renderer/modules/mediasession/media_session_test.cc
index 2516aa32..b561bc2 100644
--- a/third_party/blink/renderer/modules/mediasession/media_session_test.cc
+++ b/third_party/blink/renderer/modules/mediasession/media_session_test.cc
@@ -36,6 +36,10 @@
   MOCK_METHOD1(SetPositionState,
                void(media_session::mojom::blink::MediaPositionPtr));
   void SetMetadata(mojom::blink::SpecMediaMetadataPtr metadata) override {}
+  void SetMicrophoneState(
+      media_session::mojom::MicrophoneState microphone_state) override {}
+  void SetCameraState(media_session::mojom::CameraState camera_state) override {
+  }
   void EnableAction(
       media_session::mojom::blink::MediaSessionAction action) override {}
   void DisableAction(
diff --git a/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper_test.cc b/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper_test.cc
index 9ad5059..c25b184 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/chunk_to_layer_mapper_test.cc
@@ -36,10 +36,9 @@
           CreateClip(c0(), *layer_transform_, FloatRoundedRect(12, 34, 56, 78));
       layer_effect_ = EffectPaintPropertyNode::Create(
           e0(), EffectPaintPropertyNode::State{
-                    layer_transform_, layer_clip_, kColorFilterLuminanceToAlpha,
-                    CompositorFilterOperations(), 0.789f,
-                    CompositorFilterOperations(), base::Optional<gfx::RRectF>(),
-                    SkBlendMode::kSrcIn});
+                    layer_transform_, layer_clip_, CompositorFilterOperations(),
+                    0.789f, CompositorFilterOperations(),
+                    base::Optional<gfx::RRectF>(), SkBlendMode::kSrcIn});
     }
     return PropertyTreeState(*layer_transform_, *layer_clip_, *layer_effect_);
   }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index d663bb0..0b480fd 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -2221,13 +2221,12 @@
             ElementIdToEffectNodeIndex(real_effect->GetCompositorElementId()));
 }
 
-TEST_P(PaintArtifactCompositorTest, NonCompositedSimpleLuminanceMask) {
+TEST_P(PaintArtifactCompositorTest, NonCompositedSimpleMask) {
   auto masked = CreateOpacityEffect(
       e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
   EffectPaintPropertyNode::State masking_state;
   masking_state.local_transform_space = &t0();
   masking_state.output_clip = &c0();
-  masking_state.color_filter = kColorFilterLuminanceToAlpha;
   masking_state.blend_mode = SkBlendMode::kDstIn;
   auto masking =
       EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
@@ -2256,84 +2255,6 @@
   EXPECT_EQ(masked_group, GetPropertyTrees().effect_tree.back());
 }
 
-TEST_P(PaintArtifactCompositorTest, CompositedLuminanceMaskOneChild) {
-  auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
-  EffectPaintPropertyNode::State masking_state;
-  masking_state.local_transform_space = &t0();
-  masking_state.output_clip = &c0();
-  masking_state.color_filter = kColorFilterLuminanceToAlpha;
-  masking_state.blend_mode = SkBlendMode::kDstIn;
-  masking_state.direct_compositing_reasons = CompositingReason::kLayerForMask;
-  auto masking =
-      EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
-
-  TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c0(), *masked)
-      .RectDrawing(IntRect(100, 100, 200, 200), Color::kGray);
-  artifact.Chunk(t0(), c0(), *masking)
-      .RectDrawing(IntRect(150, 150, 100, 100), Color::kWhite);
-  Update(artifact.Build());
-  ASSERT_EQ(2u, LayerCount());
-
-  const cc::Layer* masking_layer = LayerAt(1);
-  const cc::EffectNode* masking_group =
-      GetPropertyTrees().effect_tree.Node(masking_layer->effect_tree_index());
-
-  // Render surface is not needed for one child.
-  EXPECT_FALSE(masking_group->HasRenderSurface());
-  ASSERT_EQ(1u, masking_group->filters.size());
-  EXPECT_EQ(cc::FilterOperation::REFERENCE,
-            masking_group->filters.at(0).type());
-  EXPECT_EQ(SkBlendMode::kDstIn, masking_group->blend_mode);
-
-  // The parent also has a render surface to define the scope of the backdrop
-  // of the kDstIn blend mode.
-  EXPECT_TRUE(
-      GetPropertyTrees().effect_tree.parent(masking_group)->HasRenderSurface());
-}
-
-TEST_P(PaintArtifactCompositorTest, CompositedLuminanceMaskTwoChildren) {
-  auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
-  EffectPaintPropertyNode::State masking_state;
-  masking_state.local_transform_space = &t0();
-  masking_state.output_clip = &c0();
-  masking_state.color_filter = kColorFilterLuminanceToAlpha;
-  masking_state.blend_mode = SkBlendMode::kDstIn;
-  auto masking =
-      EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
-
-  auto child_of_masked = CreateOpacityEffect(
-      *masking, 1.0, CompositingReason::kIsolateCompositedDescendants);
-
-  TestPaintArtifact artifact;
-  artifact.Chunk(t0(), c0(), *masked)
-      .RectDrawing(IntRect(100, 100, 200, 200), Color::kGray);
-  artifact.Chunk(t0(), c0(), *child_of_masked)
-      .RectDrawing(IntRect(100, 100, 200, 200), Color::kGray);
-  artifact.Chunk(t0(), c0(), *masking)
-      .RectDrawing(IntRect(150, 150, 100, 100), Color::kWhite);
-  Update(artifact.Build());
-  ASSERT_EQ(3u, LayerCount());
-
-  const cc::Layer* masking_layer = LayerAt(2);
-  const cc::EffectNode* masking_group =
-      GetPropertyTrees().effect_tree.Node(masking_layer->effect_tree_index());
-
-  // There is a render surface because there are two children.
-  EXPECT_TRUE(masking_group->HasRenderSurface());
-  ASSERT_EQ(1u, masking_group->filters.size());
-  EXPECT_EQ(cc::FilterOperation::REFERENCE,
-            masking_group->filters.at(0).type());
-  EXPECT_EQ(SkBlendMode::kDstIn, masking_group->blend_mode);
-
-  // The parent also has a render surface to define the scope of the backdrop
-  // of the kDstIn blend mode.
-  EXPECT_TRUE(
-      GetPropertyTrees().effect_tree.parent(masking_group)->HasRenderSurface());
-}
-
 TEST_P(PaintArtifactCompositorTest, CompositedMaskOneChild) {
   auto masked = CreateOpacityEffect(
       e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
index 6c13c34..cbff208 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
@@ -535,8 +535,7 @@
   // effects, so we can handle them separately.
   bool has_filter = !effect.Filter().IsEmpty();
   bool has_opacity = effect.Opacity() != 1.f;
-  bool has_other_effects = effect.BlendMode() != SkBlendMode::kSrcOver ||
-                           effect.GetColorFilter() != kColorFilterNone;
+  bool has_other_effects = effect.BlendMode() != SkBlendMode::kSrcOver;
   DCHECK(!has_filter || !(has_opacity || has_other_effects));
   // We always composite backdrop filters.
   DCHECK(effect.BackdropFilter().IsEmpty());
@@ -551,8 +550,6 @@
       PaintFlags flags;
       flags.setBlendMode(effect.BlendMode());
       flags.setAlpha(alpha);
-      flags.setColorFilter(GraphicsContext::WebCoreColorFilterToSkiaColorFilter(
-          effect.GetColorFilter()));
       save_layer_id = cc_list_.push<cc::SaveLayerOp>(nullptr, &flags);
     } else {
       save_layer_id = cc_list_.push<cc::SaveLayerAlphaOp>(nullptr, alpha);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index d9b4bc7..8285003 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -19,7 +19,6 @@
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
 #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h"
-#include "third_party/skia/include/effects/SkLumaColorFilter.h"
 
 namespace blink {
 
@@ -1155,29 +1154,17 @@
   effect_node.render_surface_reason = RenderSurfaceReasonForEffect(effect);
   effect_node.opacity = effect.Opacity();
   const auto& transform = effect.LocalTransformSpace().Unalias();
-  if (effect.GetColorFilter() != kColorFilterNone) {
-    // Currently color filter is only used by SVG masks.
-    // We are cutting corner here by support only specific configuration.
-    DCHECK_EQ(effect.GetColorFilter(), kColorFilterLuminanceToAlpha);
-    DCHECK_EQ(effect.BlendMode(), SkBlendMode::kDstIn);
+  effect_node.transform_id = EnsureCompositorTransformNode(transform);
+  if (effect.HasBackdropEffect()) {
+    // We never have backdrop effect and filter on the same effect node.
     DCHECK(effect.Filter().IsEmpty());
-    effect_node.filters.Append(cc::FilterOperation::CreateReferenceFilter(
-        sk_make_sp<ColorFilterPaintFilter>(SkLumaColorFilter::Make(),
-                                           nullptr)));
-    effect_node.blend_mode = SkBlendMode::kDstIn;
+    effect_node.backdrop_filters =
+        effect.BackdropFilter().AsCcFilterOperations();
+    effect_node.backdrop_filter_bounds = effect.BackdropFilterBounds();
+    effect_node.blend_mode = effect.BlendMode();
+    effect_node.backdrop_mask_element_id = effect.BackdropMaskElementId();
   } else {
-    effect_node.transform_id = EnsureCompositorTransformNode(transform);
-    if (effect.HasBackdropEffect()) {
-      // We never have backdrop effect and filter on the same effect node.
-      DCHECK(effect.Filter().IsEmpty());
-      effect_node.backdrop_filters =
-          effect.BackdropFilter().AsCcFilterOperations();
-      effect_node.backdrop_filter_bounds = effect.BackdropFilterBounds();
-      effect_node.blend_mode = effect.BlendMode();
-      effect_node.backdrop_mask_element_id = effect.BackdropMaskElementId();
-    } else {
-      effect_node.filters = effect.Filter().AsCcFilterOperations();
-    }
+    effect_node.filters = effect.Filter().AsCcFilterOperations();
   }
   effect_node.double_sided = !transform.IsBackfaceHidden();
   effect_node.effect_changed = effect.NodeChangeAffectsRaster();
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
index 29b0935f..277445a0 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
@@ -60,8 +60,6 @@
   json->SetString("localTransformSpace",
                   String::Format("%p", state_.local_transform_space.get()));
   json->SetString("outputClip", String::Format("%p", state_.output_clip.get()));
-  if (state_.color_filter != kColorFilterNone)
-    json->SetInteger("colorFilter", state_.color_filter);
   if (!state_.filter.IsEmpty())
     json->SetString("filter", state_.filter.ToString());
   if (!state_.backdrop_filter.IsEmpty())
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
index d6b4a6e0..f201358 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
@@ -8,7 +8,6 @@
 #include <algorithm>
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_filter_operations.h"
-#include "third_party/blink/renderer/platform/graphics/graphics_types.h"
 #include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h"
@@ -93,7 +92,6 @@
     // Optionally a number of effects can be applied to the composited output.
     // The chain of effects will be applied in the following order:
     // === Begin of effects ===
-    ColorFilter color_filter = kColorFilterNone;
     CompositorFilterOperations filter;
     float opacity = 1;
     CompositorFilterOperations backdrop_filter;
@@ -116,7 +114,6 @@
         const AnimationState& animation_state) {
       if (local_transform_space != other.local_transform_space ||
           output_clip != other.output_clip ||
-          color_filter != other.color_filter ||
           backdrop_filter_bounds != other.backdrop_filter_bounds ||
           blend_mode != other.blend_mode) {
         return PaintPropertyChangeType::kChangedOnlyValues;
@@ -200,9 +197,6 @@
   const CompositorFilterOperations& Filter() const {
     return state_.filter;
   }
-  ColorFilter GetColorFilter() const {
-    return state_.color_filter;
-  }
 
   const CompositorFilterOperations& BackdropFilter() const {
     return state_.backdrop_filter;
@@ -221,14 +215,12 @@
   }
 
   bool HasRealEffects() const {
-    return Opacity() != 1.0f || GetColorFilter() != kColorFilterNone ||
-           BlendMode() != SkBlendMode::kSrcOver || !Filter().IsEmpty() ||
-           !BackdropFilter().IsEmpty();
+    return Opacity() != 1.0f || BlendMode() != SkBlendMode::kSrcOver ||
+           !Filter().IsEmpty() || !BackdropFilter().IsEmpty();
   }
 
   bool IsOpacityOnly() const {
-    return GetColorFilter() == kColorFilterNone &&
-           BlendMode() == SkBlendMode::kSrcOver && Filter().IsEmpty() &&
+    return BlendMode() == SkBlendMode::kSrcOver && Filter().IsEmpty() &&
            BackdropFilter().IsEmpty();
   }
 
diff --git a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.cc b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.cc
index bf28ded3..5f00f68a 100644
--- a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.cc
+++ b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/platform/heap/thread_state.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
@@ -38,6 +39,7 @@
                                     const HTMLFrameOwnerElement& owner) final {}
   void OnBeforeContentFrameDetached(const Frame& frame,
                                     const HTMLFrameOwnerElement& owner) final {}
+  void FireBackgroundTracingTrigger(const String& trigger_name) final {}
 };
 
 }  // namespace
diff --git a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.h b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.h
index 1986d7a..f41e7a8 100644
--- a/third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.h
+++ b/third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.h
@@ -7,6 +7,10 @@
 
 #include "third_party/blink/renderer/platform/platform_export.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 // TODO(chrisha): Remove knowledge of ExecutionContext class from this code!
@@ -15,6 +19,7 @@
 class HTMLFrameOwnerElement;
 class ScriptState;
 
+// This object is a process-wide singleton, and thread-safe.
 class PLATFORM_EXPORT RendererResourceCoordinator {
  public:
   static void Set(RendererResourceCoordinator* instance);
@@ -59,6 +64,12 @@
   virtual void OnBeforeContentFrameDetached(
       const Frame& frame,
       const HTMLFrameOwnerElement& owner) = 0;
+
+  // Used to fire a named tracing trigger from a renderer. This is a nop unless
+  // the tracing machinery has been appropriately configured in the browser
+  // process.
+  virtual void FireBackgroundTracingTrigger(
+      const WTF::String& trigger_name) = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/widget/DEPS b/third_party/blink/renderer/platform/widget/DEPS
index 9c7456d5..cd9ab15 100644
--- a/third_party/blink/renderer/platform/widget/DEPS
+++ b/third_party/blink/renderer/platform/widget/DEPS
@@ -8,7 +8,7 @@
     "+gpu/command_buffer/common/context_creation_attribs.h",
     "+gpu/ipc/client/gpu_channel_host.h",
     "+services/viz/public/cpp/gpu/context_provider_command_buffer.h",
-    "+services/viz/public/mojom/compositing/delegated_ink_metadata.mojom-blink.h",
+    "+ui/gfx/mojom/delegated_ink_metadata.mojom-blink.h",
     "+services/viz/public/mojom/compositing/frame_sink_id.mojom-blink.h",
     "+ui/base/ime/text_input_mode.h",
     "+ui/base/ime/text_input_type.h",
diff --git a/third_party/blink/renderer/platform/widget/frame_widget.h b/third_party/blink/renderer/platform/widget/frame_widget.h
index 5a857173..8de9111 100644
--- a/third_party/blink/renderer/platform/widget/frame_widget.h
+++ b/third_party/blink/renderer/platform/widget/frame_widget.h
@@ -7,7 +7,6 @@
 
 #include "cc/input/layer_selection_bound.h"
 #include "mojo/public/mojom/base/text_direction.mojom-blink.h"
-#include "services/viz/public/mojom/compositing/delegated_ink_metadata.mojom-blink.h"
 #include "services/viz/public/mojom/compositing/frame_sink_id.mojom-blink.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom-blink.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom-blink.h"
@@ -18,6 +17,7 @@
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "ui/base/ime/mojom/text_input_state.mojom-blink.h"
 #include "ui/base/ime/mojom/virtual_keyboard_types.mojom-blink.h"
+#include "ui/gfx/mojom/delegated_ink_metadata.mojom-blink.h"
 
 namespace cc {
 class AnimationHost;
@@ -113,7 +113,7 @@
 
   // Sets the ink metadata on the layer tree host
   virtual void SetDelegatedInkMetadata(
-      std::unique_ptr<viz::DelegatedInkMetadata> metadata) = 0;
+      std::unique_ptr<gfx::DelegatedInkMetadata> metadata) = 0;
 
   // Called when the main thread overscrolled.
   virtual void DidOverscroll(const gfx::Vector2dF& overscroll_delta,
diff --git a/third_party/blink/renderer/platform/wtf/text/text_position.h b/third_party/blink/renderer/platform/wtf/text/text_position.h
index b4720f0..ec7418e 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_position.h
+++ b/third_party/blink/renderer/platform/wtf/text/text_position.h
@@ -50,7 +50,9 @@
   static OrdinalNumber FromOneBasedInt(int one_based_int) {
     return OrdinalNumber(one_based_int - 1);
   }
-  OrdinalNumber() : zero_based_value_(0) {}
+
+  // Use First() instead.
+  OrdinalNumber() = delete;
 
   int ZeroBasedInt() const { return zero_based_value_; }
   int OneBasedInt() const { return zero_based_value_ + 1; }
@@ -77,7 +79,10 @@
  public:
   TextPosition(OrdinalNumber line, OrdinalNumber column)
       : line_(line), column_(column) {}
-  TextPosition() = default;
+
+  // Use MinimumPosition() instead.
+  TextPosition() = delete;
+
   bool operator==(const TextPosition& other) const {
     return line_ == other.line_ && column_ == other.column_;
   }
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 0e52d848..53a8f83 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -502,6 +502,7 @@
             'ui::AXEventIntent',
             'ui::AXMode',
             'ui::AXNodeData',
+            'ui::AXTreeID',
             'ax::mojom::BoolAttribute',
             'ax::mojom::HasPopup',
             'ax::mojom::State',
@@ -899,6 +900,17 @@
     },
     {
         'paths': [
+            'third_party/blink/renderer/core/timing/background_tracing_helper.cc',
+            'third_party/blink/renderer/core/timing/background_tracing_helper.h',
+        ],
+        'allowed': [
+            'base::MD5Digest',
+            'base::MD5Sum',
+            'base::StringPiece',
+        ]
+    },
+    {
+        'paths': [
             'third_party/blink/renderer/modules/mediasource/',
         ],
         'allowed': [
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 20b7e76..0466ecce 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -234,6 +234,11 @@
 crbug.com/1126305 wpt_internal/prerender/* [ Skip ]
 crbug.com/1126305 virtual/prerender/wpt_internal/prerender/* [ Pass ]
 
+# These tests require BFCache, which is currently disabled by default on Desktop.
+# Keep this in sync with VirtualTestSuites.
+crbug.com/1171298 http/tests/inspector-protocol/bfcache/* [ Skip ]
+crbug.com/1171298 virtual/bfcache/http/tests/inspector-protocol/bfcache/* [ Pass ]
+
 # Ref test with very minor differences. We need fuzzy matching for our ref tests,
 # or upstream this to WPT.
 crbug.com/1185506 svg/text/textpath-pattern.svg [ Pass Failure ]
@@ -3492,7 +3497,6 @@
 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-implies-size-change-031.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-implies-size-change-035.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-implies-size-change-036.html [ Pass ]
-virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-baseline-align-cycles-001.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/replaced-alignment-with-aspect-ratio-001.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/replaced-alignment-with-aspect-ratio-002.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/replaced-alignment-with-aspect-ratio-006.html [ Pass ]
@@ -3524,26 +3528,11 @@
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/abspos/grid-positioned-items-padding-001.html [ Crash Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/abspos/grid-positioned-items-within-grid-implicit-track-001.html [ Crash Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/abspos/positioned-grid-items-negative-indices-003.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-align-baseline-vertical.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-align-baseline.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-align-justify-overflow.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-style-changes-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-style-changes-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-style-changes-003.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-style-changes-004.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-style-changes-005.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-style-changes-006.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-style-changes-007.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-alignment-style-changes-008.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-baseline-004.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-baseline-align-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-baseline-justify-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-column-axis-alignment-positioned-items-017.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-003.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-004.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-column-axis-self-baseline-synthesized-005.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-content-alignment-auto-sized-tracks-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-content-alignment-overflow-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-content-alignment-second-pass-002.html [ Failure ]
@@ -3576,51 +3565,14 @@
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-row-axis-alignment-positioned-items-017.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-003.html [ Failure ]
+crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-003.html [ Failure Crash ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-004.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-row-axis-self-baseline-synthesized-005.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-008.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-self-alignment-non-static-positioned-items-011.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-self-alignment-positioned-items-with-margin-border-padding-016.html [ Crash Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-self-baseline-not-applied-if-sizing-cyclic-dependency-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-self-baseline-not-applied-if-sizing-cyclic-dependency-002.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/grid-self-baseline-not-applied-if-sizing-cyclic-dependency-003.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-002-b.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-003.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-004.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-005.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-006.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-007.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-008.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-005.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-006.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-007.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-005.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-006.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-007.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-005.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-006.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-007.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/animation/grid-template-columns-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/animation/grid-template-rows-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-definition/flex-content-resolution-columns-002.html [ Failure ]
@@ -3647,12 +3599,6 @@
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/grid-minimum-size-grid-items-025.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-layout-properties.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-model/iner-ignores-first-letter-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-001.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-002.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-003.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-004.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-005.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/baseline-alignment-affects-intrinsic-size-006.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/flex-and-intrinsic-sizes-002.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/flex-sizing-rows-min-max-height-001.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/external/wpt/css/css-grid/layout-algorithm/grid-automatic-minimum-for-auto-columns-001.html [ Failure ]
@@ -3688,7 +3634,6 @@
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-item-positioning-with-orthogonal-flows.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-item-removal-track-breadth-update.html [ Failure Crash ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-item-spanning-and-orthogonal-flows.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-self-baseline-two-dimensional.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-template-shorthand-get-set.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-track-sizing-with-orthogonal-flows.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-track-sizing-with-percentages-and-orthogonal-flows.html [ Failure ]
@@ -5608,9 +5553,6 @@
 crbug.com/1178160 virtual/threaded-prefer-compositing/external/wpt/css/cssom-view/MediaQueryList-extends-EventTarget.html [ Pass Failure ]
 crbug.com/1178160 virtual/threaded-prefer-compositing/external/wpt/css/cssom-view/MediaQueryList-extends-EventTarget-interop.html [ Pass Failure ]
 
-# Failing on Webkit Linux Leak
-crbug.com/1174806 external/wpt/css/selectors/focus-visible-006.html [ Pass Failure ]
-
 # No support for key combinations like Alt + c in testdriver.Actions for content_shell
 crbug.com/893480 external/wpt/uievents/interface/keyboard-accesskey-click-event.html [ Timeout ]
 
@@ -5736,4 +5678,5 @@
 crbug.com/1187575 external/wpt/html/canvas/element/fill-and-stroke-styles/2d.strokeStyle.colorObject.transparency.html [ Failure ]
 
 # Sheriff 2021-03-15
-crbug.com/1188098 http/tests/devtools/sources/debugger-pause/skip-pauses-until-reload.js [ Pass Failure ]
+crbug.com/1188098 [ Linux ] http/tests/devtools/sources/debugger-pause/skip-pauses-until-reload.js [ Pass Failure ]
+crbug.com/1188098 [ Linux ] http/tests/devtools/elements/styles-1/color-aware-property-value-edit.js [ Pass Failure ]
\ No newline at end of file
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index afdc04d..a88b46a 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -771,6 +771,11 @@
     "args": [ "--enable-features=BackForwardCacheABExperimentControl" ]
   },
   {
+    "prefix": "bfcache",
+    "bases": [ "http/tests/inspector-protocol/bfcache" ],
+    "args": [ "--enable-features=BackForwardCache" ]
+  },
+  {
     "prefix": "interest-cohort-api-origin-trial",
     "bases": [ "http/tests/origin_trials/webexposed/interest-cohort-origin-trial-interfaces.html" ],
     "args": [ "--enable-features=InterestCohortAPIOriginTrial" ]
diff --git a/third_party/blink/web_tests/animations/custom-properties/registered-var-dynamic-dependency-expected.txt b/third_party/blink/web_tests/animations/custom-properties/registered-var-dynamic-dependency-expected.txt
deleted file mode 100644
index b2e86fb..0000000
--- a/third_party/blink/web_tests/animations/custom-properties/registered-var-dynamic-dependency-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Animated registered custom properties are invalid at computed-value time when there is a cyclic var() dependency between them. assert_equals: --a at 25% expected "initial-value" but got "150"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/cookies/attributes/resources/secure-non-secure-child.html b/third_party/blink/web_tests/external/wpt/cookies/attributes/resources/secure-non-secure-child.html
index 65996e0c..fb4392d 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/attributes/resources/secure-non-secure-child.html
+++ b/third_party/blink/web_tests/external/wpt/cookies/attributes/resources/secure-non-secure-child.html
@@ -54,7 +54,24 @@
         cookie: "test=8;       Secure     ;",
         expected: "",
         name: "(non-secure) Ignore cookie for space Secure with ;",
-      }
+      },
+      {
+        cookie: "__Secure-test=9; Secure",
+        expected: "",
+        name: "(non-secure) Ignore cookie with __Secure- prefix and Secure",
+      },
+      {
+        cookie: "__Secure-test=10",
+        expected: "",
+        name: "(non-secure) Ignore cookie with __Secure- prefix and without Secure",
+      },
+      // This is really a test that the cookie name isn't URL-decoded, but this
+      // is here to be next to the other __Secure- prefix tests.
+      {
+        cookie: "__%53ecure-test=11",
+        expected: "__%53ecure-test=11",
+        name: "(non-secure) Cookie returned with __%53ecure- prefix and without Secure",
+      },
     ];
 
     for (const test of secureNonSecureTests) {
diff --git a/third_party/blink/web_tests/external/wpt/cookies/name/name.html b/third_party/blink/web_tests/external/wpt/cookies/name/name.html
index 5e51eba..9102f110 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/name/name.html
+++ b/third_party/blink/web_tests/external/wpt/cookies/name/name.html
@@ -139,6 +139,11 @@
           expected: "",
           name: "Ignore cookie with no name or value",
         },
+        {
+          cookie: "%74%65%73%74=20",
+          expected: "%74%65%73%74=20",
+          name: "URL-encoded cookie name is not decoded",
+        },
       ];
 
       for (const test of nameTests) {
diff --git a/third_party/blink/web_tests/external/wpt/cookies/value/value.html b/third_party/blink/web_tests/external/wpt/cookies/value/value.html
index 729e363..d811e66 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/value/value.html
+++ b/third_party/blink/web_tests/external/wpt/cookies/value/value.html
@@ -131,6 +131,11 @@
           expected: "testA=22; test22=; testB=22",
           name: "Set valueless cookie, given `Set-Cookie: test22=`",
         },
+        {
+          cookie: "test=%32%33",
+          expected: "test=%32%33",
+          name: "URL-encoded cookie value is not decoded",
+        },
       ];
 
       for (const test of valueTests) {
@@ -138,4 +143,4 @@
       }
     </script>
   </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-025.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-025.html
new file mode 100644
index 0000000..c7369e2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-025.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>
+  Nested abpos fragmentation in a new column.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+    height: 100px;
+    width: 100px;
+    background: red;
+  }
+  .rel {
+    position: relative;
+  }
+  .abs {
+    position: absolute;
+    width: 50px;
+    background-color: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div class="rel">
+    <div class="abs" style="top: 200px;">
+      <div class="abs" style="top: -200px; height: 200px;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-026.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-026.html
new file mode 100644
index 0000000..e852c09
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-026.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>
+  Nested fixedpos in a multicol.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+    height: 100px;
+    width: 100px;
+    background: red;
+  }
+  .rel {
+    position: relative;
+  }
+  .abs {
+    position: absolute;
+  }
+  .fixed {
+    position: fixed;
+    height: 100px;
+    width: 100px;
+    background-color: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div class="rel">
+    <div class="abs">
+      <div class="fixed"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-027.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-027.html
new file mode 100644
index 0000000..ffac90b2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-027.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<title>
+  Nested abpos in a nested fragmentation context.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+  }
+  #outer {
+    height: 100px;
+    width: 100px;
+    background: red;
+  }
+  #inner {
+    width: 50px;
+  }
+  .rel {
+    position: relative;
+  }
+  .abs {
+    position: absolute;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol" id="outer">
+  <div class="rel">
+    <div class="abs">
+      <div class="multicol" id="inner">
+        <div class="rel" style="height: 400px;">
+          <div class="abs" style="height: 400px; width: 25px; background-color: green;"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-028.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-028.html
new file mode 100644
index 0000000..b5378e2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-028.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>
+  Nested abpos in a nested fragmentation context.
+</title>
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<style>
+  .multicol {
+    column-count: 2;
+    column-fill: auto;
+    column-gap: 0px;
+  }
+  #outer {
+    height: 100px;
+    width: 100px;
+    background: red;
+  }
+  .rel {
+    position: relative;
+  }
+  .abs {
+    position: absolute;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol" id="outer">
+  <div class="multicol" style="width: 50px;">
+    <div class="rel">
+      <div class="abs">
+        <div class="multicol" style="width: 25px;">
+          <div class="rel" style="height: 800px;">
+            <div class="abs" style="height: 800px; width: 12.5px; background-color: green;"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-env/env-in-custom-properties.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-env/env-in-custom-properties.tentative.html
index 24afe296..6dadcc5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-env/env-in-custom-properties.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-env/env-in-custom-properties.tentative.html
@@ -28,8 +28,8 @@
 
     test(() => {
       const style = window.getComputedStyle(child);
-      assert_equals(style.getPropertyValue("--var1"), "");
-    }, 'Substitution of unrecognized env() causes guaranteed-invalid');
+      assert_equals(style.getPropertyValue("--var1"), " inherited");
+    }, 'Substitution of unrecognized env() causes unset');
     </script>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-scroll-resize-visibility-hidden-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-scroll-resize-visibility-hidden-ref.html
new file mode 100644
index 0000000..571ba348
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-scroll-resize-visibility-hidden-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+.scroller {
+  width: 100px;
+  height: 100px;
+  overflow: scroll;
+  /* to make the scroller a white mask over the content */
+  filter: brightness(0) invert(1);
+  position: relative;
+  top: -200px;
+}
+.content {
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<div class="content"></div>
+<div class="content"></div>
+<div class="scroller"></div>
+<div class="scroller"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-scroll-resize-visibility-hidden.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-scroll-resize-visibility-hidden.html
new file mode 100644
index 0000000..e8d0bc91
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-scroll-resize-visibility-hidden.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>CSS Overflow: overflow: scroll with resize: both and visibility: hidden</title>
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3#propdef-overflow">
+<link rel="match" href="overflow-scroll-resize-visibility-hidden-ref.html">
+<style>
+.scroller {
+  overflow: scroll;
+  width: 100px;
+  height: 100px;
+  resize: both;
+  visibility: hidden;
+}
+.content {
+  width: 1000px;
+  height: 1000px;
+  background: green;
+  visibility: visible;
+}
+</style>
+<div class="scroller">
+  <div class="content"></div>
+</div>
+<div class="scroller" style="will-change: transform">
+  <div class="content"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/animate-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/animate-invalid.html
deleted file mode 100644
index 41cbd067..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/animate-invalid.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<title>Do not crash when animating to unresolved var()</title>
-<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty">
-<link rel="help" href="https://crbug.com/1185524">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="target"></div>
-<script>
-  promise_test(async function(){
-    CSS.registerProperty({
-      name: '--x',
-      syntax: '<number>',
-      initialValue: '1',
-      inherits: false
-    });
-    let animation = target.animate({'--x': [ 'var(--unknown)']}, 100);
-    await animation.ready;
-    assert_equals(getComputedStyle(target).getPropertyValue('--x'), '1');
-  }, 'Do not crash when animating to unresolved var()');
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/unit-cycles.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/unit-cycles.html
index f8eba17..5301b6fc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/unit-cycles.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/unit-cycles.html
@@ -4,7 +4,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-    function register_length(name, inherits=false) {
+    function register_length(name, inherits=true) {
         CSS.registerProperty({
             name: name,
             syntax: '<length>',
@@ -96,19 +96,19 @@
     test(function() {
         target.style = 'font-size: var(--font-size-em);';
         assert_property_equals('font-size', unsetFontSize);
-        assert_property_equals('--font-size-em', '0px');
+        assert_property_equals('--font-size-em', '');
     }, 'Lengths with em units may not be referenced from font-size');
 
     test(function() {
         target.style = 'font-size: var(--font-size-ex);';
         assert_property_equals('font-size', unsetFontSize);
-        assert_property_equals('--font-size-ex', '0px');
+        assert_property_equals('--font-size-ex', '');
     }, 'Lengths with ex units may not be referenced from font-size');
 
     test(function() {
         target.style = 'font-size: var(--font-size-ch);';
         assert_property_equals('font-size', unsetFontSize);
-        assert_property_equals('--font-size-ch', '0px');
+        assert_property_equals('--font-size-ch', '');
     }, 'Lengths with ch units may not be referenced from font-size');
 
     test(function() {
@@ -122,7 +122,7 @@
         let root = document.documentElement;
         root.style = 'font-size: var(--font-size-rem);';
         assert_property_equals('font-size', unsetFontSize, root);
-        assert_property_equals('--font-size-rem', '0px', root);
+        assert_property_equals('--font-size-rem', '', root);
     }, 'Lengths with rem units may not be referenced from font-size on root element');
 
     test(function() {
@@ -155,26 +155,26 @@
     test(function() {
         target.style = 'font-size: var(--font-size-em-via-var);';
         assert_property_equals('font-size', unsetFontSize);
-        assert_property_equals('--font-size-em-via-var', '0px');
+        assert_property_equals('--font-size-em-via-var', '');
     }, 'Lengths with em units are detected via var references');
 
     test(function() {
         target.style = 'font-size: var(--font-size-ex-via-var);';
         assert_property_equals('font-size', unsetFontSize);
-        assert_property_equals('--font-size-ex-via-var', '0px');
+        assert_property_equals('--font-size-ex-via-var', '');
     }, 'Lengths with ex units are detected via var references');
 
     test(function() {
         target.style = 'font-size: var(--font-size-ch-via-var);';
         assert_property_equals('font-size', unsetFontSize);
-        assert_property_equals('--font-size-ch-via-var', '0px');
+        assert_property_equals('--font-size-ch-via-var', '');
     }, 'Lengths with ch units are detected via var references');
 
     test(function() {
         let root = document.documentElement;
         root.style = 'font-size: var(--font-size-rem-via-var);';
         assert_property_equals('font-size', unsetFontSize, root);
-        assert_property_equals('--font-size-rem-via-var', '0px', root);
+        assert_property_equals('--font-size-rem-via-var', '', root);
         root.style = 'font-size: unset';
     }, 'Lengths with rem units are detected via var references');
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/var-reference-registered-properties-cycles.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/var-reference-registered-properties-cycles.html
index 91792ef5..e94da5d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/var-reference-registered-properties-cycles.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/var-reference-registered-properties-cycles.html
@@ -25,13 +25,13 @@
     CSS.registerProperty({name: '--registered-1-d', syntax: '<length>', initialValue: '4px', inherits: false});
 
     computedStyle = getComputedStyle(test1);
-    assert_equals(computedStyle.getPropertyValue('--registered-1-a'), '1px');
-    assert_equals(computedStyle.getPropertyValue('--registered-1-b'), '2px');
-    assert_equals(computedStyle.getPropertyValue('--registered-1-c'), '2px');
-    assert_equals(computedStyle.getPropertyValue('--registered-1-d'), '2px');
-    assert_equals(computedStyle.getPropertyValue('--unregistered-1-a'), '1px');
-    assert_equals(computedStyle.left, '1px');
-    assert_equals(computedStyle.top, '2px');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-a'), '');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-b'), '');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-c'), '30px');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-d'), '4px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-1-a'), '');
+    assert_equals(computedStyle.left, '50px');
+    assert_equals(computedStyle.top, '60px');
 }, "A var() cycle between two registered properties is handled correctly.");
 </script>
 
@@ -62,18 +62,18 @@
     CSS.registerProperty({name: '--registered-2-e', syntax: '<length>', initialValue: '5px', inherits: false});
 
     computedStyle = getComputedStyle(test2);
-    assert_equals(computedStyle.getPropertyValue('--registered-2-a'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--registered-2-a'), '');
     assert_equals(computedStyle.getPropertyValue('--unregistered-2-a'), '');
 
-    assert_equals(computedStyle.getPropertyValue('--registered-2-b'), '1px');
-    assert_equals(computedStyle.getPropertyValue('--registered-2-c'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--registered-2-b'), '30px');
+    assert_equals(computedStyle.getPropertyValue('--registered-2-c'), '3px');
     assert_equals(computedStyle.getPropertyValue('--registered-2-d'), '40px');
     assert_equals(computedStyle.getPropertyValue('--registered-2-e'), '5px');
-    assert_equals(computedStyle.getPropertyValue('--unregistered-2-b'), '1px');
-    assert_equals(computedStyle.getPropertyValue('--unregistered-2-c'), '1px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-2-b'), '50px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-2-c'), '');
     assert_equals(computedStyle.getPropertyValue('--unregistered-2-d'), '60px');
     assert_equals(computedStyle.getPropertyValue('--unregistered-2-e'), '');
-    assert_equals(computedStyle.left, '1px');
+    assert_equals(computedStyle.left, '70px');
     assert_equals(computedStyle.top, '80px');
 }, "A var() cycle between a registered properties and an unregistered property is handled correctly.");
 </script>
@@ -136,7 +136,7 @@
     assert_equals(computedStyle.getPropertyValue('--unregistered-4-a'), '');
 
     assert_equals(computedStyle.getPropertyValue('--registered-4-b'), 'meow');
-    assert_equals(computedStyle.getPropertyValue('--registered-4-c'), '');
+    assert_equals(computedStyle.getPropertyValue('--registered-4-c'), 'circle');
     assert_equals(computedStyle.getPropertyValue('--unregistered-4-b'), 'woof');
     assert_equals(computedStyle.getPropertyValue('--unregistered-4-c'), '');
     assert_equals(computedStyle.transitionProperty, 'water');
@@ -174,10 +174,9 @@
     let computedStyle = getComputedStyle(test5);
     assert_equals(computedStyle.getPropertyValue('--registered-5-a'), '');
     assert_equals(computedStyle.getPropertyValue('--registered-5-b'), '');
-    assert_equals(computedStyle.getPropertyValue('--registered-5-c'), '');
-    assert_equals(computedStyle.getPropertyValue('--registered-5-d'), '');
-    assert_equals(computedStyle.getPropertyValue('--registered-5-e'), '');
+    assert_equals(computedStyle.getPropertyValue('--registered-5-c'), 'foo');
+    assert_equals(computedStyle.getPropertyValue('--registered-5-d'), 'bar');
+    assert_equals(computedStyle.getPropertyValue('--registered-5-e'), 'baz');
     assert_equals(computedStyle.getPropertyValue('color'), 'rgb(0, 128, 0)');
-}, "Custom properties with universal syntax become guaranteed-invalid when " +
-        "invalid at computed-value time");
+}, "Invalid at computed-value time triggers 'unset' behavior");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-variables/variable-substitution-variable-declaration.html b/third_party/blink/web_tests/external/wpt/css/css-variables/variable-substitution-variable-declaration.html
index 0b0ab1f..678d05a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-variables/variable-substitution-variable-declaration.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-variables/variable-substitution-variable-declaration.html
@@ -140,7 +140,7 @@
 
             { element: "target10",      propertyName: "--varA",         expectedPropertyValue: "" },
             { element: "target10",      propertyName: "--varB",         expectedPropertyValue: "" },
-            { element: "target10",      propertyName: "--varC",         expectedPropertyValue: "" },
+            { element: "target10",      propertyName: "--varC",         expectedPropertyValue: " another good one" },
         ];
 
         testcases.forEach(function (testcase) {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-variables/variables-substitute-guaranteed-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-variables/variables-substitute-guaranteed-invalid.html
index 4abfe28..c30e874 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-variables/variables-substitute-guaranteed-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-variables/variables-substitute-guaranteed-invalid.html
@@ -30,15 +30,15 @@
         let cs = getComputedStyle(target1);
         assert_equals(cs.getPropertyValue('--var1'), '');
         assert_equals(cs.getPropertyValue('--var2'), '');
-    }, 'Custom properties in a cycle become guaranteed-invalid');
+    }, 'Custom properties in a cycle are guaranteed-invalid');
 
     test( function () {
         let cs = getComputedStyle(target1);
-        assert_equals(cs.getPropertyValue('--var3'), '');
-    }, 'A custom property referencing a cycle becomes guaranteed-invalid');
+        assert_equals(cs.getPropertyValue('--var3'), ' inherited');
+    }, 'A custom property referencing a cycle is treated as unset');
 
     test( function () {
         let cs = getComputedStyle(target1);
-        assert_equals(cs.getPropertyValue('--var4'), '');
-    }, 'A custom property referencing a non-existent variable becomes guaranteed-invalid');
+        assert_equals(cs.getPropertyValue('--var4'), ' inherited');
+    }, 'A custom property referencing a non-existent variable is treated as unset');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
index c203c06f..fa7bfc1 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/focus-visible-006.html
@@ -45,15 +45,17 @@
     <span id="el" contenteditable>Focus me</span>
   </div>
   <script>
-    setup({ explicit_done: true });
+    var actions_promise;
 
     async_test(function(t) {
-      el.addEventListener("focus", t.step_func_done(function() {
+      el.addEventListener("focus", t.step_func(function() {
         assert_equals(getComputedStyle(el).outlineColor, "rgb(0, 128, 0)", `outlineColor for ${el.tagName}#${el.id} should be green`);
         assert_not_equals(getComputedStyle(el).backgroundColor, "rgb(255, 0, 0)", `backgroundColor for ${el.tagName}#${el.id} should NOT be red`);
+        // Make sure the test finishes after all the input actions are completed.
+        actions_promise.then( () => t.done() );
       }));
 
-      test_driver.click(el).then(done());
+      actions_promise = test_driver.click(el);
     }, "Focus should always match :focus-visible on content editable divs");
   </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-blank-iframe.html b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-blank-iframe.html
new file mode 100644
index 0000000..fabde32
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-blank-iframe.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html>
+  <head>
+    <title>about:blank in child browsing context aliases security origin</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+      test(() => {
+        let iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        // Should not throw: srcdoc should always be same-origin.
+        iframe.contentWindow.document.body.innerHTML = '<p>Hello world!</p>';
+
+        // Explicitly set `domain` component of origin: any other same-origin
+        // browsing contexts are now cross-origin unless they also explicitly
+        // set document.domain to the same value.
+        document.domain = document.domain;
+        // Should not throw: the origin should be aliased, so setting
+        // document.domain in one Document should affect both Documents.
+        assert_equals(
+            iframe.contentWindow.document.body.textContent,
+            'Hello world!');
+      });
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-blank-window.html b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-blank-window.html
new file mode 100644
index 0000000..cc3177f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-blank-window.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+  <head>
+    <title>about:blank in auxiliary browsing context aliases security origin</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+      test(() => {
+        let newWindow = window.open();
+        // Should not throw: the newly-opened window should be same-origin.
+        newWindow.document.body.innerHTML = '<p>Hello world!</p>';
+
+        // Explicitly set `domain` component of origin: any other same-origin
+        // browsing contexts are now cross-origin unless they also explicitly
+        // set document.domain to the same value.
+        document.domain = document.domain;
+        // Should not throw: the origin should be aliased, so setting
+        // document.domain in one Document should affect both Documents.
+        assert_equals(newWindow.document.body.textContent, 'Hello world!');
+      });
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-srcdoc.html b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-srcdoc.html
new file mode 100644
index 0000000..971811ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/about-srcdoc.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+  <head>
+    <title>about:srcdoc aliases security origin</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+      test(() => {
+        let iframe = document.createElement('iframe');
+        iframe.srcdoc = '<body></body>';
+        document.body.appendChild(iframe);
+        // Should not throw: srcdoc should always be same-origin.
+        iframe.contentWindow.document.body.innerHTML = '<p>Hello world!</p>';
+
+        // Explicitly set `domain` component of origin: any other same-origin
+        // browsing contexts are now cross-origin unless they also explicitly
+        // set document.domain to the same value.
+        document.domain = document.domain;
+        // Should not throw: the origin should be aliased, so setting
+        // document.domain in one Document should affect both Documents.
+        assert_equals(
+            iframe.contentWindow.document.body.textContent,
+            'Hello world!');
+      });
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/javascript-url-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/javascript-url-expected.txt
new file mode 100644
index 0000000..d7f8cf9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/javascript-url-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL javascript: aliases security origin promise_test: Unhandled rejection with value: object "SecurityError: Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame."
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/javascript-url.html b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/javascript-url.html
new file mode 100644
index 0000000..7dfb113
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/origin/inheritance/javascript-url.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+  <head>
+    <title>javascript: aliases security origin</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+      promise_test(t => {
+        let iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        // Should not throw: srcdoc should always be same-origin.
+        iframe.contentDocument;
+
+        iframe.contentWindow.location = 'javascript:"Hello world!"';
+        return new Promise(resolve => {
+          iframe.addEventListener('load', resolve);
+        }).then(() => {
+          // Explicitly set `domain` component of origin: any other same-origin
+          // browsing contexts are now cross-origin unless they also explicitly
+          // set document.domain to the same value.
+          document.domain = document.domain;
+          // Should not throw: the origin should be aliased, so setting
+          // document.domain in one Document should affect both Documents.
+          assert_equals(
+              iframe.contentWindow.document.body.textContent,
+              'Hello world!');
+        });
+      });
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/storage-buckets.tentative.idl b/third_party/blink/web_tests/external/wpt/interfaces/storage-buckets.tentative.idl
index 153cd0d..73d72cea 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/storage-buckets.tentative.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/storage-buckets.tentative.idl
@@ -9,7 +9,6 @@
 };
 
 dictionary StorageBucketOptions {
-  DOMString? title = null;
   boolean persisted = false;
   StorageBucketDurability durability = "relaxed";
   unsigned long long? quota = null;
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counter-scroll-and-transform.html b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counter-scroll-and-transform.html
new file mode 100644
index 0000000..b4e4a99
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counter-scroll-and-transform.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Layout Instability: shift with counter scroll and transform not counted</title>
+<link rel="help" href="https://wicg.github.io/layout-instability/" />
+<style>
+.scroller {
+  overflow: scroll;
+  position: absolute;
+  left: 20px;
+  top: 20px;
+  width: 200px;
+  height: 200px;
+}
+.content {
+  width: 600px;
+  height: 600px;
+}
+.changer {
+  position: relative;
+  background: yellow;
+  left: 10px;
+  top: 100px;
+  width: 150px;
+  height: 150px;
+}
+
+</style>
+<div id="scroller1" class="scroller">
+  <div class="content">
+    <div id="changer1" class="changer"></div>
+  </div>
+</div>
+<div id="scroller2" class="scroller">
+  <div class="content">
+    <div id="changer2" class="changer"></div>
+  </div>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/util.js"></script>
+<script>
+
+promise_test(async () => {
+  const watcher = new ScoreWatcher;
+
+  // Wait for the initial render to complete.
+  await waitForAnimationFrames(2);
+
+  changer1.style.top = "250px";
+  changer1.style.transform = "translateY(-50px)";
+  // 250 - 50 = 200; old position is 100; hence scrollTop to counter is 100.
+  scroller1.scrollTop = 100;
+
+  changer2.style.left = "220px";
+  changer2.style.transform = "translateX(80px)";
+  // 220 + 80 = 300; old position is 10; hence scrollTop to counter is 290.
+  scroller2.scrollLeft = 290;
+
+  await waitForAnimationFrames(3);
+  assert_equals(watcher.score, 0);
+}, "Shift with counter scroll and transform not counted.");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll-2.html b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll-2.html
new file mode 100644
index 0000000..d9972301
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll-2.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<title>Layout Instability: shift with counterscroll not counted, with 2 scrollers</title>
+<link rel="help" href="https://wicg.github.io/layout-instability/" />
+<style>
+.scroller {
+  overflow: scroll;
+  position: absolute;
+  left: 20px;
+  top: 20px;
+  width: 200px;
+  height: 200px;
+}
+.content {
+  width: 170px;
+  height: 600px;
+}
+.changer {
+  position: relative;
+  background: yellow;
+  left: 10px;
+  top: 100px;
+  width: 150px;
+  height: 150px;
+}
+
+</style>
+<div id="scroller1" class="scroller">
+  <div class="content">
+    <div id="changer1" class="changer"></div>
+  </div>
+</div>
+<div id="scroller2" class="scroller">
+  <div class="content">
+    <div id="changer2" class="changer"></div>
+  </div>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/util.js"></script>
+<script>
+
+promise_test(async () => {
+  const watcher = new ScoreWatcher;
+
+  // Wait for the initial render to complete.
+  await waitForAnimationFrames(2);
+
+  // Top goes from 100 to 200. scroll by 100 to counter it.
+  changer1.style.top = "200px";
+  scroller1.scrollTop = 100;
+  // Top goes from 100 to 300. scroll by 200 to counter it.
+  changer2.style.top = "300px";
+  scroller2.scrollTop = 200;
+
+  await waitForAnimationFrames(3);
+  assert_equals(watcher.score, 0);
+}, "Shift with counterscroll not counted, with 2 scrollers.");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll.html b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll.html
index 8ad1a46..85a8ed9 100644
--- a/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll.html
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/shift-with-counterscroll.html
@@ -52,5 +52,3 @@
 }, "Shift with counterscroll not counted.");
 
 </script>
-
-
diff --git a/third_party/blink/web_tests/external/wpt/svg/painting/will-change-under-mask.html b/third_party/blink/web_tests/external/wpt/svg/painting/will-change-under-mask.html
new file mode 100644
index 0000000..feed381d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/painting/will-change-under-mask.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta name="assert" content="will-change descendants should not affect masking">
+<link rel="match" href="../struct/reftests/reference/green-100x100.html">
+<svg width="200" height="200">
+  <mask id="mask">
+    <rect x="0" y="0" width="100" height="50" fill="white"/>
+    <rect x="0" y="50" width="100" height="50" fill="black"/>
+  </mask>
+  <rect x="0" y="0" width="100" height="50" fill="red" />
+  <rect x="0" y="50" width="100" height="50" fill="green" />
+  <g mask="url('#mask')">
+    <rect style="will-change: transform;" x="0" y="0" width="100" height="50" fill="green" />
+    <rect style="will-change: transform;" x="0" y="50" width="100" height="50" fill="red" />
+  </g>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/touch-events/multi-touch-interfaces.html b/third_party/blink/web_tests/external/wpt/touch-events/multi-touch-interfaces.html
index fae8844..8b5965c 100644
--- a/third_party/blink/web_tests/external/wpt/touch-events/multi-touch-interfaces.html
+++ b/third_party/blink/web_tests/external/wpt/touch-events/multi-touch-interfaces.html
@@ -165,7 +165,7 @@
         assert_equals(touch.clientY, touch.pageY - window.pageYOffset, "touch.clientY is touch.pageY - window.pageYOffset.");
     }
 
-    function run() {
+    async function run() {
         var target0 = document.getElementById("target0");
         var target1 = document.getElementById("target1");
 
@@ -175,10 +175,9 @@
         var test_mousedown = async_test("Interaction with mouse events");
 
         var touchstart_received = 0;
-        var touchmove_received = false;
+        var touchmove_received = 0;
         var touchend_received = false;
         var invalid_touchmove_received = false;
-        var actions_promise;
 
         on_event(target0, "touchstart", function onTouchStart(ev) {
             ev.preventDefault();
@@ -186,7 +185,7 @@
             if(!touchstart_received) {
                 // Check event ordering TA: 1.6.2
                 test_touchstart.step(function() {
-                    assert_false(touchmove_received, "touchstart precedes touchmove");
+                    assert_true(touchmove_received==0, "touchstart precedes touchmove");
                     assert_false(touchend_received, "touchstart precedes touchend");
                 });
                 test_touchstart.done();
@@ -200,17 +199,16 @@
         on_event(target0, "touchmove", function onTouchMove(ev) {
             ev.preventDefault();
 
-            if (touchmove_received)
-              return;
-            touchmove_received = true;
+            if(!touchmove_received) {
+                test_touchmove.step(function() {
+                    assert_true(touchstart_received>0, "touchmove follows touchstart");
+                    assert_false(touchend_received, "touchmove precedes touchend");
+                });
+                test_touchmove.done();
+            }
 
-            test_touchmove.step(function() {
-                assert_true(touchstart_received>0, "touchmove follows touchstart");
-                assert_false(touchend_received, "touchmove precedes touchend");
-            });
-            test_touchmove.done();
-
-            check_TouchEvent(ev);
+            if(++touchmove_received <= 2)
+                check_TouchEvent(ev, touchmove_received);
         });
 
         on_event(target1, "touchmove", function onTouchMove(ev) {
@@ -222,12 +220,12 @@
 
             test_touchend.step(function() {
                 assert_true(touchstart_received>0, "touchend follows touchstart");
-                assert_true(touchmove_received, "touchend follows touchmove");
+                assert_true(touchmove_received>0, "touchend follows touchmove");
                 assert_false(invalid_touchmove_received, "touchmove dispatched to correct target");
             });
             test_touchend.done();
 
-            check_TouchEvent(ev);
+            check_TouchEvent(ev, touchstart_received);
             done();
         });
 
@@ -249,7 +247,7 @@
             }
         });
 
-        actions_promise = new test_driver.Actions()
+        await new test_driver.Actions()
           .addPointer("touchPointer1", "touch")
           .addPointer("touchPointer2", "touch")
           .pointerMove(0, 0, {origin: target0, sourceName: "touchPointer1"})
@@ -257,8 +255,12 @@
           .pointerDown({sourceName: "touchPointer1"})
           .pointerDown({sourceName: "touchPointer2"})
           .pointerMove(0, 10, {origin: target0, sourceName: "touchPointer1"})
+          .pause(10, "pointer", {sourceName: "touchPointer2"})
+          .pause(10, "pointer", {sourceName: "touchPointer1"})
           .pointerMove(3, 10, {origin: target0, sourceName: "touchPointer2"})
           .pointerMove(0, 0, {origin: target1, sourceName: "touchPointer1"})
+          .pause(10, "pointer", {sourceName: "touchPointer2"})
+          .pause(10, "pointer", {sourceName: "touchPointer1"})
           .pointerMove(3, 0, {origin: target1, sourceName: "touchPointer2"})
           .pointerUp({sourceName: "touchPointer1"})
           .pointerUp({sourceName: "touchPointer2"})
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/styles-variables-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/styles-variables-expected.txt
index e516da1..737472d 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/styles-variables-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-3/styles-variables-expected.txt
@@ -94,7 +94,7 @@
 body { (<style>)
 /-- overloaded --/     --a: red;
 
-value of --a: undefined
+value of --a:  red
 ==== Computed style for ID5 ====
 display: block;
     block - div user agent stylesheet
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/bfcache/bfcache-request-basic-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/bfcache/bfcache-request-basic-expected.txt
new file mode 100644
index 0000000..fe81559
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/bfcache/bfcache-request-basic-expected.txt
@@ -0,0 +1,4 @@
+Tests that no network requests are logged for a BFCache navigation
+request for https://devtools.oopif.test:8443/inspector-protocol/resources/iframe.html will be sent
+response code: 200
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/bfcache/bfcache-request-basic.js b/third_party/blink/web_tests/http/tests/inspector-protocol/bfcache/bfcache-request-basic.js
new file mode 100644
index 0000000..a04fa45
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/bfcache/bfcache-request-basic.js
@@ -0,0 +1,29 @@
+(async function(testRunner) {
+  var {page, session, dp} = await testRunner.startURL(
+    'http://localhost:8000/inspector-protocol/resources/test-page.html',
+    'Tests that no network requests are logged for a BFCache navigation');
+  await dp.Network.enable();
+  await dp.Page.enable();
+
+  // Regular navigation.
+  const requestWillBeSentPromise = dp.Network.onceRequestWillBeSent();
+  const responseReceivedPromise = dp.Network.onceResponseReceived();
+  await session.navigate('https://devtools.oopif.test:8443/inspector-protocol/resources/iframe.html');
+  const requestParams = (await requestWillBeSentPromise).params;
+  const responseParams = (await responseReceivedPromise).params;
+  testRunner.log(`request for ${requestParams.documentURL} will be sent`);
+  testRunner.log(`response code: ${responseParams.response.status}`);
+
+  // No network requests should be reported for back-forward cache navigations.
+  dp.Network.onceRequestWillBeSent().then(request => {
+    testRunner.fail(`request for ${request.params.documentURL} will be sent`);
+  });
+  dp.Network.onceResponseReceived().then(response => {
+    testRunner.fail(`response for ${request.params.documentURL} was received`);
+  });
+
+  // Navigate back - should use back-forward cache.
+  session.evaluate('window.history.back()');
+  await dp.Page.onceFrameNavigated();
+  testRunner.completeTest();
+});
diff --git a/third_party/blink/web_tests/http/tests/mediasession/resources/mediasessionservice-mock.js b/third_party/blink/web_tests/http/tests/mediasession/resources/mediasessionservice-mock.js
index d4aed5c..6bbd15e 100644
--- a/third_party/blink/web_tests/http/tests/mediasession/resources/mediasessionservice-mock.js
+++ b/third_party/blink/web_tests/http/tests/mediasession/resources/mediasessionservice-mock.js
@@ -76,6 +76,24 @@
     this.setPositionStateCallback_ = callback;
   }
 
+  setMicrophoneState(microphoneState) {
+    if (!!this.setMicrophoneStateCallback_)
+      this.setMicrophoneStateCallback_(microphoneState);
+  }
+
+  setMicrophoneStateCallback(callback) {
+    this.setMicrophoneStateCallback_ = callback;
+  }
+
+  setCameraState(cameraState) {
+    if (!!this.setCameraStateCallback_)
+      this.setCameraStateCallback_(cameraState);
+  }
+
+  setCameraStateCallback(callback) {
+    this.setCameraStateCallback_ = callback;
+  }
+
   enableAction(action) {
     if (!!this.enableDisableActionCallback_)
       this.enableDisableActionCallback_(action, true);
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
index e9072caa..86b7797 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png b/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
index c264392c..887d231 100644
--- a/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png b/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
index 7492e7cc..6338f58 100644
--- a/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/as-background-image/svg-as-background-6-expected.png b/third_party/blink/web_tests/platform/linux/svg/as-background-image/svg-as-background-6-expected.png
index 8674d03..93b9d5e 100644
--- a/third_party/blink/web_tests/platform/linux/svg/as-background-image/svg-as-background-6-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/as-background-image/svg-as-background-6-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/batik/masking/maskRegions-expected.png b/third_party/blink/web_tests/platform/linux/svg/batik/masking/maskRegions-expected.png
index f899446..e4e208f 100644
--- a/third_party/blink/web_tests/platform/linux/svg/batik/masking/maskRegions-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/batik/masking/maskRegions-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/custom/clip-mask-negative-scale-expected.png b/third_party/blink/web_tests/platform/linux/svg/custom/clip-mask-negative-scale-expected.png
index 90e38f3..4fa1bb6 100644
--- a/third_party/blink/web_tests/platform/linux/svg/custom/clip-mask-negative-scale-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/custom/clip-mask-negative-scale-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/transforms/text-with-mask-with-svg-transform-expected.png b/third_party/blink/web_tests/platform/linux/svg/transforms/text-with-mask-with-svg-transform-expected.png
index d8668e7..5386b09 100644
--- a/third_party/blink/web_tests/platform/linux/svg/transforms/text-with-mask-with-svg-transform-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/transforms/text-with-mask-with-svg-transform-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/zoom/page/zoom-mask-with-percentages-expected.png b/third_party/blink/web_tests/platform/linux/svg/zoom/page/zoom-mask-with-percentages-expected.png
index add1cb3c..7745fb9 100644
--- a/third_party/blink/web_tests/platform/linux/svg/zoom/page/zoom-mask-with-percentages-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/zoom/page/zoom-mask-with-percentages-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png b/third_party/blink/web_tests/platform/mac-mac-arm11.0/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
new file mode 100644
index 0000000..61ece1b
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png b/third_party/blink/web_tests/platform/mac-mac-arm11.0/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
new file mode 100644
index 0000000..92d7435f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/svg/as-background-image/svg-as-background-6-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/svg/as-background-image/svg-as-background-6-expected.png
index d12134d5..185b49e 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.12/svg/as-background-image/svg-as-background-6-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/svg/as-background-image/svg-as-background-6-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png b/third_party/blink/web_tests/platform/mac/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
index 61ece1b..65fdc95 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png b/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
index dfa3d19b..81519f6 100644
--- a/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png b/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
index 92d7435f..9f2fa4c 100644
--- a/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/as-background-image/svg-as-background-6-expected.png b/third_party/blink/web_tests/platform/mac/svg/as-background-image/svg-as-background-6-expected.png
index e68bca3..124199a 100644
--- a/third_party/blink/web_tests/platform/mac/svg/as-background-image/svg-as-background-6-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/as-background-image/svg-as-background-6-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/batik/masking/maskRegions-expected.png b/third_party/blink/web_tests/platform/mac/svg/batik/masking/maskRegions-expected.png
index b35cf54..5df8969 100644
--- a/third_party/blink/web_tests/platform/mac/svg/batik/masking/maskRegions-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/batik/masking/maskRegions-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/custom/clip-mask-negative-scale-expected.png b/third_party/blink/web_tests/platform/mac/svg/custom/clip-mask-negative-scale-expected.png
index b1528f8..d5f458a 100644
--- a/third_party/blink/web_tests/platform/mac/svg/custom/clip-mask-negative-scale-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/custom/clip-mask-negative-scale-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/transforms/text-with-mask-with-svg-transform-expected.png b/third_party/blink/web_tests/platform/mac/svg/transforms/text-with-mask-with-svg-transform-expected.png
index c821051..5032a0b 100644
--- a/third_party/blink/web_tests/platform/mac/svg/transforms/text-with-mask-with-svg-transform-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/transforms/text-with-mask-with-svg-transform-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/zoom/page/zoom-mask-with-percentages-expected.png b/third_party/blink/web_tests/platform/mac/svg/zoom/page/zoom-mask-with-percentages-expected.png
index ab81bf0..710ba50 100644
--- a/third_party/blink/web_tests/platform/mac/svg/zoom/page/zoom-mask-with-percentages-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/zoom/page/zoom-mask-with-percentages-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png b/third_party/blink/web_tests/platform/win/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
index e929f1f..f60b119 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/svg/absolute-sized-content-with-resources-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png b/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
index 70bd2556..845143888 100644
--- a/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/masking-intro-01-f-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png b/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
index 785c6c53..e42ce41b 100644
--- a/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/masking-mask-01-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/as-background-image/svg-as-background-6-expected.png b/third_party/blink/web_tests/platform/win/svg/as-background-image/svg-as-background-6-expected.png
index 4367768a..7aa5733 100644
--- a/third_party/blink/web_tests/platform/win/svg/as-background-image/svg-as-background-6-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/as-background-image/svg-as-background-6-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/batik/masking/maskRegions-expected.png b/third_party/blink/web_tests/platform/win/svg/batik/masking/maskRegions-expected.png
index ae2281c..4121adc4 100644
--- a/third_party/blink/web_tests/platform/win/svg/batik/masking/maskRegions-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/batik/masking/maskRegions-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/custom/clip-mask-negative-scale-expected.png b/third_party/blink/web_tests/platform/win/svg/custom/clip-mask-negative-scale-expected.png
index 0e8908e..a3a2e995 100644
--- a/third_party/blink/web_tests/platform/win/svg/custom/clip-mask-negative-scale-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/custom/clip-mask-negative-scale-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/transforms/text-with-mask-with-svg-transform-expected.png b/third_party/blink/web_tests/platform/win/svg/transforms/text-with-mask-with-svg-transform-expected.png
index 875ddfa..462a1a7 100644
--- a/third_party/blink/web_tests/platform/win/svg/transforms/text-with-mask-with-svg-transform-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/transforms/text-with-mask-with-svg-transform-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/zoom/page/zoom-mask-with-percentages-expected.png b/third_party/blink/web_tests/platform/win/svg/zoom/page/zoom-mask-with-percentages-expected.png
index f69e052e..8fa3214 100644
--- a/third_party/blink/web_tests/platform/win/svg/zoom/page/zoom-mask-with-percentages-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/zoom/page/zoom-mask-with-percentages-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/custom/grayscale-gradient-mask-2-expected.png b/third_party/blink/web_tests/svg/custom/grayscale-gradient-mask-2-expected.png
index a6e576a08..08cbed5 100644
--- a/third_party/blink/web_tests/svg/custom/grayscale-gradient-mask-2-expected.png
+++ b/third_party/blink/web_tests/svg/custom/grayscale-gradient-mask-2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/custom/grayscale-gradient-mask-expected.png b/third_party/blink/web_tests/svg/custom/grayscale-gradient-mask-expected.png
index f83f67d..217d01e 100644
--- a/third_party/blink/web_tests/svg/custom/grayscale-gradient-mask-expected.png
+++ b/third_party/blink/web_tests/svg/custom/grayscale-gradient-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/filters/filter-clip-expected.png b/third_party/blink/web_tests/svg/filters/filter-clip-expected.png
index abd49ab6..5a45a64 100644
--- a/third_party/blink/web_tests/svg/filters/filter-clip-expected.png
+++ b/third_party/blink/web_tests/svg/filters/filter-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/bfcache/README.md b/third_party/blink/web_tests/virtual/bfcache/README.md
new file mode 100644
index 0000000..4068c98
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/bfcache/README.md
@@ -0,0 +1 @@
+Tests run with back-forward cache enabled (`--enable-features=BackForwardCache`).
diff --git a/third_party/blink/web_tests/wpt_internal/geolocation-api/resources/geolocation-mock.js b/third_party/blink/web_tests/wpt_internal/geolocation-api/resources/geolocation-mock.js
index 7eb59f45..1e9a588 100644
--- a/third_party/blink/web_tests/wpt_internal/geolocation-api/resources/geolocation-mock.js
+++ b/third_party/blink/web_tests/wpt_internal/geolocation-api/resources/geolocation-mock.js
@@ -45,6 +45,8 @@
     this.permissionStatus_ = PermissionStatus.ASK;
     this.rejectGeolocationServiceConnections_ = false;
 
+    this.systemPermissionStatus_ = PermissionStatus.GRANTED;
+
     /**
      * Set by interceptQueryNextPosition() and used to resolve the promise
      * returned by that call once the next incoming queryPosition() is received.
@@ -104,6 +106,15 @@
       });
     }
 
+    if (this.systemPermissionStatus_ != PermissionStatus.GRANTED) {
+      this.geoposition_ = {
+        valid: false,
+        timestamp: {internalValue: 0n},
+        errorMessage: "User has not allowed access to system location.",
+        errorCode: Geoposition_ErrorCode.PERMISSION_DENIED,
+      };
+    }
+
     if (!this.geoposition_) {
       this.setGeolocationPositionUnavailableError(
           'Test error: position not set before call to queryNextPosition()');
@@ -206,4 +217,9 @@
     this.permissionStatus_ = allowed ? PermissionStatus.GRANTED
                                      : PermissionStatus.DENIED;
   }
+
+  setSystemGeolocationPermission(allowed) {
+    this.systemPermissionStatus_ = allowed ? PermissionStatus.GRANTED
+                                           : PermissionStatus.DENIED;
+  }
 }
diff --git a/third_party/blink/web_tests/wpt_internal/geolocation-api/system-permission-denied-error.https.html b/third_party/blink/web_tests/wpt_internal/geolocation-api/system-permission-denied-error.https.html
new file mode 100644
index 0000000..bff93b8
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/geolocation-api/system-permission-denied-error.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script type="module">
+import {GeolocationMock} from './resources/geolocation-mock.js';
+
+async_test(function(t) {
+    const mock = new GeolocationMock();
+    mock.setGeolocationPermission(true);
+    mock.setSystemGeolocationPermission(false);
+
+    let errorCallbackInvoked = false;
+    navigator.geolocation.watchPosition(t.unreached_func("Success callback invoked unexpectedly"), function(error) {
+        assert_false(errorCallbackInvoked, "Second error callback invoked unexpectedly", error.message);
+        errorCallbackInvoked = true;
+
+        assert_equals(error.code, error.PERMISSION_DENIED);
+        assert_equals(error.message, "User has not allowed access to system location.");
+
+        // Update the mock Geolocation service to report a new position, then
+        // yield to allow a chance for the success callback to be invoked.
+        mock.setGeolocationPosition(55.478, -0.166, 100);
+        window.setTimeout(t.step_func_done(() => {}), 0);
+    });
+}, "Tests that when System Geolocation permission is denied the error callback is invoked and watchers are destroyed.");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/storage/buckets/bucket_names.tentative.https.any.js b/third_party/blink/web_tests/wpt_internal/storage/buckets/bucket_names.tentative.https.any.js
new file mode 100644
index 0000000..81d0baa
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/storage/buckets/bucket_names.tentative.https.any.js
@@ -0,0 +1,93 @@
+// META: title=Buckets API: Basic tests for bucket names.
+// META: global=window,worker
+
+const kGoodBucketNameTests = [
+  ['abcdefghijklmnopqrstuvwxyz0123456789-_', 'with allowed characters'],
+  ['2021-01-01', 'with `-` in the middle'],
+  ['2021_01_01', 'with `_` in the middle'],
+  ['2021_01_01_', 'ending with `_`'],
+  ['2021-01-01-', 'ending with `-`'],
+];
+
+const kBadBucketNameTests = [
+  ['_bucket', 'start with `_`'],
+  ['-bucket', 'start with `-`'],
+  ['bucket name', 'have a space'],
+  ['bUcKet123', 'are not all lower case'],
+  ['bucket♦♥♠♣', 'are not in ASCII'],
+  ['2021/01/01', 'include an invalid special character'],
+  ['   ', 'have no characters'],
+  ['', 'are an empty string'],
+  ['mjnkhtwsiyjsrxvrzzqafldfvomqopdjfiuxqelfkllcugrhvvblkvmiqlguhhqepoggyu',
+   'exceed 64 chars']
+];
+
+// Test valid bucket names on open().
+kGoodBucketNameTests.forEach(test_data => {
+  const bucket_name = test_data[0];
+  const test_description = test_data[1];
+
+  promise_test(async testCase => {
+    await navigator.storageBuckets.open(bucket_name);
+    testCase.add_cleanup(async () => {
+      await navigator.storageBuckets.delete(bucket_name);
+    });
+
+    const buckets = await navigator.storageBuckets.keys();
+    assert_equals(buckets.length, 1);
+    assert_equals(buckets[0], bucket_name);
+  }, `open() allows bucket names ${test_description}`);
+});
+
+// Test invalid bucket names on open().
+kBadBucketNameTests.forEach(test_data => {
+  const bucket_name = test_data[0];
+  const test_description = test_data[1];
+
+  promise_test(async testCase => {
+    await promise_rejects_dom(
+        testCase, 'InvalidCharacterError',
+        navigator.storageBuckets.open(bucket_name));
+  }, `open() throws an error if bucket names ${test_description}`);
+});
+
+// Test valid bucket names on delete().
+kGoodBucketNameTests.forEach(test_data => {
+  const bucket_name = test_data[0];
+  const test_description = test_data[1];
+
+  promise_test(async testCase => {
+    await navigator.storageBuckets.open(bucket_name);
+    let buckets = await navigator.storageBuckets.keys();
+    assert_equals(buckets.length, 1);
+
+    await navigator.storageBuckets.delete(bucket_name);
+
+    buckets = await navigator.storageBuckets.keys();
+    assert_equals(buckets.length, 0);
+  }, `delete() allows bucket names ${test_description}`);
+});
+
+// Test invalid bucket names on delete().
+kBadBucketNameTests.forEach(test_data => {
+  const bucket_name = test_data[0];
+  const test_description = test_data[1];
+
+  promise_test(async testCase => {
+    await promise_rejects_dom(
+        testCase, 'InvalidCharacterError',
+        navigator.storageBuckets.delete(bucket_name));
+  }, `delete() throws an error if bucket names ${test_description}`);
+});
+
+promise_test(async testCase => {
+  await navigator.storageBuckets.open('bucket_name');
+  await navigator.storageBuckets.open('bucket_name');
+  testCase.add_cleanup(async () => {
+    await navigator.storageBuckets.delete('bucket_name');
+  });
+
+  const buckets = await navigator.storageBuckets.keys();
+  assert_equals(buckets.length, 1);
+  assert_equals(buckets[0], 'bucket_name');
+}, 'open() does not store duplicate bucket names');
diff --git a/third_party/blink/web_tests/wpt_internal/storage/buckets/buckets_basic.tentative.https.any.js b/third_party/blink/web_tests/wpt_internal/storage/buckets/buckets_basic.tentative.https.any.js
index df24baa..a3b15ed 100644
--- a/third_party/blink/web_tests/wpt_internal/storage/buckets/buckets_basic.tentative.https.any.js
+++ b/third_party/blink/web_tests/wpt_internal/storage/buckets/buckets_basic.tentative.https.any.js
@@ -7,29 +7,6 @@
 // Split and add extensive testing once implementation for the endpoints are
 // added and method definitions are more defined.
 promise_test(async testCase => {
-  await navigator.storageBuckets.open('bucket_name');
-  testCase.add_cleanup(async () => {
-    await navigator.storageBuckets.delete('bucket_name');
-  });
-
-  const buckets = await navigator.storageBuckets.keys();
-  assert_equals(buckets.length, 1);
-  assert_equals(buckets[0], 'bucket_name');
-}, 'open() stores bucket name');
-
-promise_test(async testCase => {
-  await navigator.storageBuckets.open('bucket_name');
-  await navigator.storageBuckets.open('bucket_name');
-  testCase.add_cleanup(async () => {
-    await navigator.storageBuckets.delete('bucket_name');
-  });
-
-  const buckets = await navigator.storageBuckets.keys();
-  assert_equals(buckets.length, 1);
-  assert_equals(buckets[0], 'bucket_name');
-}, 'open() does not store duplicate bucket name');
-
-promise_test(async testCase => {
   await navigator.storageBuckets.open('bucket_name3');
   await navigator.storageBuckets.open('bucket_name1');
   await navigator.storageBuckets.open('bucket_name2');
diff --git a/third_party/lottie/BUILD.gn b/third_party/lottie/BUILD.gn
index c9747a6..6e2bb195 100644
--- a/third_party/lottie/BUILD.gn
+++ b/third_party/lottie/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//chrome/browser/resources/tools/optimize_webui.gni")
 import("//third_party/closure_compiler/compile_js.gni")
+import("//ui/webui/resources/tools/generate_grd.gni")
 
 assert(!is_android && !is_ios)
 
@@ -19,3 +20,12 @@
     rebase_path(target_gen_dir, root_build_dir),
   ]
 }
+
+generate_grd("build_grdp") {
+  grd_prefix = "lottie"
+  out_grd = "$target_gen_dir/resources.grdp"
+  deps = [ ":minify" ]
+  input_files_base_dir = rebase_path(target_gen_dir, root_build_dir)
+  input_files = [ "lottie_worker.min.js" ]
+  resource_path_prefix = "lottie"
+}
diff --git a/tools/l10n/.style.yapf b/tools/l10n/.style.yapf
new file mode 100644
index 0000000..557fa7b
--- /dev/null
+++ b/tools/l10n/.style.yapf
@@ -0,0 +1,2 @@
+[style]
+based_on_style = pep8
diff --git a/tools/l10n/OWNERS b/tools/l10n/OWNERS
new file mode 100644
index 0000000..a474a02
--- /dev/null
+++ b/tools/l10n/OWNERS
@@ -0,0 +1,3 @@
+claudiomagni@chromium.org
+dvallet@chromium.org
+mlcui@google.com
diff --git a/tools/l10n/generate_locales_list.py b/tools/l10n/generate_locales_list.py
new file mode 100755
index 0000000..18229e2
--- /dev/null
+++ b/tools/l10n/generate_locales_list.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# 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.
+"""Generates the list of locales with strings for the current platform.
+
+This program generates a file which solely contains a list of locales that will
+be built for this platform in this build of Chrome (equivalently the locales
+that we build strings for) passed in as arguments to this script. This allows us
+to determine what locales we have strings for at runtime without checking the
+locale data paks on the filesystem with a blocking I/O call.
+
+This program can be run with no arguments to run its own unit tests. The first
+argument is the output filename, and all later arguments are interpreted as
+locales.
+
+This script is run by the //ui/base:locales_list_gen build rule, passing in the
+gn variable `locales` defined in build/config/locales.gni as arguments.
+The generated file is then included in ui/base/l10n/l10n_util.h.
+"""
+
+from __future__ import print_function
+
+import sys
+
+
+def gen_locale(locale):  # type: (str) -> str
+    """Returns the generated code for a given locale in the list."""
+    # We assume that all locale codes have only letters, numbers and hyphens.
+    assert locale.replace('-', '').isalnum(), locale
+    # clang-format enforces a four-space indent for initializer lists.
+    return '    PLATFORM_LOCALE({locale})'.format(locale=locale)
+
+
+def gen_locales(locales):  # type: (list) -> str
+    """Returns the generated code for the locale list.
+
+    The list is guaranteed to be in sorted order without duplicates.
+
+    >>> locales = ['en-GB', 'en', 'de', 'en']
+    >>> generated = gen_locales(locales)
+    >>> locales.pop()  # remove the duplicate
+    'en'
+    >>> locales.sort()
+    >>> index_in_generated = lambda locale: generated.index(gen_locale(locale))
+    >>> all(
+    ...     index_in_generated(prev_locale) < index_in_generated(next_locale)
+    ...     for prev_locale, next_locale in zip(locales, locales[1:]))
+    True
+    """
+    return '\n'.join(gen_locale(locale) for locale in sorted(set(locales)))
+
+
+def main():  # type: () -> None
+    import doctest
+    doctest.testmod()
+
+    if len(sys.argv) < 2:
+        print('{}: only ran tests'.format(sys.argv[0]))
+        return
+
+    output = gen_locales(sys.argv[2:])
+    with open(sys.argv[1], 'w') as f:
+        f.write(output)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 9d53e0b..72ce3d98 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -123,7 +123,7 @@
       'android-nougat-arm64-rel': 'android_release_bot_minimal_symbols_arm64_fastbuild_webview_google',
       # TODO(crbug/1182468) Remove android coverage bots after coverage is
       # running on CQ.
-      'android-pie-arm64-coverage-experimental-rel': 'android_release_bot_arm64_webview_google_expectations_native_coverage_v8_arm64',
+      'android-pie-arm64-coverage-experimental-rel': 'android_release_bot_arm64_webview_google_expectations_native_coverage',
       'android-pie-arm64-rel': 'android_release_bot_minimal_symbols_arm64_webview_google',
       'android-pie-x86-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
       'android-weblayer-x86-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
@@ -136,6 +136,7 @@
       'Android WebView P FYI (rel)': 'android_release_bot_minimal_symbols_arm64_webview_google',
       'android-11-x86-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
       'android-pie-arm64-wpt-rel-non-cq': 'android_release_bot_minimal_symbols_arm64_webview_google',
+      'android-web-platform-pie-x86-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
       'android-weblayer-pie-x86-wpt-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
       'android-weblayer-with-aosp-webview-x86-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_resource_allowlisting_static_angle',
     },
@@ -791,7 +792,7 @@
       # TODO(crbug/1182468) Remove android coverage bots after coverage is
       # running on CQ.
       'android-pie-arm64-coverage-rel': 'android_release_trybot_arm64_webview_google_native_coverage',
-      'android-pie-arm64-coverage-experimental-rel': 'android_release_trybot_arm64_webview_google_expectations_native_coverage_v8_arm64',
+      'android-pie-arm64-coverage-experimental-rel': 'android_release_trybot_arm64_webview_google_expectations_native_coverage',
       'android-pie-arm64-rel': 'android_release_trybot_arm64_webview_google_expectations',
       'android-pie-arm64-wpt-rel-non-cq': 'android_release_trybot_arm64_webview_google',
       'android-pie-x86-rel': 'android_release_trybot_x86_fastbuild_webview_google',
@@ -1431,20 +1432,18 @@
 
     # TODO(crbug/1182468) Remove these android coverage expectations after
     # existing CQ bot is running them.
-    'android_release_bot_arm64_webview_google_expectations_native_coverage_v8_arm64': [
+    'android_release_bot_arm64_webview_google_expectations_native_coverage': [
       'android', 'release_bot', 'arm64', 'strip_debug_info',
       'webview_google', 'fail_on_android_expectations',
       'use_clang_coverage', 'partial_code_coverage_instrumentation',
-      'v8_arm64',
     ],
 
     # TODO(crbug/1182468) Remove these android coverage expectations after
     # existing CQ bot is running them.
-    'android_release_trybot_arm64_webview_google_expectations_native_coverage_v8_arm64': [
+    'android_release_trybot_arm64_webview_google_expectations_native_coverage': [
       'android', 'release_trybot', 'arm64', 'strip_debug_info',
       'webview_google', 'fail_on_android_expectations',
       'use_clang_coverage', 'partial_code_coverage_instrumentation',
-      'v8_arm64',
     ],
 
     'android_release_trybot_arm64_webview_google_native_coverage': [
@@ -3508,10 +3507,6 @@
       'gn_args': 'use_javascript_coverage=true',
     },
 
-    'v8_arm64': {
-      'gn_args': 'v8_target_cpu="arm64"',
-    },
-
     'v8_simulate_arm': {
       'gn_args': 'target_cpu="x86" v8_target_cpu="arm"',
     },
diff --git a/tools/mb/mb_config_expectations/chromium.android.fyi.json b/tools/mb/mb_config_expectations/chromium.android.fyi.json
index 9460fb6..d78af95 100644
--- a/tools/mb/mb_config_expectations/chromium.android.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.android.fyi.json
@@ -70,6 +70,22 @@
       "use_goma": true
     }
   },
+  "android-web-platform-pie-x86-fyi-rel": {
+    "gn_args": {
+      "disable_android_lint": true,
+      "ffmpeg_branding": "Chrome",
+      "is_component_build": false,
+      "is_debug": false,
+      "proprietary_codecs": true,
+      "strip_debug_info": true,
+      "symbol_level": 1,
+      "system_webview_package_name": "com.google.android.webview",
+      "target_cpu": "x86",
+      "target_os": "android",
+      "use_errorprone_java_compiler": false,
+      "use_goma": true
+    }
+  },
   "android-weblayer-pie-x86-wpt-fyi-rel": {
     "gn_args": {
       "disable_android_lint": true,
diff --git a/tools/mb/mb_config_expectations/chromium.android.json b/tools/mb/mb_config_expectations/chromium.android.json
index e4b7918..3dad586 100644
--- a/tools/mb/mb_config_expectations/chromium.android.json
+++ b/tools/mb/mb_config_expectations/chromium.android.json
@@ -483,8 +483,7 @@
       "target_cpu": "arm64",
       "target_os": "android",
       "use_clang_coverage": true,
-      "use_goma": true,
-      "v8_target_cpu": "arm64"
+      "use_goma": true
     }
   },
   "android-pie-arm64-rel": {
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.android.json b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
index a1eb608..d5e0c9f 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.android.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
@@ -309,8 +309,7 @@
       "target_cpu": "arm64",
       "target_os": "android",
       "use_clang_coverage": true,
-      "use_goma": true,
-      "v8_target_cpu": "arm64"
+      "use_goma": true
     }
   },
   "android-pie-arm64-coverage-rel": {
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 7899734..9ccb51c 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -2960,6 +2960,15 @@
   </description>
 </action>
 
+<action name="Autofill_OfferNotificationInfoBar_DeepLinkClicked">
+  <owner>siashah@chromium.org</owner>
+  <owner>siyua@chromium.org</owner>
+  <description>
+    This user action is logged when user clicks on the &quot;See details&quot;
+    link shown in the Offer Notification InfoBar on mobile.
+  </description>
+</action>
+
 <action name="Autofill_OnWillSubmitForm">
   <owner>battre@chromium.org</owner>
   <description>
diff --git a/tools/metrics/histograms/README.md b/tools/metrics/histograms/README.md
index 81c6d88..3a8e103 100644
--- a/tools/metrics/histograms/README.md
+++ b/tools/metrics/histograms/README.md
@@ -374,12 +374,8 @@
 or basically when it is replaced on the "stable" channel by the following
 release.
 
-After a histogram expires, it ceases to be displayed on the dashboard. However,
-the client may continue to send data for that histogram for some time after the
-official expiry date so simply bumping the 'expires_after' date at HEAD may be
-sufficient to resurrect it without any discontinuity. If too much time has
-passed and the client is no longer sending data, it can be re-enabled via Finch:
-see [Expired histogram allowlist](#Expired-histogram-allowlist).
+After a histogram expires, it ceases to be displayed on the dashboard.
+Follow [these directions](#extending) to extend it.
 
 Once a histogram has expired, the code that records it becomes dead code and
 should be removed from the codebase along with marking the histogram definition
@@ -422,6 +418,31 @@
 the date of the run. Googlers can view the [design
 doc](https://docs.google.com/document/d/1IEAeBF9UnYQMDfyh2gdvE7WlUKsfIXIZUw7qNoU89A4).
 
+#### How to extend an expired histogram {#extending}
+
+You can revive an expired histogram by setting the expiration date to a
+date in the future.
+
+There's some leeway here. A client may continue to send data for that
+histogram for some time after the official expiry date so simply bumping
+the 'expires_after' date at HEAD may be sufficient to resurrect it without
+any data discontinuity.
+
+If a histogram expired more than a month ago (for histograms with an
+expiration date) or more than one milestone ago (for histograms with
+expiration milestones; this means top-of-tree is two or more milestones away
+from expired milestone), then you may be outside the safety window. In this
+case, when extending the histogram add to the histogram description a
+message: "Warning: this histogram was expired from DATE to DATE; data may be
+missing." (For milestones, write something similar.)
+
+When reviving a histogram outside the safety window, realize the change to
+histograms.xml to revive it rolls out with the binary release. It takes
+some time to get to the stable channel.
+
+It you need to revive it faster, the histogram can be re-enabled via adding to
+the [expired histogram allowlist](#Expired-histogram-allowlist).
+
 ### Expired histogram notifier
 
 The expired histogram notifier notifies histogram owners before their histograms
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 10d1b7d3..b6a4a40 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -2053,6 +2053,16 @@
   <int value="3" label="LowMemory"/>
 </enum>
 
+<enum name="AndroidMultiWindowActivityType">
+  <int value="0" label="Enter"/>
+  <int value="1" label="Exit"/>
+</enum>
+
+<enum name="AndroidMultiWindowState">
+  <int value="0" label="SingleWindow"/>
+  <int value="1" label="MultiWindow"/>
+</enum>
+
 <enum name="AndroidProcessedCrashCounts">
   <int value="0" label="Gpu foreground OOM">
     GPU is killed by Android due to OOM while app is foreground.
@@ -5922,6 +5932,12 @@
   <int value="3" label="Bubble lost focus and was deactivated"/>
 </enum>
 
+<enum name="AutofillOfferNotificationInfoBarResult">
+  <int value="0" label="User closed infobar via OK button"/>
+  <int value="1" label="User closed infobar via corner X"/>
+  <int value="2" label="User did not interact with the infobar"/>
+</enum>
+
 <enum name="AutofillPredictionSource">
   <int value="0" label="PREDICTION_SOURCE_UNKNOWN"/>
   <int value="1" label="PREDICTION_SOURCE_HEURISTIC"/>
@@ -14901,6 +14917,8 @@
   <int value="11" label="OldIncompleteMeta"/>
   <int value="12" label="FinishedUploading"/>
   <int value="13" label="AlreadyUploaded"/>
+  <int value="14" label="TooManyRequests"/>
+  <int value="15" label="RetryUploading"/>
 </enum>
 
 <enum name="CrosDiagnosticsRoutineResult">
@@ -17566,6 +17584,13 @@
   <int value="1" label="Portrait"/>
 </enum>
 
+<!-- This is not accessible from where the above enum is defined in code. -->
+
+<enum name="DeviceOrientation2">
+  <int value="0" label="Landscape"/>
+  <int value="1" label="Portrait"/>
+</enum>
+
 <enum name="DeviceOrientationSensorTypeAndroid">
   <int value="0" label="Not Available"/>
   <int value="1" label="ROTATION_VECTOR"/>
@@ -23878,6 +23903,8 @@
   <int value="834" label="SamlLockScreenOfflineSigninTimeLimitDays"/>
   <int value="835" label="ReportDevicePrintJobs"/>
   <int value="836" label="AudioProcessHighPriorityEnabled"/>
+  <int value="837" label="SerialAllowAllPortsForUrls"/>
+  <int value="838" label="SerialAllowUsbDevicesForUrls"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -31322,7 +31349,7 @@
   <int value="3374" label="V8RTCRtpTransceiver_Stopped_AttributeGetter"/>
   <int value="3375" label="V8RTCRtpTransceiver_Stop_Method"/>
   <int value="3376" label="SecurePaymentConfirmation"/>
-  <int value="3377" label="OBSOLETE_CSSInvalidVariableUnset"/>
+  <int value="3377" label="CSSInvalidVariableUnset"/>
   <int value="3378" label="ElementInternalsShadowRoot"/>
   <int value="3379" label="AnyPiiFieldDetected_PredictedTypeMatch"/>
   <int value="3380" label="EmailFieldDetected_PredictedTypeMatch"/>
@@ -47205,6 +47232,7 @@
   <int value="1238190462" label="PerDeskShelf:disabled"/>
   <int value="1239720601" label="OmniboxZeroSuggestionsOnNTPRealbox:enabled"/>
   <int value="1240073971" label="ash-disable-smooth-screen-rotation"/>
+  <int value="1241162639" label="debug-chime-notification"/>
   <int value="1242100010" label="AutofillProfileClientValidation:disabled"/>
   <int value="1242632259" label="ContentSuggestionsCategoryOrder:disabled"/>
   <int value="1243180545" label="HarfBuzzPDFSubsetter:enabled"/>
@@ -50579,6 +50607,10 @@
   <int value="12" label="Scrub to"/>
   <int value="13" label="Enter picture-in-picture"/>
   <int value="14" label="Exit picture-in-picture"/>
+  <int value="15" label="Switch audio device"/>
+  <int value="16" label="Toggle microphone"/>
+  <int value="17" label="Toggle camera"/>
+  <int value="18" label="Hang up"/>
 </enum>
 
 <enum name="MediaSinkType">
@@ -78047,6 +78079,11 @@
   <int value="15" label="kExtensionMessagingNeitherPrivileged"/>
 </enum>
 
+<enum name="UserAgentRequestType">
+  <int value="0" label="RequestDesktop"/>
+  <int value="1" label="RequestMobile"/>
+</enum>
+
 <enum name="UserCertContentDisposition">
   <int value="0" label="No Content-Disposition"/>
   <int value="1" label="Content-Disposition"/>
diff --git a/tools/metrics/histograms/histograms_xml/autofill/histograms.xml b/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
index e4a2f26..ce5414c 100644
--- a/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
@@ -21,9 +21,10 @@
 
 <histograms>
 
-<variants name="Autofill.OfferNotificationBubble.Type">
+<variants name="Autofill.OfferNotification.Type">
   <variant name="CardLinkedOffer"
-      summary="The bubble for an offer that is linked to a credit card."/>
+      summary="The bubble/infobar for an offer that is linked to a credit
+               card."/>
 </variants>
 
 <variants name="Autofill.PaymentBubble.Show">
@@ -1328,7 +1329,7 @@
     the user has been shown or is reshown due to user actions. The metric is
     recorded when the bubble is shown.
   </summary>
-  <token key="BubbleType" variants="Autofill.OfferNotificationBubble.Type"/>
+  <token key="BubbleType" variants="Autofill.OfferNotification.Type"/>
 </histogram>
 
 <histogram
@@ -1340,10 +1341,34 @@
     Records the reason for closing the offer notification bubble. Recorded when
     the bubble is closed.
   </summary>
-  <token key="BubbleType" variants="Autofill.OfferNotificationBubble.Type"/>
+  <token key="BubbleType" variants="Autofill.OfferNotification.Type"/>
   <token key="ShowType" variants="Autofill.PaymentBubble.Show"/>
 </histogram>
 
+<histogram name="Autofill.OfferNotificationInfoBarOffer.{OfferType}"
+    enum="BooleanShown" expires_after="2021-09-01">
+  <owner>siashah@chromium.org</owner>
+  <owner>siyua@chromium.org</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Emits true when the offer notification infobar is displayed and does not
+    emit any value otherwise.
+  </summary>
+  <token key="OfferType" variants="Autofill.OfferNotification.Type"/>
+</histogram>
+
+<histogram name="Autofill.OfferNotificationInfoBarResult.{OfferType}"
+    enum="AutofillOfferNotificationInfoBarResult" expires_after="2021-09-01">
+  <owner>siashah@chromium.org</owner>
+  <owner>siyua@chromium.org</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Records the reason for closing the offer notification infobar for
+    {OfferType}. Recorded when the infobar is closed.
+  </summary>
+  <token key="OfferType" variants="Autofill.OfferNotification.Type"/>
+</histogram>
+
 <histogram name="Autofill.PageTranslationStatus" enum="Boolean"
     expires_after="2021-10-25">
   <owner>marsin@google.com</owner>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 1ab37c7..62de6dd 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -4241,6 +4241,22 @@
 
 <histogram name="Discarding.OnCriticalPressure.TotalRSS_Mb" units="MB"
     expires_after="2021-01-31">
+  <obsolete>
+    Removed 03/2021, replaced by Discarding.OnCriticalPressure.TotalRSS_Mb2. The
+    new histogram is now guaranteed to be recorded before discarding any tab.
+  </obsolete>
+  <owner>sebmarchand@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    An estimate of the total resident set of Chrome when receiving a critical
+    memory pressure event, in megabytes. Note that the estimate is based on the
+    most recent data collected by Performance Manager, which are refreshed at a
+    low frequency (up to 2 minutes).
+  </summary>
+</histogram>
+
+<histogram name="Discarding.OnCriticalPressure.TotalRSS_Mb2" units="MB"
+    expires_after="2021-10-30">
   <owner>sebmarchand@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -4253,6 +4269,23 @@
 
 <histogram name="Discarding.OnCriticalPressure.TotalRSS_PercentOfRAM" units="%"
     expires_after="2021-08-09">
+  <obsolete>
+    Removed 03/2021, replaced by
+    Discarding.OnCriticalPressure.TotalRSS_PercentOfRAM2. The new histogram is
+    now guaranteed to be recorded before discarding any tab.
+  </obsolete>
+  <owner>sebmarchand@chromium.org</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    An estimate of the total resident set of Chrome when receiving a critical
+    memory pressure event, in percentage of the total amount of RAM. Note that
+    the estimate is based on the most recent data collected by Performance
+    Manager, which are refreshed at a low frequency (up to 2 minutes).
+  </summary>
+</histogram>
+
+<histogram name="Discarding.OnCriticalPressure.TotalRSS_PercentOfRAM2"
+    units="%" expires_after="2021-10-30">
   <owner>sebmarchand@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/quota/histograms.xml b/tools/metrics/histograms/histograms_xml/quota/histograms.xml
index c248e283..44755f2e 100644
--- a/tools/metrics/histograms/histograms_xml/quota/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/quota/histograms.xml
@@ -32,7 +32,7 @@
 </histogram>
 
 <histogram name="Quota.AgeOfOriginInDays" units="days"
-    expires_after="2021-03-09">
+    expires_after="2022-03-15">
   <owner>jarrydg@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/sync/histograms.xml b/tools/metrics/histograms/histograms_xml/sync/histograms.xml
index cab58cc..0b96c199 100644
--- a/tools/metrics/histograms/histograms_xml/sync/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/sync/histograms.xml
@@ -396,7 +396,10 @@
 
 <histogram name="Sync.DuplicateBookmarkEntityOnRemoteUpdateCondition"
     enum="DuplicateBookmarkEntityOnRemoteUpdateCondition"
-    expires_after="2021-07-13">
+    expires_after="2021-05-31">
+  <obsolete>
+    Removed as of 3/2021.
+  </obsolete>
   <owner>rushans@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 81ff2553..6ae331a 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -581,6 +581,87 @@
   </metric>
 </event>
 
+<event name="Android.MultiWindowChangeActivity">
+  <owner>gangwu@chromium.org</owner>
+  <owner>fgorski@chromium.org</owner>
+  <summary>
+    Records when the activity enter or exit Android N+ multi-window mode.
+  </summary>
+  <metric name="ActivityType" enum="AndroidMultiWindowActivityType">
+    <summary>
+      Enter or exit the multi-window mode.
+    </summary>
+    <aggregation>
+      <history>
+        <statistics>
+          <enumeration/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
+</event>
+
+<event name="Android.MultiWindowState">
+  <owner>gangwu@chromium.org</owner>
+  <owner>fgorski@chromium.org</owner>
+  <summary>
+    Records the multi window state when the activity is shown.
+  </summary>
+  <metric name="WindowState" enum="AndroidMultiWindowState">
+    <summary>
+      The state of the multi-window mode, single or multi-window.
+    </summary>
+    <aggregation>
+      <history>
+        <statistics>
+          <enumeration/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
+</event>
+
+<event name="Android.ScreenRotation">
+  <owner>gangwu@chromium.org</owner>
+  <owner>fgorski@chromium.org</owner>
+  <summary>
+    Records when a user rotates the device triggering a screen orientation
+    change.
+  </summary>
+  <metric name="TargetDeviceOrientation" enum="DeviceOrientation2">
+    <summary>
+      The orientation of the device rotated to.
+    </summary>
+    <aggregation>
+      <history>
+        <statistics>
+          <enumeration/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
+</event>
+
+<event name="Android.UserRequestedUserAgentChange">
+  <owner>gangwu@chromium.org</owner>
+  <owner>fgorski@chromium.org</owner>
+  <summary>
+    Records when a user pressed 'Request Desktop Site' in the app menu.
+  </summary>
+  <metric name="UserAgentType" enum="UserAgentRequestType">
+    <summary>
+      An enum that records the type of user agent.
+    </summary>
+    <aggregation>
+      <history>
+        <statistics>
+          <enumeration/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
+</event>
+
 <event name="AppListAppClickData">
   <owner>pdyson@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 692109e..06602ac6 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,12 +5,12 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/win/066c7f00bf7af468db2a2f52da89b8963248c426/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "94643ca39632711d083800fca935515f309f9c0f",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/066c7f00bf7af468db2a2f52da89b8963248c426/trace_processor_shell"
+            "hash": "0a851874f1a85eddc033b36017a0945bd0ca2627",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/a4edfc0db10b623d6fdf3281b358bc131bade994/trace_processor_shell"
         },
         "linux": {
             "hash": "7a79f317edcbc5e07fd74df8c2548d75f0fe4efa",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/c061d214c9da2dd3f9ced1ccfa2e9244c09f017f/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/4973272defb4081572496c831ca3e55c6d12c99a/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/page_sets/login_helpers/pinterest_login.py b/tools/perf/page_sets/login_helpers/pinterest_login.py
index 5b7a3b6..991c555 100644
--- a/tools/perf/page_sets/login_helpers/pinterest_login.py
+++ b/tools/perf/page_sets/login_helpers/pinterest_login.py
@@ -59,6 +59,13 @@
     exceptions.Error: See ExecuteJavaScript()
     for a detailed list of possible exceptions.
   """
+  wait_for_local_storage = """
+  (function() {
+    try {
+      const state = JSON.parse(window.localStorage.REDUX_STATE);
+      return state.users[state.session.userId].login_state;
+    } catch(e) { return false; }
+  })()
+  """
   _LoginAccount(action_runner, credential, credentials_path)
-  action_runner.WaitForElement(selector='div.mobileGrid div[role="button"]')
-  action_runner.Wait(1)
+  action_runner.WaitForJavaScriptCondition(wait_for_local_storage, timeout=10)
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index e3d1108..5cb999878 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -226,6 +226,7 @@
     "ax_table_info_unittest.cc",
     "ax_text_utils_unittest.cc",
     "ax_tree_combiner_unittest.cc",
+    "ax_tree_id_unittest.cc",
     "ax_tree_serializer_unittest.cc",
     "ax_tree_source_checker_unittest.cc",
     "ax_tree_unittest.cc",
diff --git a/ui/accessibility/ax_tree_id.cc b/ui/accessibility/ax_tree_id.cc
index d829f43d..d7f8877 100644
--- a/ui/accessibility/ax_tree_id.cc
+++ b/ui/accessibility/ax_tree_id.cc
@@ -44,7 +44,10 @@
 
 // static
 AXTreeID AXTreeID::FromToken(const base::UnguessableToken& token) {
-  return AXTreeID(token.ToString());
+  AXTreeID id;
+  id.type_ = ax::mojom::AXTreeIDType::kToken;
+  id.token_ = token;
+  return id;
 }
 
 // static
diff --git a/ui/accessibility/ax_tree_id_unittest.cc b/ui/accessibility/ax_tree_id_unittest.cc
new file mode 100644
index 0000000..3801832
--- /dev/null
+++ b/ui/accessibility/ax_tree_id_unittest.cc
@@ -0,0 +1,24 @@
+// 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 "ui/accessibility/ax_tree_id.h"
+
+#include "base/unguessable_token.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ui {
+
+TEST(AXTreeIDTest, ToStringFromString) {
+  AXTreeID tree_id = AXTreeID::CreateNewAXTreeID();
+  AXTreeID new_tree_id = AXTreeID::FromString(tree_id.ToString());
+  ASSERT_EQ(tree_id, new_tree_id);
+}
+
+TEST(AXTreeIDTest, ToTokenFromToken) {
+  AXTreeID tree_id = AXTreeID::CreateNewAXTreeID();
+  AXTreeID new_tree_id = AXTreeID::FromToken(tree_id.token().value());
+  ASSERT_EQ(tree_id, new_tree_id);
+}
+
+}  // namespace ui
diff --git a/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java b/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java
index 7ed0fd7c..f0132b7e0 100644
--- a/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java
+++ b/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java
@@ -132,10 +132,8 @@
             switch (mImageReaderStatus) {
                 case NEW:
                     mImageReaderStatus = ImageReaderStatus.INITIALIZING;
-                    mState.mRequestNewDraw = false;
                     break;
                 case UPDATED:
-                    mState.mRequestNewDraw = false;
                     mImageReaderStatus = ImageReaderStatus.RUNNING;
                     break;
                 case INITIALIZING:
@@ -157,7 +155,7 @@
         @Override
         public void onImageAvailable(ImageReader reader) {
             try (TraceEvent e = TraceEvent.scoped("AcceleratedImageReader::onImageAvailable")) {
-                android.media.Image image = mReaderDelegate.acquireNextImage();
+                android.media.Image image = reader.acquireNextImage();
                 assert image != null;
                 android.media.Image.Plane[] planes = image.getPlanes();
                 assert planes.length != 0;
@@ -303,6 +301,7 @@
                 captureCommon(canvas);
 
                 renderNode.endRecording();
+                currentState.mRequestNewDraw = false;
                 mReader.requestDraw(renderNode);
             }
         }
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index eee1067..5b1b39a 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -8,6 +8,7 @@
 import("//build/config/dcheck_always_on.gni")
 import("//build/config/linux/gtk/gtk.gni")
 import("//build/config/linux/pangocairo/pangocairo.gni")
+import("//build/config/locales.gni")
 import("//build/config/ozone.gni")
 import("//build/config/sanitizers/sanitizers.gni")
 import("//build/config/ui.gni")
@@ -429,6 +430,7 @@
   ]
 
   deps = [
+    ":locales_list",
     "//base:base_static",
     "//base:i18n",
     "//base/third_party/dynamic_annotations",
@@ -1232,3 +1234,25 @@
     "//ui/base/clipboard:file_info",
   ]
 }
+
+# Same as locales, but includes "en" on Apple platforms.
+# Apple platforms use "en" instead of "en-US" (see the definition of
+# |locales_as_mac_outputs| in locales.gni). However, we still want to keep
+# "en-US" in the list as the |ResourceBundle::GetLocaleFilePath| implementations
+# in ui/base/resource/resource_bundle_{mac,ios}.mm return a valid path for
+# "en-US" (as they internally rewrite it as "en" instead).
+locales_for_platform_list = locales
+if (is_apple) {
+  locales_for_platform_list += [ "en" ]
+}
+
+action("locales_list_gen") {
+  script = "//tools/l10n/generate_locales_list.py"
+  outputs = [ "$root_gen_dir/ui/base/l10n/l10n_util_locales_list.inc" ]
+  args = rebase_path(outputs, root_build_dir) + locales_for_platform_list
+}
+
+source_set("locales_list") {
+  deps = [ ":locales_list_gen" ]
+  sources = [ "$root_gen_dir/ui/base/l10n/l10n_util_locales_list.inc" ]
+}
diff --git a/ui/base/l10n/l10n_util.cc b/ui/base/l10n/l10n_util.cc
index aab51938..9f47a482 100644
--- a/ui/base/l10n/l10n_util.cc
+++ b/ui/base/l10n/l10n_util.cc
@@ -20,6 +20,8 @@
 #include "base/i18n/rtl.h"
 #include "base/i18n/string_compare.h"
 #include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
 #include "base/notreached.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
@@ -224,6 +226,32 @@
     "zu",      // Zulu
 };
 
+// The list of locales that expected on the current platform, generated from the
+// |locales| variable in GN (defined in build/config/locales.gni). This is
+// equivalently the list of locales that we expect to have translation strings
+// for on the current platform. Guaranteed to be in sorted order and guaranteed
+// to have no duplicates.
+//
+// Note that this could have false positives at runtime on Android and iOS:
+// - On Android, some locales aren't shipped (|android_apk_omitted_locales| in
+//   GN), and some locales files are dynamically shipped in app bundles
+//   (|android_bundle_only_locales|). Both of these lists are included in
+//   this variable.
+// - On iOS, some locales aren't shipped (|ios_unsupported_locales|) as they are
+//   not supported by the operating system. These locales are included in this
+//   variable.
+//
+// To avoid false positives on these platforms, use
+// ui::ResourceBundle::LocaleDataPakExists() to check whether the locales exist
+// on disk instead (requires I/O).
+static const char* const kPlatformLocales[] = {
+#define PLATFORM_LOCALE(locale) #locale,
+// The below is generated by tools/l10n/generate_locales_list.py, which is
+// run in the //ui/base:locales_list_gen build rule.
+#include "ui/base/l10n/l10n_util_locales_list.inc"
+#undef PLATFORM_LOCALE
+};
+
 // Returns true if |locale_name| has an alias in the ICU data file.
 bool IsDuplicateName(const std::string& locale_name) {
   static const char* const kDuplicateNames[] = {
@@ -264,8 +292,15 @@
   return !l10n_util::IsLocaleNameTranslated("en", locale_name);
 }
 
-#if !defined(OS_APPLE)
-bool IsLocaleAvailable(const std::string& locale) {
+// If |perform_io| is false, this will not perform any I/O but may return false
+// positives on Android and iOS. See the |kPlatformLocales| documentation for
+// more information.
+bool HasStringsForLocale(const std::string& locale,
+                         const bool perform_io = true) {
+  if (!perform_io) {
+    return std::binary_search(std::begin(kPlatformLocales),
+                              std::end(kPlatformLocales), locale);
+  }
   // If locale has any illegal characters in it, we don't want to try to
   // load it because it may be pointing outside the locale data file directory.
   if (!base::i18n::IsFilenameLegal(base::ASCIIToUTF16(locale)))
@@ -273,12 +308,11 @@
 
   // IsLocalePartiallyPopulated() can be called here for an early return w/o
   // checking the resource availability below. It'd help when Chrome is run
-  // under a system locale Chrome is not localized to (e.g.Farsi on Linux),
+  // under a system locale Chrome is not localized to (e.g. Farsi on Linux),
   // but it'd slow down the start up time a little bit for locales Chrome is
   // localized to. So, we don't call it here.
   return ui::ResourceBundle::LocaleDataPakExists(locale);
 }
-#endif
 
 // On Linux, the text layout engine Pango determines paragraph directionality
 // by looking at the first strongly-directional character in the text. This
@@ -340,12 +374,12 @@
   return std::string(locale, 0, hyphen_pos);
 }
 
-// TODO(jshin): revamp this function completely to use a more sytematic
+// TODO(jshin): revamp this function completely to use a more systematic
 // and generic locale fallback based on ICU/CLDR.
 bool CheckAndResolveLocale(const std::string& locale,
-                           std::string* resolved_locale) {
-#if !defined(OS_APPLE)
-  if (IsLocaleAvailable(locale)) {
+                           std::string* resolved_locale,
+                           const bool perform_io) {
+  if (HasStringsForLocale(locale, perform_io)) {
     *resolved_locale = locale;
     return true;
   }
@@ -370,10 +404,17 @@
     // Spanish locale).
     if (base::LowerCaseEqualsASCII(lang, "es") &&
         !base::LowerCaseEqualsASCII(region, "es")) {
+#if defined(OS_IOS)
+      // iOS uses a different name for es-419 (es-MX).
+      tmp_locale.append("-MX");
+#else
       tmp_locale.append("-419");
-    } else if (base::LowerCaseEqualsASCII(lang, "pt")) {
+#endif
+    } else if (base::LowerCaseEqualsASCII(lang, "pt") &&
+               !base::LowerCaseEqualsASCII(region, "br")) {
       // Map pt-RR other than pt-BR to pt-PT. Note that "pt" by itself maps to
-      // pt-BR (logic below).
+      // pt-BR (logic below), and we need to explicitly check for pt-BR here as
+      // it is unavailable on iOS.
       tmp_locale.append("-PT");
     } else if (base::LowerCaseEqualsASCII(lang, "zh")) {
       // Map zh-HK and zh-MO to zh-TW. Otherwise, zh-FOO is mapped to zh-CN.
@@ -395,7 +436,7 @@
         tmp_locale.append("-GB");
       }
     }
-    if (IsLocaleAvailable(tmp_locale)) {
+    if (HasStringsForLocale(tmp_locale, perform_io)) {
       resolved_locale->swap(tmp_locale);
       return true;
     }
@@ -413,19 +454,21 @@
   for (const auto& alias : kAliasMap) {
     if (base::LowerCaseEqualsASCII(lang, alias.source)) {
       std::string tmp_locale(alias.dest);
-      if (IsLocaleAvailable(tmp_locale)) {
+      if (HasStringsForLocale(tmp_locale, perform_io)) {
         resolved_locale->swap(tmp_locale);
         return true;
       }
     }
   }
-#else
-  NOTIMPLEMENTED();
-#endif  // !defined(OS_APPLE)
 
   return false;
 }
 
+bool CheckAndResolveLocale(const std::string& locale,
+                           std::string* resolved_locale) {
+  return CheckAndResolveLocale(locale, resolved_locale, /*perform_io=*/true);
+}
+
 #if defined(OS_APPLE)
 std::string GetApplicationLocaleInternalMac(const std::string& pref_locale) {
   // Use any override (Cocoa for the browser), otherwise use the preference
@@ -505,7 +548,7 @@
 
   // Fallback on en-US.
   const std::string fallback_locale("en-US");
-  if (IsLocaleAvailable(fallback_locale))
+  if (HasStringsForLocale(fallback_locale))
     return fallback_locale;
 
   return std::string();
@@ -871,10 +914,37 @@
   SortVectorWithStringKey(locale, strings, false);
 }
 
-const std::vector<std::string>& GetAvailableLocales() {
+const std::vector<std::string>& GetAvailableICULocales() {
   return g_available_locales.Get();
 }
 
+const std::vector<std::string>& GetLocalesWithStrings() {
+  static base::NoDestructor<std::vector<std::string>> available_locales([] {
+    std::vector<std::string> locales;
+    for (const char* accept_language : kAcceptLanguageList) {
+      std::string locale(accept_language);
+      std::string resolved_locale;
+
+      // As there are many callers of GetLocalesWithStrings from threads where
+      // I/O is prohibited, we cannot perform I/O here.
+      if (!l10n_util::CheckAndResolveLocale(locale, &resolved_locale,
+                                            /*perform_io=*/false))
+        continue;
+
+      // We shouldn't show the user any other Chinese locales other than the
+      // ones that we have strings for (i.e. when resolved_locale == locale).
+      if (resolved_locale != locale &&
+          base::LowerCaseEqualsASCII(l10n_util::GetLanguage(locale), "zh"))
+        continue;
+
+      locales.push_back(locale);
+    }
+    return locales;
+  }());
+
+  return *available_locales;
+}
+
 void GetAcceptLanguagesForLocale(const std::string& display_locale,
                                  std::vector<std::string>* locale_codes) {
   for (const char* accept_language : kAcceptLanguageList) {
@@ -919,4 +989,12 @@
   return base::size(kAcceptLanguageList);
 }
 
+const char* const* GetPlatformLocalesForTesting() {
+  return kPlatformLocales;
+}
+
+size_t GetPlatformLocalesSizeForTesting() {
+  return base::size(kPlatformLocales);
+}
+
 }  // namespace l10n_util
diff --git a/ui/base/l10n/l10n_util.h b/ui/base/l10n/l10n_util.h
index 81f78f1..0be52f53 100644
--- a/ui/base/l10n/l10n_util.h
+++ b/ui/base/l10n/l10n_util.h
@@ -28,6 +28,15 @@
 
 // This method translates a generic locale name to one of the locally defined
 // ones. This method returns true if it succeeds.
+// If |perform_io| is false, this will not perform any I/O but may return false
+// positives on Android and iOS. See the |kPlatformLocales| documentation in
+// l10n_util.cc for more information.
+COMPONENT_EXPORT(UI_BASE)
+bool CheckAndResolveLocale(const std::string& locale,
+                           std::string* resolved_locale,
+                           const bool perform_io);
+
+// Convenience wrapper for the above (with |perform_io| set to true).
 COMPONENT_EXPORT(UI_BASE)
 bool CheckAndResolveLocale(const std::string& locale,
                            std::string* resolved_locale);
@@ -216,9 +225,17 @@
 void SortStrings16(const std::string& locale,
                    std::vector<std::u16string>* strings);
 
-// Returns a vector of available locale codes. E.g., a vector containing
-// en-US, es, fr, fi, pt-PT, pt-BR, etc.
-COMPONENT_EXPORT(UI_BASE) const std::vector<std::string>& GetAvailableLocales();
+// Returns a vector of available locale codes from ICU. E.g., a vector
+// containing en-US, es, fr, fi, pt-PT, pt-BR, etc.
+COMPONENT_EXPORT(UI_BASE)
+const std::vector<std::string>& GetAvailableICULocales();
+
+// Returns a vector of locale codes for which we have translation strings for,
+// including locales which have valid fallbacks.
+// E.g., a vector containing en-US, en-CA, en-GB, es, fr, pt-PT, pt-BR, etc.
+// This is a strict subset of the vector returned from GetAcceptLanguages.
+COMPONENT_EXPORT(UI_BASE)
+const std::vector<std::string>& GetLocalesWithStrings();
 
 // Returns a vector of locale codes usable for accept-languages.
 COMPONENT_EXPORT(UI_BASE)
@@ -244,6 +261,10 @@
 
 COMPONENT_EXPORT(UI_BASE) size_t GetAcceptLanguageListSizeForTesting();
 
+COMPONENT_EXPORT(UI_BASE) const char* const* GetPlatformLocalesForTesting();
+
+COMPONENT_EXPORT(UI_BASE) size_t GetPlatformLocalesSizeForTesting();
+
 }  // namespace l10n_util
 
 #endif  // UI_BASE_L10N_L10N_UTIL_H_
diff --git a/ui/base/l10n/l10n_util_unittest.cc b/ui/base/l10n/l10n_util_unittest.cc
index 81a3f5e..91a6f5be 100644
--- a/ui/base/l10n/l10n_util_unittest.cc
+++ b/ui/base/l10n/l10n_util_unittest.cc
@@ -4,8 +4,10 @@
 
 #include <stddef.h>
 
+#include <cstring>
 #include <memory>
 
+#include "base/containers/flat_set.h"
 #include "base/environment.h"
 #include "base/files/file_util.h"
 #include "base/i18n/case_conversion.h"
@@ -583,7 +585,7 @@
   // Verify that base::TimeDurationFormat() works for all available locales:
   // http://crbug.com/707515
   base::TimeDelta kDelta = base::TimeDelta::FromMinutes(15 * 60 + 42);
-  for (const std::string& locale : l10n_util::GetAvailableLocales()) {
+  for (const std::string& locale : l10n_util::GetAvailableICULocales()) {
     base::i18n::SetICUDefaultLocale(locale);
     std::u16string str;
     const bool result =
@@ -593,3 +595,50 @@
       EXPECT_FALSE(str.empty()) << "Got empty string for " << locale;
   }
 }
+
+TEST_F(L10nUtilTest, GetLocalesWithStrings) {
+  // Convert the vector to a set for easy lookup.
+  const base::flat_set<std::string> locales =
+      l10n_util::GetLocalesWithStrings();
+
+  // Common locales which should be available on all platforms.
+  EXPECT_TRUE(locales.contains("en") || locales.contains("en-US"));
+  EXPECT_TRUE(locales.contains("en-GB"));
+  EXPECT_TRUE(locales.contains("es") || locales.contains("es-ES"));
+  EXPECT_TRUE(locales.contains("fr") || locales.contains("fr-FR"));
+
+  // Locales that we should have valid fallbacks for.
+  EXPECT_TRUE(locales.contains("en-CA"));
+  EXPECT_TRUE(locales.contains("es-AR"));
+  EXPECT_TRUE(locales.contains("fr-CA"));
+
+  // Locales that should not be included:
+  // English (Germany). A valid locale and in ICU's list of locales, but not in
+  // our list of Accept-Language locales.
+  EXPECT_FALSE(locales.contains("en-DE"));
+  // Esperanto. Unlikely to be localised and historically included in
+  // GetAvailableICULocales.
+  EXPECT_FALSE(locales.contains("eo"));
+}
+
+TEST_F(L10nUtilTest, PlatformLocalesIsSorted) {
+  const char* const* locales = l10n_util::GetPlatformLocalesForTesting();
+  const size_t locales_size = l10n_util::GetPlatformLocalesSizeForTesting();
+
+  // Check adjacent pairs and ensure they are in sorted order without
+  // duplicates.
+
+  // All 0-length and 1-length lists are sorted.
+  if (locales_size <= 1) {
+    return;
+  }
+
+  const char* last_locale = locales[0];
+  for (size_t i = 1; i < locales_size; i++) {
+    const char* cur_locale = locales[i];
+    EXPECT_LT(strcmp(last_locale, cur_locale), 0)
+        << "Incorrect ordering in kPlatformLocales: " << last_locale
+        << " >= " << cur_locale;
+    last_locale = cur_locale;
+  }
+}
diff --git a/ui/color/BUILD.gn b/ui/color/BUILD.gn
index 938bb68..b0c1eba7 100644
--- a/ui/color/BUILD.gn
+++ b/ui/color/BUILD.gn
@@ -28,8 +28,6 @@
   sources = [
     "color_mixer.cc",
     "color_provider.cc",
-    "color_provider_manager.cc",
-    "color_provider_manager.h",
     "color_provider_utils.cc",
     "color_provider_utils.h",
     "color_recipe.cc",
@@ -73,6 +71,7 @@
 
   deps = [
     ":color",
+    ":mixers",
     "//base/test:test_support",
     "//testing/gtest",
   ]
@@ -81,6 +80,8 @@
 component("mixers") {
   sources = [
     "color_mixers.h",
+    "color_provider_manager.cc",
+    "color_provider_manager.h",
     "core_default_color_mixer.cc",
     "ui_color_mixer.cc",
   ]
@@ -109,6 +110,8 @@
       "mac/scoped_current_nsappearance.h",
       "mac/scoped_current_nsappearance.mm",
     ]
+  } else if (is_ios) {
+    sources += [ "ios/native_color_mixers.mm" ]
   } else if (is_win) {
     sources += [ "win/native_color_mixers.cc" ]
   }
diff --git a/ui/color/color_provider_manager.cc b/ui/color/color_provider_manager.cc
index c77ce4b6..56cfa51 100644
--- a/ui/color/color_provider_manager.cc
+++ b/ui/color/color_provider_manager.cc
@@ -6,13 +6,19 @@
 
 #include <algorithm>
 
+#include "base/bind.h"
 #include "base/check.h"
 #include "base/logging.h"
 #include "base/no_destructor.h"
 #include "base/optional.h"
+#include "build/build_config.h"
 #include "ui/color/color_provider.h"
 #include "ui/color/color_provider_utils.h"
 
+#if !defined(OS_ANDROID)
+#include "ui/color/color_mixers.h"
+#endif
+
 namespace ui {
 
 namespace {
@@ -42,9 +48,37 @@
 // static
 ColorProviderManager& ColorProviderManager::Get() {
   base::Optional<GlobalManager>& manager = GetGlobalManager();
+  if (!manager.has_value()) {
+    manager.emplace();
+#if !defined(OS_ANDROID)
+    manager.value().SetColorProviderInitializer(base::BindRepeating(
+        [](ColorProvider* provider, ColorProviderManager::ColorMode color_mode,
+           ColorProviderManager::ContrastMode contrast_mode) {
+          const bool dark_mode =
+              color_mode == ColorProviderManager::ColorMode::kDark;
+          const bool high_contrast =
+              contrast_mode == ColorProviderManager::ContrastMode::kHigh;
+          ui::AddCoreDefaultColorMixer(provider, dark_mode, high_contrast);
+          ui::AddNativeCoreColorMixer(provider, dark_mode, high_contrast);
+          ui::AddUiColorMixer(provider);
+          ui::AddNativeUiColorMixer(provider, dark_mode, high_contrast);
+#if defined(OS_MAC)
+          // Always keep this mixer at the last so the system tint will be
+          // applied after getting the proper color.
+          ui::AddSystemTintMixer(provider);
+#endif
+        }));
+#endif  // !defined(OS_ANDROID)
+  }
+
+  return manager.value();
+}
+
+// static
+ColorProviderManager& ColorProviderManager::GetForTesting() {
+  base::Optional<GlobalManager>& manager = GetGlobalManager();
   if (!manager.has_value())
     manager.emplace();
-
   return manager.value();
 }
 
@@ -60,18 +94,17 @@
   initializer_ = std::move(initializer);
 }
 
-ColorProvider* ColorProviderManager::GetColorProviderFor(
-    ColorMode color_mode,
-    ContrastMode contrast_mode) {
-  auto key = ColorProviderKey(color_mode, contrast_mode);
+ColorProvider* ColorProviderManager::GetColorProviderFor(ColorProviderKey key) {
   auto iter = color_providers_.find(key);
   if (iter == color_providers_.end()) {
     auto provider = std::make_unique<ColorProvider>();
     if (!initializer_.is_null()) {
       DVLOG(2) << "ColorProviderManager: Initializing Color Provider"
-               << " - ColorMode: " << ColorModeName(color_mode)
-               << " - ContrastMode: " << ContrastModeName(contrast_mode);
-      initializer_.Run(provider.get(), color_mode, contrast_mode);
+               << " - ColorMode: " << ColorModeName(std::get<ColorMode>(key))
+               << " - ContrastMode: "
+               << ContrastModeName(std::get<ContrastMode>(key));
+      initializer_.Run(provider.get(), std::get<ColorMode>(key),
+                       std::get<ContrastMode>(key));
     }
 
     iter = color_providers_.emplace(key, std::move(provider)).first;
diff --git a/ui/color/color_provider_manager.h b/ui/color/color_provider_manager.h
index 7baa65e9..6ac4a31 100644
--- a/ui/color/color_provider_manager.h
+++ b/ui/color/color_provider_manager.h
@@ -31,6 +31,7 @@
     kNormal,
     kHigh,
   };
+  using ColorProviderKey = std::tuple<ColorMode, ContrastMode>;
   using ColorProviderInitializer =
       base::RepeatingCallback<void(ColorProvider*, ColorMode, ContrastMode)>;
 
@@ -38,23 +39,21 @@
   ColorProviderManager& operator=(const ColorProviderManager&) = delete;
 
   static ColorProviderManager& Get();
+  static ColorProviderManager& GetForTesting();
   static void ResetForTesting();
 
   // Sets the initializer for all ColorProviders returned from
   // GetColorProviderFor().
   void SetColorProviderInitializer(ColorProviderInitializer initializer);
 
-  // Returns a color provider for |color_mode| and |contrast_mode|, creating one
-  // if necessary.
-  ColorProvider* GetColorProviderFor(ColorMode color_mode,
-                                     ContrastMode contrast_mode);
+  // Returns a color provider for |key|, creating one if necessary.
+  ColorProvider* GetColorProviderFor(ColorProviderKey key);
 
  protected:
   ColorProviderManager();
   virtual ~ColorProviderManager();
 
  private:
-  using ColorProviderKey = std::tuple<ColorMode, ContrastMode>;
   ColorProviderInitializer initializer_;
   base::flat_map<ColorProviderKey, std::unique_ptr<ColorProvider>>
       color_providers_;
diff --git a/ui/color/color_provider_manager_unittest.cc b/ui/color/color_provider_manager_unittest.cc
index c7cd30b6..5fbc762 100644
--- a/ui/color/color_provider_manager_unittest.cc
+++ b/ui/color/color_provider_manager_unittest.cc
@@ -28,8 +28,8 @@
 
 ColorProvider* GetLightNormalColorProvider() {
   return ColorProviderManager::Get().GetColorProviderFor(
-      ColorProviderManager::ColorMode::kLight,
-      ColorProviderManager::ContrastMode::kNormal);
+      {ColorProviderManager::ColorMode::kLight,
+       ColorProviderManager::ContrastMode::kNormal});
 }
 
 }  // namespace
@@ -47,9 +47,10 @@
 // Verifies that the initializer is called for each newly created color
 // provider.
 TEST_F(ColorProviderManagerTest, SetInitializer) {
-  ColorProviderManager::Get().SetColorProviderInitializer(base::BindRepeating(
-      [](ColorProvider* provider, ColorProviderManager::ColorMode,
-         ColorProviderManager::ContrastMode) {
+  ColorProviderManager::GetForTesting().SetColorProviderInitializer(
+      base::BindRepeating([](ColorProvider* provider,
+                             ColorProviderManager::ColorMode,
+                             ColorProviderManager::ContrastMode) {
         provider->AddMixer().AddSet(
             {kColorSetTest0, {{kColorTest0, SK_ColorBLUE}}});
       }));
diff --git a/ui/color/ios/native_color_mixers.mm b/ui/color/ios/native_color_mixers.mm
new file mode 100644
index 0000000..9f273d0
--- /dev/null
+++ b/ui/color/ios/native_color_mixers.mm
@@ -0,0 +1,23 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/color/color_mixers.h"
+
+#include "base/notreached.h"
+
+namespace ui {
+
+void AddNativeCoreColorMixer(ColorProvider* provider,
+                             bool dark_window,
+                             bool high_contrast) {
+  NOTIMPLEMENTED();
+}
+
+void AddNativeUiColorMixer(ColorProvider* provider,
+                           bool dark_window,
+                           bool high_contrast) {
+  NOTIMPLEMENTED();
+}
+
+}  // namespace ui
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn
index 7662208..d39757a7 100644
--- a/ui/gfx/BUILD.gn
+++ b/ui/gfx/BUILD.gn
@@ -75,6 +75,10 @@
     "color_transform.h",
     "decorated_text.cc",
     "decorated_text.h",
+    "delegated_ink_metadata.cc",
+    "delegated_ink_metadata.h",
+    "delegated_ink_point.cc",
+    "delegated_ink_point.h",
     "extension_set.cc",
     "extension_set.h",
     "favicon_size.cc",
@@ -310,6 +314,7 @@
     "//base/third_party/dynamic_annotations",
     "//build:chromeos_buildflags",
     "//device/vr/buildflags",
+    "//mojo/public/cpp/bindings:struct_traits",
     "//skia",
     "//third_party/zlib",
   ]
diff --git a/ui/gfx/DEPS b/ui/gfx/DEPS
index b58bc04..26e17db5 100644
--- a/ui/gfx/DEPS
+++ b/ui/gfx/DEPS
@@ -11,3 +11,9 @@
 
   "-testing/gmock",
 ]
+
+specific_include_rules = {
+  "delegated_ink_point\.h" : [
+    "+mojo/public/cpp/bindings/struct_traits.h",
+  ],
+}
diff --git a/components/viz/common/delegated_ink_metadata.cc b/ui/gfx/delegated_ink_metadata.cc
similarity index 87%
rename from components/viz/common/delegated_ink_metadata.cc
rename to ui/gfx/delegated_ink_metadata.cc
index d45fb269..eea84b1 100644
--- a/components/viz/common/delegated_ink_metadata.cc
+++ b/ui/gfx/delegated_ink_metadata.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 "ui/gfx/delegated_ink_metadata.h"
+
 #include <inttypes.h>
 
 #include "base/strings/stringprintf.h"
-#include "components/viz/common/delegated_ink_metadata.h"
 
-namespace viz {
+namespace gfx {
 
 std::string DelegatedInkMetadata::ToString() const {
   std::string str = base::StringPrintf(
@@ -20,4 +21,4 @@
   return str;
 }
 
-}  // namespace viz
+}  // namespace gfx
diff --git a/components/viz/common/delegated_ink_metadata.h b/ui/gfx/delegated_ink_metadata.h
similarity index 78%
rename from components/viz/common/delegated_ink_metadata.h
rename to ui/gfx/delegated_ink_metadata.h
index 42f2b0a6..a110315 100644
--- a/components/viz/common/delegated_ink_metadata.h
+++ b/ui/gfx/delegated_ink_metadata.h
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_VIZ_COMMON_DELEGATED_INK_METADATA_H_
-#define COMPONENTS_VIZ_COMMON_DELEGATED_INK_METADATA_H_
+#ifndef UI_GFX_DELEGATED_INK_METADATA_H_
+#define UI_GFX_DELEGATED_INK_METADATA_H_
 
 #include <string>
 
 #include "base/time/time.h"
-#include "components/viz/common/viz_common_export.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/gfx_export.h"
 
-namespace viz {
+namespace gfx {
 
 // This class stores all the metadata that is gathered when the WebAPI
 // updateInkTrailStartPoint is called. This metadata flows from blink,
@@ -21,14 +21,14 @@
 //
 // Explainer for the feature:
 // https://github.com/WICG/ink-enhancement/blob/master/README.md
-class VIZ_COMMON_EXPORT DelegatedInkMetadata {
+class GFX_EXPORT DelegatedInkMetadata {
  public:
   DelegatedInkMetadata() = default;
-  DelegatedInkMetadata(const gfx::PointF& pt,
+  DelegatedInkMetadata(const PointF& pt,
                        double diameter,
                        SkColor color,
                        base::TimeTicks timestamp,
-                       const gfx::RectF& area,
+                       const RectF& area,
                        bool hovering)
       : point_(pt),
         diameter_(diameter),
@@ -36,11 +36,11 @@
         timestamp_(timestamp),
         presentation_area_(area),
         is_hovering_(hovering) {}
-  DelegatedInkMetadata(const gfx::PointF& pt,
+  DelegatedInkMetadata(const PointF& pt,
                        double diameter,
                        SkColor color,
                        base::TimeTicks timestamp,
-                       const gfx::RectF& area,
+                       const RectF& area,
                        base::TimeTicks frame_time,
                        bool hovering)
       : point_(pt),
@@ -52,11 +52,11 @@
         is_hovering_(hovering) {}
   DelegatedInkMetadata(const DelegatedInkMetadata& other) = default;
 
-  const gfx::PointF& point() const { return point_; }
+  const PointF& point() const { return point_; }
   double diameter() const { return diameter_; }
   SkColor color() const { return color_; }
   base::TimeTicks timestamp() const { return timestamp_; }
-  const gfx::RectF& presentation_area() const { return presentation_area_; }
+  const RectF& presentation_area() const { return presentation_area_; }
   base::TimeTicks frame_time() const { return frame_time_; }
   bool is_hovering() const { return is_hovering_; }
 
@@ -66,7 +66,7 @@
 
  private:
   // Location of the pointerevent relative to the root frame.
-  gfx::PointF point_;
+  PointF point_;
 
   // Width of the trail, in physical pixels.
   double diameter_ = 0;
@@ -78,7 +78,7 @@
   base::TimeTicks timestamp_;
 
   // The rect to clip the ink trail to, defaults to the containing viewport.
-  gfx::RectF presentation_area_;
+  RectF presentation_area_;
 
   // Frame time of the layer tree that this metadata is on.
   base::TimeTicks frame_time_;
@@ -89,6 +89,6 @@
   bool is_hovering_ = false;
 };
 
-}  // namespace viz
+}  // namespace gfx
 
-#endif  // COMPONENTS_VIZ_COMMON_DELEGATED_INK_METADATA_H_
+#endif  // UI_GFX_DELEGATED_INK_METADATA_H_
diff --git a/components/viz/common/delegated_ink_point.cc b/ui/gfx/delegated_ink_point.cc
similarity index 85%
rename from components/viz/common/delegated_ink_point.cc
rename to ui/gfx/delegated_ink_point.cc
index e90bad7..8648d30 100644
--- a/components/viz/common/delegated_ink_point.cc
+++ b/ui/gfx/delegated_ink_point.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/viz/common/delegated_ink_point.h"
+#include "ui/gfx/delegated_ink_point.h"
 
 #include <inttypes.h>
 
 #include "base/strings/stringprintf.h"
 
-namespace viz {
+namespace gfx {
 
 std::string DelegatedInkPoint::ToString() const {
   return base::StringPrintf("point: %s, timestamp: %" PRId64 ", pointer_id: %d",
@@ -17,4 +17,4 @@
                             pointer_id_);
 }
 
-}  // namespace viz
+}  // namespace gfx
diff --git a/components/viz/common/delegated_ink_point.h b/ui/gfx/delegated_ink_point.h
similarity index 82%
rename from components/viz/common/delegated_ink_point.h
rename to ui/gfx/delegated_ink_point.h
index 9e6c23d..ec46f36 100644
--- a/components/viz/common/delegated_ink_point.h
+++ b/ui/gfx/delegated_ink_point.h
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_VIZ_COMMON_DELEGATED_INK_POINT_H_
-#define COMPONENTS_VIZ_COMMON_DELEGATED_INK_POINT_H_
+#ifndef UI_GFX_DELEGATED_INK_POINT_H_
+#define UI_GFX_DELEGATED_INK_POINT_H_
 
 #include <string>
 
 #include "base/time/time.h"
-#include "components/viz/common/viz_common_export.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/gfx_export.h"
 
-namespace viz {
+namespace gfx {
 
 namespace mojom {
 class DelegatedInkPointDataView;
@@ -28,15 +28,15 @@
 //
 // Explainer for the feature:
 // https://github.com/WICG/ink-enhancement/blob/master/README.md
-class VIZ_COMMON_EXPORT DelegatedInkPoint {
+class GFX_EXPORT DelegatedInkPoint {
  public:
   DelegatedInkPoint() = default;
-  DelegatedInkPoint(const gfx::PointF& pt,
+  DelegatedInkPoint(const PointF& pt,
                     base::TimeTicks timestamp,
                     int32_t pointer_id)
       : point_(pt), timestamp_(timestamp), pointer_id_(pointer_id) {}
 
-  const gfx::PointF& point() const { return point_; }
+  const PointF& point() const { return point_; }
   base::TimeTicks timestamp() const { return timestamp_; }
   int32_t pointer_id() const { return pointer_id_; }
   std::string ToString() const;
@@ -47,7 +47,7 @@
 
   // Location of the input event relative to the root window in device pixels.
   // Scale is device scale factor at time of input.
-  gfx::PointF point_;
+  PointF point_;
 
   // Timestamp from the input event.
   base::TimeTicks timestamp_;
@@ -59,6 +59,6 @@
   int32_t pointer_id_;
 };
 
-}  // namespace viz
+}  // namespace gfx
 
-#endif  // COMPONENTS_VIZ_COMMON_DELEGATED_INK_POINT_H_
+#endif  // UI_GFX_DELEGATED_INK_POINT_H_
diff --git a/ui/gfx/mojom/BUILD.gn b/ui/gfx/mojom/BUILD.gn
index d52a4e9..07b61c8 100644
--- a/ui/gfx/mojom/BUILD.gn
+++ b/ui/gfx/mojom/BUILD.gn
@@ -12,6 +12,8 @@
     "buffer_types.mojom",
     "ca_layer_params.mojom",
     "color_space.mojom",
+    "delegated_ink_metadata.mojom",
+    "delegated_ink_point.mojom",
     "display_color_spaces.mojom",
     "font_render_params.mojom",
     "gpu_extra_info.mojom",
@@ -30,6 +32,7 @@
   public_deps = [
     ":native_handle_types",
     "//mojo/public/mojom/base",
+    "//skia/public/mojom",
     "//ui/gfx/geometry/mojom",
   ]
 
@@ -187,6 +190,30 @@
     {
       types = [
         {
+          mojom = "gfx.mojom.DelegatedInkMetadata"
+          cpp = "::std::unique_ptr<::gfx::DelegatedInkMetadata>"
+          move_only = true
+          nullable_is_same_type = true
+        },
+      ]
+      traits_sources = [ "delegated_ink_metadata_mojom_traits.cc" ]
+      traits_headers = [ "delegated_ink_metadata_mojom_traits.h" ]
+      traits_public_deps = [ "//ui/gfx" ]
+    },
+    {
+      types = [
+        {
+          mojom = "gfx.mojom.DelegatedInkPoint"
+          cpp = "::gfx::DelegatedInkPoint"
+        },
+      ]
+      traits_sources = [ "delegated_ink_point_mojom_traits.cc" ]
+      traits_headers = [ "delegated_ink_point_mojom_traits.h" ]
+      traits_public_deps = [ "//ui/gfx" ]
+    },
+    {
+      types = [
+        {
           mojom = "gfx.mojom.MasteringMetadata"
           cpp = "::gfx::MasteringMetadata"
         },
@@ -258,6 +285,20 @@
 
   cpp_typemaps += shared_cpp_typemaps
   blink_cpp_typemaps = shared_cpp_typemaps
+  blink_cpp_typemaps += [
+    {
+      types = [
+        {
+          mojom = "gfx.mojom.DelegatedInkMetadata"
+          cpp = "::std::unique_ptr<::gfx::DelegatedInkMetadata>"
+          move_only = true
+          nullable_is_same_type = true
+        },
+      ]
+      traits_headers = [ "delegated_ink_metadata_mojom_traits.h" ]
+      traits_public_deps = [ "//ui/gfx" ]
+    },
+  ]
 }
 
 mojom("native_handle_types") {
diff --git a/ui/gfx/mojom/DEPS b/ui/gfx/mojom/DEPS
index ef8ad28..507f9cd 100644
--- a/ui/gfx/mojom/DEPS
+++ b/ui/gfx/mojom/DEPS
@@ -1,3 +1,9 @@
 include_rules = [
   "+mojo/public",
 ]
+
+specific_include_rules = {
+  "delegated_ink_metadata_mojom_traits\.h" : [
+    "+skia/public/mojom/skcolor_mojom_traits.h",
+  ],
+}
\ No newline at end of file
diff --git a/services/viz/public/mojom/compositing/delegated_ink_metadata.mojom b/ui/gfx/mojom/delegated_ink_metadata.mojom
similarity index 77%
rename from services/viz/public/mojom/compositing/delegated_ink_metadata.mojom
rename to ui/gfx/mojom/delegated_ink_metadata.mojom
index 695c666b..c3b5438 100644
--- a/services/viz/public/mojom/compositing/delegated_ink_metadata.mojom
+++ b/ui/gfx/mojom/delegated_ink_metadata.mojom
@@ -2,19 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-module viz.mojom;
+module gfx.mojom;
 
 import "mojo/public/mojom/base/time.mojom";
 import "skia/public/mojom/skcolor.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 
-// See components/viz/common/delegated_ink_metadata.h.
+// See ui/gfx/delegated_ink_metadata.h.
 struct DelegatedInkMetadata {
-  gfx.mojom.PointF point;
+  PointF point;
   double diameter;
   skia.mojom.SkColor color;
   mojo_base.mojom.TimeTicks timestamp;
-  gfx.mojom.RectF presentation_area;
+  RectF presentation_area;
   mojo_base.mojom.TimeTicks frame_time;
   bool is_hovering;
 };
diff --git a/services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.cc b/ui/gfx/mojom/delegated_ink_metadata_mojom_traits.cc
similarity index 69%
rename from services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.cc
rename to ui/gfx/mojom/delegated_ink_metadata_mojom_traits.cc
index f23dc53..f3f10a8 100644
--- a/services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.cc
+++ b/ui/gfx/mojom/delegated_ink_metadata_mojom_traits.cc
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "services/viz/public/cpp/compositing/delegated_ink_metadata_mojom_traits.h"
+#include "ui/gfx/mojom/delegated_ink_metadata_mojom_traits.h"
 
 namespace mojo {
 
 // static
-bool StructTraits<viz::mojom::DelegatedInkMetadataDataView,
-                  std::unique_ptr<viz::DelegatedInkMetadata>>::
-    Read(viz::mojom::DelegatedInkMetadataDataView data,
-         std::unique_ptr<viz::DelegatedInkMetadata>* out) {
+bool StructTraits<gfx::mojom::DelegatedInkMetadataDataView,
+                  std::unique_ptr<gfx::DelegatedInkMetadata>>::
+    Read(gfx::mojom::DelegatedInkMetadataDataView data,
+         std::unique_ptr<gfx::DelegatedInkMetadata>* out) {
   // Diameter isn't expected to ever be below 0, so stop here if it is in order
   // to avoid unexpected calculations in viz.
   if (data.diameter() < 0)
@@ -26,7 +26,7 @@
       !data.ReadColor(&color) || !data.ReadFrameTime(&frame_time)) {
     return false;
   }
-  *out = std::make_unique<viz::DelegatedInkMetadata>(
+  *out = std::make_unique<gfx::DelegatedInkMetadata>(
       point, data.diameter(), color, timestamp, presentation_area, frame_time,
       data.is_hovering());
   return true;
diff --git a/ui/gfx/mojom/delegated_ink_metadata_mojom_traits.h b/ui/gfx/mojom/delegated_ink_metadata_mojom_traits.h
new file mode 100644
index 0000000..b9c875d1
--- /dev/null
+++ b/ui/gfx/mojom/delegated_ink_metadata_mojom_traits.h
@@ -0,0 +1,70 @@
+// 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 UI_GFX_MOJOM_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_
+#define UI_GFX_MOJOM_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_
+
+#include <memory>
+
+#include "mojo/public/cpp/base/time_mojom_traits.h"
+#include "skia/public/mojom/skcolor_mojom_traits.h"
+#include "ui/gfx/delegated_ink_metadata.h"
+#include "ui/gfx/mojom/delegated_ink_metadata.mojom-shared.h"
+#include "ui/gfx/mojom/rrect_f_mojom_traits.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<gfx::mojom::DelegatedInkMetadataDataView,
+                    std::unique_ptr<gfx::DelegatedInkMetadata>> {
+  static bool IsNull(const std::unique_ptr<gfx::DelegatedInkMetadata>& input) {
+    return !input;
+  }
+
+  static void SetToNull(std::unique_ptr<gfx::DelegatedInkMetadata>* input) {
+    input->reset();
+  }
+
+  static const gfx::PointF& point(
+      const std::unique_ptr<gfx::DelegatedInkMetadata>& input) {
+    return input->point();
+  }
+
+  static double diameter(
+      const std::unique_ptr<gfx::DelegatedInkMetadata>& input) {
+    return input->diameter();
+  }
+
+  static SkColor color(
+      const std::unique_ptr<gfx::DelegatedInkMetadata>& input) {
+    return input->color();
+  }
+
+  static base::TimeTicks timestamp(
+      const std::unique_ptr<gfx::DelegatedInkMetadata>& input) {
+    return input->timestamp();
+  }
+
+  static const gfx::RectF& presentation_area(
+      const std::unique_ptr<gfx::DelegatedInkMetadata>& input) {
+    return input->presentation_area();
+  }
+
+  static base::TimeTicks frame_time(
+      const std::unique_ptr<gfx::DelegatedInkMetadata>& input) {
+    return input->frame_time();
+  }
+
+  static bool is_hovering(
+      const std::unique_ptr<gfx::DelegatedInkMetadata>& input) {
+    return input->is_hovering();
+  }
+
+  static bool Read(gfx::mojom::DelegatedInkMetadataDataView data,
+                   std::unique_ptr<gfx::DelegatedInkMetadata>* out);
+};
+
+}  // namespace mojo
+
+#endif  // UI_GFX_MOJOM_DELEGATED_INK_METADATA_MOJOM_TRAITS_H_
diff --git a/ui/gfx/mojom/delegated_ink_point.mojom b/ui/gfx/mojom/delegated_ink_point.mojom
new file mode 100644
index 0000000..8ed6561
--- /dev/null
+++ b/ui/gfx/mojom/delegated_ink_point.mojom
@@ -0,0 +1,15 @@
+// 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.
+
+module gfx.mojom;
+
+import "mojo/public/mojom/base/time.mojom";
+import "ui/gfx/geometry/mojom/geometry.mojom";
+
+// See ui/gfx/delegated_ink_point.h.
+struct DelegatedInkPoint {
+  PointF point;
+  mojo_base.mojom.TimeTicks timestamp;
+  int32 pointer_id;
+};
diff --git a/ui/gfx/mojom/delegated_ink_point_mojom_traits.cc b/ui/gfx/mojom/delegated_ink_point_mojom_traits.cc
new file mode 100644
index 0000000..97fd785
--- /dev/null
+++ b/ui/gfx/mojom/delegated_ink_point_mojom_traits.cc
@@ -0,0 +1,18 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/mojom/delegated_ink_point_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<
+    gfx::mojom::DelegatedInkPointDataView,
+    gfx::DelegatedInkPoint>::Read(gfx::mojom::DelegatedInkPointDataView data,
+                                  gfx::DelegatedInkPoint* out) {
+  out->pointer_id_ = data.pointer_id();
+  return data.ReadPoint(&out->point_) && data.ReadTimestamp(&out->timestamp_);
+}
+
+}  // namespace mojo
diff --git a/ui/gfx/mojom/delegated_ink_point_mojom_traits.h b/ui/gfx/mojom/delegated_ink_point_mojom_traits.h
new file mode 100644
index 0000000..b188b30
--- /dev/null
+++ b/ui/gfx/mojom/delegated_ink_point_mojom_traits.h
@@ -0,0 +1,36 @@
+// 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 UI_GFX_MOJOM_DELEGATED_INK_POINT_MOJOM_TRAITS_H_
+#define UI_GFX_MOJOM_DELEGATED_INK_POINT_MOJOM_TRAITS_H_
+
+#include "mojo/public/cpp/base/time_mojom_traits.h"
+#include "ui/gfx/delegated_ink_point.h"
+#include "ui/gfx/geometry/mojom/geometry_mojom_traits.h"
+#include "ui/gfx/mojom/delegated_ink_point.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<gfx::mojom::DelegatedInkPointDataView,
+                    gfx::DelegatedInkPoint> {
+  static const gfx::PointF& point(const gfx::DelegatedInkPoint& input) {
+    return input.point();
+  }
+
+  static base::TimeTicks timestamp(const gfx::DelegatedInkPoint& input) {
+    return input.timestamp();
+  }
+
+  static int32_t pointer_id(const gfx::DelegatedInkPoint& input) {
+    return input.pointer_id();
+  }
+
+  static bool Read(gfx::mojom::DelegatedInkPointDataView data,
+                   gfx::DelegatedInkPoint* out);
+};
+
+}  // namespace mojo
+
+#endif  // UI_GFX_MOJOM_DELEGATED_INK_POINT_MOJOM_TRAITS_H_
diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index d4bf2bc..fe18a2a8 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -317,16 +317,9 @@
   env->SetVar("NO_AT_BRIDGE", "1");
   GtkInitFromCommandLine(*base::CommandLine::ForCurrentProcess());
   native_theme_ = NativeThemeGtk::instance();
-#if GTK_CHECK_VERSION(3, 98, 1)
-  fake_window_ = gtk_window_new();
-#else
-  fake_window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-#endif
-  gtk_widget_realize(fake_window_);
 }
 
 GtkUi::~GtkUi() {
-  GtkWindowDestroy(fake_window_);
   g_gtk_ui = nullptr;
 }
 
@@ -370,7 +363,7 @@
   // Listen for scale factor changes.  We would prefer to listen on
   // a GdkScreen, but there is no scale-factor property, so use an
   // unmapped window instead.
-  g_signal_connect(fake_window_, "notify::scale-factor",
+  g_signal_connect(GetDummyWindow(), "notify::scale-factor",
                    G_CALLBACK(OnDeviceScaleFactorMaybeChangedThunk), this);
 
   LoadGtkValues();
@@ -387,7 +380,7 @@
 
   indicators_count = 0;
 
-  GetDelegate()->OnInitialized();
+  GetDelegate()->OnInitialized(GetDummyWindow());
 }
 
 bool GtkUi::GetTint(int id, color_utils::HSL* tint) const {
@@ -511,21 +504,20 @@
   for (size_t i = 0; i < base::size(content_types); ++i) {
     auto icon = TakeGObject(g_content_type_get_icon(content_type.c_str()));
 #if GTK_CHECK_VERSION(3, 98, 0)
-    ScopedGObject<GtkIconPaintable> icon_paintable(
-        gtk_icon_theme_lookup_by_gicon(theme, icon.get(), size, 1,
-                                       GTK_TEXT_DIR_NONE,
-                                       static_cast<GtkIconLookupFlags>(0)));
+    auto icon_paintable = TakeGObject(gtk_icon_theme_lookup_by_gicon(
+        theme, icon.get(), size, 1, GTK_TEXT_DIR_NONE,
+        static_cast<GtkIconLookupFlags>(0)));
     if (!icon_paintable)
       continue;
 
     auto* paintable = GDK_PAINTABLE(icon_paintable);
     auto* snapshot = gtk_snapshot_new();
     gdk_paintable_snapshot(paintable, snapshot, size, size);
-    ScopedGObject<GskRenderNode> node(gtk_snapshot_free_to_node(snapshot));
+    auto node = TakeGObject(gtk_snapshot_free_to_node(snapshot));
     auto rect = GRAPHENE_RECT_INIT(0, 0, size, size);
-    ScopedGObject<GskRenderer> renderer(gsk_cairo_renderer_new());
-    ScopedGObject<GdkTexture> texture(
-        gsk_renderer_render_texture(renderer, node, &rect));
+    auto renderer = TakeGObject(gsk_cairo_renderer_new());
+    auto texture =
+        TakeGObject(gsk_renderer_render_texture(renderer, node, &rect));
 
     SkBitmap bitmap;
     bitmap.allocN32Pixels(size, size);
@@ -533,8 +525,8 @@
 
     CairoSurface surface(bitmap);
     cairo_t* cr = surface.cairo();
-    gtk_render_icon(gtk_widget_get_style_context(fake_window_), cr, texture, 0,
-                    0);
+    gtk_render_icon(gtk_widget_get_style_context(GetDummyWindow()), cr, texture,
+                    0, 0);
 #else
     auto icon_info = TakeGObject(gtk_icon_theme_lookup_by_gicon(
         theme, icon.get(), size,
@@ -1057,7 +1049,7 @@
   if (display::Display::HasForceDeviceScaleFactor())
     return display::Display::GetForcedDeviceScaleFactor();
 
-  float scale = gtk_widget_get_scale_factor(fake_window_);
+  float scale = gtk_widget_get_scale_factor(GetDummyWindow());
   DCHECK_GT(scale, 0.0);
 
   auto* settings = gtk_settings_get_default();
diff --git a/ui/gtk/gtk_ui.h b/ui/gtk/gtk_ui.h
index d747c1d..83112ebd 100644
--- a/ui/gtk/gtk_ui.h
+++ b/ui/gtk/gtk_ui.h
@@ -24,7 +24,6 @@
 typedef struct _GtkParamSpec GtkParamSpec;
 typedef struct _GtkSettings GtkSettings;
 typedef struct _GtkStyle GtkStyle;
-typedef struct _GtkWidget GtkWidget;
 
 namespace gtk {
 using ColorMap = std::map<int, SkColor>;
@@ -156,9 +155,6 @@
 
   NativeThemeGtk* native_theme_;
 
-  // A regular GtkWindow.
-  GtkWidget* fake_window_;
-
   // Colors calculated by LoadGtkValues() that are given to the
   // caller while |use_gtk_| is true.
   ColorMap colors_;
diff --git a/ui/gtk/gtk_ui_delegate.h b/ui/gtk/gtk_ui_delegate.h
index 7e7c37b0..983dace 100644
--- a/ui/gtk/gtk_ui_delegate.h
+++ b/ui/gtk/gtk_ui_delegate.h
@@ -11,6 +11,7 @@
 
 using GdkKeymap = struct _GdkKeymap;
 using GtkWindow = struct _GtkWindow;
+using GtkWidget = struct _GtkWidget;
 
 #if BUILDFLAG(GTK_VERSION) == 3
 using GdkWindow = struct _GdkWindow;
@@ -40,8 +41,9 @@
   // Returns the current active instance.
   static GtkUiDelegate* instance();
 
-  // Called when the GtkUi instance initialization process finished.
-  virtual void OnInitialized() = 0;
+  // Called when the GtkUi instance initialization process finished. |widget| is
+  // a dummy window passed in for context.
+  virtual void OnInitialized(GtkWidget* widget) = 0;
 
   // Gets the GdkKeymap instance, which is used to translate KeyEvents into
   // GdkEvents before filtering them through GtkIM API.
diff --git a/ui/gtk/gtk_util.cc b/ui/gtk/gtk_util.cc
index b40873f..9a2fbaa 100644
--- a/ui/gtk/gtk_util.cc
+++ b/ui/gtk/gtk_util.cc
@@ -87,39 +87,12 @@
                                       << ui::kPropertyKeyboardIBusFlagOffset);
 }
 
-GdkModifierType GetGdkKeyEventState(ui::KeyEvent key_event) {
-  // ui::KeyEvent uses a normalized modifier state which is not respected by
-  // Gtk, so we need to get the state from the display backend. Gtk instead
-  // follows the X11 spec in which the state of a key event is expected to be
-  // the mask of modifier keys _prior_ to this event. Some IMEs rely on this
-  // behavior. See https://crbug.com/1086946#c11.
-
-  GdkModifierType state = GetIbusFlags(key_event);
-  if (key_event.key_code() != ui::VKEY_PROCESSKEY) {
-    // This is an synthetized event when |key_code| is VKEY_PROCESSKEY.
-    // In such a case there is no event being dispatching in the display
-    // backend.
-    state = static_cast<GdkModifierType>(
-        state | ui::GtkUiDelegate::instance()->GetGdkKeyState());
-  }
-
-  return state;
-}
-
-int GetKeyEventProperty(const ui::KeyEvent& key_event,
-                        const char* property_key) {
-  auto* properties = key_event.properties();
-  if (!properties)
-    return 0;
-  auto it = properties->find(property_key);
-  DCHECK(it == properties->end() || it->second.size() == 1);
-  return (it != properties->end()) ? it->second[0] : 0;
-}
-
+#if BUILDFLAG(GTK_VERSION) < 4
 float GetDeviceScaleFactor() {
   views::LinuxUI* linux_ui = views::LinuxUI::instance();
   return linux_ui ? linux_ui->GetDeviceScaleFactor() : 1;
 }
+#endif
 
 GtkCssContext AppendCssNodeToStyleContextImpl(
     GtkCssContext context,
@@ -133,7 +106,7 @@
                             ? g_object_new(GTK_TYPE_WIDGET, nullptr)
                             : g_object_new(GTK_TYPE_WIDGET, "css-name",
                                            object_name.c_str(), nullptr);
-  ScopedGObject<GtkWidget> widget(GTK_WIDGET(widget_object));
+  auto widget = TakeGObject(GTK_WIDGET(widget_object));
 
   if (!name.empty())
     gtk_widget_set_name(widget, name.c_str());
@@ -194,6 +167,16 @@
 #endif
 }
 
+GtkWidget* CreateDummyWindow() {
+#if BUILDFLAG(GTK_VERSION) >= 4
+  GtkWidget* window = gtk_window_new();
+#else
+  GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#endif
+  gtk_widget_realize(window);
+  return window;
+}
+
 }  // namespace
 
 void GtkInitFromCommandLine(const base::CommandLine& command_line) {
@@ -412,8 +395,10 @@
           object_name = t.token();
           break;
         case CSS_TYPE: {
+#if BUILDFLAG(GTK_VERSION) < 4
           gtype = g_type_from_name(t.token().c_str());
           DCHECK(gtype);
+#endif
           break;
         }
         case CSS_CLASS:
@@ -544,17 +529,18 @@
   auto context = GetStyleContextFromCss(css_selector);
   if (GtkCheckVersion(3, 20))
     return GetBgColorFromStyleContext(context);
+#if BUILDFLAG(GTK_VERSION) < 4
   // This is verbatim how Gtk gets the selection color on versions before 3.20.
   GdkRGBA selection_color;
   G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
-#if GTK_CHECK_VERSION(3, 90, 0)
-  gtk_style_context_get_background_color(context, &selection_color);
-#else
   gtk_style_context_get_background_color(
       context, gtk_style_context_get_state(context), &selection_color);
-#endif
   G_GNUC_END_IGNORE_DEPRECATIONS;
   return GdkRgbaToSkColor(selection_color);
+#else
+  NOTREACHED();
+  return SK_ColorRED;
+#endif
 }
 
 bool ContextHasClass(GtkCssContext context, const std::string& style_class) {
@@ -572,10 +558,14 @@
     return GetFgColor(css_selector);
 
   auto context = GetStyleContextFromCss(css_selector);
+  bool horizontal = ContextHasClass(context, "horizontal");
+
   int w = 1, h = 1;
   GtkBorder border, padding;
 #if GTK_CHECK_VERSION(3, 90, 0)
-  gtk_style_context_get(context, "min-width", &w, "min-height", &h, nullptr);
+  auto size = GetSeparatorSize(horizontal);
+  w = size.width();
+  h = size.height();
   gtk_style_context_get_border(context, &border);
   gtk_style_context_get_padding(context, &padding);
 #else
@@ -588,7 +578,6 @@
   w += border.left + padding.left + padding.right + border.right;
   h += border.top + padding.top + padding.bottom + border.bottom;
 
-  bool horizontal = ContextHasClass(context, "horizontal");
   if (horizontal) {
     w = 24;
     h = std::max(h, 1);
@@ -619,6 +608,36 @@
   return state | ((group & 0x3) << 13);
 }
 
+int GetKeyEventProperty(const ui::KeyEvent& key_event,
+                        const char* property_key) {
+  auto* properties = key_event.properties();
+  if (!properties)
+    return 0;
+  auto it = properties->find(property_key);
+  DCHECK(it == properties->end() || it->second.size() == 1);
+  return (it != properties->end()) ? it->second[0] : 0;
+}
+
+GdkModifierType GetGdkKeyEventState(const ui::KeyEvent& key_event) {
+  // ui::KeyEvent uses a normalized modifier state which is not respected by
+  // Gtk, so we need to get the state from the display backend. Gtk instead
+  // follows the X11 spec in which the state of a key event is expected to be
+  // the mask of modifier keys _prior_ to this event. Some IMEs rely on this
+  // behavior. See https://crbug.com/1086946#c11.
+
+  GdkModifierType state = GetIbusFlags(key_event);
+  if (key_event.key_code() != ui::VKEY_PROCESSKEY) {
+    // This is an synthetized event when |key_code| is VKEY_PROCESSKEY.
+    // In such a case there is no event being dispatching in the display
+    // backend.
+    state = static_cast<GdkModifierType>(
+        state | ui::GtkUiDelegate::instance()->GetGdkKeyState());
+  }
+
+  return state;
+}
+
+#if BUILDFLAG(GTK_VERSION) < 4
 GdkEvent* GdkEventFromKeyEvent(const ui::KeyEvent& key_event) {
   GdkEventType event_type =
       key_event.type() == ui::ET_KEY_PRESSED ? GDK_KEY_PRESS : GDK_KEY_RELEASE;
@@ -653,6 +672,7 @@
 
   return gdk_event;
 }
+#endif
 
 GtkIconTheme* GetDefaultIconTheme() {
 #if GTK_CHECK_VERSION(3, 90, 0)
@@ -670,4 +690,17 @@
 #endif
 }
 
+GtkWidget* GetDummyWindow() {
+  static GtkWidget* window = CreateDummyWindow();
+  return window;
+}
+
+gfx::Size GetSeparatorSize(bool horizontal) {
+  auto widget = TakeGObject(gtk_separator_new(
+      horizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL));
+  GtkRequisition natural_size;
+  gtk_widget_get_preferred_size(widget, nullptr, &natural_size);
+  return {natural_size.width, natural_size.height};
+}
+
 }  // namespace gtk
diff --git a/ui/gtk/gtk_util.h b/ui/gtk/gtk_util.h
index 52ce471..4e079dc 100644
--- a/ui/gtk/gtk_util.h
+++ b/ui/gtk/gtk_util.h
@@ -228,17 +228,28 @@
 // https://gitlab.freedesktop.org/xorg/proto/xorgproto/blob/master/include/X11/extensions/XKB.h#L372
 int BuildXkbStateFromGdkEvent(unsigned int state, unsigned char group);
 
+int GetKeyEventProperty(const ui::KeyEvent& key_event,
+                        const char* property_key);
+
+GdkModifierType GetGdkKeyEventState(const ui::KeyEvent& key_event);
+
+#if BUILDFLAG(GTK_VERSION) < 4
 // Translates |key_event| into a GdkEvent. GdkEvent::key::window is the only
 // field not set by this function, callers must set it, as the way for
 // retrieving it may vary depending on the event being processed. E.g: for IME
 // Context impl, X11 window XID is obtained through Event::target() which is
 // root aura::Window targeted by that key event.
 GdkEvent* GdkEventFromKeyEvent(const ui::KeyEvent& key_event);
+#endif
 
 GtkIconTheme* GetDefaultIconTheme();
 
 void GtkWindowDestroy(GtkWidget* widget);
 
+GtkWidget* GetDummyWindow();
+
+gfx::Size GetSeparatorSize(bool horizontal);
+
 }  // namespace gtk
 
 #endif  // UI_GTK_GTK_UTIL_H_
diff --git a/ui/gtk/input_method_context_impl_gtk.cc b/ui/gtk/input_method_context_impl_gtk.cc
index e40291f..74b8922ad 100644
--- a/ui/gtk/input_method_context_impl_gtk.cc
+++ b/ui/gtk/input_method_context_impl_gtk.cc
@@ -15,6 +15,7 @@
 #include "ui/base/ime/linux/composition_text_util_pango.h"
 #include "ui/base/ime/text_input_client.h"
 #include "ui/events/event.h"
+#include "ui/events/event_utils.h"
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/native_widget_types.h"
@@ -27,6 +28,7 @@
 
 namespace {
 
+#if BUILDFLAG(GTK_VERSION) < 4
 // Get IME KeyEvent's target window. Assumes root aura::Window is set to
 // Event::target(), otherwise returns null.
 GdkWindow* GetTargetWindow(const ui::KeyEvent& key_event) {
@@ -54,17 +56,14 @@
   event->key.window = target_window;
   return event;
 }
+#endif
 
 }  // namespace
 
 InputMethodContextImplGtk::InputMethodContextImplGtk(
     ui::LinuxInputMethodContextDelegate* delegate,
     bool is_simple)
-    : delegate_(delegate),
-      is_simple_(is_simple),
-      has_focus_(false),
-      gtk_context_(nullptr),
-      gdk_last_set_client_window_(nullptr) {
+    : delegate_(delegate), is_simple_(is_simple) {
   CHECK(delegate_);
 
   gtk_context_ =
@@ -80,6 +79,10 @@
   // TODO(shuchen): Handle operations on surrounding text.
   // "delete-surrounding" and "retrieve-surrounding" signals should be
   // handled.
+
+#if BUILDFLAG(GTK_VERSION) >= 4
+  gtk_im_context_set_client_widget(gtk_context_, GetDummyWindow());
+#endif
 }
 
 InputMethodContextImplGtk::~InputMethodContextImplGtk() {
@@ -95,6 +98,7 @@
   if (!gtk_context_)
     return false;
 
+#if BUILDFLAG(GTK_VERSION) < 4
   GdkEvent* event = GdkEventFromImeKeyEvent(key_event);
   if (!event) {
     LOG(ERROR) << "Cannot translate a Keyevent to a GdkEvent.";
@@ -108,14 +112,19 @@
   }
 
   SetContextClientWindow(target_window);
+#endif
 
   // Convert the last known caret bounds relative to the screen coordinates
   // to a GdkRectangle relative to the client window.
   gint win_x = 0;
   gint win_y = 0;
+#if BUILDFLAG(GTK_VERSION) >= 4
+  gint factor = gtk_widget_get_scale_factor(GetDummyWindow());
+#else
   gdk_window_get_origin(target_window, &win_x, &win_y);
-
   gint factor = gdk_window_get_scale_factor(target_window);
+#endif
+
   gint caret_x = last_caret_bounds_.x() / factor;
   gint caret_y = last_caret_bounds_.y() / factor;
   gint caret_w = last_caret_bounds_.width() / factor;
@@ -124,10 +133,29 @@
   GdkRectangle gdk_rect = {caret_x - win_x, caret_y - win_y, caret_w, caret_h};
   gtk_im_context_set_cursor_location(gtk_context_, &gdk_rect);
 
+#if BUILDFLAG(GTK_VERSION) >= 4
+  // In GTK4, clients can no longer create or modify events.  This makes using
+  // the gtk_im_context_filter_keypress() API impossible.  Fortunately, an
+  // alternative API called gtk_im_context_filter_key() was added for clients
+  // that would have needed to construct their own event.  The parameters to
+  // the new API are just a deconstructed version of a KeyEvent.
+  bool press = key_event.type() == ui::ET_KEY_PRESSED;
+  auto* surface =
+      gtk_native_get_surface(gtk_widget_get_native(GetDummyWindow()));
+  auto* device = gdk_seat_get_keyboard(
+      gdk_display_get_default_seat(gdk_display_get_default()));
+  auto time = (key_event.time_stamp() - base::TimeTicks()).InMilliseconds();
+  auto keycode = GetKeyEventProperty(key_event, ui::kPropertyKeyboardHwKeyCode);
+  auto state = GetGdkKeyEventState(key_event);
+  auto group = GetKeyEventProperty(key_event, ui::kPropertyKeyboardGroup);
+  return gtk_im_context_filter_key(gtk_context_, press, surface, device, time,
+                                   keycode, state, group);
+#else
   const bool handled =
       gtk_im_context_filter_keypress(gtk_context_, &event->key);
   gdk_event_free(event);
   return handled;
+#endif
 }
 
 void InputMethodContextImplGtk::Reset() {
@@ -211,20 +239,17 @@
   delegate_->OnPreeditStart();
 }
 
+#if BUILDFLAG(GTK_VERSION) < 4
 void InputMethodContextImplGtk::SetContextClientWindow(GdkWindow* window) {
   if (window == gdk_last_set_client_window_)
     return;
-#if GTK_CHECK_VERSION(3, 90, 0)
-  asdf;
-  gtk_im_context_set_client_widget(gtk_context_, GTK_WIDGET(window));
-#else
   gtk_im_context_set_client_window(gtk_context_, window);
-#endif
 
   // Prevent leaks when overriding last client window
   if (gdk_last_set_client_window_)
     g_object_unref(gdk_last_set_client_window_);
   gdk_last_set_client_window_ = window;
 }
+#endif
 
 }  // namespace gtk
diff --git a/ui/gtk/input_method_context_impl_gtk.h b/ui/gtk/input_method_context_impl_gtk.h
index 874ddba..08a5c3a 100644
--- a/ui/gtk/input_method_context_impl_gtk.h
+++ b/ui/gtk/input_method_context_impl_gtk.h
@@ -62,23 +62,27 @@
                      OnPreeditStart,
                      GtkIMContext*);
 
+#if BUILDFLAG(GTK_VERSION) < 4
   void SetContextClientWindow(GdkWindow* window);
+#endif
 
   // A set of callback functions.  Must not be nullptr.
-  ui::LinuxInputMethodContextDelegate* delegate_;
+  ui::LinuxInputMethodContextDelegate* const delegate_;
 
   // Input method context type flag.
   //   - true if it supports table-based input methods
   //   - false if it supports multiple, loadable input methods
-  bool is_simple_;
+  const bool is_simple_;
 
   // Keeps track of current focus state.
-  bool has_focus_;
+  bool has_focus_ = false;
 
   // IME's input GTK context.
-  GtkIMContext* gtk_context_;
+  GtkIMContext* gtk_context_ = nullptr;
 
-  gpointer gdk_last_set_client_window_;
+#if BUILDFLAG(GTK_VERSION) < 4
+  gpointer gdk_last_set_client_window_ = nullptr;
+#endif
 
   // Last known caret bounds relative to the screen coordinates.
   gfx::Rect last_caret_bounds_;
diff --git a/ui/gtk/native_theme_gtk.cc b/ui/gtk/native_theme_gtk.cc
index 5882e18..8bcc5c17 100644
--- a/ui/gtk/native_theme_gtk.cc
+++ b/ui/gtk/native_theme_gtk.cc
@@ -410,6 +410,8 @@
 }
 
 NativeThemeGtk::NativeThemeGtk() {
+  // g_type_from_name() is only used in GTK3.
+#if BUILDFLAG(GTK_VERSION) < 4
   // These types are needed by g_type_from_name(), but may not be registered at
   // this point.  We need the g_type_class magic to make sure the compiler
   // doesn't optimize away this code.
@@ -440,6 +442,7 @@
   auto model =
       TakeGObject(GTK_TREE_MODEL(gtk_tree_store_new(1, G_TYPE_STRING)));
   auto combo = TakeGObject(gtk_combo_box_new_with_model(model));
+#endif
 
   OnThemeChanged(gtk_settings_get_default(), nullptr);
 }
@@ -450,15 +453,28 @@
 
 void NativeThemeGtk::SetThemeCssOverride(ScopedCssProvider provider) {
   if (theme_css_override_) {
+#if BUILDFLAG(GTK_VERSION) >= 4
+    gtk_style_context_remove_provider_for_display(
+        gdk_display_get_default(),
+        GTK_STYLE_PROVIDER(theme_css_override_.get()));
+#else
     gtk_style_context_remove_provider_for_screen(
         gdk_screen_get_default(),
         GTK_STYLE_PROVIDER(theme_css_override_.get()));
+#endif
   }
   theme_css_override_ = std::move(provider);
   if (theme_css_override_) {
+#if BUILDFLAG(GTK_VERSION) >= 4
+    gtk_style_context_add_provider_for_display(
+        gdk_display_get_default(),
+        GTK_STYLE_PROVIDER(theme_css_override_.get()),
+        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+#else
     gtk_style_context_add_provider_for_screen(
         gdk_screen_get_default(), GTK_STYLE_PROVIDER(theme_css_override_.get()),
         GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+#endif
   }
 }
 
@@ -559,16 +575,16 @@
 
   switch (direction) {
     case kScrollbarUpArrow:
-      gtk_style_context_add_class(context, GTK_STYLE_CLASS_TOP);
+      gtk_style_context_add_class(context, "top");
       break;
     case kScrollbarRightArrow:
-      gtk_style_context_add_class(context, GTK_STYLE_CLASS_RIGHT);
+      gtk_style_context_add_class(context, "right");
       break;
     case kScrollbarDownArrow:
-      gtk_style_context_add_class(context, GTK_STYLE_CLASS_BOTTOM);
+      gtk_style_context_add_class(context, "bottom");
       break;
     case kScrollbarLeftArrow:
-      gtk_style_context_add_class(context, GTK_STYLE_CLASS_LEFT);
+      gtk_style_context_add_class(context, "left");
       break;
     default:
       NOTREACHED();
@@ -675,7 +691,7 @@
     gtk_style_context_get_margin(context, &margin);
     gtk_style_context_get_border(context, &border);
     gtk_style_context_get_padding(context, &padding);
-    gtk_style_context_get(context, "min-height", &min_height, nullptr);
+    min_height = GetSeparatorSize(true).height();
 #else
     GtkStateFlags state = gtk_style_context_get_state(context);
     gtk_style_context_get_margin(context, state, &margin);
diff --git a/ui/gtk/nav_button_provider_gtk.cc b/ui/gtk/nav_button_provider_gtk.cc
index a9498d6d..40370bf 100644
--- a/ui/gtk/nav_button_provider_gtk.cc
+++ b/ui/gtk/nav_button_provider_gtk.cc
@@ -122,7 +122,7 @@
     NavButtonIcon* icon = nullptr) {
   const char* icon_name = IconNameFromButtonType(type);
 #if GTK_CHECK_VERSION(3, 90, 0)
-  ScopedGObject<GtkIconPaintable> icon_paintable(gtk_icon_theme_lookup_icon(
+  auto icon_paintable = TakeGObject(gtk_icon_theme_lookup_icon(
       GetDefaultIconTheme(), icon_name, nullptr, kNavButtonIconSize, scale,
       GTK_TEXT_DIR_NONE, static_cast<GtkIconLookupFlags>(0)));
   auto* paintable = GDK_PAINTABLE(icon_paintable);
@@ -131,10 +131,10 @@
   if (icon) {
     auto* snapshot = gtk_snapshot_new();
     gdk_paintable_snapshot(paintable, snapshot, width, height);
-    ScopedGObject<GskRenderNode> node(gtk_snapshot_free_to_node(snapshot));
+    auto node = TakeGObject(gtk_snapshot_free_to_node(snapshot));
     auto rect = GRAPHENE_RECT_INIT(0, 0, width, height);
-    ScopedGObject<GskRenderer> renderer(gsk_cairo_renderer_new());
-    *icon = NavButtonIcon(gsk_renderer_render_texture(renderer, node, &rect));
+    auto renderer = TakeGObject(gsk_cairo_renderer_new());
+    *icon = TakeGObject(gsk_renderer_render_texture(renderer, node, &rect));
   }
   return {width, height};
 #else
@@ -260,8 +260,7 @@
     cairo_pattern_t* cr_pattern = nullptr;
     cairo_surface_t* cr_surface = nullptr;
 #if GTK_CHECK_VERSION(3, 90, 0)
-    gtk_style_context_get(button_context, GTK_STYLE_PROPERTY_BACKGROUND_IMAGE,
-                          &cr_pattern, nullptr);
+    NOTIMPLEMENTED_LOG_ONCE();
 #else
     gtk_style_context_get(button_context, button_state,
                           GTK_STYLE_PROPERTY_BACKGROUND_IMAGE, &cr_pattern,
diff --git a/ui/gtk/select_file_dialog_impl_gtk.cc b/ui/gtk/select_file_dialog_impl_gtk.cc
index 3b42b82..633e362 100644
--- a/ui/gtk/select_file_dialog_impl_gtk.cc
+++ b/ui/gtk/select_file_dialog_impl_gtk.cc
@@ -62,7 +62,7 @@
 void GtkFileChooserSetCurrentFolder(GtkFileChooser* dialog,
                                     const base::FilePath& path) {
 #if GTK_CHECK_VERSION(3, 90, 0)
-  ScopedGObject<GFile> file(g_file_new_for_path(path.value().c_str()));
+  auto file = TakeGObject(g_file_new_for_path(path.value().c_str()));
   gtk_file_chooser_set_current_folder(dialog, file, nullptr);
 #else
   gtk_file_chooser_set_current_folder(dialog, path.value().c_str());
@@ -72,7 +72,7 @@
 void GtkFileChooserSetFilename(GtkFileChooser* dialog,
                                const base::FilePath& path) {
 #if GTK_CHECK_VERSION(3, 90, 0)
-  ScopedGObject<GFile> file(g_file_new_for_path(path.value().c_str()));
+  auto file = TakeGObject(g_file_new_for_path(path.value().c_str()));
   gtk_file_chooser_set_file(dialog, file, nullptr);
 #else
   gtk_file_chooser_set_filename(dialog, path.value().c_str());
@@ -83,8 +83,8 @@
   GtkFileFilter* selected_filter =
       gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
 #if GTK_CHECK_VERSION(3, 90, 0)
-  ScopedGObject<GListModel> filters(
-      gtk_file_chooser_get_filters(GTK_FILE_CHOOSER(dialog)));
+  auto filters =
+      TakeGObject(gtk_file_chooser_get_filters(GTK_FILE_CHOOSER(dialog)));
   int size = g_list_model_get_n_items(filters);
   int idx = -1;
   for (; idx < size; ++idx) {
@@ -102,8 +102,7 @@
 std::string GtkFileChooserGetFilename(GtkWidget* dialog) {
   const char* filename = nullptr;
 #if GTK_CHECK_VERSION(3, 90, 0)
-  ScopedGObject<GFile> file(
-      gtk_file_chooser_get_file(GTK_FILE_CHOOSER(dialog)));
+  auto file = TakeGObject(gtk_file_chooser_get_file(GTK_FILE_CHOOSER(dialog)));
   if (file)
     filename = g_file_peek_path(file);
 #else
@@ -120,11 +119,11 @@
 std::vector<base::FilePath> GtkFileChooserGetFilenames(GtkWidget* dialog) {
   std::vector<base::FilePath> filenames_fp;
 #if GTK_CHECK_VERSION(3, 90, 0)
-  ScopedGObject<GListModel> files(
-      gtk_file_chooser_get_files(GTK_FILE_CHOOSER(dialog)));
+  auto files =
+      TakeGObject(gtk_file_chooser_get_files(GTK_FILE_CHOOSER(dialog)));
   auto size = g_list_model_get_n_items(files);
   for (unsigned int i = 0; i < size; ++i) {
-    ScopedGObject<GFile> file(G_FILE(g_list_model_get_object(files, i)));
+    auto file = TakeGObject(G_FILE(g_list_model_get_object(files, i)));
     filenames_fp.emplace_back(g_file_peek_path(file));
   }
 #else
diff --git a/ui/gtk/x/gtk_event_loop_x11.cc b/ui/gtk/x/gtk_event_loop_x11.cc
index bfef18dc..7e1c01dc 100644
--- a/ui/gtk/x/gtk_event_loop_x11.cc
+++ b/ui/gtk/x/gtk_event_loop_x11.cc
@@ -8,7 +8,6 @@
 #include <xcb/xcb.h>
 #include <xcb/xproto.h>
 
-#include "base/memory/singleton.h"
 #include "ui/base/x/x11_util.h"
 #include "ui/gfx/x/event.h"
 
@@ -80,43 +79,7 @@
 #endif
 }
 
-}  // namespace
-
-// static
-GtkEventLoopX11* GtkEventLoopX11::EnsureInstance() {
-  return base::Singleton<GtkEventLoopX11>::get();
-}
-
-GtkEventLoopX11::GtkEventLoopX11() {
-  gdk_event_handler_set(DispatchGdkEvent, nullptr, nullptr);
-}
-
-GtkEventLoopX11::~GtkEventLoopX11() {
-  gdk_event_handler_set(reinterpret_cast<GdkEventFunc>(gtk_main_do_event),
-                        nullptr, nullptr);
-}
-
-// static
-void GtkEventLoopX11::DispatchGdkEvent(GdkEvent* gdk_event, gpointer) {
-#if GTK_CHECK_VERSION(3, 90, 0)
-  auto event_type = gdk_event_get_event_type(gdk_event);
-#else
-  auto event_type = gdk_event->type;
-#endif
-  switch (event_type) {
-    case GDK_KEY_PRESS:
-    case GDK_KEY_RELEASE:
-      ProcessGdkEventKey(gdk_event);
-      break;
-    default:
-      break;  // Do nothing.
-  }
-
-  gtk_main_do_event(gdk_event);
-}
-
-// static
-void GtkEventLoopX11::ProcessGdkEventKey(GdkEvent* gdk_event) {
+void ProcessGdkEvent(GdkEvent* gdk_event) {
   // This function translates GdkEventKeys into XKeyEvents and puts them to
   // the X event queue.
   //
@@ -131,10 +94,57 @@
   // corresponding key event in the X event queue.  So we have to handle this
   // case.  ibus-gtk is used through gtk-immodule to support IMEs.
 
+#if GTK_CHECK_VERSION(3, 90, 0)
+  auto event_type = gdk_event_get_event_type(gdk_event);
+#else
+  auto event_type = gdk_event->type;
+#endif
+  switch (event_type) {
+    case GDK_KEY_PRESS:
+    case GDK_KEY_RELEASE:
+      break;
+    default:
+      return;
+  }
+
   // We want to process the gtk event; mapped to an X11 event immediately
   // otherwise if we put it back on the queue we may get items out of order.
   x11::Connection::Get()->DispatchEvent(
       x11::Event{ConvertGdkEventToKeyEvent(gdk_event)});
 }
 
+}  // namespace
+
+GtkEventLoopX11::GtkEventLoopX11(GtkWidget* widget) {
+#if BUILDFLAG(GTK_VERSION) >= 4
+  surface_ = gtk_native_get_surface(gtk_widget_get_native(widget));
+  signal_id_ =
+      g_signal_connect(surface_, "event", G_CALLBACK(OnEventThunk), this);
+#else
+  gdk_event_handler_set(DispatchGdkEvent, nullptr, nullptr);
+#endif
+}
+
+GtkEventLoopX11::~GtkEventLoopX11() {
+#if BUILDFLAG(GTK_VERSION) >= 4
+  g_signal_handler_disconnect(surface_, signal_id_);
+#else
+  gdk_event_handler_set(reinterpret_cast<GdkEventFunc>(gtk_main_do_event),
+                        nullptr, nullptr);
+#endif
+}
+
+#if BUILDFLAG(GTK_VERSION) >= 4
+gboolean GtkEventLoopX11::OnEvent(GdkEvent* gdk_event) {
+  ProcessGdkEvent(gdk_event);
+  return false;
+}
+#else
+// static
+void GtkEventLoopX11::DispatchGdkEvent(GdkEvent* gdk_event, gpointer) {
+  ProcessGdkEvent(gdk_event);
+  gtk_main_do_event(gdk_event);
+}
+#endif
+
 }  // namespace ui
diff --git a/ui/gtk/x/gtk_event_loop_x11.h b/ui/gtk/x/gtk_event_loop_x11.h
index baa53a5b..91cbe124 100644
--- a/ui/gtk/x/gtk_event_loop_x11.h
+++ b/ui/gtk/x/gtk_event_loop_x11.h
@@ -6,31 +6,30 @@
 #define UI_GTK_X_GTK_EVENT_LOOP_X11_H_
 
 #include <gdk/gdk.h>
+#include <gtk/gtk.h>
 
 #include "ui/base/glib/glib_integers.h"
-
-namespace base {
-template <typename Type>
-struct DefaultSingletonTraits;
-}
+#include "ui/base/glib/glib_signal.h"
+#include "ui/gtk/gtk_buildflags.h"
 
 namespace ui {
 
 class GtkEventLoopX11 {
  public:
-  static GtkEventLoopX11* EnsureInstance();
+  explicit GtkEventLoopX11(GtkWidget* widget);
+  ~GtkEventLoopX11();
 
   GtkEventLoopX11(const GtkEventLoopX11&) = delete;
   GtkEventLoopX11& operator=(const GtkEventLoopX11&) = delete;
 
  private:
-  friend struct base::DefaultSingletonTraits<GtkEventLoopX11>;
-
-  GtkEventLoopX11();
-  ~GtkEventLoopX11();
-
+#if BUILDFLAG(GTK_VERSION) >= 4
+  CHROMEG_CALLBACK_0(GtkEventLoopX11, gboolean, OnEvent, GdkEvent*);
+  GdkSurface* surface_ = nullptr;
+  gulong signal_id_ = 0;
+#else
   static void DispatchGdkEvent(GdkEvent* gdk_event, gpointer);
-  static void ProcessGdkEventKey(GdkEvent* gdk_event);
+#endif
 };
 
 }  // namespace ui
diff --git a/ui/gtk/x/gtk_ui_delegate_x11.cc b/ui/gtk/x/gtk_ui_delegate_x11.cc
index 2160c854..b1bd174 100644
--- a/ui/gtk/x/gtk_ui_delegate_x11.cc
+++ b/ui/gtk/x/gtk_ui_delegate_x11.cc
@@ -25,7 +25,11 @@
 GdkWindow* gdk_x11_window_lookup_for_display(GdkDisplay* display,
                                              unsigned long window);
 
+#if BUILDFLAG(GTK_VERSION) >= 4
+unsigned long gdk_x11_surface_get_xid(GdkSurface* surface);
+#else
 unsigned long gdk_x11_window_get_xid(GdkWindow* window);
+#endif
 }
 
 namespace ui {
@@ -42,9 +46,10 @@
 
 GtkUiDelegateX11::~GtkUiDelegateX11() = default;
 
-void GtkUiDelegateX11::OnInitialized() {
+void GtkUiDelegateX11::OnInitialized(GtkWidget* widget) {
   // Ensure the singleton instance of GtkEventLoopX11 is created and started.
-  GtkEventLoopX11::EnsureInstance();
+  if (!event_loop_)
+    event_loop_ = std::make_unique<GtkEventLoopX11>(widget);
 
   // GTK sets an Xlib error handler that exits the process on any async errors.
   // We don't want this behavior, so reset the error handler to something that
@@ -53,10 +58,20 @@
 }
 
 GdkKeymap* GtkUiDelegateX11::GetGdkKeymap() {
+#if BUILDFLAG(GTK_VERSION) >= 4
+  NOTREACHED();
+  return nullptr;
+#else
   return gdk_keymap_get_for_display(GetGdkDisplay());
+#endif
 }
 
 GdkWindow* GtkUiDelegateX11::GetGdkWindow(gfx::AcceleratedWidget window_id) {
+#if BUILDFLAG(GTK_VERSION) >= 4
+  // GTK4 dropped support for foreign windows.
+  NOTIMPLEMENTED_LOG_ONCE();
+  return nullptr;
+#else
   GdkDisplay* display = GetGdkDisplay();
   GdkWindow* gdk_window = gdk_x11_window_lookup_for_display(
       display, static_cast<uint32_t>(window_id));
@@ -66,11 +81,17 @@
     gdk_window = gdk_x11_window_foreign_new_for_display(
         display, static_cast<uint32_t>(window_id));
   return gdk_window;
+#endif
 }
 
 bool GtkUiDelegateX11::SetGdkWindowTransientFor(GdkWindow* window,
                                                 gfx::AcceleratedWidget parent) {
+#if BUILDFLAG(GTK_VERSION) >= 4
+  auto x11_window = static_cast<x11::Window>(gdk_x11_surface_get_xid(
+      gtk_native_get_surface(gtk_widget_get_native(GTK_WIDGET(window)))));
+#else
   auto x11_window = static_cast<x11::Window>(gdk_x11_window_get_xid(window));
+#endif
   SetProperty(x11_window, x11::Atom::WM_TRANSIENT_FOR, x11::Atom::WINDOW,
               parent);
 
diff --git a/ui/gtk/x/gtk_ui_delegate_x11.h b/ui/gtk/x/gtk_ui_delegate_x11.h
index e92f9a3c..a33a09e 100644
--- a/ui/gtk/x/gtk_ui_delegate_x11.h
+++ b/ui/gtk/x/gtk_ui_delegate_x11.h
@@ -11,9 +11,12 @@
 #include "ui/gtk/gtk_ui_delegate.h"
 
 using GdkDisplay = struct _GdkDisplay;
+using GtkWidget = struct _GtkWidget;
 
 namespace ui {
 
+class GtkEventLoopX11;
+
 // GtkUiDelegate implementation for desktop Linux X11 backends.
 //
 // TODO(crbug.com/1002674): For now, this is used by both Aura (legacy) and
@@ -27,7 +30,7 @@
   ~GtkUiDelegateX11() override;
 
   // GtkUiDelegate:
-  void OnInitialized() override;
+  void OnInitialized(GtkWidget* widget) override;
   GdkKeymap* GetGdkKeymap() override;
   GdkWindow* GetGdkWindow(gfx::AcceleratedWidget window_id) override;
   bool SetGdkWindowTransientFor(GdkWindow* window,
@@ -41,6 +44,7 @@
 
   x11::Connection* const connection_;
   GdkDisplay* display_ = nullptr;
+  std::unique_ptr<GtkEventLoopX11> event_loop_;
 };
 
 }  // namespace ui
diff --git a/ui/native_theme/native_theme.cc b/ui/native_theme/native_theme.cc
index 2f4d54a..3ee6518 100644
--- a/ui/native_theme/native_theme.cc
+++ b/ui/native_theme/native_theme.cc
@@ -21,10 +21,6 @@
 #include "ui/color/color_provider_manager.h"
 #include "ui/native_theme/common_theme.h"
 
-#if !defined(OS_ANDROID)
-#include "ui/color/color_mixers.h"
-#endif
-
 namespace ui {
 
 namespace {
@@ -222,32 +218,7 @@
     : should_use_dark_colors_(should_use_dark_colors || IsForcedDarkMode()),
       forced_colors_(IsForcedHighContrast()),
       preferred_color_scheme_(CalculatePreferredColorScheme()),
-      preferred_contrast_(CalculatePreferredContrast()) {
-#if !defined(OS_ANDROID)
-  // TODO(http://crbug.com/1057754): Merge this into the ColorProviderManager.
-  static base::OnceClosure color_provider_manager_init = base::BindOnce([]() {
-    ColorProviderManager::Get().SetColorProviderInitializer(base::BindRepeating(
-        [](ColorProvider* provider, ColorProviderManager::ColorMode color_mode,
-           ColorProviderManager::ContrastMode contrast_mode) {
-          const bool dark_mode =
-              color_mode == ColorProviderManager::ColorMode::kDark;
-          const bool high_contrast =
-              contrast_mode == ColorProviderManager::ContrastMode::kHigh;
-          ui::AddCoreDefaultColorMixer(provider, dark_mode, high_contrast);
-          ui::AddNativeCoreColorMixer(provider, dark_mode, high_contrast);
-          ui::AddUiColorMixer(provider);
-          ui::AddNativeUiColorMixer(provider, dark_mode, high_contrast);
-#if defined(OS_MAC)
-          // Always keep this mixer at the last so the system tint will be
-          // applied after getting the proper color.
-          ui::AddSystemTintMixer(provider);
-#endif
-        }));
-  });
-  if (!color_provider_manager_init.is_null())
-    std::move(color_provider_manager_init).Run();
-#endif  // !defined(OS_ANDROID)
-}
+      preferred_contrast_(CalculatePreferredContrast()) {}
 
 NativeTheme::~NativeTheme() = default;
 
@@ -257,15 +228,13 @@
   if (base::FeatureList::IsEnabled(features::kColorProviderRedirection) &&
       AllowColorPipelineRedirection(color_scheme)) {
     if (auto provider_color_id = NativeThemeColorIdToColorId(color_id)) {
-      auto color_mode = (color_scheme == NativeTheme::ColorScheme::kDark)
-                            ? ColorProviderManager::ColorMode::kDark
-                            : ColorProviderManager::ColorMode::kLight;
-      auto contrast_mode =
-          (color_scheme == NativeTheme::ColorScheme::kPlatformHighContrast)
-              ? ColorProviderManager::ContrastMode::kHigh
-              : ColorProviderManager::ContrastMode::kNormal;
       auto* color_provider = ColorProviderManager::Get().GetColorProviderFor(
-          color_mode, contrast_mode);
+          {(color_scheme == NativeTheme::ColorScheme::kDark)
+               ? ColorProviderManager::ColorMode::kDark
+               : ColorProviderManager::ColorMode::kLight,
+           (color_scheme == NativeTheme::ColorScheme::kPlatformHighContrast)
+               ? ColorProviderManager::ContrastMode::kHigh
+               : ColorProviderManager::ContrastMode::kNormal});
       ReportHistogramBooleanUsesColorProvider(true);
       return color_provider->GetColor(provider_color_id.value());
     }
diff --git a/ui/ozone/platform/wayland/host/gtk_ui_delegate_wayland.cc b/ui/ozone/platform/wayland/host/gtk_ui_delegate_wayland.cc
index cb501cb..e6f18db 100644
--- a/ui/ozone/platform/wayland/host/gtk_ui_delegate_wayland.cc
+++ b/ui/ozone/platform/wayland/host/gtk_ui_delegate_wayland.cc
@@ -43,7 +43,7 @@
 
 GtkUiDelegateWayland::~GtkUiDelegateWayland() = default;
 
-void GtkUiDelegateWayland::OnInitialized() {
+void GtkUiDelegateWayland::OnInitialized(GtkWidget* widget) {
   // Nothing to do upon initialization for Wayland.
 }
 
diff --git a/ui/ozone/platform/wayland/host/gtk_ui_delegate_wayland.h b/ui/ozone/platform/wayland/host/gtk_ui_delegate_wayland.h
index d198057b..f05bd6a3 100644
--- a/ui/ozone/platform/wayland/host/gtk_ui_delegate_wayland.h
+++ b/ui/ozone/platform/wayland/host/gtk_ui_delegate_wayland.h
@@ -23,7 +23,7 @@
   ~GtkUiDelegateWayland() override;
 
   // GtkUiDelegate:
-  void OnInitialized() override;
+  void OnInitialized(GtkWidget* widget) override;
   GdkKeymap* GetGdkKeymap() override;
   GdkWindow* GetGdkWindow(gfx::AcceleratedWidget window_id) override;
   bool SetGdkWindowTransientFor(GdkWindow* window,
diff --git a/ui/resources/BUILD.gn b/ui/resources/BUILD.gn
index 4fb9164..1182b83 100644
--- a/ui/resources/BUILD.gn
+++ b/ui/resources/BUILD.gn
@@ -44,9 +44,6 @@
   if (is_chromeos_ash) {
     deps += [ "//ui/chromeos/colors:cros_colors_css" ]
   }
-  if (!is_android && !is_ios) {
-    deps += [ "//third_party/lottie:minify" ]
-  }
 
   outputs = [
     "grit/webui_resources.h",
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index e21249a..945ca33 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -505,6 +505,8 @@
     "//ui/accessibility",
     "//ui/base/clipboard",
     "//ui/base/dragdrop/mojom",
+    "//ui/color",
+    "//ui/color:mixers",
     "//ui/display",
     "//ui/latency",
     "//ui/native_theme",
diff --git a/ui/views/DEPS b/ui/views/DEPS
index 960c549e..ef048658 100644
--- a/ui/views/DEPS
+++ b/ui/views/DEPS
@@ -11,6 +11,7 @@
   "+ui/accessibility",
   "+ui/aura",
   "+ui/base",
+  "+ui/color",
   "+ui/compositor",
   "+ui/display",
   "+ui/events",
diff --git a/ui/views/view.cc b/ui/views/view.cc
index c779118..5782d34a9 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -29,6 +29,7 @@
 #include "ui/base/dragdrop/drag_drop_types.h"
 #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
 #include "ui/base/ime/input_method.h"
+#include "ui/color/color_provider_manager.h"
 #include "ui/compositor/clip_recorder.h"
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/layer.h"
@@ -1198,10 +1199,15 @@
 }
 
 const ui::ThemeProvider* View::GetThemeProvider() const {
-  const Widget* widget = GetWidget();
+  const auto* widget = GetWidget();
   return widget ? widget->GetThemeProvider() : nullptr;
 }
 
+const ui::ColorProvider* View::GetColorProvider() const {
+  const auto* widget = GetWidget();
+  return widget ? widget->GetColorProvider() : nullptr;
+}
+
 const ui::NativeTheme* View::GetNativeTheme() const {
   if (native_theme_)
     return native_theme_;
diff --git a/ui/views/view.h b/ui/views/view.h
index f97ac44..47cdf752 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -66,6 +66,7 @@
 namespace ui {
 struct AXActionData;
 struct AXNodeData;
+class ColorProvider;
 class Compositor;
 class InputMethod;
 class Layer;
@@ -892,6 +893,13 @@
   // Get the theme provider from the parent widget.
   const ui::ThemeProvider* GetThemeProvider() const;
 
+  // Returns the ColorProvider from the ColorProviderManager.
+  ui::ColorProvider* GetColorProvider() {
+    return const_cast<ui::ColorProvider*>(
+        static_cast<const View*>(this)->GetColorProvider());
+  }
+  const ui::ColorProvider* GetColorProvider() const;
+
   // Returns the NativeTheme to use for this View. This calls through to
   // GetNativeTheme() on the Widget this View is in, or provides a default
   // theme if there's no widget, or returns |native_theme_| if that's
@@ -899,7 +907,7 @@
   // override OnThemeChanged().
   ui::NativeTheme* GetNativeTheme() {
     return const_cast<ui::NativeTheme*>(
-        const_cast<const View*>(this)->GetNativeTheme());
+        static_cast<const View*>(this)->GetNativeTheme());
   }
   const ui::NativeTheme* GetNativeTheme() const;
 
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index bbd8a1a..59f7da0 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -20,6 +20,7 @@
 #include "ui/base/ime/input_method.h"
 #include "ui/base/l10n/l10n_font_util.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/color/color_provider_manager.h"
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/layer.h"
 #include "ui/display/screen.h"
@@ -803,6 +804,17 @@
                                               : nullptr;
 }
 
+const ui::ColorProvider* Widget::GetColorProvider() const {
+  auto color_scheme = GetNativeTheme()->GetDefaultSystemColorScheme();
+  return ui::ColorProviderManager::Get().GetColorProviderFor(
+      {(color_scheme == ui::NativeTheme::ColorScheme::kDark)
+           ? ui::ColorProviderManager::ColorMode::kDark
+           : ui::ColorProviderManager::ColorMode::kLight,
+       (color_scheme == ui::NativeTheme::ColorScheme::kPlatformHighContrast)
+           ? ui::ColorProviderManager::ContrastMode::kHigh
+           : ui::ColorProviderManager::ContrastMode::kNormal});
+}
+
 FocusManager* Widget::GetFocusManager() {
   Widget* toplevel_widget = GetTopLevelWidget();
   return toplevel_widget ? toplevel_widget->focus_manager_.get() : nullptr;
diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h
index cd0c29c..ff1d9ab 100644
--- a/ui/views/widget/widget.h
+++ b/ui/views/widget/widget.h
@@ -39,6 +39,7 @@
 
 namespace ui {
 class Accelerator;
+class ColorProvider;
 class Compositor;
 class GestureRecognizer;
 class InputMethod;
@@ -714,10 +715,17 @@
 
   ui::NativeTheme* GetNativeTheme() {
     return const_cast<ui::NativeTheme*>(
-        const_cast<const Widget*>(this)->GetNativeTheme());
+        static_cast<const Widget*>(this)->GetNativeTheme());
   }
   virtual const ui::NativeTheme* GetNativeTheme() const;
 
+  // Returns the ui::ColorProvider associated with this Widget.
+  ui::ColorProvider* GetColorProvider() {
+    return const_cast<ui::ColorProvider*>(
+        static_cast<const Widget*>(this)->GetColorProvider());
+  }
+  const ui::ColorProvider* GetColorProvider() const;
+
   // Returns the FocusManager for this widget.
   // Note that all widgets in a widget hierarchy share the same focus manager.
   FocusManager* GetFocusManager();
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index 6bb3412c..30d635e 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -58,6 +58,11 @@
     deps += [ "//third_party/web-animations-js:build_grdp" ]
     grdp_files += [ "$root_gen_dir/third_party/web-animations-js/web_animations_resources.grdp" ]
   }
+
+  if (!is_android && !is_ios) {
+    deps += [ "//third_party/lottie:build_grdp" ]
+    grdp_files += [ "$root_gen_dir/third_party/lottie/resources.grdp" ]
+  }
 }
 
 group("preprocess") {
diff --git a/ui/webui/resources/cr_components/BUILD.gn b/ui/webui/resources/cr_components/BUILD.gn
index 9b69a386..ae1d058 100644
--- a/ui/webui/resources/cr_components/BUILD.gn
+++ b/ui/webui/resources/cr_components/BUILD.gn
@@ -30,6 +30,7 @@
       "chromeos/multidevice_setup/all_set_2x.svg",
       "chromeos/multidevice_setup/start_setup_icon_1x.png",
       "chromeos/multidevice_setup/start_setup_icon_2x.png",
+      "chromeos/cellular_setup/sim_detect_error.svg",
       "chromeos/cellular_setup/error_1x.png",
       "chromeos/cellular_setup/error_2x.png",
       "chromeos/cellular_setup/final_page_success_1x.png",
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.html
index a79f9b3..0571520 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.html
@@ -27,7 +27,7 @@
         selected="[[selectedPSimPageName_]]"
         selected-item="{{selectedPage_}}">
       <setup-loading-page id="simDetectPage"
-          delegate="[[delegate]]" state="[[getLoadingPageState_(showError_)]]"
+          delegate="[[delegate]]" state="[[getLoadingPageState_(state_)]]"
           loading-message="[[i18n('establishNetworkConnectionMessage')]]">
       </setup-loading-page>
       <provisioning-page id="provisioningPage"
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.js
index 714179c..7bf15bc 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.js
@@ -16,6 +16,7 @@
     STARTING_ACTIVATION: 'starting-activation',
     WAITING_FOR_ACTIVATION_TO_START: 'waiting-for-activation-to-start',
     TIMEOUT_START_ACTIVATION: 'timeout-start-activation',
+    FINAL_TIMEOUT_START_ACTIVATION: 'final-timeout-start-activation',
     WAITING_FOR_PORTAL_TO_LOAD: 'waiting-for-portal-to-load',
     TIMEOUT_PORTAL_LOAD: 'timeout-portal-load',
     WAITING_FOR_USER_PAYMENT: 'waiting-for-user-payment',
@@ -196,13 +197,18 @@
      */
     carrierPortalHandler_: null,
 
-
     /**
      * Whether there was a carrier portal error.
      * @private {boolean}
      */
     didCarrierPortalResultFail_: false,
 
+    /**
+     * The function used to initiate a timer. Can be overwritten in tests.
+     * @private {function(Function, number)}
+     */
+    setTimeoutFunction_: setTimeout.bind(window),
+
     /** @override */
     created() {
       this.cellularSetupRemote_ = cellular_setup.getCellularSetupRemote();
@@ -220,6 +226,7 @@
           resultCode = PSimSetupFlowResult.CANCELLED_COLD_SIM_DEFER;
           break;
         case PSimUIState.TIMEOUT_START_ACTIVATION:
+        case PSimUIState.FINAL_TIMEOUT_START_ACTIVATION:
           resultCode = PSimSetupFlowResult.CANCELLED_NO_SIM;
           break;
         case PSimUIState.WAITING_FOR_PORTAL_TO_LOAD:
@@ -279,6 +286,7 @@
           break;
         case PSimUIState.WAITING_FOR_ACTIVATION_TO_FINISH:
         case PSimUIState.TIMEOUT_FINISH_ACTIVATION:
+        case PSimUIState.FINAL_TIMEOUT_START_ACTIVATION:
           this.fire('exit-cellular-setup');
           break;
         case PSimUIState.TIMEOUT_START_ACTIVATION:
@@ -298,6 +306,15 @@
       return false;
     },
 
+    /**
+     * Sets the function used to initiate a timer.
+     * @param {function(Function, number)}
+     *     timerFunction
+     */
+    setTimerFunctionForTest(timerFunction) {
+      this.setTimeoutFunction_ = timerFunction;
+    },
+
     /** @private */
     updateButtonBarState_() {
       let buttonState;
@@ -333,6 +350,7 @@
           break;
         case PSimUIState.ALREADY_ACTIVATED:
         case PSimUIState.ACTIVATION_FAILURE:
+        case PSimUIState.FINAL_TIMEOUT_START_ACTIVATION:
           this.forwardButtonLabel = this.i18n('done');
           buttonState = {
             backward: cellularSetup.ButtonState.ENABLED,
@@ -389,7 +407,6 @@
     /** @private */
     updateShowError_() {
       switch (this.state_) {
-        case PSimUIState.TIMEOUT_START_ACTIVATION:
         case PSimUIState.TIMEOUT_PORTAL_LOAD:
         case PSimUIState.TIMEOUT_FINISH_ACTIVATION:
         case PSimUIState.ACTIVATION_FAILURE:
@@ -408,6 +425,7 @@
         case PSimUIState.STARTING_ACTIVATION:
         case PSimUIState.WAITING_FOR_ACTIVATION_TO_START:
         case PSimUIState.TIMEOUT_START_ACTIVATION:
+        case PSimUIState.FINAL_TIMEOUT_START_ACTIVATION:
           this.selectedPSimPageName_ = PSimPageName.SIM_DETECT;
           return;
         case PSimUIState.WAITING_FOR_PORTAL_TO_LOAD:
@@ -437,7 +455,7 @@
       const timeoutMs = getTimeoutMsForPSimUIState(this.state_);
       if (timeoutMs !== null) {
         this.currentTimeoutId_ =
-            setTimeout(this.onTimeout_.bind(this), timeoutMs);
+            this.setTimeoutFunction_(this.onTimeout_.bind(this), timeoutMs);
       }
 
       if (this.state_ === PSimUIState.STARTING_ACTIVATION) {
@@ -457,7 +475,7 @@
           if (this.startActivationAttempts_ < MAX_START_ACTIVATION_ATTEMPTS) {
             this.state_ = PSimUIState.TIMEOUT_START_ACTIVATION;
           } else {
-            this.state_ = PSimUIState.ACTIVATION_FAILURE;
+            this.state_ = PSimUIState.FINAL_TIMEOUT_START_ACTIVATION;
           }
           return;
         case PSimUIState.WAITING_FOR_PORTAL_TO_LOAD:
@@ -533,12 +551,16 @@
     },
 
     /**
-     * @param {boolean} showError
+     * @return {LoadingPageState}
      * @private
      */
-    getLoadingPageState_(showError) {
-      return showError ? LoadingPageState.SIM_DETECT_ERROR :
-                         LoadingPageState.LOADING;
+    getLoadingPageState_() {
+      if (this.state_ === PSimUIState.TIMEOUT_START_ACTIVATION) {
+        return LoadingPageState.SIM_DETECT_ERROR;
+      } else if (this.state_ === PSimUIState.FINAL_TIMEOUT_START_ACTIVATION) {
+        return LoadingPageState.FINAL_SIM_DETECT_ERROR;
+      }
+      return LoadingPageState.LOADING;
     },
   });
 
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html
index 9e77036..3b03de44 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html
@@ -18,9 +18,7 @@
       }
 
       #simDetectError {
-        background-image: -webkit-image-set(
-            url(chrome://resources/cr_components/chromeos/cellular_setup/error_1x.png) 1x,
-            url(chrome://resources/cr_components/chromeos/cellular_setup/error_2x.png) 2x);
+        background-image: url(chrome://resources/cr_components/chromeos/cellular_setup/sim_detect_error.svg);
         background-position: center center;
         background-repeat: no-repeat;
         background-size: contain;
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.js
index 284e9ce..4656295e 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.js
@@ -6,7 +6,8 @@
 /* #export */ const LoadingPageState = {
   LOADING: 1,
   SIM_DETECT_ERROR: 2,
-  CELLULAR_DISCONNECT_WARNING: 3,
+  FINAL_SIM_DETECT_ERROR: 3,
+  CELLULAR_DISCONNECT_WARNING: 4,
 };
 
 /**
@@ -61,6 +62,8 @@
     switch (state) {
       case LoadingPageState.SIM_DETECT_ERROR:
         return this.i18n('simDetectPageErrorMessage');
+      case LoadingPageState.FINAL_SIM_DETECT_ERROR:
+        return this.i18n('simDetectPageFinalErrorMessage');
       case LoadingPageState.CELLULAR_DISCONNECT_WARNING:
         return this.i18n('eSimConnectionWarning');
       case LoadingPageState.LOADING:
@@ -86,6 +89,7 @@
    * @private
    */
   shouldShowSimDetectError_(state) {
-    return state === LoadingPageState.SIM_DETECT_ERROR;
+    return state === LoadingPageState.SIM_DETECT_ERROR ||
+        state === LoadingPageState.FINAL_SIM_DETECT_ERROR;
   },
 });
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/sim_detect_error.svg b/ui/webui/resources/cr_components/chromeos/cellular_setup/sim_detect_error.svg
new file mode 100644
index 0000000..0dfdd5f
--- /dev/null
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/sim_detect_error.svg
@@ -0,0 +1 @@
+<svg width="765" height="600" viewBox="0 0 765 600" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M0 701h1208.616V.431H0z"/><path d="M0 149.31c0 82.453 66.934 149.294 149.5 149.294 82.564 0 149.5-66.841 149.5-149.294C299 66.855 232.064.016 149.5.016 66.934.016 0 66.856 0 149.31z" id="c"/><path d="M0 149.31c0 82.453 66.934 149.294 149.5 149.294 82.564 0 149.5-66.841 149.5-149.294C299 66.855 232.064.016 149.5.016 66.934.016 0 66.856 0 149.31z" id="e"/><radialGradient cx="44.652%" cy="44.352%" fx="44.652%" fy="44.352%" r="70.569%" gradientTransform="matrix(.78431 0 0 .80737 .096 .085)" id="g"><stop stop-color="#FFF" stop-opacity="0" offset="0%"/><stop stop-color="#FFF" stop-opacity="0" offset="40.031%"/><stop stop-color="#FFF" offset="100%"/></radialGradient></defs><g fill="none" fill-rule="evenodd"><g transform="translate(82 85)"><path d="M234.321 550.883c-12.371 0-22.435-10.058-22.435-22.417v-510.6h802.677v510.6c0 12.36-10.064 22.417-22.435 22.417H234.32z" fill="#F1F3F4"/><path d="M1017.055 15.375H209.394v513.091c0 13.756 11.162 24.908 24.927 24.908h757.807c13.765 0 24.927-11.152 24.927-24.908V15.376zm-4.985 4.982v508.11c0 10.986-8.947 19.925-19.942 19.925H234.32c-10.995 0-19.942-8.94-19.942-19.926V20.356h797.69z" fill="#DADCE0"/><path d="M219.365 370.305h787.72v158.161c0 8.254-6.699 14.945-14.957 14.945H234.32c-8.258 0-14.956-6.69-14.956-14.945V370.305z" fill="#F8F9FA"/><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path fill="#FFF" mask="url(#b)" d="M482.354 543.411h261.742V370.305H482.354z"/><path d="M149.567 22.847a2.494 2.494 0 0 1-2.493-2.49v-9.963c0-4.12 3.355-7.473 7.478-7.473h917.345c4.123 0 7.478 3.353 7.478 7.473v9.963a2.494 2.494 0 0 1-2.493 2.49H149.567z" fill="#F1F3F4" mask="url(#b)"/><path d="M1071.897.43H154.552c-5.506 0-9.97 4.462-9.97 9.964v9.963a4.985 4.985 0 0 0 4.985 4.981h927.315a4.985 4.985 0 0 0 4.986-4.981v-9.963c0-5.502-4.465-9.963-9.971-9.963m0 4.981a4.989 4.989 0 0 1 4.985 4.982v9.963H149.567v-9.963a4.989 4.989 0 0 1 4.985-4.982h917.345M299.153 341.661H259.27c-2.745 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.241-4.981 4.986-4.981h39.884c2.743 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.243 4.981-4.986 4.981m-41.67-106.739h78.324a3.218 3.218 0 0 1 3.21 3.208v43.399a3.218 3.218 0 0 1-3.21 3.208h-78.324a3.218 3.218 0 0 1-3.21-3.208V238.13a3.218 3.218 0 0 1 3.21-3.208M358.98 341.661h-39.884c-2.745 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.241-4.981 4.986-4.981h39.884c2.742 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.244 4.981-4.986 4.981m59.827 0h-39.884c-2.745 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.241-4.981 4.986-4.981h39.884c2.742 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.244 4.981-4.986 4.981m234.249-106.739h39.884c2.742 0 4.986 2.242 4.986 4.982v39.851c0 2.74-2.244 4.982-4.986 4.982h-39.884c-2.745 0-4.986-2.242-4.986-4.982v-39.851c0-2.74 2.241-4.982 4.986-4.982m-59.827 0h39.884c2.742 0 4.986 2.242 4.986 4.982v39.851c0 2.74-2.244 4.982-4.986 4.982H593.23c-2.745 0-4.986-2.242-4.986-4.982v-39.851c0-2.74 2.241-4.982 4.986-4.982m-59.828 0h39.885c2.742 0 4.985 2.242 4.985 4.982v39.851c0 2.74-2.243 4.982-4.985 4.982h-39.885c-2.744 0-4.985-2.242-4.985-4.982v-39.851c0-2.74 2.24-4.982 4.985-4.982m-59.827 0h39.885c2.742 0 4.985 2.242 4.985 4.982v39.851c0 2.74-2.243 4.982-4.985 4.982h-39.885c-2.744 0-4.985-2.242-4.985-4.982v-39.851c0-2.74 2.24-4.982 4.985-4.982m-59.827 0h39.885c2.742 0 4.986 2.242 4.986 4.982v39.851c0 2.74-2.244 4.982-4.986 4.982h-39.885c-2.744 0-4.985-2.242-4.985-4.982v-39.851c0-2.74 2.24-4.982 4.985-4.982m-59.826 0h39.884c2.742 0 4.986 2.242 4.986 4.982v39.851c0 2.74-2.244 4.982-4.986 4.982h-39.884c-2.745 0-4.986-2.242-4.986-4.982v-39.851c0-2.74 2.241-4.982 4.986-4.982m-94.663-56.923h49.856c2.742 0 4.985 2.242 4.985 4.981v39.852c0 2.74-2.243 4.982-4.985 4.982h-49.856c-2.745 0-4.986-2.242-4.986-4.982V182.98c0-2.74 2.241-4.981 4.986-4.981m369.076 0h39.884c2.742 0 4.986 2.242 4.986 4.981v39.852c0 2.74-2.244 4.982-4.986 4.982h-39.884c-2.742 0-4.986-2.242-4.986-4.982V182.98c0-2.74 2.244-4.981 4.986-4.981m-59.827 0h39.884c2.743 0 4.986 2.242 4.986 4.981v39.852c0 2.74-2.243 4.982-4.986 4.982h-39.884c-2.742 0-4.986-2.242-4.986-4.982V182.98c0-2.74 2.244-4.981 4.986-4.981m-59.827 0h39.885c2.742 0 4.985 2.242 4.985 4.981v39.852c0 2.74-2.243 4.982-4.985 4.982H508.68c-2.742 0-4.985-2.242-4.985-4.982V182.98c0-2.74 2.243-4.981 4.985-4.981m-59.826 0h39.885c2.742 0 4.985 2.242 4.985 4.981v39.852c0 2.74-2.243 4.982-4.985 4.982h-39.885c-2.742 0-4.985-2.242-4.985-4.982V182.98c0-2.74 2.243-4.981 4.985-4.981m-59.826 0h39.884c2.742 0 4.986 2.242 4.986 4.981v39.852c0 2.74-2.244 4.982-4.986 4.982h-39.884c-2.743 0-4.986-2.242-4.986-4.982V182.98c0-2.74 2.243-4.981 4.986-4.981m-59.828 0h39.885c2.742 0 4.986 2.242 4.986 4.981v39.852c0 2.74-2.244 4.982-4.986 4.982h-39.884c-2.742 0-4.986-2.242-4.986-4.982V182.98c0-2.74 2.244-4.981 4.986-4.981m81.964-7.058h-39.884c-2.745 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.241-4.981 4.986-4.981h39.884c2.743 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.243 4.981-4.986 4.981m59.827 0h-39.884c-2.745 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.241-4.981 4.986-4.981h39.884c2.742 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.244 4.981-4.986 4.981m59.828 0h-39.885c-2.745 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.241-4.981 4.986-4.981h39.884c2.742 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.244 4.981-4.986 4.981m59.827 0H550.76c-2.744 0-4.985-2.241-4.985-4.981v-39.852c0-2.74 2.24-4.981 4.985-4.981h39.885c2.742 0 4.985 2.241 4.985 4.981v39.852c0 2.74-2.243 4.981-4.985 4.981m59.828 0h-39.885c-2.744 0-4.985-2.241-4.985-4.981v-39.852c0-2.74 2.24-4.981 4.985-4.981h39.885c2.742 0 4.985 2.241 4.985 4.981v39.852c0 2.74-2.243 4.981-4.985 4.981m59.827 0h-39.885c-2.745 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.241-4.981 4.986-4.981h39.884c2.743 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.243 4.981-4.986 4.981m-398.692-49.814h39.885c2.742 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.244 4.981-4.986 4.981h-39.885c-2.744 0-4.985-2.241-4.985-4.981v-39.852c0-2.74 2.24-4.981 4.985-4.981m-52.348-34.871h52.847c2.742 0 4.986 2.242 4.986 4.982v14.944c0 2.74-2.244 4.982-4.986 4.982h-52.847c-2.745 0-4.986-2.242-4.986-4.982V91.238c0-2.74 2.241-4.982 4.986-4.982m0 34.871h32.406c2.742 0 4.986 2.241 4.986 4.981v39.852c0 2.74-2.244 4.981-4.986 4.981H259.26c-2.745 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.241-4.981 4.986-4.981m528.5 220.534H438.69c-2.742 0-4.986-2.241-4.986-4.981v-39.852c0-2.74 2.244-4.981 4.986-4.981h349.07c2.742 0 4.985 2.241 4.985 4.981v39.852c0 2.74-2.243 4.981-4.985 4.981M332.046 86.256h52.848c2.742 0 4.985 2.242 4.985 4.982v14.944c0 2.74-2.243 4.982-4.985 4.982h-52.848c-2.742 0-4.985-2.242-4.985-4.982V91.238c0-2.74 2.243-4.982 4.985-4.982m72.79 0h52.845c2.744 0 4.985 2.242 4.985 4.982v14.944c0 2.74-2.24 4.982-4.985 4.982h-52.845c-2.744 0-4.985-2.242-4.985-4.982V91.238c0-2.74 2.24-4.982 4.985-4.982m72.788 0h52.847c2.742 0 4.985 2.242 4.985 4.982v14.944c0 2.74-2.243 4.982-4.985 4.982h-52.847c-2.743 0-4.986-2.242-4.986-4.982V91.238c0-2.74 2.243-4.982 4.986-4.982m72.786 0h52.848c2.744 0 4.985 2.242 4.985 4.982v14.944c0 2.74-2.24 4.982-4.985 4.982H550.41c-2.742 0-4.986-2.242-4.986-4.982V91.238c0-2.74 2.244-4.982 4.986-4.982m72.79 0h52.848c2.742 0 4.985 2.242 4.985 4.982v14.944c0 2.74-2.243 4.982-4.985 4.982H623.2c-2.745 0-4.986-2.242-4.986-4.982V91.238c0-2.74 2.241-4.982 4.986-4.982m-314.095-50.8h64.813v-9.963h-64.813zm0 9.964h64.813v-9.964h-64.813z" fill="#DADCE0" mask="url(#b)"/><path d="M301.627 212.42c0 82.536-66.964 149.444-149.567 149.444-82.604 0-149.567-66.908-149.567-149.444 0-82.535 66.963-149.444 149.567-149.444 82.603 0 149.567 66.909 149.567 149.444" fill="#FFF" mask="url(#b)"/></g><g transform="translate(85 148)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M258.751 634.912c-13.89 0-25.193-11.286-25.193-25.158V-8.896h969.535v618.65c0 13.872-11.302 25.158-25.193 25.158H258.75z" fill="#F1F3F4" mask="url(#d)"/></g><g transform="translate(85 148)"><mask id="f" fill="#fff"><use xlink:href="#e"/></mask><path d="M1205.584-11.384H231.066v621.136c0 15.27 12.394 27.65 27.685 27.65H1177.9c15.288 0 27.684-12.38 27.684-27.65V-11.384zm-4.983 4.977v616.159c0 12.5-10.186 22.673-22.701 22.673H258.75c-12.518 0-22.701-10.172-22.701-22.673V-6.408H1200.6z" fill="#DADCE0" mask="url(#f)"/></g><path d="M383.634 297.543c0 82.54-66.979 149.45-149.601 149.45-82.623 0-149.602-66.91-149.602-149.45 0-82.539 66.98-149.45 149.602-149.45s149.601 66.911 149.601 149.45z" stroke="#D2E3FC" stroke-width="4"/><g transform="matrix(-1 0 0 1 305 231)"><path d="M176.578 6H66.974C47.106 6 31 22.104 31 41.969v72.61C31 120.334 35.666 125 41.422 125h135.156c5.756 0 10.422-4.666 10.422-10.42V16.42C187 10.667 182.334 6 176.578 6zM66.974 12.05h109.604a4.37 4.37 0 0 1 4.37 4.37v98.16a4.37 4.37 0 0 1-4.37 4.37H41.422a4.37 4.37 0 0 1-4.37-4.37V41.968c0-16.523 13.396-29.918 29.922-29.918z" fill="#F1F3F4" fill-rule="nonzero"/><g transform="translate(0 24)"><g fill="#4682F4" fill-rule="nonzero"><path d="M1.695 71.126l-.318-55.82L12.824.882l89.606.3c6.156 0 11.193 4.497 11.193 9.992v59.952c0 5.496-5.037 9.992-11.193 9.992H12.888c-6.156 0-11.193-4.496-11.193-9.992z"/><path d="M76.945 63.4H15.78V17.931h42.7l18.464 18.3z"/></g><path d="M108.65 16.713v48.574c0 6.075-4.924 11-11 11H22.03c-6.076 0-11-4.925-11-11V16.713c0-6.075 4.924-11 11-11h75.62c6.076 0 11 4.925 11 11z" fill="#FFF" fill-rule="nonzero"/><rect fill="#E8F0FE" x="15.71" y="11.762" width="24.071" height="16.803" rx="3"/><rect fill="#E8F0FE" x="78.562" y="11.762" width="24.071" height="16.803" rx="3"/><rect fill="#D2E3FC" x="15.71" y="33.27" width="24.071" height="16.803" rx="3"/><rect fill="#D2E3FC" x="78.562" y="33.27" width="24.071" height="16.803" rx="3"/><path d="M45.13 54.778V14.762a3 3 0 0 1 3-3h23.42a3 3 0 0 1 3 3v53.82a3 3 0 0 1-3 3H48.13a3 3 0 0 1-3-3V58.139h-5.349v10.443a3 3 0 0 1-3 3h-18.07a3 3 0 0 1-3-3V57.779a3 3 0 0 1 3-3h26.42z" fill="#8DB6F9"/><rect fill="#8DB6F9" x="78.562" y="54.779" width="24.071" height="16.803" rx="3"/></g><path fill="#F1F3F4" d="M46 14h138v6H46zm-12 96h150v6H34z"/><rect fill="#BDC1C6" x="180" width="7" height="128" rx="1"/><path fill="#F1F3F4" d="M148 6h32v119h-32z"/></g><path fill="url(#g)" d="M0 0h765v600H0z"/></g></svg>
\ No newline at end of file
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.html b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.html
index d2129f03..855fe44 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.html
+++ b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.html
@@ -54,7 +54,7 @@
 
     <!-- SIM locked -->
     <div id="simLocked" class="property-box two-line"
-        hidden$="[[!showSimLocked_(deviceState)]]">
+        hidden$="[[!showSimLocked_(deviceState, isActiveSim_)]]">
       <div class="start layout horizontal center">
         <iron-icon icon="cr:sim-lock"></iron-icon>
         <div class="error">[[i18n('networkSimCardLocked')]]</div>
@@ -72,10 +72,11 @@
         [[i18n('networkSimLockEnable')]]
       </div>
       <cr-button id="changePinButton" on-click="onChangePinTap_"
-          hidden$="[[!deviceState.simLockStatus.lockEnabled]]">
+          hidden$="[[!showChangePinButton_(deviceState, isActiveSim_)]]">
         [[i18n('networkSimChangePin')]]
       </cr-button>
       <cr-toggle id="simLockButton"
+          disabled$="[[!isActiveSim_]]"
           on-change="onSimLockEnabledChange_" checked="{{lockEnabled_}}"
           aria-labelledby="simLockToggleLabel">
       </cr-toggle>
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
index 72bcc80c..12e38ec2 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
@@ -23,6 +23,12 @@
       observer: 'deviceStateChanged_',
     },
 
+    /** @type {?OncMojo.NetworkStateProperties} */
+    networkState: {
+      type: Object,
+      value: null,
+    },
+
     /**
      * Reflects deviceState.simLockStatus.lockEnabled for the
      * toggle button.
@@ -47,6 +53,16 @@
     showChangePin_: {
       type: Boolean,
       value: false,
+    },
+
+    /**
+     * Indicates that the current network is on the active sim slot.
+     * @private {boolean}
+     */
+    isActiveSim_: {
+      type: Boolean,
+      value: false,
+      computed: 'computeIsActiveSim_(networkState, deviceState)'
     }
   },
 
@@ -169,7 +185,7 @@
     if (!simLockStatus) {
       return false;
     }
-    return !!simLockStatus.lockType;
+    return !!simLockStatus.lockType && this.isActiveSim_;
   },
 
   /**
@@ -192,5 +208,39 @@
     this.showChangePin_ = showChangePin;
     this.isDialogOpen_ = true;
   },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computeIsActiveSim_() {
+    const mojom = chromeos.networkConfig.mojom;
+    if (!this.networkState ||
+        this.networkState.type !== mojom.NetworkType.kCellular) {
+      return false;
+    }
+
+    const iccid = this.networkState.typeState.cellular.iccid;
+    if (!iccid || !this.deviceState || !this.deviceState.simInfos) {
+      return false;
+    }
+    const isActiveSim = this.deviceState.simInfos.find(simInfo => {
+      return simInfo.iccid === iccid && simInfo.isPrimary;
+    });
+
+    return !!isActiveSim;
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  showChangePinButton_() {
+    if (!this.deviceState || !this.deviceState.simLockStatus) {
+      return false;
+    }
+
+    return this.deviceState.simLockStatus.lockEnabled && this.isActiveSim_;
+  }
 });
 })();
diff --git a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
index 26ec3f4..0ec688f2 100644
--- a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
+++ b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js
@@ -218,10 +218,6 @@
       return;
     }
 
-    // TODO(dpapad): This is necessary to make the code work both for Polymer 1
-    // and Polymer 2. Remove once migration to Polymer 2 is completed.
-    e.stopPropagation();
-
     // Catch and re-fire the 'close' event such that it bubbles across Shadow
     // DOM v1.
     this.fire('close');
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
index 45b6819..dd0316c 100644
--- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
+++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.js
@@ -222,10 +222,6 @@
       return;
     }
 
-    // TODO(dpapad): This is necessary to make the code work both for Polymer 1
-    // and Polymer 2. Remove once migration to Polymer 2 is completed.
-    e.stopPropagation();
-
     // Catch and re-fire the 'close' event such that it bubbles across Shadow
     // DOM v1.
     this.fire('close');
diff --git a/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.js b/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.js
index b11ab23..2067cd5 100644
--- a/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.js
+++ b/ui/webui/resources/cr_elements/cr_drawer/cr_drawer.js
@@ -136,10 +136,6 @@
    * @private
    */
   onDialogClose_(event) {
-    // TODO(dpapad): This is necessary to make the code work both for Polymer 1
-    // and Polymer 2. Remove once migration to Polymer 2 is completed.
-    event.stopPropagation();
-
     // Catch and re-fire the 'close' event such that it bubbles across Shadow
     // DOM v1.
     this.fire('close');
diff --git a/ui/webui/resources/webui_resources.grd b/ui/webui/resources/webui_resources.grd
index 442233d6..37bd486 100644
--- a/ui/webui/resources/webui_resources.grd
+++ b/ui/webui/resources/webui_resources.grd
@@ -23,13 +23,6 @@
                  type="chrome_html"
                  use_base_dir="false" />
       </if>
-      <if expr="not is_android and not is_ios">
-        <include name="IDR_LOTTIE_LOTTIE_WORKER_MIN_JS"
-                 file="${root_gen_dir}/third_party/lottie/lottie_worker.min.js"
-                 resource_path="lottie/lottie_worker.min.js"
-                 use_base_dir="false"
-                 type="BINDATA"/>
-      </if>
     </includes>
     <structures>
       <structure name="IDR_WEBUI_CSS_MENU_CSS"
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index a7ad914..37426a6 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -203,8 +203,6 @@
     "browser/feature_list_creator.h",
     "browser/file_select_helper.cc",
     "browser/file_select_helper.h",
-    "browser/heavy_ad_service_factory.cc",
-    "browser/heavy_ad_service_factory.h",
     "browser/host_content_settings_map_factory.cc",
     "browser/host_content_settings_map_factory.h",
     "browser/i18n_util.cc",
@@ -421,7 +419,6 @@
     "//components/favicon/core:database",
     "//components/favicon_base",
     "//components/find_in_page",
-    "//components/heavy_ad_intervention",
     "//components/infobars/core",
     "//components/js_injection/browser",
     "//components/js_injection/renderer",
diff --git a/weblayer/browser/DEPS b/weblayer/browser/DEPS
index f69a56e..2854b6b 100644
--- a/weblayer/browser/DEPS
+++ b/weblayer/browser/DEPS
@@ -30,7 +30,6 @@
   "+components/favicon_base",
   "+components/favicon",
   "+components/find_in_page",
-  "+components/heavy_ad_intervention",
   "+components/infobars/android",
   "+components/infobars/content",
   "+components/infobars/core",
diff --git a/weblayer/browser/ads_page_load_metrics_observer_browsertest.cc b/weblayer/browser/ads_page_load_metrics_observer_browsertest.cc
index 49b30a5c9..b5cde665 100644
--- a/weblayer/browser/ads_page_load_metrics_observer_browsertest.cc
+++ b/weblayer/browser/ads_page_load_metrics_observer_browsertest.cc
@@ -6,17 +6,13 @@
 
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
-#include "components/heavy_ad_intervention/heavy_ad_features.h"
 #include "components/page_load_metrics/browser/ads_page_load_metrics_test_waiter.h"
-#include "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/ad_metrics/frame_data.h"
 #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
 #include "components/subresource_filter/core/common/common_features.h"
 #include "components/subresource_filter/core/common/test_ruleset_utils.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/test/test_navigation_observer.h"
-#include "net/test/embedded_test_server/controllable_http_response.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "weblayer/shell/browser/shell.h"
 #include "weblayer/test/subresource_filter_browser_test_harness.h"
@@ -30,28 +26,6 @@
     "PageLoad.Clients.Ads.FrameCounts.AdFrames.PerFrame."
     "OriginStatus";
 
-const char kHeavyAdInterventionTypeHistogramId[] =
-    "PageLoad.Clients.Ads.HeavyAds.InterventionType2";
-
-// Use the maximum possible threshold so tests are deterministic.
-const int kMaxHeavyAdNetworkSize =
-    heavy_ad_thresholds::kMaxNetworkBytes +
-    AdsPageLoadMetricsObserver::HeavyAdThresholdNoiseProvider::
-        kMaxNetworkThresholdNoiseBytes;
-
-const char kHttpOkResponseHeader[] =
-    "HTTP/1.1 200 OK\r\n"
-    "Content-Type: text/html; charset=utf-8\r\n"
-    "\r\n";
-
-void LoadLargeResource(net::test_server::ControllableHttpResponse* response,
-                       int bytes) {
-  response->WaitForRequest();
-  response->Send(kHttpOkResponseHeader);
-  response->Send(std::string(bytes, ' '));
-  response->Done();
-}
-
 }  // namespace
 
 class AdsPageLoadMetricsObserverBrowserTest
@@ -177,10 +151,7 @@
  public:
   AdsPageLoadMetricsObserverResourceBrowserTest() {
     scoped_feature_list_.InitWithFeaturesAndParameters(
-        {{subresource_filter::kAdTagging, {}},
-         {features::kHeavyAdIntervention, {}},
-         {features::kHeavyAdPrivacyMitigations, {{"host-threshold", "3"}}}},
-        {});
+        {{subresource_filter::kAdTagging, {}}}, {});
   }
 
   ~AdsPageLoadMetricsObserverResourceBrowserTest() override = default;
@@ -190,8 +161,7 @@
 
     SetRulesetWithRules(
         {subresource_filter::testing::CreateSuffixRule("ad_script.js"),
-         subresource_filter::testing::CreateSuffixRule("ad_script_2.js"),
-         subresource_filter::testing::CreateSuffixRule("ad_iframe_writer.js")});
+         subresource_filter::testing::CreateSuffixRule("ad_script_2.js")});
   }
 
  protected:
@@ -223,85 +193,4 @@
   waiter->Wait();
 }
 
-// Verifies that the frame is navigated to the intervention page when a
-// heavy ad intervention triggers.
-IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
-                       HeavyAdInterventionEnabled_ErrorPageLoaded) {
-  base::HistogramTester histogram_tester;
-  auto incomplete_resource_response =
-      std::make_unique<net::test_server::ControllableHttpResponse>(
-          embedded_test_server(), "/ads_observer/incomplete_resource.js",
-          true /*relative_url_is_prefix*/);
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  // Create a navigation observer that will watch for the intervention to
-  // navigate the frame.
-  content::TestNavigationObserver error_observer(web_contents(),
-                                                 net::ERR_BLOCKED_BY_CLIENT);
-
-  auto waiter = CreateAdsPageLoadMetricsTestWaiter();
-  GURL url = embedded_test_server()->GetURL(
-      "/ads_observer/ad_with_incomplete_resource.html");
-  NavigateAndWaitForCompletion(url, shell());
-
-  // Load a resource large enough to trigger the intervention.
-  LoadLargeResource(incomplete_resource_response.get(), kMaxHeavyAdNetworkSize);
-
-  // Wait for the intervention page navigation to finish on the frame.
-  error_observer.WaitForNavigationFinished();
-
-  histogram_tester.ExpectUniqueSample(kHeavyAdInterventionTypeHistogramId,
-                                      ad_metrics::HeavyAdStatus::kNetwork, 1);
-
-  // Check that the ad frame was navigated to the intervention page.
-  EXPECT_FALSE(error_observer.last_navigation_succeeded());
-
-  histogram_tester.ExpectUniqueSample(kHeavyAdInterventionTypeHistogramId,
-                                      ad_metrics::HeavyAdStatus::kNetwork, 1);
-  histogram_tester.ExpectBucketCount(
-      "Blink.UseCounter.Features",
-      blink::mojom::WebFeature::kHeavyAdIntervention, 1);
-}
-
-class AdsPageLoadMetricsObserverResourceIncognitoBrowserTest
-    : public AdsPageLoadMetricsObserverResourceBrowserTest {
- public:
-  AdsPageLoadMetricsObserverResourceIncognitoBrowserTest() {
-    SetShellStartsInIncognitoMode();
-  }
-};
-
-// Verifies that the blocklist is setup correctly and the intervention triggers
-// in incognito mode.
-IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceIncognitoBrowserTest,
-                       HeavyAdInterventionIncognitoMode_InterventionFired) {
-  base::HistogramTester histogram_tester;
-  auto incomplete_resource_response =
-      std::make_unique<net::test_server::ControllableHttpResponse>(
-          embedded_test_server(), "/ads_observer/incomplete_resource.js",
-          true /*relative_url_is_prefix*/);
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  // Create a navigation observer that will watch for the intervention to
-  // navigate the frame.
-  content::TestNavigationObserver error_observer(web_contents(),
-                                                 net::ERR_BLOCKED_BY_CLIENT);
-
-  // Create a waiter for the incognito contents.
-  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
-      web_contents());
-  GURL url = embedded_test_server()->GetURL(
-      "/ads_observer/ad_with_incomplete_resource.html");
-  NavigateAndWaitForCompletion(url, shell());
-
-  // Load a resource large enough to trigger the intervention.
-  LoadLargeResource(incomplete_resource_response.get(), kMaxHeavyAdNetworkSize);
-
-  // Wait for the intervention page navigation to finish on the frame.
-  error_observer.WaitForNavigationFinished();
-
-  // Check that the ad frame was navigated to the intervention page.
-  EXPECT_FALSE(error_observer.last_navigation_succeeded());
-}
-
 }  // namespace weblayer
diff --git a/weblayer/browser/background_fetch/background_fetch_download.cc b/weblayer/browser/background_fetch/background_fetch_download.cc
index 1cf294b3..c2a5937 100644
--- a/weblayer/browser/background_fetch/background_fetch_download.cc
+++ b/weblayer/browser/background_fetch/background_fetch_download.cc
@@ -4,7 +4,7 @@
 
 #include "weblayer/browser/background_fetch/background_fetch_download.h"
 
-#include "base/files/file_path.h"
+#include "base/strings/utf_string_conversions.h"
 #include "content/public/browser/background_fetch_description.h"
 #include "weblayer/browser/background_fetch/background_fetch_delegate_impl.h"
 #include "weblayer/browser/background_fetch/job_details.h"
@@ -70,10 +70,8 @@
   return {};
 }
 
-base::FilePath BackgroundFetchDownload::GetFileNameToReportToUser() {
-  // TODO(estade): this method should return a string instead of a FilePath.
-  // It's used as the notification's title.
-  return base::FilePath::FromUTF8Unsafe(job_->fetch_description->title);
+std::u16string BackgroundFetchDownload::GetFileNameToReportToUser() {
+  return base::UTF8ToUTF16(job_->fetch_description->title);
 }
 
 std::string BackgroundFetchDownload::GetMimeType() {
diff --git a/weblayer/browser/background_fetch/background_fetch_download.h b/weblayer/browser/background_fetch/background_fetch_download.h
index e7d0823..69c69d7 100644
--- a/weblayer/browser/background_fetch/background_fetch_download.h
+++ b/weblayer/browser/background_fetch/background_fetch_download.h
@@ -33,7 +33,7 @@
   void Resume() override;
   void Cancel() override;
   base::FilePath GetLocation() override;
-  base::FilePath GetFileNameToReportToUser() override;
+  std::u16string GetFileNameToReportToUser() override;
   std::string GetMimeType() override;
   DownloadError GetError() override;
 
diff --git a/weblayer/browser/browser_context_impl.cc b/weblayer/browser/browser_context_impl.cc
index ea8d6911..031ddb3 100644
--- a/weblayer/browser/browser_context_impl.cc
+++ b/weblayer/browser/browser_context_impl.cc
@@ -11,7 +11,6 @@
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/download/public/common/in_progress_download_manager.h"
 #include "components/embedder_support/pref_names.h"
-#include "components/heavy_ad_intervention/heavy_ad_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/language/core/browser/language_prefs.h"
 #include "components/payments/core/payment_prefs.h"
@@ -43,7 +42,6 @@
 #include "weblayer/browser/browsing_data_remover_delegate_factory.h"
 #include "weblayer/browser/client_hints_factory.h"
 #include "weblayer/browser/default_search_engine.h"
-#include "weblayer/browser/heavy_ad_service_factory.h"
 #include "weblayer/browser/permissions/permission_manager_factory.h"
 #include "weblayer/browser/stateful_ssl_host_state_delegate_factory.h"
 #include "weblayer/public/common/switches.h"
@@ -107,13 +105,6 @@
   BrowserContextDependencyManager::GetInstance()->CreateBrowserContextServices(
       this);
 
-  auto* heavy_ad_service = HeavyAdServiceFactory::GetForBrowserContext(this);
-  if (IsOffTheRecord()) {
-    heavy_ad_service->InitializeOffTheRecord();
-  } else {
-    heavy_ad_service->Initialize(GetPath());
-  }
-
   site_isolation::SiteIsolationPolicy::ApplyPersistedIsolatedOrigins(this);
 
   // Set the DSE permissions every time the browser context is created for
diff --git a/weblayer/browser/browser_main_parts_impl.cc b/weblayer/browser/browser_main_parts_impl.cc
index 7a9a0ab2..1107b74 100644
--- a/weblayer/browser/browser_main_parts_impl.cc
+++ b/weblayer/browser/browser_main_parts_impl.cc
@@ -33,7 +33,6 @@
 #include "weblayer/browser/browser_process.h"
 #include "weblayer/browser/cookie_settings_factory.h"
 #include "weblayer/browser/feature_list_creator.h"
-#include "weblayer/browser/heavy_ad_service_factory.h"
 #include "weblayer/browser/host_content_settings_map_factory.h"
 #include "weblayer/browser/i18n_util.h"
 #include "weblayer/browser/no_state_prefetch/no_state_prefetch_link_manager_factory.h"
@@ -116,7 +115,6 @@
 #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
   CaptivePortalServiceFactory::GetInstance();
 #endif
-  HeavyAdServiceFactory::GetInstance();
   HostContentSettingsMapFactory::GetInstance();
   StatefulSSLHostStateDelegateFactory::GetInstance();
   CookieSettingsFactory::GetInstance();
diff --git a/weblayer/browser/download_impl.cc b/weblayer/browser/download_impl.cc
index 40d88079..f30720816 100644
--- a/weblayer/browser/download_impl.cc
+++ b/weblayer/browser/download_impl.cc
@@ -42,8 +42,8 @@
 base::android::ScopedJavaLocalRef<jstring>
 DownloadImpl::GetFileNameToReportToUserImpl(JNIEnv* env) {
   return base::android::ScopedJavaLocalRef<jstring>(
-      base::android::ConvertUTF8ToJavaString(
-          env, GetFileNameToReportToUser().value()));
+      base::android::ConvertUTF16ToJavaString(env,
+                                              GetFileNameToReportToUser()));
 }
 
 base::android::ScopedJavaLocalRef<jstring> DownloadImpl::GetMimeTypeImpl(
diff --git a/weblayer/browser/heavy_ad_service_factory.cc b/weblayer/browser/heavy_ad_service_factory.cc
deleted file mode 100644
index 1d4f55e3..0000000
--- a/weblayer/browser/heavy_ad_service_factory.cc
+++ /dev/null
@@ -1,43 +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 "weblayer/browser/heavy_ad_service_factory.h"
-
-#include "components/heavy_ad_intervention/heavy_ad_service.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "content/public/browser/browser_context.h"
-
-namespace weblayer {
-
-// static
-HeavyAdService* HeavyAdServiceFactory::GetForBrowserContext(
-    content::BrowserContext* context) {
-  return static_cast<HeavyAdService*>(
-      GetInstance()->GetServiceForBrowserContext(context, true));
-}
-
-// static
-HeavyAdServiceFactory* HeavyAdServiceFactory::GetInstance() {
-  static base::NoDestructor<HeavyAdServiceFactory> factory;
-  return factory.get();
-}
-
-HeavyAdServiceFactory::HeavyAdServiceFactory()
-    : BrowserContextKeyedServiceFactory(
-          "HeavyAdService",
-          BrowserContextDependencyManager::GetInstance()) {}
-
-HeavyAdServiceFactory::~HeavyAdServiceFactory() = default;
-
-KeyedService* HeavyAdServiceFactory::BuildServiceInstanceFor(
-    content::BrowserContext* context) const {
-  return new HeavyAdService();
-}
-
-content::BrowserContext* HeavyAdServiceFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  return context;
-}
-
-}  // namespace weblayer
diff --git a/weblayer/browser/heavy_ad_service_factory.h b/weblayer/browser/heavy_ad_service_factory.h
deleted file mode 100644
index 2d1ab08d..0000000
--- a/weblayer/browser/heavy_ad_service_factory.h
+++ /dev/null
@@ -1,44 +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 WEBLAYER_BROWSER_HEAVY_AD_SERVICE_FACTORY_H_
-#define WEBLAYER_BROWSER_HEAVY_AD_SERVICE_FACTORY_H_
-
-#include "base/no_destructor.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-
-namespace content {
-class BrowserContext;
-}
-
-class HeavyAdService;
-
-namespace weblayer {
-
-class HeavyAdServiceFactory : public BrowserContextKeyedServiceFactory {
- public:
-  HeavyAdServiceFactory(const HeavyAdServiceFactory&) = delete;
-  HeavyAdServiceFactory& operator=(const HeavyAdServiceFactory&) = delete;
-
-  // Gets the HeavyAdService instance for |context|.
-  static HeavyAdService* GetForBrowserContext(content::BrowserContext* context);
-
-  static HeavyAdServiceFactory* GetInstance();
-
- private:
-  friend class base::NoDestructor<HeavyAdServiceFactory>;
-
-  HeavyAdServiceFactory();
-  ~HeavyAdServiceFactory() override;
-
-  // BrowserContextKeyedServiceFactory:
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* context) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
-};
-
-}  // namespace weblayer
-
-#endif  // WEBLAYER_BROWSER_HEAVY_AD_SERVICE_FACTORY_H_
diff --git a/weblayer/browser/page_load_metrics_initialize.cc b/weblayer/browser/page_load_metrics_initialize.cc
index 666f4c7..7668440 100644
--- a/weblayer/browser/page_load_metrics_initialize.cc
+++ b/weblayer/browser/page_load_metrics_initialize.cc
@@ -11,7 +11,6 @@
 #include "components/page_load_metrics/browser/page_load_metrics_memory_tracker.h"
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/page_load_tracker.h"
-#include "weblayer/browser/heavy_ad_service_factory.h"
 #include "weblayer/browser/i18n_util.h"
 #include "weblayer/browser/no_state_prefetch/prerender_utils.h"
 #include "weblayer/browser/page_load_metrics_observer_impl.h"
@@ -67,8 +66,9 @@
       std::unique_ptr<AdsPageLoadMetricsObserver> ads_observer =
           AdsPageLoadMetricsObserver::CreateIfNeeded(
               tracker->GetWebContents(),
-              HeavyAdServiceFactory::GetForBrowserContext(
-                  tracker->GetWebContents()->GetBrowserContext()),
+              // TODO(crbug.com/1110695): Bring up HeavyAdService and ad
+              // interventions in WebLayer.
+              /*heavy_ad_service=*/nullptr,
               base::BindRepeating(&i18n::GetApplicationLocale));
       if (ads_observer)
         tracker->AddObserver(std::move(ads_observer));
diff --git a/weblayer/browser/persistent_download.cc b/weblayer/browser/persistent_download.cc
index b31e453..bd27ca2531 100644
--- a/weblayer/browser/persistent_download.cc
+++ b/weblayer/browser/persistent_download.cc
@@ -84,8 +84,8 @@
   return item_->GetTargetFilePath();
 }
 
-base::FilePath PersistentDownload::GetFileNameToReportToUser() {
-  return item_->GetFileNameToReportUser();
+std::u16string PersistentDownload::GetFileNameToReportToUser() {
+  return item_->GetFileNameToReportUser().LossyDisplayName();
 }
 
 std::string PersistentDownload::GetMimeType() {
diff --git a/weblayer/browser/persistent_download.h b/weblayer/browser/persistent_download.h
index 0cda66c..8d45e380 100644
--- a/weblayer/browser/persistent_download.h
+++ b/weblayer/browser/persistent_download.h
@@ -33,7 +33,7 @@
   void Resume() override;
   void Cancel() override;
   base::FilePath GetLocation() override;
-  base::FilePath GetFileNameToReportToUser() override;
+  std::u16string GetFileNameToReportToUser() override;
   std::string GetMimeType() override;
   DownloadError GetError() override;
 
diff --git a/weblayer/grit_resources_allowlist.txt b/weblayer/grit_resources_allowlist.txt
index a5db135..c154319 100644
--- a/weblayer/grit_resources_allowlist.txt
+++ b/weblayer/grit_resources_allowlist.txt
@@ -1,6 +1,5 @@
 IDR_ANDROID_INFOBAR_BLOCKED_POPUPS
 IDR_SAD_PLUGIN
 IDR_SAD_WEBVIEW
-IDR_SECURITY_INTERSTITIAL_QUIET_HTML
 IDR_SSL_ERROR_ASSISTANT_PB
 IDR_TRANSLATE_JS
diff --git a/weblayer/grit_strings_allowlist.txt b/weblayer/grit_strings_allowlist.txt
index cd01e62..4a32bebae0 100644
--- a/weblayer/grit_strings_allowlist.txt
+++ b/weblayer/grit_strings_allowlist.txt
@@ -96,9 +96,6 @@
 IDS_GEOLOCATION_INFOBAR_PERMISSION_FRAGMENT
 IDS_GEOLOCATION_INFOBAR_TEXT
 IDS_GEOLOCATION_INFOBAR_TEXT
-IDS_HEAVY_AD_INTERVENTION_BUTTON_DETAILS
-IDS_HEAVY_AD_INTERVENTION_HEADING
-IDS_HEAVY_AD_INTERVENTION_SUMMARY
 IDS_HTTP_POST_WARNING
 IDS_HTTP_POST_WARNING_RESEND
 IDS_HTTP_POST_WARNING_TITLE
diff --git a/weblayer/public/download.h b/weblayer/public/download.h
index 5f1122f..abfe0d3 100644
--- a/weblayer/public/download.h
+++ b/weblayer/public/download.h
@@ -79,9 +79,8 @@
   // available until the download completes successfully.
   virtual base::FilePath GetLocation() = 0;
 
-  // Returns the file name for the download that should be displayed to the
-  // user.
-  virtual base::FilePath GetFileNameToReportToUser() = 0;
+  // Returns the display name for the download.
+  virtual std::u16string GetFileNameToReportToUser() = 0;
 
   // Returns the effective MIME type of downloaded content.
   virtual std::string GetMimeType() = 0;
diff --git a/weblayer/test/BUILD.gn b/weblayer/test/BUILD.gn
index 82e9250..c31b1dd 100644
--- a/weblayer/test/BUILD.gn
+++ b/weblayer/test/BUILD.gn
@@ -114,7 +114,6 @@
     "//components/content_settings/core/browser",
     "//components/error_page/content/browser",
     "//components/favicon/content",
-    "//components/heavy_ad_intervention",
     "//components/network_session_configurator/common",
     "//components/network_time",
     "//components/no_state_prefetch/browser",