diff --git a/BUILD.gn b/BUILD.gn
index 46462a3..86c82ac 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -81,8 +81,6 @@
     "//services/service_manager/public/cpp",
     "//skia:skia_unittests",
     "//sql:sql_unittests",
-    "//third_party/angle/src/tests:angle_end2end_tests",
-    "//third_party/angle/src/tests:angle_white_box_tests",
     "//third_party/flatbuffers:flatbuffers_unittests",
     "//third_party/liburlpattern:liburlpattern_unittests",
     "//tools/binary_size:binary_size_trybot_py",
@@ -192,6 +190,7 @@
       "//mojo:mojo_unittests",
       "//net:net_perftests",
       "//storage:storage_unittests",
+      "//third_party/angle/src/tests:angle_end2end_tests",
       "//third_party/angle/src/tests:angle_unittests",
       "//third_party/blink/common:blink_common_unittests",
       "//third_party/blink/renderer/controller:blink_unittests",
@@ -242,6 +241,7 @@
       "//google_apis/gcm:mcs_probe",
       "//media/capture:capture_unittests",
       "//media/cast:cast_unittests",
+      "//third_party/angle/src/tests:angle_white_box_tests",
       "//third_party/catapult/telemetry:bitmaptools($host_toolchain)",
     ]
   } else if (is_ios) {
diff --git a/DEPS b/DEPS
index 2ede054..fd61281 100644
--- a/DEPS
+++ b/DEPS
@@ -203,7 +203,7 @@
   # 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': '43ed322b6a782285f8fcfbaadd3926c371c3a60b',
+  'v8_revision': '724907341fdd1a17fced5758d13d736c3c19cc6b',
   # 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.
@@ -211,7 +211,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': 'de32c3d2de006a2c8d2acfa829634fcf2d1564ab',
+  'angle_revision': '7f0e7d0d262aa0ee71c862846c72f737cbbfca04',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -219,7 +219,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': '84c7d11419c50573f3025beb6898391588a13321',
+  'pdfium_revision': 'a3806cb359231cd0f4286a0982255ff3d2077b71',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -274,7 +274,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': 'd08c5531762807570b5a929d3496eae8632bf2b5',
+  'devtools_frontend_revision': 'a60056fee789b9256679a8d1e60f00650303f9af',
   # 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.
@@ -314,7 +314,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'ea26a8ce553fe2efa94830f3d7326072487bde3f',
+  'dawn_revision': '2bd95f1cf45451119b6644e41919b7bef443d912',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -353,7 +353,7 @@
   'ukey2_revision': '0275885d8e6038c39b8a8ca55e75d1d4d1727f47',
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'tint_revision': '1d9935cf37fdd1b9eb18f3e41d6142a345bba4a6',
+  'tint_revision': '987376cd21a8785c3e0b66bd6ba2052219400ab3',
 
   # TODO(crbug.com/941824): The values below need to be kept in sync
   # between //DEPS and //buildtools/DEPS, so if you're updating one,
@@ -893,7 +893,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '82b992a1656d7d1cd0ee3cbea8ff609ffdfed380',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '59b0ce20d3e5a64cc0b233ea199f254b136ef7f2',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1556,7 +1556,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'SydJmfq0VW7jq5fTDGtIQ-7WoB0vNc-LJAi3Xd4fKvQC',
+          'version': '_LOGbVhF_buvk7XI34hbaIYtgbUAkuiM0-VxgWcpwk4C',
         },
       ],
       'dep_type': 'cipd',
@@ -1566,7 +1566,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'mvme2We4n3wONUhzQu6ATl9Ow4Fb4li6ET-Yhv7Ph3EC',
+          'version': '4r6X5OSxy-ZDOlqtn41HMmKG2qlEfMNdLwpzyufLFzcC',
         },
       ],
       'dep_type': 'cipd',
@@ -1576,7 +1576,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'oxKWG6EzN6FHwQ-4J6gUYMHuNuHyARpVl4EvirOsXSgC',
+          'version': 'GMDKIbJ4mwbVXaPYncGej4OyIS1ZjiCVhESk0xO0fVMC',
         },
       ],
       'dep_type': 'cipd',
@@ -1590,7 +1590,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6f7379c80b03c2f1aac2182807e684f7694accbd',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@aa6859ae90565a98ff34356f00dac4291b91ed57',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
index 1602f4b..6acecc8 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
@@ -490,6 +490,11 @@
             return mDisabled;
         }
 
+        @Override
+        public boolean isAwGCurrentAutofillService() {
+            return sIsAwGCurrentAutofillService;
+        }
+
         public boolean isQuerySucceed() {
             return mQuerySucceed;
         }
@@ -584,6 +589,7 @@
         private static final int TOTAL_CONTROLS = 1; // text1
 
         public static final int NO_FORM_SUBMISSION = -1;
+        public static final int NO_RECORD = -1;
 
         private int mCnt;
         private AwAutofillTest mTest;
@@ -596,8 +602,16 @@
         private MetricsUtils.HistogramDelta mUserChangedNonAutofilledField;
         private MetricsUtils.HistogramDelta mAutofillWebViewCreatedByActivityContext;
         private MetricsUtils.HistogramDelta mAutofillWebViewCreatedByAppContext;
+        private MetricsUtils.HistogramDelta mAutofillHasInvalidServerPrediction;
+        private MetricsUtils.HistogramDelta mAutofillHasValidServerPrediction;
+        private MetricsUtils.HistogramDelta mAwGIsCurrentService;
+        private MetricsUtils.HistogramDelta mAwGIsNotCurrentService;
         private volatile Integer mSourceValue;
+        private volatile Integer mServerPredictionAvailabilityValue;
+        private volatile Integer mAwGSuggestionAvailabilityValue;
         private HashMap<MetricsUtils.HistogramDelta, Integer> mSubmissionSourceDelta;
+        private HashMap<MetricsUtils.HistogramDelta, Integer> mServerPredictionAvailablityDelta;
+        private HashMap<MetricsUtils.HistogramDelta, Integer> mAwGSuggestionAvailablityDelta;
         private volatile Integer mHistogramSimpleCount;
 
         public AwAutofillSessionUMATestHelper(AwAutofillTest test, TestWebServer webServer) {
@@ -608,25 +622,44 @@
 
         public int getSessionValue() {
             TestThreadUtils.runOnUiThreadBlocking(
-                    () -> { mSessionValue = getUMAEnumerateValue(mSessionDelta); });
+                    () -> { mSessionValue = getUMAEnumerateValue(mSessionDelta, null); });
             return mSessionValue;
         }
 
         public int getSubmissionSourceValue() {
-            TestThreadUtils.runOnUiThreadBlocking(
-                    () -> { mSourceValue = getUMAEnumerateValue(mSubmissionSourceDelta); });
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                mSourceValue = getUMAEnumerateValue(mSubmissionSourceDelta, NO_FORM_SUBMISSION);
+            });
             return mSourceValue;
         }
 
-        private int getUMAEnumerateValue(HashMap<MetricsUtils.HistogramDelta, Integer> deltas) {
-            int value = NO_FORM_SUBMISSION;
+        public int getServerPredictionAvailabilityValue() {
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                mServerPredictionAvailabilityValue =
+                        getUMAEnumerateValue(mServerPredictionAvailablityDelta, null);
+            });
+            return mServerPredictionAvailabilityValue;
+        }
+
+        public int getAwGSuggestionAvailabilityValue() {
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                mAwGSuggestionAvailabilityValue =
+                        getUMAEnumerateValue(mAwGSuggestionAvailablityDelta, NO_RECORD);
+            });
+            return mAwGSuggestionAvailabilityValue;
+        }
+
+        private int getUMAEnumerateValue(
+                HashMap<MetricsUtils.HistogramDelta, Integer> deltas, Integer defaultValue) {
+            Integer value = null;
             for (MetricsUtils.HistogramDelta delta : deltas.keySet()) {
                 if (delta.getDelta() != 0) {
-                    assertEquals(NO_FORM_SUBMISSION, value);
+                    assertNull(value);
                     value = deltas.get(delta);
                 }
             }
-            return value;
+            if (defaultValue == null) assertNotNull(value);
+            return value != null ? value : defaultValue;
         }
 
         public void triggerAutofill() throws Throwable {
@@ -639,6 +672,27 @@
                             AUTOFILL_VALUE_CHANGED});
         }
 
+        public void simulateServerPredictionBeforeTriggeringAutofill(int serverType)
+                throws Throwable {
+            final String url = mWebServer.setResponse(FILE, DATA, null);
+            mTest.loadUrlSync(url);
+            simulateServerPrediction(serverType);
+            mTest.executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
+            mTest.dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
+            mCnt += mTest.waitForCallbackAndVerifyTypes(mCnt,
+                    new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED,
+                            AUTOFILL_VALUE_CHANGED});
+        }
+
+        public void simulateServerPrediction(int serverType) throws Throwable {
+            TestThreadUtils.runOnUiThreadBlocking(
+                    ()
+                            -> AutofillProviderTestHelper
+                                       .simulateMainFrameAutofillServerResponseForTesting(
+                                               mTest.mAwContents.getWebContents(),
+                                               new String[] {"text1"}, new int[] {serverType}));
+        }
+
         public void simulateUserSelectSuggestion() throws Throwable {
             // Simulate user select suggestion
             TestViewStructure viewStructure = mTest.mTestValues.testViewStructure;
@@ -701,6 +755,23 @@
                                     AutofillProviderUMA.UMA_AUTOFILL_SUBMISSION_SOURCE, i),
                             i);
                 }
+                mServerPredictionAvailablityDelta =
+                        new HashMap<MetricsUtils.HistogramDelta, Integer>();
+                for (int i = 0; i < AutofillProviderUMA.SERVER_PREDICTION_AVAILABLE_COUNT; i++) {
+                    mServerPredictionAvailablityDelta.put(
+                            new MetricsUtils.HistogramDelta(
+                                    AutofillProviderUMA.UMA_AUTOFILL_SERVER_PREDICTION_AVAILABILITY,
+                                    i),
+                            i);
+                }
+                mAwGSuggestionAvailablityDelta =
+                        new HashMap<MetricsUtils.HistogramDelta, Integer>();
+                for (int i = 0; i < AutofillProviderUMA.AWG_SUGGSTION_AVAILABLE_COUNT; i++) {
+                    mAwGSuggestionAvailablityDelta.put(
+                            new MetricsUtils.HistogramDelta(
+                                    AutofillProviderUMA.UMA_AUTOFILL_AWG_SUGGSTION_AVAILABILITY, i),
+                            i);
+                }
                 mAutofillWebViewViewEnabled = new MetricsUtils.HistogramDelta(
                         AutofillProviderUMA.UMA_AUTOFILL_ENABLED, 1 /*true*/);
                 mAutofillWebViewViewDisabled = new MetricsUtils.HistogramDelta(
@@ -713,7 +784,15 @@
                         AutofillProviderUMA.UMA_AUTOFILL_USER_CHANGED_AUTOFILLED_FIELD, 1 /*true*/);
                 mUserChangedNonAutofilledField = new MetricsUtils.HistogramDelta(
                         AutofillProviderUMA.UMA_AUTOFILL_USER_CHANGED_AUTOFILLED_FIELD,
-                        0 /*falsTe*/);
+                        0 /*false*/);
+                mAutofillHasInvalidServerPrediction = new MetricsUtils.HistogramDelta(
+                        AutofillProviderUMA.UMA_AUTOFILL_VALID_SERVER_PREDICTION, 0 /*false*/);
+                mAutofillHasValidServerPrediction = new MetricsUtils.HistogramDelta(
+                        AutofillProviderUMA.UMA_AUTOFILL_VALID_SERVER_PREDICTION, 1 /*true*/);
+                mAwGIsNotCurrentService = new MetricsUtils.HistogramDelta(
+                        AutofillProviderUMA.UMA_AUTOFILL_AWG_IS_CURRENT_SERVICE, 0 /*false*/);
+                mAwGIsCurrentService = new MetricsUtils.HistogramDelta(
+                        AutofillProviderUMA.UMA_AUTOFILL_AWG_IS_CURRENT_SERVICE, 1 /*true*/);
             });
         }
 
@@ -739,6 +818,20 @@
             });
         }
 
+        public void verifyAwGIsCurrentService(boolean current) {
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                assertEquals(current ? 0 : 1, mAwGIsNotCurrentService.getDelta());
+                assertEquals(current ? 1 : 0, mAwGIsCurrentService.getDelta());
+            });
+        }
+
+        public void verifyServerPredictionValid(boolean valid) {
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                assertEquals(valid ? 0 : 1, mAutofillHasInvalidServerPrediction.getDelta());
+                assertEquals(valid ? 1 : 0, mAutofillHasValidServerPrediction.getDelta());
+            });
+        }
+
         public void verifyUserChangedAutofilledField() {
             TestThreadUtils.runOnUiThreadBlocking(() -> {
                 assertEquals(0, mUserChangedNonAutofilledField.getDelta());
@@ -770,6 +863,8 @@
         }
     }
 
+    private static boolean sIsAwGCurrentAutofillService = true;
+
     @Rule
     public AwActivityTestRule mRule = new AwActivityTestRule();
 
@@ -805,6 +900,13 @@
         AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
     }
 
+    public void setUpAwGNotCurrent() throws Exception {
+        sIsAwGCurrentAutofillService = false;
+        mWebServer.shutdown();
+        // Initialize everything again.
+        setUp();
+    }
+
     @After
     public void tearDown() {
         mWebServer.shutdown();
@@ -1637,6 +1739,8 @@
                 mUMATestHelper.getSessionValue());
         assertEquals(
                 AutofillProviderUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
+        assertEquals(AutofillProviderUMA.AWG_HAS_SUGGESTION_AUTOFILLED,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1653,6 +1757,8 @@
                 mUMATestHelper.getSessionValue());
         assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
                 mUMATestHelper.getSubmissionSourceValue());
+        assertEquals(AwAutofillSessionUMATestHelper.NO_RECORD,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1674,6 +1780,8 @@
         assertEquals(count + 1,
                 mUMATestHelper.getHistogramSampleCount(
                         AutofillProviderUMA.UMA_AUTOFILL_SUGGESTION_TIME));
+        assertEquals(AwAutofillSessionUMATestHelper.NO_RECORD,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1689,6 +1797,8 @@
                 mUMATestHelper.getSessionValue());
         assertEquals(
                 AutofillProviderUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
+        assertEquals(AutofillProviderUMA.AWG_HAS_SUGGESTION_NO_AUTOFILL,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1704,6 +1814,8 @@
         assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
                 mUMATestHelper.getSubmissionSourceValue());
         mUMATestHelper.verifyUserChangedNonAutofilledField();
+        assertEquals(AwAutofillSessionUMATestHelper.NO_RECORD,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1719,6 +1831,8 @@
         assertEquals(
                 AutofillProviderUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
         mUMATestHelper.verifyUserChangedNonAutofilledField();
+        assertEquals(AutofillProviderUMA.AWG_NO_SUGGESTION,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1735,6 +1849,8 @@
         assertEquals(
                 AutofillProviderUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
         mUMATestHelper.verifyUserDidntChangeForm();
+        assertEquals(AutofillProviderUMA.AWG_HAS_SUGGESTION_AUTOFILLED,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1752,12 +1868,14 @@
         assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
                 mUMATestHelper.getSubmissionSourceValue());
         mUMATestHelper.verifyUserDidntChangeForm();
+        assertEquals(AwAutofillSessionUMATestHelper.NO_RECORD,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
-    public void testUMAUserSelectNotSuggestionUserNotChangeFormNoFormSubmitted() throws Throwable {
+    public void testUMAUserNotSelectSuggestionUserNotChangeFormNoFormSubmitted() throws Throwable {
         mUMATestHelper.triggerAutofill();
         invokeOnProvideAutoFillVirtualStructure();
         invokeOnInputUIShown();
@@ -1768,6 +1886,8 @@
         assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
                 mUMATestHelper.getSubmissionSourceValue());
         mUMATestHelper.verifyUserDidntChangeForm();
+        assertEquals(AwAutofillSessionUMATestHelper.NO_RECORD,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1784,6 +1904,8 @@
         assertEquals(
                 AutofillProviderUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
         mUMATestHelper.verifyUserDidntChangeForm();
+        assertEquals(AutofillProviderUMA.AWG_HAS_SUGGESTION_NO_AUTOFILL,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1798,6 +1920,8 @@
         assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
                 mUMATestHelper.getSubmissionSourceValue());
         mUMATestHelper.verifyUserDidntChangeForm();
+        assertEquals(AwAutofillSessionUMATestHelper.NO_RECORD,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1812,6 +1936,8 @@
         assertEquals(
                 AutofillProviderUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
         mUMATestHelper.verifyUserDidntChangeForm();
+        assertEquals(AutofillProviderUMA.AWG_NO_SUGGESTION,
+                mUMATestHelper.getAwGSuggestionAvailabilityValue());
     }
 
     @Test
@@ -1829,6 +1955,59 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
+    @MinAndroidSdkLevel(Build.VERSION_CODES.P)
+    public void testUMAAwGIsCurrentService() throws Throwable {
+        mUMATestHelper.triggerAutofill();
+        mUMATestHelper.verifyAwGIsCurrentService(/*current=*/true);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @MinAndroidSdkLevel(Build.VERSION_CODES.P)
+    public void testUMAAwGIsNotCurrentService() throws Throwable {
+        setUpAwGNotCurrent();
+        mUMATestHelper.triggerAutofill();
+        mUMATestHelper.verifyAwGIsCurrentService(/*current=*/false);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({"enable-features=AndroidAutofillQueryServerFieldTypes"})
+    public void testUMANoServerPrediction() throws Throwable {
+        mUMATestHelper.triggerAutofill();
+        mUMATestHelper.startNewSession();
+        assertEquals(AutofillProviderUMA.SERVER_PREDICTION_NOT_AVAILABLE,
+                mUMATestHelper.getServerPredictionAvailabilityValue());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({"enable-features=AndroidAutofillQueryServerFieldTypes"})
+    public void testUMAServerPredictionArriveBeforeSessionStart() throws Throwable {
+        mUMATestHelper.simulateServerPredictionBeforeTriggeringAutofill(/*USERNAME*/ 86);
+        assertEquals(AutofillProviderUMA.SERVER_PREDICTION_AVAILABLE_ON_SESSION_STARTS,
+                mUMATestHelper.getServerPredictionAvailabilityValue());
+        mUMATestHelper.verifyServerPredictionValid(true);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({"enable-features=AndroidAutofillQueryServerFieldTypes"})
+    public void testUMAServerPredictionArriveAfterSessionStart() throws Throwable {
+        mUMATestHelper.triggerAutofill();
+        mUMATestHelper.simulateServerPrediction(/*NO_SERVER_DATA*/ 0);
+        assertEquals(AutofillProviderUMA.SERVER_PREDICTION_AVAILABLE_AFTER_SESSION_STARTS,
+                mUMATestHelper.getServerPredictionAvailabilityValue());
+        mUMATestHelper.verifyServerPredictionValid(false);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
     public void testUMAAutofillDisabled() throws Throwable {
         mTestAutofillManagerWrapper.setDisabled();
         mUMATestHelper.triggerAutofill();
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 869928b..e75705e2 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2249,6 +2249,7 @@
     "system/session/logout_confirmation_controller_unittest.cc",
     "system/session/session_limit_notification_controller_unittest.cc",
     "system/status_area_widget_unittest.cc",
+    "system/time/time_tray_item_view_unittest.cc",
     "system/time/time_view_unittest.cc",
     "system/toast/toast_manager_unittest.cc",
     "system/tracing_notification_controller_unittest.cc",
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index a8cf9de..50a0573 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -933,6 +933,9 @@
       <message name="IDS_ASH_STATUS_TRAY_DATE" desc="The date displayed on ash system bubble, Depending on launguage, please choose the best separator(eg ',') between abbreviated weekday and date">
         <ph name="short_weekday">$1<ex>Fri</ex></ph>, <ph name="date">$2<ex>Aug 31, 2012</ex></ph>
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_DATE_TIME" desc="The time displayed on ash system tray, Depending on launguage, please choose the best separator(eg ',') between abbreviated date and time">
+        <ph name="date">$1<ex>Aug 31</ex></ph>, <ph name="time">$2<ex>03:00</ex></ph>
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_REBOOT" desc="The accessible text for the reboot button.">
         Restart
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_DATE_TIME.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_DATE_TIME.png.sha1
new file mode 100644
index 0000000..41a5ada1
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_DATE_TIME.png.sha1
@@ -0,0 +1 @@
+56b9ecb9b85721cdee47f52a37df3dcd4f6867a5
\ No newline at end of file
diff --git a/ash/ime/ime_mode_indicator_view.cc b/ash/ime/ime_mode_indicator_view.cc
index a5a6d2ec..b6402b58 100644
--- a/ash/ime/ime_mode_indicator_view.cc
+++ b/ash/ime/ime_mode_indicator_view.cc
@@ -14,6 +14,7 @@
 #include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/fill_layout.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
 #include "ui/wm/core/window_animations.h"
 
 namespace ash {
@@ -29,8 +30,12 @@
 
 class ModeIndicatorFrameView : public views::BubbleFrameView {
  public:
+  METADATA_HEADER(ModeIndicatorFrameView);
+
   explicit ModeIndicatorFrameView()
       : views::BubbleFrameView(gfx::Insets(), gfx::Insets()) {}
+  ModeIndicatorFrameView(const ModeIndicatorFrameView&) = delete;
+  ModeIndicatorFrameView& operator=(const ModeIndicatorFrameView&) = delete;
   ~ModeIndicatorFrameView() override {}
 
  private:
@@ -40,10 +45,11 @@
         ->GetDisplayNearestPoint(rect.CenterPoint())
         .bounds();
   }
-
-  DISALLOW_COPY_AND_ASSIGN(ModeIndicatorFrameView);
 };
 
+BEGIN_METADATA(ModeIndicatorFrameView, views::BubbleFrameView)
+END_METADATA
+
 }  // namespace
 
 ImeModeIndicatorView::ImeModeIndicatorView(const gfx::Rect& cursor_bounds,
@@ -84,10 +90,6 @@
   return size;
 }
 
-const char* ImeModeIndicatorView::GetClassName() const {
-  return "ImeModeIndicatorView";
-}
-
 void ImeModeIndicatorView::Init() {
   SetLayoutManager(std::make_unique<views::FillLayout>());
   AddChildView(label_view_);
@@ -105,4 +107,7 @@
   return frame;
 }
 
+BEGIN_METADATA(ImeModeIndicatorView, views::BubbleDialogDelegateView)
+END_METADATA
+
 }  // namespace ash
diff --git a/ash/ime/ime_mode_indicator_view.h b/ash/ime/ime_mode_indicator_view.h
index e4b68c1..2208a09 100644
--- a/ash/ime/ime_mode_indicator_view.h
+++ b/ash/ime/ime_mode_indicator_view.h
@@ -11,6 +11,7 @@
 #include "base/timer/timer.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
+#include "ui/views/metadata/metadata_header_macros.h"
 #include "ui/views/widget/widget.h"
 
 namespace views {
@@ -23,9 +24,13 @@
 // Dvorak) after switching IMEs with an accelerator (e.g. Ctrl-Space).
 class ASH_EXPORT ImeModeIndicatorView : public views::BubbleDialogDelegateView {
  public:
+  METADATA_HEADER(ImeModeIndicatorView);
+
   // The cursor bounds is in the universal screen coordinates in DIP.
   ImeModeIndicatorView(const gfx::Rect& cursor_bounds,
                        const base::string16& label);
+  ImeModeIndicatorView(const ImeModeIndicatorView&) = delete;
+  ImeModeIndicatorView& operator=(const ImeModeIndicatorView&) = delete;
   ~ImeModeIndicatorView() override;
 
   // Show the mode indicator then hide with fading animation.
@@ -35,7 +40,6 @@
   void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
                                 views::Widget* widget) const override;
   gfx::Size CalculatePreferredSize() const override;
-  const char* GetClassName() const override;
   void Init() override;
 
  protected:
@@ -47,8 +51,6 @@
   gfx::Rect cursor_bounds_;
   views::Label* label_view_;
   base::OneShotTimer timer_;
-
-  DISALLOW_COPY_AND_ASSIGN(ImeModeIndicatorView);
 };
 
 }  // namespace ash
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index 769d3b0f..7dc4fd3 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -143,6 +143,9 @@
 const base::Feature kDragUnpinnedAppToPin{"DragUnpinnedAppToPin",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kScalableStatusArea{"ScalableStatusArea",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kKeyboardBasedDisplayArrangementInSettings{
     "KeyboardBasedDisplayArrangementInSettings",
     base::FEATURE_ENABLED_BY_DEFAULT};
@@ -321,6 +324,10 @@
   return base::FeatureList::IsEnabled(kDragUnpinnedAppToPin);
 }
 
+bool IsScalableStatusAreaEnabled() {
+  return base::FeatureList::IsEnabled(kScalableStatusArea);
+}
+
 namespace {
 
 // The boolean flag indicating if "WebUITabStrip" feature is enabled in Chrome.
diff --git a/ash/public/cpp/ash_features.h b/ash/public/cpp/ash_features.h
index d7ab211..2c477c3 100644
--- a/ash/public/cpp/ash_features.h
+++ b/ash/public/cpp/ash_features.h
@@ -194,6 +194,9 @@
 // Enables dragging an unpinned open app to pinned app side to pin.
 ASH_PUBLIC_EXPORT extern const base::Feature kDragUnpinnedAppToPin;
 
+// Enables the system tray to show more information in larger screen.
+ASH_PUBLIC_EXPORT extern const base::Feature kScalableStatusArea;
+
 ASH_PUBLIC_EXPORT bool IsAllowAmbientEQEnabled();
 
 ASH_PUBLIC_EXPORT bool IsAltTabLimitedToActiveDesk();
@@ -274,6 +277,8 @@
 
 ASH_PUBLIC_EXPORT bool IsDragUnpinnedAppToPinEnabled();
 
+ASH_PUBLIC_EXPORT bool IsScalableStatusAreaEnabled();
+
 // These two functions are supposed to be temporary functions to set or get
 // whether "WebUITabStrip" feature is enabled from Chrome.
 ASH_PUBLIC_EXPORT void SetWebUITabStripEnabled(bool enabled);
diff --git a/ash/system/time/time_tray_item_view.cc b/ash/system/time/time_tray_item_view.cc
index 2f77e16..1567031 100644
--- a/ash/system/time/time_tray_item_view.cc
+++ b/ash/system/time/time_tray_item_view.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/time/time_tray_item_view.h"
 
+#include "ash/public/cpp/ash_features.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
@@ -16,14 +17,18 @@
 
 namespace tray {
 
-TimeTrayItemView::TimeTrayItemView(Shelf* shelf)
-    : TrayItemView(shelf), session_observer_(this) {
+TimeTrayItemView::TimeTrayItemView(Shelf* shelf, UnifiedSystemTrayModel* model)
+    : TrayItemView(shelf), model_(model), session_observer_(this) {
+  system_tray_model_observation_.Observe(model_);
+
   TimeView::ClockLayout clock_layout =
       shelf->IsHorizontalAlignment() ? TimeView::ClockLayout::HORIZONTAL_CLOCK
                                      : TimeView::ClockLayout::VERTICAL_CLOCK;
   time_view_ =
       new TimeView(clock_layout, Shell::Get()->system_tray_model()->clock());
   AddChildView(time_view_);
+
+  OnSystemTrayButtonSizeChanged(model_->GetSystemTrayButtonSize());
 }
 
 TimeTrayItemView::~TimeTrayItemView() = default;
@@ -44,6 +49,17 @@
   time_view_->SetTextColor(TrayIconColor(state));
 }
 
+void TimeTrayItemView::OnSystemTrayButtonSizeChanged(
+    UnifiedSystemTrayModel::SystemTrayButtonSize system_tray_size) {
+  time_view_->SetShowDateWhenHorizontal(
+      features::IsScalableStatusAreaEnabled() &&
+      system_tray_size == UnifiedSystemTrayModel::SystemTrayButtonSize::kLarge);
+}
+
+void TimeTrayItemView::Reset() {
+  system_tray_model_observation_.Reset();
+}
+
 const char* TimeTrayItemView::GetClassName() const {
   return "TimeTrayItemView";
 }
diff --git a/ash/system/time/time_tray_item_view.h b/ash/system/time/time_tray_item_view.h
index d1c29ae..ea190f6 100644
--- a/ash/system/time/time_tray_item_view.h
+++ b/ash/system/time/time_tray_item_view.h
@@ -8,7 +8,9 @@
 #include "ash/ash_export.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/system/tray/tray_item_view.h"
+#include "ash/system/unified/unified_system_tray_model.h"
 #include "base/macros.h"
+#include "base/scoped_observation.h"
 
 namespace ash {
 class Shelf;
@@ -17,9 +19,11 @@
 
 class TimeView;
 
-class TimeTrayItemView : public TrayItemView, public SessionObserver {
+class ASH_EXPORT TimeTrayItemView : public TrayItemView,
+                                    public SessionObserver,
+                                    public UnifiedSystemTrayModel::Observer {
  public:
-  explicit TimeTrayItemView(Shelf* shelf);
+  TimeTrayItemView(Shelf* shelf, UnifiedSystemTrayModel* model);
   ~TimeTrayItemView() override;
 
   void UpdateAlignmentForShelf(Shelf* shelf);
@@ -31,13 +35,26 @@
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
 
+  // UnifiedSystemTrayModel::Observer:
+  void OnSystemTrayButtonSizeChanged(
+      UnifiedSystemTrayModel::SystemTrayButtonSize system_tray_size) override;
+
+  // Reset the view by removing observer to |model_|.
+  void Reset();
+
   // views::View:
   const char* GetClassName() const override;
   void OnThemeChanged() override;
 
  private:
+  friend class TimeTrayItemViewTest;
+
+  UnifiedSystemTrayModel* model_ = nullptr;
   TimeView* time_view_ = nullptr;
   ScopedSessionObserver session_observer_;
+  base::ScopedObservation<UnifiedSystemTrayModel,
+                          UnifiedSystemTrayModel::Observer>
+      system_tray_model_observation_{this};
   DISALLOW_COPY_AND_ASSIGN(TimeTrayItemView);
 };
 
diff --git a/ash/system/time/time_tray_item_view_unittest.cc b/ash/system/time/time_tray_item_view_unittest.cc
new file mode 100644
index 0000000..93c84cb5
--- /dev/null
+++ b/ash/system/time/time_tray_item_view_unittest.cc
@@ -0,0 +1,97 @@
+// 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 "ash/system/time/time_tray_item_view.h"
+
+#include "ash/public/cpp/ash_features.h"
+#include "ash/shelf/shelf.h"
+#include "ash/system/time/time_view.h"
+#include "ash/system/unified/unified_system_tray_model.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+
+namespace ash {
+namespace tray {
+
+class TimeTrayItemViewTest : public AshTestBase,
+                             public testing::WithParamInterface<bool> {
+ public:
+  TimeTrayItemViewTest() = default;
+  ~TimeTrayItemViewTest() override = default;
+
+  // AshTestBase:
+  void SetUp() override {
+    AshTestBase::SetUp();
+    scoped_feature_list_.InitWithFeatureState(
+        features::kScalableStatusArea, is_scalable_status_area_enabled());
+
+    model_ = std::make_unique<UnifiedSystemTrayModel>(GetPrimaryShelf());
+    time_tray_item_view_ =
+        std::make_unique<TimeTrayItemView>(GetPrimaryShelf(), model_.get());
+  }
+
+  void TearDown() override {
+    time_tray_item_view_.reset();
+    model_.reset();
+    AshTestBase::TearDown();
+  }
+
+  bool is_scalable_status_area_enabled() { return GetParam(); }
+
+  // Returns true if the time view is in horizontal layout, false if it is in
+  // vertical layout.
+  bool IsTimeViewInHorizontalLayout() {
+    // Time view is in horizontal layout if its subview is in use (it transfers
+    // the ownership to the views hierarchy and becomes nullptr),
+    return !time_tray_item_view_->time_view_->horizontal_view_;
+  }
+
+  bool ShouldShowDateInTimeView() {
+    return time_tray_item_view_->time_view_->show_date_when_horizontal_;
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<UnifiedSystemTrayModel> model_;
+  std::unique_ptr<TimeTrayItemView> time_tray_item_view_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         TimeTrayItemViewTest,
+                         testing::Bool() /* is_scalable_status_area_enabled */);
+
+TEST_P(TimeTrayItemViewTest, ShelfAlignment) {
+  // The tray should show time horizontal view when the shelf is bottom.
+  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kBottom);
+  time_tray_item_view_->UpdateAlignmentForShelf(GetPrimaryShelf());
+  EXPECT_TRUE(IsTimeViewInHorizontalLayout());
+
+  // The tray should show time vertical view when the shelf is left or right.
+  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kLeft);
+  time_tray_item_view_->UpdateAlignmentForShelf(GetPrimaryShelf());
+  EXPECT_FALSE(IsTimeViewInHorizontalLayout());
+
+  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kRight);
+  time_tray_item_view_->UpdateAlignmentForShelf(GetPrimaryShelf());
+  EXPECT_FALSE(IsTimeViewInHorizontalLayout());
+
+  // The tray should show time horizontal view when switching back to bottom
+  // shelf.
+  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kBottom);
+  time_tray_item_view_->UpdateAlignmentForShelf(GetPrimaryShelf());
+  EXPECT_TRUE(IsTimeViewInHorizontalLayout());
+}
+
+TEST_P(TimeTrayItemViewTest, DisplayChanged) {
+  UpdateDisplay("800x800");
+  EXPECT_FALSE(ShouldShowDateInTimeView());
+
+  // Date should be shown in large screen size (when scalable status area is
+  // enabled).
+  UpdateDisplay("1680x800");
+  EXPECT_EQ(is_scalable_status_area_enabled(), ShouldShowDateInTimeView());
+}
+
+}  // namespace tray
+}  // namespace ash
diff --git a/ash/system/time/time_view.cc b/ash/system/time/time_view.cc
index d91137ad..3fcc448 100644
--- a/ash/system/time/time_view.cc
+++ b/ash/system/time/time_view.cc
@@ -54,6 +54,12 @@
 // when the shelf is vertically aligned.
 const int kClockLeadingPadding = 8;
 
+base::string16 FormatDate(const base::Time& time) {
+  // Use 'short' month format (e.g., "Oct") followed by non-padded day of
+  // month (e.g., "2", "10").
+  return base::TimeFormatWithPattern(time, "LLLd");
+}
+
 }  // namespace
 
 TimeView::TimeView(ClockLayout clock_layout, ClockModel* model)
@@ -112,6 +118,14 @@
   vertical_label_minutes_->SetShadows(shadows);
 }
 
+void TimeView::SetShowDateWhenHorizontal(bool show_date_when_horizontal) {
+  if (show_date_when_horizontal_ == show_date_when_horizontal)
+    return;
+  show_date_when_horizontal_ = show_date_when_horizontal;
+  UpdateText();
+  PreferredSizeChanged();
+}
+
 void TimeView::OnDateFormatChanged() {
   UpdateTimeFormat();
 }
@@ -189,7 +203,11 @@
 
   base::string16 current_time = base::TimeFormatTimeOfDayWithHourClockType(
       now, model_->hour_clock_type(), base::kDropAmPm);
-  horizontal_label_->SetText(current_time);
+  base::string16 current_date_time = l10n_util::GetStringFUTF16(
+      IDS_ASH_STATUS_TRAY_DATE_TIME, FormatDate(now), current_time);
+
+  horizontal_label_->SetText(show_date_when_horizontal_ ? current_date_time
+                                                        : current_time);
   horizontal_label_->SetTooltipText(base::TimeFormatFriendlyDate(now));
   horizontal_label_->NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged,
                                               true);
diff --git a/ash/system/time/time_view.h b/ash/system/time/time_view.h
index 576de84..550b1a7 100644
--- a/ash/system/time/time_view.h
+++ b/ash/system/time/time_view.h
@@ -56,6 +56,9 @@
   // Updates the time text shadow values.
   void SetTextShadowValues(const gfx::ShadowValues& shadows);
 
+  // Shows the date in horizontal view when |show_date_when_horizontal| is true.
+  void SetShowDateWhenHorizontal(bool show_date_when_horizontal);
+
   // ClockObserver:
   void OnDateFormatChanged() override;
   void OnSystemClockTimeUpdated() override;
@@ -69,6 +72,7 @@
 
  private:
   friend class TimeViewTest;
+  friend class TimeTrayItemViewTest;
 
   // ActionableView:
   bool PerformAction(const ui::Event& event) override;
@@ -107,6 +111,9 @@
   views::Label* vertical_label_hours_;
   views::Label* vertical_label_minutes_;
 
+  // Indicates if date should be show in horizontal view.
+  bool show_date_when_horizontal_ = false;
+
   // Invokes UpdateText() when the displayed time should change.
   base::OneShotTimer timer_;
 
diff --git a/ash/system/time/time_view_unittest.cc b/ash/system/time/time_view_unittest.cc
index b764b0b0..803a54a 100644
--- a/ash/system/time/time_view_unittest.cc
+++ b/ash/system/time/time_view_unittest.cc
@@ -76,5 +76,29 @@
   EXPECT_FALSE(vertical_view()->parent());
 }
 
+// Test the show date mode in the time view.
+TEST_F(TimeViewTest, ShowDateMode) {
+  CreateTimeView(TimeView::ClockLayout::HORIZONTAL_CLOCK);
+  base::string16 time_text = horizontal_label()->GetText();
+
+  // When showing date, the text is expected to be longer since it's showing
+  // more content.
+  time_view()->SetShowDateWhenHorizontal(true /* show_date_when_horizontal */);
+  EXPECT_GT(horizontal_label()->GetText(), time_text);
+
+  // Resetting show date mode should show only the time.
+  time_view()->SetShowDateWhenHorizontal(false /* show_date_when_horizontal */);
+  EXPECT_EQ(time_text, horizontal_label()->GetText());
+
+  time_view()->UpdateClockLayout(TimeView::ClockLayout::VERTICAL_CLOCK);
+  base::string16 hours_text = vertical_label_hours()->GetText();
+  base::string16 minutes_text = vertical_label_minutes()->GetText();
+
+  // Show date mode should not affect vertical view.
+  time_view()->SetShowDateWhenHorizontal(true /* show_date_when_horizontal */);
+  EXPECT_EQ(hours_text, vertical_label_hours()->GetText());
+  EXPECT_EQ(minutes_text, vertical_label_minutes()->GetText());
+}
+
 }  // namespace tray
 }  // namespace ash
diff --git a/ash/system/unified/unified_system_tray.cc b/ash/system/unified/unified_system_tray.cc
index e686448..edf3808 100644
--- a/ash/system/unified/unified_system_tray.cc
+++ b/ash/system/unified/unified_system_tray.cc
@@ -20,7 +20,6 @@
 #include "ash/system/network/network_tray_view.h"
 #include "ash/system/power/tray_power.h"
 #include "ash/system/privacy_screen/privacy_screen_toast_controller.h"
-#include "ash/system/status_area_widget.h"
 #include "ash/system/time/time_tray_item_view.h"
 #include "ash/system/time/time_view.h"
 #include "ash/system/tray/tray_constants.h"
@@ -120,8 +119,7 @@
 UnifiedSystemTray::UnifiedSystemTray(Shelf* shelf)
     : TrayBackgroundView(shelf),
       ui_delegate_(std::make_unique<UiDelegate>(this)),
-      model_(std::make_unique<UnifiedSystemTrayModel>(
-          shelf->GetStatusAreaWidget()->GetRootView())),
+      model_(std::make_unique<UnifiedSystemTrayModel>(shelf)),
       slider_bubble_controller_(
           std::make_unique<UnifiedSliderBubbleController>(this)),
       privacy_screen_toast_controller_(
@@ -136,7 +134,7 @@
           new CameraMicTrayItemView(shelf, CameraMicTrayItemView::Type::kMic)),
       notification_counter_item_(new NotificationCounterView(shelf)),
       quiet_mode_view_(new QuietModeView(shelf)),
-      time_view_(new tray::TimeTrayItemView(shelf)) {
+      time_view_(new tray::TimeTrayItemView(shelf, model())) {
   tray_container()->SetMargin(
       kUnifiedTrayContentPadding -
           ShelfConfig::Get()->status_area_hit_region_padding(),
@@ -175,6 +173,10 @@
   if (bubble_)
     bubble_->CloseNow();
   bubble_.reset();
+
+  // Reset the view to remove its dependency from |model_|, since this view is
+  // destructed after |model_|.
+  time_view_->Reset();
 }
 
 bool UnifiedSystemTray::IsBubbleShown() const {
diff --git a/ash/system/unified/unified_system_tray_model.cc b/ash/system/unified/unified_system_tray_model.cc
index 14dd43b..37f8559 100644
--- a/ash/system/unified/unified_system_tray_model.cc
+++ b/ash/system/unified/unified_system_tray_model.cc
@@ -5,11 +5,24 @@
 #include "ash/system/unified/unified_system_tray_model.h"
 
 #include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/shelf/shelf_widget.h"
 #include "ash/shell.h"
+#include "ash/shell_observer.h"
 #include "ash/system/brightness_control_delegate.h"
+#include "ash/system/status_area_widget.h"
 #include "base/bind.h"
 #include "chromeos/dbus/power_manager/backlight.pb.h"
 
+namespace {
+
+// The minimum width for system tray with size of kMedium.
+constexpr int kMinWidthMediumSystemTray = 800;
+
+// The maximum width for system tray with size of kMedium.
+constexpr int kMaxWidthMediumSystemTray = 1280;
+
+}  // namespace
+
 namespace ash {
 
 class UnifiedSystemTrayModel::DBusObserver
@@ -34,6 +47,31 @@
   DISALLOW_COPY_AND_ASSIGN(DBusObserver);
 };
 
+class UnifiedSystemTrayModel::SizeObserver : public display::DisplayObserver,
+                                             public ShellObserver {
+ public:
+  explicit SizeObserver(UnifiedSystemTrayModel* owner);
+  ~SizeObserver() override;
+  SizeObserver(const SizeObserver&) = delete;
+  SizeObserver& operator=(const SizeObserver&) = delete;
+
+ private:
+  // display::DisplayObserver:
+  void OnDisplayMetricsChanged(const display::Display& display,
+                               uint32_t changed_metrics) override;
+
+  // ShellObserver:
+  void OnShelfAlignmentChanged(aura::Window* root_window,
+                               ShelfAlignment old_alignment) override;
+
+  void Update();
+
+  UnifiedSystemTrayModel* const owner_;
+
+  // Keep track of current system tray size.
+  UnifiedSystemTrayModel::SystemTrayButtonSize system_tray_size_;
+};
+
 UnifiedSystemTrayModel::DBusObserver::DBusObserver(
     UnifiedSystemTrayModel* owner)
     : owner_(owner) {
@@ -72,9 +110,51 @@
           power_manager::BacklightBrightnessChange_Cause_USER_REQUEST);
 }
 
-UnifiedSystemTrayModel::UnifiedSystemTrayModel(views::View* owner_view)
-    : dbus_observer_(std::make_unique<DBusObserver>(this)),
-      pagination_model_(std::make_unique<PaginationModel>(owner_view)) {}
+UnifiedSystemTrayModel::SizeObserver::SizeObserver(
+    UnifiedSystemTrayModel* owner)
+    : owner_(owner) {
+  display::Screen::GetScreen()->AddObserver(this);
+  Shell::Get()->AddShellObserver(this);
+  system_tray_size_ = owner_->GetSystemTrayButtonSize();
+}
+
+UnifiedSystemTrayModel::SizeObserver::~SizeObserver() {
+  display::Screen::GetScreen()->RemoveObserver(this);
+  Shell::Get()->RemoveShellObserver(this);
+}
+
+void UnifiedSystemTrayModel::SizeObserver::OnDisplayMetricsChanged(
+    const display::Display& display,
+    uint32_t changed_metrics) {
+  if (owner_->GetDisplay().id() != display.id())
+    return;
+  Update();
+}
+
+void UnifiedSystemTrayModel::SizeObserver::OnShelfAlignmentChanged(
+    aura::Window* root_window,
+    ShelfAlignment old_alignment) {
+  Update();
+}
+
+void UnifiedSystemTrayModel::SizeObserver::Update() {
+  UnifiedSystemTrayModel::SystemTrayButtonSize new_size =
+      owner_->GetSystemTrayButtonSize();
+  if (system_tray_size_ == new_size)
+    return;
+
+  system_tray_size_ = new_size;
+  owner_->SystemTrayButtonSizeChanged(system_tray_size_);
+}
+
+UnifiedSystemTrayModel::UnifiedSystemTrayModel(Shelf* shelf)
+    : shelf_(shelf),
+      dbus_observer_(std::make_unique<DBusObserver>(this)),
+      size_observer_(std::make_unique<SizeObserver>(this)) {
+  // |shelf_| might be null in unit tests.
+  pagination_model_ = std::make_unique<PaginationModel>(
+      shelf_ ? shelf_->GetStatusAreaWidget()->GetRootView() : nullptr);
+}
 
 UnifiedSystemTrayModel::~UnifiedSystemTrayModel() = default;
 
@@ -124,6 +204,23 @@
   notification_changes_.clear();
 }
 
+UnifiedSystemTrayModel::SystemTrayButtonSize
+UnifiedSystemTrayModel::GetSystemTrayButtonSize() const {
+  // |shelf_| might be null in unit tests, returns medium size as default.
+  if (!shelf_)
+    return SystemTrayButtonSize::kMedium;
+
+  int display_size = shelf_->IsHorizontalAlignment()
+                         ? GetDisplay().size().width()
+                         : GetDisplay().size().height();
+
+  if (display_size < kMinWidthMediumSystemTray)
+    return SystemTrayButtonSize::kSmall;
+  if (display_size <= kMaxWidthMediumSystemTray)
+    return SystemTrayButtonSize::kMedium;
+  return SystemTrayButtonSize::kLarge;
+}
+
 void UnifiedSystemTrayModel::DisplayBrightnessChanged(float brightness,
                                                       bool by_user) {
   display_brightness_ = brightness;
@@ -138,4 +235,22 @@
     observer.OnKeyboardBrightnessChanged(by_user);
 }
 
+void UnifiedSystemTrayModel::SystemTrayButtonSizeChanged(
+    SystemTrayButtonSize system_tray_size) {
+  for (auto& observer : observers_)
+    observer.OnSystemTrayButtonSizeChanged(system_tray_size);
+}
+
+const display::Display UnifiedSystemTrayModel::GetDisplay() const {
+  // |shelf_| might be null in unit tests, returns primary display as default.
+  if (!shelf_)
+    return display::Screen::GetScreen()->GetPrimaryDisplay();
+
+  return display::Screen::GetScreen()->GetDisplayNearestWindow(
+      shelf_->GetStatusAreaWidget()
+          ->GetRootView()
+          ->GetWidget()
+          ->GetNativeWindow());
+}
+
 }  // namespace ash
diff --git a/ash/system/unified/unified_system_tray_model.h b/ash/system/unified/unified_system_tray_model.h
index 559954e3..e1253bd 100644
--- a/ash/system/unified/unified_system_tray_model.h
+++ b/ash/system/unified/unified_system_tray_model.h
@@ -10,8 +10,14 @@
 #include "base/observer_list.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 
+namespace display {
+class Display;
+}  // namespace display
+
 namespace ash {
 
+class Shelf;
+
 // Model class that stores UnifiedSystemTray's UI specific variables. Owned by
 // UnifiedSystemTray status area button. Not to be confused with UI agnostic
 // SystemTrayModel.
@@ -36,6 +42,19 @@
     NOTIFICATION_ID,
   };
 
+  // Enumeration of possible sizes of the system tray button. Larger screen will
+  // have larger tray button with additional information.
+  enum class SystemTrayButtonSize {
+    // Display wifi, battery, notification counter icons and time.
+    kSmall = 0,
+    // Display those in small unified system tray, plus important notification
+    // icons.
+    kMedium = 1,
+    // Display those in medium unified system tray, plus the current date.
+    kLarge = 2,
+    kMaxValue = kLarge,
+  };
+
   class Observer {
    public:
     virtual ~Observer() {}
@@ -43,9 +62,11 @@
     // |by_user| is true when brightness is changed by user action.
     virtual void OnDisplayBrightnessChanged(bool by_user) {}
     virtual void OnKeyboardBrightnessChanged(bool by_user) {}
+    virtual void OnSystemTrayButtonSizeChanged(
+        SystemTrayButtonSize system_tray_size) {}
   };
 
-  explicit UnifiedSystemTrayModel(views::View* owner_view);
+  explicit UnifiedSystemTrayModel(Shelf* shelf);
   ~UnifiedSystemTrayModel();
 
   void AddObserver(Observer* observer);
@@ -78,6 +99,9 @@
   // NOTIFICATION_ID.
   void SetTargetNotification(const std::string& notification_id);
 
+  // Get the size of the system tray depends on the size of the display screen.
+  SystemTrayButtonSize GetSystemTrayButtonSize() const;
+
   float display_brightness() const { return display_brightness_; }
   float keyboard_brightness() const { return keyboard_brightness_; }
 
@@ -102,8 +126,15 @@
  private:
   class DBusObserver;
 
+  // Keeps track all the sources that can change the size of system tray button.
+  class SizeObserver;
+
   void DisplayBrightnessChanged(float brightness, bool by_user);
   void KeyboardBrightnessChanged(float brightness, bool by_user);
+  void SystemTrayButtonSizeChanged(SystemTrayButtonSize system_tray_size);
+
+  // Get the display that owns the tray.
+  const display::Display GetDisplay() const;
 
   // Target mode which is used to decide the scroll position of the message
   // center on opening. See the comment in |NotificationTargetMode|.
@@ -128,8 +159,12 @@
   // <notification ID, if notification is manually expanded>
   std::map<std::string, bool> notification_changes_;
 
+  Shelf* const shelf_;
+
   std::unique_ptr<DBusObserver> dbus_observer_;
 
+  std::unique_ptr<SizeObserver> size_observer_;
+
   base::ObserverList<Observer>::Unchecked observers_;
 
   std::unique_ptr<PaginationModel> pagination_model_;
diff --git a/base/allocator/partition_allocator/thread_cache.cc b/base/allocator/partition_allocator/thread_cache.cc
index 9621da0..d72100ea 100644
--- a/base/allocator/partition_allocator/thread_cache.cc
+++ b/base/allocator/partition_allocator/thread_cache.cc
@@ -106,7 +106,9 @@
 }
 
 void ThreadCacheRegistry::StartPeriodicPurge() {
-  PostDelayedPurgeTask();
+  // Can be called several times, don't post multiple tasks.
+  if (!has_pending_purge_task_)
+    PostDelayedPurgeTask();
 }
 
 void ThreadCacheRegistry::PostDelayedPurgeTask() {
diff --git a/base/run_loop.cc b/base/run_loop.cc
index a0517fa..1865a3b1 100644
--- a/base/run_loop.cc
+++ b/base/run_loop.cc
@@ -265,7 +265,7 @@
 }
 
 #if DCHECK_IS_ON()
-RunLoop::ScopedDisallowRunningForTesting::ScopedDisallowRunningForTesting()
+RunLoop::ScopedDisallowRunning::ScopedDisallowRunning()
     : current_delegate_(GetTlsDelegate().Get()),
       previous_run_allowance_(
           current_delegate_ ? current_delegate_->allow_running_for_testing_
@@ -274,7 +274,7 @@
     current_delegate_->allow_running_for_testing_ = false;
 }
 
-RunLoop::ScopedDisallowRunningForTesting::~ScopedDisallowRunningForTesting() {
+RunLoop::ScopedDisallowRunning::~ScopedDisallowRunning() {
   DCHECK_EQ(current_delegate_, GetTlsDelegate().Get());
   if (current_delegate_)
     current_delegate_->allow_running_for_testing_ = previous_run_allowance_;
@@ -283,10 +283,8 @@
 // Defined out of line so that the compiler doesn't inline these and realize
 // the scope has no effect and then throws an "unused variable" warning in
 // non-dcheck builds.
-RunLoop::ScopedDisallowRunningForTesting::ScopedDisallowRunningForTesting() =
-    default;
-RunLoop::ScopedDisallowRunningForTesting::~ScopedDisallowRunningForTesting() =
-    default;
+RunLoop::ScopedDisallowRunning::ScopedDisallowRunning() = default;
+RunLoop::ScopedDisallowRunning::~ScopedDisallowRunning() = default;
 #endif  // DCHECK_IS_ON()
 
 RunLoop::RunLoopTimeout::RunLoopTimeout() = default;
@@ -309,7 +307,7 @@
 #if DCHECK_IS_ON()
   DCHECK(delegate_->allow_running_for_testing_)
       << "RunLoop::Run() isn't allowed in the scope of a "
-         "ScopedDisallowRunningForTesting. Hint: if mixing "
+         "ScopedDisallowRunning. Hint: if mixing "
          "TestMockTimeTaskRunners on same thread, use TestMockTimeTaskRunner's "
          "API instead of RunLoop to drive individual task runners.";
   DCHECK(!run_called_);
diff --git a/base/run_loop.h b/base/run_loop.h
index efd4895..483147e 100644
--- a/base/run_loop.h
+++ b/base/run_loop.h
@@ -253,7 +253,7 @@
   static void QuitCurrentWhenIdleDeprecated();
   static RepeatingClosure QuitCurrentWhenIdleClosureDeprecated();
 
-  // Run() will DCHECK if called while there's a ScopedDisallowRunningForTesting
+  // Run() will DCHECK if called while there's a ScopedDisallowRunning
   // in scope on its thread. This is useful to add safety to some test
   // constructs which allow multiple task runners to share the main thread in
   // unit tests. While the main thread can be shared by multiple runners to
@@ -261,14 +261,12 @@
   // RunLoop::Delegate per thread and RunLoop::Run() should only be invoked from
   // it (or it would result in incorrectly driving TaskRunner A while in
   // TaskRunner B's context).
-  class BASE_EXPORT ScopedDisallowRunningForTesting {
+  class BASE_EXPORT ScopedDisallowRunning {
    public:
-    ScopedDisallowRunningForTesting();
-    ScopedDisallowRunningForTesting(const ScopedDisallowRunningForTesting&) =
-        delete;
-    ScopedDisallowRunningForTesting& operator=(
-        const ScopedDisallowRunningForTesting&) = delete;
-    ~ScopedDisallowRunningForTesting();
+    ScopedDisallowRunning();
+    ScopedDisallowRunning(const ScopedDisallowRunning&) = delete;
+    ScopedDisallowRunning& operator=(const ScopedDisallowRunning&) = delete;
+    ~ScopedDisallowRunning();
 
    private:
 #if DCHECK_IS_ON()
diff --git a/base/run_loop_unittest.cc b/base/run_loop_unittest.cc
index 3c0494d..7b44cb3 100644
--- a/base/run_loop_unittest.cc
+++ b/base/run_loop_unittest.cc
@@ -532,13 +532,13 @@
   RunLoop::RemoveNestingObserverOnCurrentThread(&nesting_observer);
 }
 
-TEST_P(RunLoopTest, DisallowRunningForTesting) {
-  RunLoop::ScopedDisallowRunningForTesting disallow_running;
+TEST_P(RunLoopTest, DisallowRunning) {
+  RunLoop::ScopedDisallowRunning disallow_running;
   EXPECT_DCHECK_DEATH({ run_loop_.RunUntilIdle(); });
 }
 
-TEST_P(RunLoopTest, ExpiredDisallowRunningForTesting) {
-  { RunLoop::ScopedDisallowRunningForTesting disallow_running; }
+TEST_P(RunLoopTest, ExpiredDisallowRunning) {
+  { RunLoop::ScopedDisallowRunning disallow_running; }
   // Running should be fine after |disallow_running| goes out of scope.
   run_loop_.RunUntilIdle();
 }
diff --git a/base/test/test_mock_time_task_runner.cc b/base/test/test_mock_time_task_runner.cc
index 3b8cbda..b1f0196 100644
--- a/base/test/test_mock_time_task_runner.cc
+++ b/base/test/test_mock_time_task_runner.cc
@@ -147,7 +147,7 @@
 // Ref. TestMockTimeTaskRunner::RunsTasksInCurrentSequence().
 TestMockTimeTaskRunner::ScopedContext::ScopedContext(
     scoped_refptr<TestMockTimeTaskRunner> scope)
-    : on_destroy_(ThreadTaskRunnerHandle::OverrideForTesting(scope)) {
+    : thread_task_runner_handle_override_(scope) {
   scope->RunUntilIdle();
 }
 
@@ -344,11 +344,10 @@
 
   // Multiple test task runners can share the same thread for determinism in
   // unit tests. Make sure this TestMockTimeTaskRunner's tasks run in its scope.
-  ScopedClosureRunner undo_override;
+  base::Optional<ThreadTaskRunnerHandleOverrideForTesting> ttrh_override;
   if (!ThreadTaskRunnerHandle::IsSet() ||
       ThreadTaskRunnerHandle::Get() != proxy_task_runner_.get()) {
-    undo_override =
-        ThreadTaskRunnerHandle::OverrideForTesting(proxy_task_runner_.get());
+    ttrh_override.emplace(proxy_task_runner_.get());
   }
 
   const TimeTicks original_now_ticks = NowTicks();
diff --git a/base/test/test_mock_time_task_runner.h b/base/test/test_mock_time_task_runner.h
index 833f3eb..dbf075f 100644
--- a/base/test/test_mock_time_task_runner.h
+++ b/base/test/test_mock_time_task_runner.h
@@ -21,6 +21,7 @@
 #include "base/synchronization/lock.h"
 #include "base/test/test_pending_task.h"
 #include "base/threading/thread_checker_impl.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/clock.h"
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
@@ -117,7 +118,8 @@
     ~ScopedContext();
 
    private:
-    ScopedClosureRunner on_destroy_;
+    ThreadTaskRunnerHandleOverrideForTesting
+        thread_task_runner_handle_override_;
     DISALLOW_COPY_AND_ASSIGN(ScopedContext);
   };
 
diff --git a/base/test/test_simple_task_runner.cc b/base/test/test_simple_task_runner.cc
index 3e5d70ee..e8e5781 100644
--- a/base/test/test_simple_task_runner.cc
+++ b/base/test/test_simple_task_runner.cc
@@ -85,10 +85,10 @@
 
   // Multiple test task runners can share the same thread for determinism in
   // unit tests. Make sure this TestSimpleTaskRunner's tasks run in its scope.
-  ScopedClosureRunner undo_override;
+  base::Optional<ThreadTaskRunnerHandleOverrideForTesting> ttrh_override;
   if (!ThreadTaskRunnerHandle::IsSet() ||
       ThreadTaskRunnerHandle::Get() != this) {
-    undo_override = ThreadTaskRunnerHandle::OverrideForTesting(this);
+    ttrh_override.emplace(this);
   }
 
   for (auto& task : tasks_to_run)
diff --git a/base/threading/sequenced_task_runner_handle.h b/base/threading/sequenced_task_runner_handle.h
index 4822495..e74b5d19 100644
--- a/base/threading/sequenced_task_runner_handle.h
+++ b/base/threading/sequenced_task_runner_handle.h
@@ -36,8 +36,7 @@
   ~SequencedTaskRunnerHandle();
 
  private:
-  // Friend needed for ThreadTaskRunnerHandle::OverrideForTesting().
-  friend class ThreadTaskRunnerHandle;
+  friend class ThreadTaskRunnerHandleOverride;
 
   scoped_refptr<SequencedTaskRunner> task_runner_;
 
diff --git a/base/threading/thread_task_runner_handle.cc b/base/threading/thread_task_runner_handle.cc
index ca57b36d..3690a60 100644
--- a/base/threading/thread_task_runner_handle.cc
+++ b/base/threading/thread_task_runner_handle.cc
@@ -38,51 +38,6 @@
   return !!thread_task_runner_tls.Pointer()->Get();
 }
 
-// static
-ScopedClosureRunner ThreadTaskRunnerHandle::OverrideForTesting(
-    scoped_refptr<SingleThreadTaskRunner> overriding_task_runner) {
-  // OverrideForTesting() is not compatible with a SequencedTaskRunnerHandle
-  // already being set on this thread (except when it's set by the current
-  // ThreadTaskRunnerHandle).
-  DCHECK(!SequencedTaskRunnerHandle::IsSet() || IsSet());
-
-  if (!IsSet()) {
-    auto top_level_ttrh = std::make_unique<ThreadTaskRunnerHandle>(
-        std::move(overriding_task_runner));
-    return ScopedClosureRunner(base::BindOnce(
-        [](std::unique_ptr<ThreadTaskRunnerHandle> ttrh_to_release) {},
-        std::move(top_level_ttrh)));
-  }
-
-  ThreadTaskRunnerHandle* ttrh = thread_task_runner_tls.Pointer()->Get();
-  // Swap the two (and below bind |overriding_task_runner|, which is now the
-  // previous one, as the |task_runner_to_restore|).
-  ttrh->sequenced_task_runner_handle_.task_runner_ = overriding_task_runner;
-  ttrh->task_runner_.swap(overriding_task_runner);
-
-  auto no_running_during_override =
-      std::make_unique<RunLoop::ScopedDisallowRunningForTesting>();
-
-  return ScopedClosureRunner(base::BindOnce(
-      [](scoped_refptr<SingleThreadTaskRunner> task_runner_to_restore,
-         SingleThreadTaskRunner* expected_task_runner_before_restore,
-         std::unique_ptr<RunLoop::ScopedDisallowRunningForTesting>
-             no_running_during_override) {
-        ThreadTaskRunnerHandle* ttrh = thread_task_runner_tls.Pointer()->Get();
-
-        DCHECK_EQ(expected_task_runner_before_restore, ttrh->task_runner_.get())
-            << "Nested overrides must expire their ScopedClosureRunners "
-               "in LIFO order.";
-
-        ttrh->sequenced_task_runner_handle_.task_runner_ =
-            task_runner_to_restore;
-        ttrh->task_runner_.swap(task_runner_to_restore);
-      },
-      std::move(overriding_task_runner),
-      base::Unretained(ttrh->task_runner_.get()),
-      std::move(no_running_during_override)));
-}
-
 ThreadTaskRunnerHandle::ThreadTaskRunnerHandle(
     scoped_refptr<SingleThreadTaskRunner> task_runner)
     : task_runner_(std::move(task_runner)),
@@ -98,4 +53,47 @@
   thread_task_runner_tls.Pointer()->Set(nullptr);
 }
 
+ThreadTaskRunnerHandleOverride::ThreadTaskRunnerHandleOverride(
+    scoped_refptr<SingleThreadTaskRunner> overriding_task_runner,
+    bool allow_nested_runloop) {
+  DCHECK(!SequencedTaskRunnerHandle::IsSet() || ThreadTaskRunnerHandle::IsSet())
+      << "ThreadTaskRunnerHandleOverride is not compatible with a "
+         "SequencedTaskRunnerHandle already being set on this thread (except "
+         "when it's set by the current ThreadTaskRunnerHandle).";
+
+  if (!ThreadTaskRunnerHandle::IsSet()) {
+    top_level_thread_task_runner_handle_.emplace(
+        std::move(overriding_task_runner));
+    return;
+  }
+
+#if DCHECK_IS_ON()
+  expected_task_runner_before_restore_ = overriding_task_runner.get();
+#endif
+  ThreadTaskRunnerHandle* ttrh = thread_task_runner_tls.Pointer()->Get();
+  ttrh->sequenced_task_runner_handle_.task_runner_ = overriding_task_runner;
+  ttrh->task_runner_.swap(overriding_task_runner);
+  // Due to the swap, now `ttrh->task_runner_` points to the overriding task
+  // runner and `overriding_task_runner_` points to the previous task runner.
+  task_runner_to_restore_ = std::move(overriding_task_runner);
+
+  if (!allow_nested_runloop)
+    no_running_during_override_.emplace();
+}
+
+ThreadTaskRunnerHandleOverride::~ThreadTaskRunnerHandleOverride() {
+  if (task_runner_to_restore_) {
+    ThreadTaskRunnerHandle* ttrh = thread_task_runner_tls.Pointer()->Get();
+
+#if DCHECK_IS_ON()
+    DCHECK_EQ(expected_task_runner_before_restore_, ttrh->task_runner_.get())
+        << "Nested overrides must expire their ThreadTaskRunnerHandleOverride "
+           "in LIFO order.";
+#endif
+
+    ttrh->sequenced_task_runner_handle_.task_runner_ = task_runner_to_restore_;
+    ttrh->task_runner_.swap(task_runner_to_restore_);
+  }
+}
+
 }  // namespace base
diff --git a/base/threading/thread_task_runner_handle.h b/base/threading/thread_task_runner_handle.h
index 892affc..105d29e0 100644
--- a/base/threading/thread_task_runner_handle.h
+++ b/base/threading/thread_task_runner_handle.h
@@ -6,13 +6,20 @@
 #define BASE_THREADING_THREAD_TASK_RUNNER_HANDLE_H_
 
 #include "base/base_export.h"
-#include "base/callback_helpers.h"
 #include "base/compiler_specific.h"
+#include "base/dcheck_is_on.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 
+namespace blink {
+namespace scheduler {
+class MainThreadSchedulerImpl;
+}  // namespace scheduler
+}  // namespace blink
+
 namespace base {
 
 // ThreadTaskRunnerHandle stores a reference to a thread's TaskRunner
@@ -29,18 +36,6 @@
   // the current thread.
   static bool IsSet() WARN_UNUSED_RESULT;
 
-  // Overrides ThreadTaskRunnerHandle::Get()'s |task_runner_| to point at
-  // |overriding_task_runner| until the returned ScopedClosureRunner goes out of
-  // scope (instantiates a ThreadTaskRunnerHandle for that scope if |!IsSet()|).
-  // Nested overrides are allowed but callers must ensure the
-  // ScopedClosureRunners expire in LIFO (stack) order. Note: nesting
-  // ThreadTaskRunnerHandles isn't generally desired but it's useful in unit
-  // tests where multiple task runners can share the main thread for simplicity
-  // and determinism.
-  static ScopedClosureRunner OverrideForTesting(
-      scoped_refptr<SingleThreadTaskRunner> overriding_task_runner)
-      WARN_UNUSED_RESULT;
-
   // Binds |task_runner| to the current thread. |task_runner| must belong
   // to the current thread for this to succeed.
   explicit ThreadTaskRunnerHandle(
@@ -48,6 +43,7 @@
   ~ThreadTaskRunnerHandle();
 
  private:
+  friend class ThreadTaskRunnerHandleOverride;
   scoped_refptr<SingleThreadTaskRunner> task_runner_;
 
   // Registers |task_runner_|'s SequencedTaskRunner interface as the
@@ -57,6 +53,71 @@
   DISALLOW_COPY_AND_ASSIGN(ThreadTaskRunnerHandle);
 };
 
+// ThreadTaskRunnerHandleOverride overrides the task runner returned by
+// |ThreadTaskRunnerHandle::Get()| to point at |overriding_task_runner| until
+// the |ThreadTaskRunnerHandleOverride| goes out of scope.
+// ThreadTaskRunnerHandleOverride instantiates a new ThreadTaskRunnerHandle if
+// ThreadTaskRunnerHandle is not instantiated on the current thread. Nested
+// overrides are allowed but callers must ensure the
+// |ThreadTaskRunnerHandleOverride| expire in LIFO (stack) order.
+//
+// Note: nesting ThreadTaskRunnerHandle is subtle and should be done with care,
+// hence the need to friend and request a //base/OWNERS review for usage outside
+// of tests. Use ThreadTaskRunnerHandleOverrideForTesting to bypass the friend
+// requirement in tests.
+class BASE_EXPORT ThreadTaskRunnerHandleOverride {
+ public:
+  ThreadTaskRunnerHandleOverride(const ThreadTaskRunnerHandleOverride&) =
+      delete;
+  ThreadTaskRunnerHandleOverride& operator=(
+      const ThreadTaskRunnerHandleOverride&) = delete;
+  ~ThreadTaskRunnerHandleOverride();
+
+ private:
+  friend class ThreadTaskRunnerHandleOverrideForTesting;
+  FRIEND_TEST_ALL_PREFIXES(ThreadTaskRunnerHandleTest, NestedRunLoop);
+
+  // We expect ThreadTaskRunnerHandleOverride to be only needed under special
+  // circumstances. Require them to be enumerated as friends to require
+  // //base/OWNERS review. Use ThreadTaskRunnerHandleOverrideForTesting
+  // in unit tests to avoid the friend requirement.
+
+  friend class blink::scheduler::MainThreadSchedulerImpl;
+
+  // Constructs a ThreadTaskRunnerHandleOverride which will make
+  // ThreadTaskRunnerHandle::Get() return |overriding_task_runner| for its
+  // lifetime. |allow_nested_loop| specifies whether RunLoop::Run() is allowed
+  // during this override's lifetime. It's not recommended to allow this unless
+  // the current thread's scheduler guarantees that only tasks which pertain to
+  // |overriding_task_runner|'s context will be run by nested RunLoops.
+  explicit ThreadTaskRunnerHandleOverride(
+      scoped_refptr<SingleThreadTaskRunner> overriding_task_runner,
+      bool allow_nested_runloop = false);
+
+  base::Optional<ThreadTaskRunnerHandle> top_level_thread_task_runner_handle_;
+  scoped_refptr<SingleThreadTaskRunner> task_runner_to_restore_;
+#if DCHECK_IS_ON()
+  SingleThreadTaskRunner* expected_task_runner_before_restore_{nullptr};
+#endif
+  base::Optional<RunLoop::ScopedDisallowRunning> no_running_during_override_;
+};
+
+// Note: nesting ThreadTaskRunnerHandles isn't generally desired but it's useful
+// in some unit tests where multiple task runners share the main thread for
+// simplicity and determinism. Only use this when no other constructs will work
+// (see base/test/task_environment.h and base/test/test_mock_time_task_runner.h
+// for preferred alternatives).
+class ThreadTaskRunnerHandleOverrideForTesting {
+ public:
+  explicit ThreadTaskRunnerHandleOverrideForTesting(
+      scoped_refptr<SingleThreadTaskRunner> overriding_task_runner)
+      : thread_task_runner_handle_override_(std::move(overriding_task_runner)) {
+  }
+
+ private:
+  ThreadTaskRunnerHandleOverride thread_task_runner_handle_override_;
+};
+
 }  // namespace base
 
 #endif  // BASE_THREADING_THREAD_TASK_RUNNER_HANDLE_H_
diff --git a/base/threading/thread_task_runner_handle_unittest.cc b/base/threading/thread_task_runner_handle_unittest.cc
index 1aa02d1..03ad7b2 100644
--- a/base/threading/thread_task_runner_handle_unittest.cc
+++ b/base/threading/thread_task_runner_handle_unittest.cc
@@ -5,7 +5,9 @@
 #include "base/threading/thread_task_runner_handle.h"
 
 #include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
 #include "base/test/gtest_util.h"
+#include "base/test/task_environment.h"
 #include "base/test/test_simple_task_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -33,7 +35,7 @@
       { ThreadTaskRunnerHandle overriding_ttrh(overidding_task_runner); });
 }
 
-TEST(ThreadTaskRunnerHandleTest, OverrideForTestingExistingTTRH) {
+TEST(ThreadTaskRunnerHandleTest, OverrideExistingTTRH) {
   scoped_refptr<SingleThreadTaskRunner> task_runner_1(new TestSimpleTaskRunner);
   scoped_refptr<SingleThreadTaskRunner> task_runner_2(new TestSimpleTaskRunner);
   scoped_refptr<SingleThreadTaskRunner> task_runner_3(new TestSimpleTaskRunner);
@@ -48,15 +50,13 @@
 
     {
       // Override.
-      ScopedClosureRunner undo_override_2 =
-          ThreadTaskRunnerHandle::OverrideForTesting(task_runner_2);
+      ThreadTaskRunnerHandleOverrideForTesting ttrh_override_2(task_runner_2);
       EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
       EXPECT_EQ(task_runner_2, ThreadTaskRunnerHandle::Get());
 
       {
         // Nested override.
-        ScopedClosureRunner undo_override_3 =
-            ThreadTaskRunnerHandle::OverrideForTesting(task_runner_3);
+        ThreadTaskRunnerHandleOverrideForTesting ttrh_override_3(task_runner_3);
         EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
         EXPECT_EQ(task_runner_3, ThreadTaskRunnerHandle::Get());
       }
@@ -67,8 +67,7 @@
 
       {
         // Backup to double override with another TTRH.
-        ScopedClosureRunner undo_override_4 =
-            ThreadTaskRunnerHandle::OverrideForTesting(task_runner_4);
+        ThreadTaskRunnerHandleOverrideForTesting ttrh_override_4(task_runner_4);
         EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
         EXPECT_EQ(task_runner_4, ThreadTaskRunnerHandle::Get());
       }
@@ -81,22 +80,20 @@
   EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
 }
 
-TEST(ThreadTaskRunnerHandleTest, OverrideForTestingNoExistingTTRH) {
+TEST(ThreadTaskRunnerHandleTest, OverrideNoExistingTTRH) {
   scoped_refptr<SingleThreadTaskRunner> task_runner_1(new TestSimpleTaskRunner);
   scoped_refptr<SingleThreadTaskRunner> task_runner_2(new TestSimpleTaskRunner);
 
   EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
   {
     // Override with no TTRH in place.
-    ScopedClosureRunner undo_override_1 =
-        ThreadTaskRunnerHandle::OverrideForTesting(task_runner_1);
+    ThreadTaskRunnerHandleOverrideForTesting ttrh_override_1(task_runner_1);
     EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
     EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
 
     {
       // Nested override works the same.
-      ScopedClosureRunner undo_override_2 =
-          ThreadTaskRunnerHandle::OverrideForTesting(task_runner_2);
+      ThreadTaskRunnerHandleOverrideForTesting ttrh_override_2(task_runner_2);
       EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
       EXPECT_EQ(task_runner_2, ThreadTaskRunnerHandle::Get());
     }
@@ -113,10 +110,32 @@
   scoped_refptr<SingleThreadTaskRunner> overidding_task_runner(
       new TestSimpleTaskRunner);
 
-  ScopedClosureRunner undo_override =
-      ThreadTaskRunnerHandle::OverrideForTesting(task_runner);
+  ThreadTaskRunnerHandleOverrideForTesting ttrh_override(task_runner);
   EXPECT_DCHECK_DEATH(
       { ThreadTaskRunnerHandle overriding_ttrh(overidding_task_runner); });
 }
 
+TEST(ThreadTaskRunnerHandleTest, NestedRunLoop) {
+  test::SingleThreadTaskEnvironment task_environment;
+  EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+  scoped_refptr<SingleThreadTaskRunner> task_runner(new TestSimpleTaskRunner);
+  ThreadTaskRunnerHandleOverride ttrh_override(task_runner,
+                                               /*allow_nested_runloop=*/true);
+  EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+  EXPECT_EQ(task_runner, ThreadTaskRunnerHandle::Get());
+  EXPECT_EQ(task_runner, SequencedTaskRunnerHandle::Get());
+  RunLoop().RunUntilIdle();
+}
+
+TEST(ThreadTaskRunnerHandleTest, DeathOnNestedRunLoop) {
+  test::SingleThreadTaskEnvironment task_environment;
+  EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+  scoped_refptr<SingleThreadTaskRunner> task_runner(new TestSimpleTaskRunner);
+  ThreadTaskRunnerHandleOverrideForTesting ttrh_override(task_runner);
+  EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
+  EXPECT_EQ(task_runner, ThreadTaskRunnerHandle::Get());
+  EXPECT_EQ(task_runner, SequencedTaskRunnerHandle::Get());
+  EXPECT_DCHECK_DEATH({ RunLoop().RunUntilIdle(); });
+}
+
 }  // namespace base
diff --git a/chrome/VERSION b/chrome/VERSION
index 1fdb8ae..489bff3 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=89
 MINOR=0
-BUILD=4386
+BUILD=4387
 PATCH=0
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index a5d7501..ca78a24c 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -91,6 +91,7 @@
   "junit/src/org/chromium/chrome/browser/firstrun/FirstRunAppRestrictionInfoTest.java",
   "junit/src/org/chromium/chrome/browser/firstrun/FirstRunFlowSequencerTest.java",
   "junit/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationUnitTest.java",
+  "junit/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessorTest.java",
   "junit/src/org/chromium/chrome/browser/firstrun/PolicyLoadListenerUnitTest.java",
   "junit/src/org/chromium/chrome/browser/firstrun/SkipTosDialogPolicyListenerUnitTest.java",
   "junit/src/org/chromium/chrome/browser/firstrun/TosDialogBehaviorSharedPrefInvalidatorUnitTest.java",
diff --git a/chrome/android/features/autofill_assistant/BUILD.gn b/chrome/android/features/autofill_assistant/BUILD.gn
index 66737614..df8b221 100644
--- a/chrome/android/features/autofill_assistant/BUILD.gn
+++ b/chrome/android/features/autofill_assistant/BUILD.gn
@@ -115,6 +115,7 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/DialogOnboardingCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/LayoutUtils.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/ScrollToHideGestureListener.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/SizeListenableLinearLayout.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantActionsCarouselCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantActionsDecoration.java",
@@ -127,6 +128,7 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantPlaceholdersConfiguration.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/details/ImageClickthroughData.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/form/AssistantFormCoordinator.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/form/AssistantFormCounter.java",
@@ -209,6 +211,7 @@
     "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantPlaceholdersConfiguration.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/form/AssistantFormDelegate.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/form/AssistantFormInput.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/form/AssistantFormModel.java",
diff --git a/chrome/android/features/autofill_assistant/java/res/values-v17/dimens.xml b/chrome/android/features/autofill_assistant/java/res/values-v17/dimens.xml
index e3a4261a..2300054 100644
--- a/chrome/android/features/autofill_assistant/java/res/values-v17/dimens.xml
+++ b/chrome/android/features/autofill_assistant/java/res/values-v17/dimens.xml
@@ -6,6 +6,8 @@
     <dimen name="autofill_assistant_bottombar_horizontal_spacing">24dp</dimen>
     <dimen name="autofill_assistant_bottombar_vertical_spacing">20dp</dimen>
     <dimen name="autofill_assistant_details_image_size">60dp</dimen>
+    <dimen name="autofill_assistant_details_text_placeholders_height">12dp</dimen>
+    <dimen name="autofill_assistant_details_text_placeholders_margin">4dp</dimen>
     <dimen name="autofill_assistant_poodle_size">32dp</dimen>
     <dimen name="autofill_assistant_profile_size">32dp</dimen>
     <dimen name="autofill_assistant_info_box_spacing">16dp</dimen>
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomSheetContent.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomSheetContent.java
index e16188e3..c2c53edd 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomSheetContent.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomSheetContent.java
@@ -11,9 +11,11 @@
 
 import androidx.annotation.Nullable;
 
+import org.chromium.base.Callback;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 
 /**
  * The {@link BottomSheetContent} for the Autofill Assistant. It supports notifying the
@@ -27,6 +29,9 @@
     private ScrollView mContentScrollableView;
     private Supplier<AssistantBottomBarDelegate> mBottomBarDelegateSupplier;
     private boolean mPeekModeDisabled;
+    private BottomSheetController mController;
+    @Nullable
+    private Callback<Integer> mOffsetController;
 
     public AssistantBottomSheetContent(
             Context context, Supplier<AssistantBottomBarDelegate> supplier) {
@@ -151,4 +156,18 @@
 
         return bottomBarDelegate.onBackButtonPressed();
     }
+
+    @Override
+    public boolean contentControlsOffset() {
+        return true;
+    }
+
+    @Override
+    public void setOffsetController(Callback<Integer> offsetController) {
+        mOffsetController = offsetController;
+    }
+
+    public Callback<Integer> getOffsetController() {
+        return mOffsetController;
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/ScrollToHideGestureListener.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/ScrollToHideGestureListener.java
new file mode 100644
index 0000000..7f0a52c
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/ScrollToHideGestureListener.java
@@ -0,0 +1,198 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.autofill_assistant;
+
+import android.animation.ValueAnimator;
+import android.view.animation.DecelerateInterpolator;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Callback;
+import org.chromium.base.MathUtils;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
+import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
+import org.chromium.content_public.browser.GestureStateListenerWithScroll;
+
+/**
+ * A Gesture listener that implements scroll-to-hide for the assistant bottomsheet when in FULL
+ * state.
+ */
+public class ScrollToHideGestureListener implements GestureStateListenerWithScroll {
+    /** Base duration of the animation of the sheet. 218 ms is a spec for material design. */
+    private static final int BASE_ANIMATION_DURATION_MS = 218;
+
+    private final BottomSheetController mBottomSheetController;
+    private final AssistantBottomSheetContent mContent;
+    @Nullable
+    private final BottomSheetObserver mStateChangeTracker = new StateChangeTracker();
+
+    private boolean mScrolling;
+
+    /** Remembers the last value of scroll offset, to compute the delta for the next move. */
+    private int mLastScrollOffsetY;
+
+    /**
+     * A capture of {@code mBottomSheetController.getCurrentOffset()}. At the end of a scroll, it is
+     * compared with the current value to figure out whether the sheet was overall scrolled up or
+     * down.
+     */
+    private float mOffsetMarkPx;
+
+    /** This animator moves the sheet to its final position after scrolling ended. */
+    private ValueAnimator mAnimator;
+
+    public ScrollToHideGestureListener(
+            BottomSheetController bottomSheetController, AssistantBottomSheetContent content) {
+        mBottomSheetController = bottomSheetController;
+        mContent = content;
+    }
+
+    @Override
+    public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
+        Callback<Integer> offsetController = mContent.getOffsetController();
+        if (offsetController == null) return;
+
+        // Scroll to hide only applies if the sheet is fully opened, and state is FULL or is being
+        // opened, and target state is FULL.
+        if (mBottomSheetController.getTargetSheetState() == SheetState.FULL) {
+            // This stops animation and freezes the sheet in place.
+            offsetController.onResult(mBottomSheetController.getCurrentOffset());
+        }
+        if (mBottomSheetController.getSheetState() != SheetState.FULL) return;
+
+        resetScrollingState(); // also cancels any running animations
+        mScrolling = true;
+        mLastScrollOffsetY = scrollOffsetY;
+        mOffsetMarkPx = mBottomSheetController.getCurrentOffset();
+        mBottomSheetController.addObserver(mStateChangeTracker);
+    }
+
+    @Override
+    public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
+        onScrollOffsetOrExtentChanged(scrollOffsetY, scrollExtentY);
+
+        if (!mScrolling) return;
+
+        resetScrollingState();
+
+        int maxOffsetPx = getMaxOffsetPx();
+        int currentOffsetPx = mBottomSheetController.getCurrentOffset();
+        if (currentOffsetPx == 0 || currentOffsetPx == maxOffsetPx) {
+            return;
+        }
+
+        if (currentOffsetPx >= mOffsetMarkPx || scrollOffsetY == 0) {
+            animateTowards(maxOffsetPx);
+        } else {
+            animateTowards(0);
+        }
+    }
+
+    @Override
+    public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scrollExtentY) {
+        if (!mScrolling) {
+            // It's possible for the scroll offset to reset to 0 outside of a scroll, if the page or
+            // viewport size change. Scrolling up is not possible so if the sheet is hidden or about
+            // to be hidden, show it.
+            if (scrollOffsetY == 0 && mBottomSheetController.getSheetState() == SheetState.FULL
+                    && (mBottomSheetController.getCurrentOffset() == 0 || mAnimator != null)) {
+                animateTowards(getMaxOffsetPx());
+            }
+            return;
+        }
+
+        Callback<Integer> offsetController = mContent.getOffsetController();
+        if (offsetController == null) {
+            resetScrollingState();
+            return;
+        }
+
+        // deltaPx is the value to add to the current sheet offset (height). It is negative when
+        // scrolling down, that is, when scrollOffsetY increases.
+        int deltaPx = mLastScrollOffsetY - scrollOffsetY;
+        mLastScrollOffsetY = scrollOffsetY;
+
+        int maxOffsetPx = getMaxOffsetPx();
+        int offsetPx = MathUtils.clamp(
+                mBottomSheetController.getCurrentOffset() + deltaPx, 0, maxOffsetPx);
+        offsetController.onResult(offsetPx);
+
+        // If either extremes were reached, update the mark. The decision to fully show or hide will
+        // be relative to that point.
+        if (offsetPx == 0) {
+            mOffsetMarkPx = 0;
+        } else if (offsetPx >= maxOffsetPx) {
+            mOffsetMarkPx = maxOffsetPx;
+        }
+    }
+
+    @Override
+    public void onFlingStartGesture(int scrollOffsetY, int scrollExtentY) {
+        // Flinging and scrolling are handled the same, the sheet follows the movement of the
+        // browser page.
+        onScrollStarted(scrollOffsetY, scrollExtentY);
+    }
+
+    @Override
+    public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) {
+        onScrollEnded(scrollOffsetY, scrollExtentY);
+    }
+
+    @Override
+    public void onDestroyed() {
+        resetScrollingState();
+    }
+
+    private int getMaxOffsetPx() {
+        return mContent.getContentView().getHeight() + mBottomSheetController.getTopShadowHeight();
+    }
+
+    private void resetScrollingState() {
+        mScrolling = false;
+        mLastScrollOffsetY = 0;
+        cancelAnimation();
+        mBottomSheetController.removeObserver(mStateChangeTracker);
+    }
+
+    private void cancelAnimation() {
+        if (mAnimator == null) return;
+
+        mAnimator.cancel();
+        mAnimator = null;
+    }
+
+    /** Animate the sheet towards {@code goalOffsetPx} without changing its state. */
+    private void animateTowards(int goalOffsetPx) {
+        Callback<Integer> offsetController = mContent.getOffsetController();
+        if (offsetController == null) return;
+
+        ValueAnimator animator =
+                ValueAnimator.ofInt(mBottomSheetController.getCurrentOffset(), goalOffsetPx);
+        animator.setDuration(BASE_ANIMATION_DURATION_MS);
+        animator.setInterpolator(new DecelerateInterpolator(1.0f));
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animator) {
+                if (mAnimator != animator) return;
+
+                offsetController.onResult((Integer) animator.getAnimatedValue());
+            }
+        });
+        mAnimator = animator;
+        mAnimator.start();
+    }
+
+    /** Stop scrolling if the sheet leaves the FULL state during scrolling. */
+    private class StateChangeTracker extends EmptyBottomSheetObserver {
+        @Override
+        public void onSheetStateChanged(@SheetState int newState) {
+            if (newState != SheetState.FULL) {
+                resetScrollingState();
+            }
+        }
+    }
+}
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java
index 3fe12c4..9dee6f73 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java
@@ -13,11 +13,9 @@
 @JNINamespace("autofill_assistant")
 public class AssistantDetails {
     private final String mTitle;
-    private final int mTitleMaxLines;
     private final String mImageUrl;
     private final String mImageAccessibilityHint;
     private final ImageClickthroughData mImageClickthroughData;
-    private final boolean mShowImagePlaceholder;
     private final String mDescriptionLine1;
     private final String mDescriptionLine2;
     private final String mDescriptionLine3;
@@ -32,8 +30,6 @@
     private boolean mHighlightLine2;
     /** Whether the third description line should be highlighted. */
     private boolean mHighlightLine3;
-    /** Whether empty fields should have the animated placeholder background. */
-    private final boolean mAnimatePlaceholders;
     /**
      * The correctly formatted price for the client locale, including the currency.
      * Example: '$20.50' or '20.50 €'.
@@ -41,21 +37,20 @@
     private final String mTotalPrice;
     /** An optional price label, such as 'Estimated Total incl. VAT'. */
     private final String mTotalPriceLabel;
+    /** The configuration for the placeholders. */
+    private final AssistantPlaceholdersConfiguration mPlaceholdersConfiguration;
 
-    public AssistantDetails(String title, int titleMaxLines, String imageUrl,
-            String imageAccessibilityHint, ImageClickthroughData imageClickthroughData,
-            boolean showImagePlaceholder, String totalPriceLabel, String totalPrice,
+    public AssistantDetails(String title, String imageUrl, String imageAccessibilityHint,
+            ImageClickthroughData imageClickthroughData, String totalPriceLabel, String totalPrice,
             String descriptionLine1, String descriptionLine2, String descriptionLine3,
             String priceAttribution, boolean userApprovalRequired, boolean highlightTitle,
             boolean highlightLine1, boolean highlightLine2, boolean highlightLine3,
-            boolean animatePlaceholders) {
+            AssistantPlaceholdersConfiguration placeholdersConfiguration) {
         this.mTotalPriceLabel = totalPriceLabel;
         this.mTitle = title;
-        this.mTitleMaxLines = titleMaxLines;
         this.mImageUrl = imageUrl;
         this.mImageAccessibilityHint = imageAccessibilityHint;
         this.mImageClickthroughData = imageClickthroughData;
-        this.mShowImagePlaceholder = showImagePlaceholder;
         this.mTotalPrice = totalPrice;
         this.mDescriptionLine1 = descriptionLine1;
         this.mDescriptionLine2 = descriptionLine2;
@@ -67,17 +62,13 @@
         this.mHighlightLine1 = highlightLine1;
         this.mHighlightLine2 = highlightLine2;
         this.mHighlightLine3 = highlightLine3;
-        this.mAnimatePlaceholders = animatePlaceholders;
+        this.mPlaceholdersConfiguration = placeholdersConfiguration;
     }
 
     String getTitle() {
         return mTitle;
     }
 
-    int getTitleMaxLines() {
-        return mTitleMaxLines;
-    }
-
     String getImageUrl() {
         return mImageUrl;
     }
@@ -94,10 +85,6 @@
         return mImageClickthroughData;
     }
 
-    boolean getShowImagePlaceholder() {
-        return mShowImagePlaceholder;
-    }
-
     String getDescriptionLine1() {
         return mDescriptionLine1;
     }
@@ -142,30 +129,29 @@
         return mHighlightLine3;
     }
 
-    boolean getAnimatePlaceholders() {
-        return mAnimatePlaceholders;
+    public AssistantPlaceholdersConfiguration getPlaceholdersConfiguration() {
+        return mPlaceholdersConfiguration;
     }
 
     /**
      * Create details with the given values.
      */
     @CalledByNative
-    private static AssistantDetails create(String title, int titleMaxLines, String imageUrl,
+    private static AssistantDetails create(String title, String imageUrl,
             String imageAccessibilityHint, boolean allowImageClickthrough,
             String imageClickthroughDesc, String imageClickthroughPostiveText,
             String imageClickthroughNegativeText, String imageClickthroughUrl,
-            boolean showImagePlaceholder, String totalPriceLabel, String totalPrice,
-            String descriptionLine1, String descriptionLine2, String descriptionLine3,
-            String priceAttribution, boolean userApprovalRequired, boolean highlightTitle,
-            boolean highlightLine1, boolean highlightLine2, boolean highlightLine3,
-            boolean animatePlaceholders) {
-        return new AssistantDetails(title, titleMaxLines, imageUrl, imageAccessibilityHint,
+            String totalPriceLabel, String totalPrice, String descriptionLine1,
+            String descriptionLine2, String descriptionLine3, String priceAttribution,
+            boolean userApprovalRequired, boolean highlightTitle, boolean highlightLine1,
+            boolean highlightLine2, boolean highlightLine3,
+            AssistantPlaceholdersConfiguration placeholdersConfiguration) {
+        return new AssistantDetails(title, imageUrl, imageAccessibilityHint,
                 new ImageClickthroughData(allowImageClickthrough, imageClickthroughDesc,
                         imageClickthroughPostiveText, imageClickthroughNegativeText,
                         imageClickthroughUrl),
-                showImagePlaceholder, totalPriceLabel, totalPrice, descriptionLine1,
-                descriptionLine2, descriptionLine3, priceAttribution, userApprovalRequired,
-                highlightTitle, highlightLine1, highlightLine2, highlightLine3,
-                animatePlaceholders);
+                totalPriceLabel, totalPrice, descriptionLine1, descriptionLine2, descriptionLine3,
+                priceAttribution, userApprovalRequired, highlightTitle, highlightLine1,
+                highlightLine2, highlightLine3, placeholdersConfiguration);
     }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
index a0a98a2..a82e65c 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
@@ -21,7 +21,9 @@
 import android.text.TextUtils;
 import android.util.TypedValue;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.StyleRes;
@@ -90,6 +92,8 @@
     private final int mImageHeight;
     private final int mPulseAnimationStartColor;
     private final int mPulseAnimationEndColor;
+    private final int mTextPlaceholdersHeight;
+    private final int mTextPlaceholdersMargin;
 
     private ValueAnimator mPulseAnimation;
     private ImageFetcher mImageFetcher;
@@ -102,6 +106,10 @@
                 R.dimen.autofill_assistant_details_image_size);
         mPulseAnimationStartColor = context.getResources().getColor(R.color.modern_grey_300);
         mPulseAnimationEndColor = context.getResources().getColor(R.color.modern_grey_200);
+        mTextPlaceholdersHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.autofill_assistant_details_text_placeholders_height);
+        mTextPlaceholdersMargin = context.getResources().getDimensionPixelSize(
+                R.dimen.autofill_assistant_details_text_placeholders_margin);
         mImageFetcher = imageFetcher;
     }
 
@@ -144,21 +152,44 @@
         AssistantTextUtils.applyVisualAppearanceTags(
                 viewHolder.mPriceAttributionView, details.getPriceAttribution(), null);
 
+        AssistantPlaceholdersConfiguration placeholders = details.getPlaceholdersConfiguration();
+        boolean hideDescriptionLine1 = details.getDescriptionLine1().isEmpty()
+                && !placeholders.getShowDescriptionLine1Placeholder();
+        boolean hideDescriptionLine2 = details.getDescriptionLine2().isEmpty()
+                && !placeholders.getShowDescriptionLine2Placeholder();
+        boolean hideDescriptionLine3 = details.getDescriptionLine3().isEmpty()
+                && !placeholders.getShowDescriptionLine3Placeholder();
+
         // Allow title line wrapping according to number of maximum allowed lines.
-        if (details.getTitleMaxLines() == 1) {
+        // TODO(crbug.com/806868): Should we move the hide/placeholders/maxLines logic to C++?
+        int titleMaxLines = 1;
+        if (hideDescriptionLine1) titleMaxLines++;
+        if (hideDescriptionLine2) titleMaxLines++;
+
+        if (titleMaxLines == 1) {
             viewHolder.mTitleView.setSingleLine(true);
             viewHolder.mTitleView.setEllipsize(null);
         } else {
             viewHolder.mTitleView.setSingleLine(false);
-            viewHolder.mTitleView.setMaxLines(details.getTitleMaxLines());
+            viewHolder.mTitleView.setMaxLines(titleMaxLines);
             viewHolder.mTitleView.setEllipsize(TextUtils.TruncateAt.END);
         }
 
-        hideIfEmpty(viewHolder.mDescriptionLine1View);
-        hideIfEmpty(viewHolder.mDescriptionLine2View);
-        hideIfEmpty(viewHolder.mDescriptionLine3View);
+        // Hide views without text or placeholders.
+        hideIf(viewHolder.mDescriptionLine1View, hideDescriptionLine1);
+        hideIf(viewHolder.mDescriptionLine2View, hideDescriptionLine2);
+        hideIf(viewHolder.mDescriptionLine3View, hideDescriptionLine3);
         hideIfEmpty(viewHolder.mPriceAttributionView);
 
+        // Set the height of the potential placeholders next to the image.
+        setTextHeightAndMargin(viewHolder.mTitleView, placeholders.getShowTitlePlaceholder());
+        setTextHeightAndMargin(viewHolder.mDescriptionLine1View,
+                placeholders.getShowDescriptionLine1Placeholder());
+        setTextHeightAndMargin(viewHolder.mDescriptionLine2View,
+                placeholders.getShowDescriptionLine2Placeholder());
+        setTextHeightAndMargin(viewHolder.mDescriptionLine3View,
+                placeholders.getShowDescriptionLine3Placeholder());
+
         // If no price provided, hide the price view (containing separator, price label, and price).
         viewHolder.mPriceView.setVisibility(
                 details.getTotalPrice().isEmpty() ? View.GONE : View.VISIBLE);
@@ -167,7 +198,7 @@
         setAccessibility(viewHolder.mImageView, details.getImageAccessibilityHint());
 
         if (details.getImageUrl().isEmpty()) {
-            if (details.getShowImagePlaceholder()) {
+            if (placeholders.getShowImagePlaceholder()) {
                 viewHolder.mImageView.setImageDrawable(viewHolder.mDefaultImage);
                 viewHolder.mImageView.setOnClickListener(null);
             } else {
@@ -197,7 +228,27 @@
     }
 
     private void hideIfEmpty(TextView view) {
-        view.setVisibility(view.length() == 0 ? View.GONE : View.VISIBLE);
+        hideIf(view, view.length() == 0);
+    }
+
+    private void hideIf(TextView view, boolean hide) {
+        view.setVisibility(hide ? View.GONE : View.VISIBLE);
+    }
+
+    private void setTextHeightAndMargin(TextView view, boolean isPlaceholder) {
+        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
+
+        if (isPlaceholder) {
+            layoutParams.height = mTextPlaceholdersHeight;
+            layoutParams.topMargin = mTextPlaceholdersMargin;
+            layoutParams.bottomMargin = mTextPlaceholdersMargin;
+        } else {
+            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            layoutParams.topMargin = 0;
+            layoutParams.bottomMargin = 0;
+        }
+
+        view.setLayoutParams(layoutParams);
     }
 
     private void setTextStyles(AssistantDetails details, ViewHolder viewHolder) {
@@ -217,21 +268,19 @@
         setTextStyle(viewHolder.mTotalPriceView, details.getUserApprovalRequired(),
                 /* highlight= */ false, R.style.TextAppearance_AssistantDetailsPrice);
 
-        if (shouldStartOrContinuePlaceholderAnimation(details, viewHolder)) {
-            startOrContinuePlaceholderAnimations(viewHolder);
+        if (shouldStartOrContinuePlaceholderAnimation(details.getPlaceholdersConfiguration())) {
+            startOrContinuePlaceholderAnimations(details, viewHolder);
         } else {
             stopPlaceholderAnimations();
         }
     }
 
     private boolean shouldStartOrContinuePlaceholderAnimation(
-            AssistantDetails details, ViewHolder viewHolder) {
-        boolean isAtLeastOneFieldEmpty = viewHolder.mTitleView.length() == 0
-                || viewHolder.mDescriptionLine1View.length() == 0
-                || viewHolder.mDescriptionLine2View.length() == 0
-                || viewHolder.mDescriptionLine3View.length() == 0
-                || viewHolder.mImageView.getDrawable() == viewHolder.mDefaultImage;
-        return details.getAnimatePlaceholders() && isAtLeastOneFieldEmpty;
+            AssistantPlaceholdersConfiguration placeholders) {
+        return placeholders.getShowImagePlaceholder() || placeholders.getShowTitlePlaceholder()
+                || placeholders.getShowDescriptionLine1Placeholder()
+                || placeholders.getShowDescriptionLine2Placeholder()
+                || placeholders.getShowDescriptionLine3Placeholder();
     }
 
     private void setTextStyle(
@@ -258,7 +307,8 @@
         return roundedBitmap;
     }
 
-    private void startOrContinuePlaceholderAnimations(ViewHolder viewHolder) {
+    private void startOrContinuePlaceholderAnimations(
+            AssistantDetails details, ViewHolder viewHolder) {
         if (mPulseAnimation != null) {
             return;
         }
@@ -278,23 +328,23 @@
                 viewHolder.mDefaultImage.setColor(Color.TRANSPARENT);
             }
         });
+
+        AssistantPlaceholdersConfiguration placeholders = details.getPlaceholdersConfiguration();
         mPulseAnimation.addUpdateListener(animation -> {
             int animatedValue = (int) animation.getAnimatedValue();
             viewHolder.mTitleView.setBackgroundColor(
-                    viewHolder.mTitleView.length() == 0 ? animatedValue : Color.TRANSPARENT);
+                    placeholders.getShowTitlePlaceholder() ? animatedValue : Color.TRANSPARENT);
             viewHolder.mDescriptionLine1View.setBackgroundColor(
-                    viewHolder.mDescriptionLine1View.length() == 0 ? animatedValue
-                                                                   : Color.TRANSPARENT);
+                    placeholders.getShowDescriptionLine1Placeholder() ? animatedValue
+                                                                      : Color.TRANSPARENT);
             viewHolder.mDescriptionLine2View.setBackgroundColor(
-                    viewHolder.mDescriptionLine2View.length() == 0 ? animatedValue
-                                                                   : Color.TRANSPARENT);
+                    placeholders.getShowDescriptionLine2Placeholder() ? animatedValue
+                                                                      : Color.TRANSPARENT);
             viewHolder.mDescriptionLine3View.setBackgroundColor(
-                    viewHolder.mDescriptionLine3View.length() == 0 ? animatedValue
-                                                                   : Color.TRANSPARENT);
+                    placeholders.getShowDescriptionLine3Placeholder() ? animatedValue
+                                                                      : Color.TRANSPARENT);
             viewHolder.mDefaultImage.setColor(
-                    viewHolder.mImageView.getDrawable() == viewHolder.mDefaultImage
-                            ? animatedValue
-                            : Color.TRANSPARENT);
+                    placeholders.getShowImagePlaceholder() ? animatedValue : Color.TRANSPARENT);
         });
         mPulseAnimation.start();
     }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantPlaceholdersConfiguration.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantPlaceholdersConfiguration.java
new file mode 100644
index 0000000..96dce445
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantPlaceholdersConfiguration.java
@@ -0,0 +1,51 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.autofill_assistant.details;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * Configuration for the details placeholders.
+ */
+@JNINamespace("autofill_assistant")
+public class AssistantPlaceholdersConfiguration {
+    private final boolean mShowImagePlaceholder;
+    private final boolean mShowTitlePlaceholder;
+    private final boolean mShowDescriptionLine1Placeholder;
+    private final boolean mShowDescriptionLine2Placeholder;
+    private final boolean mShowDescriptionLine3Placeholder;
+
+    @CalledByNative
+    public AssistantPlaceholdersConfiguration(boolean showImagePlaceholder,
+            boolean showTitlePlaceholder, boolean showDescriptionLine1Placeholder,
+            boolean showDescriptionLine2Placeholder, boolean showDescriptionLine3Placeholder) {
+        mShowImagePlaceholder = showImagePlaceholder;
+        mShowTitlePlaceholder = showTitlePlaceholder;
+        mShowDescriptionLine1Placeholder = showDescriptionLine1Placeholder;
+        mShowDescriptionLine2Placeholder = showDescriptionLine2Placeholder;
+        mShowDescriptionLine3Placeholder = showDescriptionLine3Placeholder;
+    }
+
+    public boolean getShowImagePlaceholder() {
+        return mShowImagePlaceholder;
+    }
+
+    public boolean getShowTitlePlaceholder() {
+        return mShowTitlePlaceholder;
+    }
+
+    public boolean getShowDescriptionLine1Placeholder() {
+        return mShowDescriptionLine1Placeholder;
+    }
+
+    public boolean getShowDescriptionLine2Placeholder() {
+        return mShowDescriptionLine2Placeholder;
+    }
+
+    public boolean getShowDescriptionLine3Placeholder() {
+        return mShowDescriptionLine3Placeholder;
+    }
+}
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScript.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScript.java
index 54e8011..e2f2580 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScript.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScript.java
@@ -20,6 +20,7 @@
 import org.chromium.chrome.browser.autofill_assistant.AssistantRootViewContainer;
 import org.chromium.chrome.browser.autofill_assistant.BottomSheetUtils;
 import org.chromium.chrome.browser.autofill_assistant.LayoutUtils;
+import org.chromium.chrome.browser.autofill_assistant.ScrollToHideGestureListener;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
 import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChipViewHolder;
 import org.chromium.chrome.browser.autofill_assistant.generic_ui.AssistantDimension;
@@ -30,6 +31,7 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
+import org.chromium.content_public.browser.GestureListenerManager;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.ApplicationViewportInsetSupplier;
 
@@ -60,6 +62,7 @@
 
     private AssistantHeaderCoordinator mHeaderCoordinator;
     private AssistantHeaderModel mHeaderModel;
+    private ScrollToHideGestureListener mGestureListener;
     private LinearLayout mChipsContainer;
     private final int mInnerChipSpacing;
     /** Height of the bottom sheet's shadow, used to compute the viewport resize offset. */
@@ -152,6 +155,7 @@
     }
 
     public void destroy() {
+        disableScrollToHide();
         mBottomSheetController.removeObserver(mBottomSheetObserver);
         if (mHeaderCoordinator != null) {
             mHeaderCoordinator.destroy();
@@ -250,7 +254,7 @@
         addChipsToContainer(mChipsContainer, mRightAlignedChips);
     }
 
-    public boolean show(boolean resizeVisualViewport) {
+    public boolean show(boolean resizeVisualViewport, boolean scrollToHide) {
         if (mHeaderModel == null || mHeaderCoordinator == null) {
             assert false : "createHeaderAndGetModel() must be called before show()";
             return false;
@@ -262,10 +266,14 @@
         mBottomSheetController.addObserver(mBottomSheetObserver);
         BottomSheetUtils.showContentAndMaybeExpand(mBottomSheetController, mContent,
                 /* shouldExpand = */ true, /* animate = */ mAnimateBottomSheet);
+
+        if (scrollToHide) enableScrollToHide();
+
         return true;
     }
 
     public void hide() {
+        disableScrollToHide();
         mBottomSheetController.removeObserver(mBottomSheetObserver);
         mBottomSheetController.hideContent(mContent, /* animate = */ mAnimateBottomSheet);
         mResizeVisualViewport = false;
@@ -297,4 +305,18 @@
 
         mInsetSupplier.set(resizing);
     }
+
+    private void disableScrollToHide() {
+        if (mGestureListener == null) return;
+
+        GestureListenerManager.fromWebContents(mWebContents).removeListener(mGestureListener);
+        mGestureListener = null;
+    }
+
+    private void enableScrollToHide() {
+        if (mGestureListener != null) return;
+
+        mGestureListener = new ScrollToHideGestureListener(mBottomSheetController, mContent);
+        GestureListenerManager.fromWebContents(mWebContents).addListener(mGestureListener);
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java
index 9f0c648..7a8ca25 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/trigger_scripts/AssistantTriggerScriptBridge.java
@@ -143,7 +143,7 @@
     private boolean showTriggerScript(String[] cancelPopupMenuItems, int[] cancelPopupMenuActions,
             List<AssistantChip> leftAlignedChips, int[] leftAlignedChipsActions,
             List<AssistantChip> rightAlignedChips, int[] rightAlignedChipsActions,
-            boolean resizeVisualViewport) {
+            boolean resizeVisualViewport, boolean scrollToHide) {
         // Trigger scripts currently do not support switching activities (such as CCT->tab).
         // TODO(b/171776026): Re-inject dependencies on activity change to support CCT->tab.
         if (TabUtils.getActivity(TabUtils.fromWebContents(mWebContents)) != mContext) {
@@ -154,7 +154,7 @@
         mTriggerScript.setCancelPopupMenu(cancelPopupMenuItems, cancelPopupMenuActions);
         mTriggerScript.setLeftAlignedChips(leftAlignedChips, leftAlignedChipsActions);
         mTriggerScript.setRightAlignedChips(rightAlignedChips, rightAlignedChipsActions);
-        boolean shown = mTriggerScript.show(resizeVisualViewport);
+        boolean shown = mTriggerScript.show(resizeVisualViewport, scrollToHide);
 
         // A trigger script was displayed, users are no longer considered first-time users.
         if (shown) {
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantBottomsheetTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantBottomsheetTest.java
index 3de3cca..4fc8276 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantBottomsheetTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantBottomsheetTest.java
@@ -153,7 +153,10 @@
                              .setShowDetails(ShowDetailsProto.newBuilder().setDetails(
                                      DetailsProto.newBuilder()
                                              .setTitle("Details title")
-                                             .setShowImagePlaceholder(true)))
+                                             .setPlaceholders(DetailsProto.PlaceholdersConfiguration
+                                                                      .newBuilder()
+                                                                      .setShowImagePlaceholder(true)
+                                                                      .build())))
                              .build());
         }
         // Add "Done" button.
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDetailsUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDetailsUiTest.java
index cbd0968..c77c01f 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDetailsUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantDetailsUiTest.java
@@ -45,6 +45,7 @@
 import org.chromium.chrome.browser.autofill_assistant.details.AssistantDetails;
 import org.chromium.chrome.browser.autofill_assistant.details.AssistantDetailsCoordinator;
 import org.chromium.chrome.browser.autofill_assistant.details.AssistantDetailsModel;
+import org.chromium.chrome.browser.autofill_assistant.details.AssistantPlaceholdersConfiguration;
 import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -53,6 +54,14 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @RunWith(ChromeJUnit4ClassRunner.class)
 public class AutofillAssistantDetailsUiTest {
+    public static final AssistantPlaceholdersConfiguration NO_PLACEHOLDERS =
+            new AssistantPlaceholdersConfiguration(
+                    /* showImagePlaceholder= */ false,
+                    /* showTitlePlaceholder= */ false,
+                    /* showDescriptionLine1Placeholder= */ false,
+                    /* showDescriptionLine2Placeholder= */ false,
+                    /* showDescriptionLine3Placeholder= */ false);
+
     @Rule
     public CustomTabActivityTestRule mTestRule = new CustomTabActivityTestRule();
 
@@ -123,9 +132,9 @@
 
         runOnUiThreadBlocking(() -> {
             model.set(AssistantDetailsModel.DETAILS,
-                    new AssistantDetails("title", 1, "image", "hint", null, false, "Total", "$12",
-                            "line 1", "line 2", "", "line 3", false, false, false, false, false,
-                            false));
+                    new AssistantDetails("title", "image", "hint", null, "Total", "$12", "line 1",
+                            "line 2", "", "line 3", false, false, false, false, false,
+                            NO_PLACEHOLDERS));
         });
 
         onView(is(viewHolder.mImageView)).check(matches(isDisplayed()));
@@ -148,9 +157,9 @@
 
         runOnUiThreadBlocking(() -> {
             model.set(AssistantDetailsModel.DETAILS,
-                    new AssistantDetails("title", 1, "image", "hint", null, false, "Total", "$12",
-                            "line 1", "line 2", "", "line 3", false, false, false, false, false,
-                            false));
+                    new AssistantDetails("title", "image", "hint", null, "Total", "$12", "line 1",
+                            "line 2", "", "line 3", false, false, false, false, false,
+                            NO_PLACEHOLDERS));
         });
 
         onView(is(viewHolder.mImageView)).check(matches(withContentDescription("hint")));
@@ -167,9 +176,9 @@
 
         runOnUiThreadBlocking(() -> {
             model.set(AssistantDetailsModel.DETAILS,
-                    new AssistantDetails("title", 1, "image", "", null, false, "Total", "$12",
-                            "line 1", "line 2", "", "line 3", false, false, false, false, false,
-                            false));
+                    new AssistantDetails("title", "image", "", null, "Total", "$12", "line 1",
+                            "line 2", "", "line 3", false, false, false, false, false,
+                            NO_PLACEHOLDERS));
         });
 
         onView(is(viewHolder.mImageView)).check(matches(withContentDescription("")));
@@ -184,25 +193,73 @@
         AssistantDetailsCoordinator coordinator = createCoordinator(model);
         ViewHolder viewHolder = runOnUiThreadBlocking(() -> new ViewHolder(coordinator.getView()));
 
-        /* All description lines are set, title must be in single line. */
+        /* Description lines 1 and 2 are set, title must be in single line. */
         runOnUiThreadBlocking(
                 ()
                         -> model.set(AssistantDetailsModel.DETAILS,
-                                new AssistantDetails("title", 1, "image", "hint", null, false,
-                                        "Total", "$12", "line 1", "line 2", "", "line 3", false,
-                                        false, false, false, false, false)));
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "line 1", "line 2", "", "price", false, false, false, false,
+                                        false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mTitleView)).check(matches(isTextMaxLines(1)));
         onView(is(viewHolder.mTitleView)).check(matches(allOf(withText("title"), isDisplayed())));
 
-        /* titleMaxLines is set to 2, check that it is properly applied. */
+        /* Description line 1 is set, title must be max 2 lines. */
         runOnUiThreadBlocking(
                 ()
                         -> model.set(AssistantDetailsModel.DETAILS,
-                                new AssistantDetails("title", 2, "image", "hint", null, false,
-                                        "Total", "$12", "line 1", "", "", "line 3", false, false,
-                                        false, false, false, false)));
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "line 1", "", "", "price", false, false, false, false,
+                                        false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mTitleView)).check(matches(isTextMaxLines(2)));
         onView(is(viewHolder.mTitleView)).check(matches(allOf(withText("title"), isDisplayed())));
+
+        /* Description line 2 is set, title must be max 2 lines. */
+        runOnUiThreadBlocking(
+                ()
+                        -> model.set(AssistantDetailsModel.DETAILS,
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "", "line 2", "", "price", false, false, false, false,
+                                        false, NO_PLACEHOLDERS)));
+        onView(is(viewHolder.mTitleView)).check(matches(isTextMaxLines(2)));
+        onView(is(viewHolder.mTitleView)).check(matches(allOf(withText("title"), isDisplayed())));
+
+        /* None of description line 1 or 2 is set, title must be max 3 lines. */
+        runOnUiThreadBlocking(
+                ()
+                        -> model.set(AssistantDetailsModel.DETAILS,
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "", "", "", "price", false, false, false, false, false,
+                                        NO_PLACEHOLDERS)));
+        onView(is(viewHolder.mTitleView)).check(matches(isTextMaxLines(3)));
+        onView(is(viewHolder.mTitleView)).check(matches(allOf(withText("title"), isDisplayed())));
+
+        /* There is a placeholder for description line 1, title must be max 2 lines. */
+        runOnUiThreadBlocking(
+                ()
+                        -> model.set(AssistantDetailsModel.DETAILS,
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "", "", "", "price", false, false, false, false, false,
+                                        new AssistantPlaceholdersConfiguration(
+                                                /* showImagePlaceholder= */ false,
+                                                /* showTitlePlaceholder= */ false,
+                                                /* showDescriptionLine1Placeholder= */ true,
+                                                /* showDescriptionLine2Placeholder= */ false,
+                                                /* showDescriptionLine3Placeholder= */ false))));
+        onView(is(viewHolder.mTitleView)).check(matches(isTextMaxLines(2)));
+
+        /* There is a placeholder for description line 1 & 2, title must be max 1 line. */
+        runOnUiThreadBlocking(
+                ()
+                        -> model.set(AssistantDetailsModel.DETAILS,
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "", "", "", "price", false, false, false, false, false,
+                                        new AssistantPlaceholdersConfiguration(
+                                                /* showImagePlaceholder= */ false,
+                                                /* showTitlePlaceholder= */ false,
+                                                /* showDescriptionLine1Placeholder= */ true,
+                                                /* showDescriptionLine2Placeholder= */ true,
+                                                /* showDescriptionLine3Placeholder= */ false))));
+        onView(is(viewHolder.mTitleView)).check(matches(isTextMaxLines(1)));
     }
 
     @Test
@@ -215,18 +272,18 @@
         /* Description line 1 is set and should be visible. */
         runOnUiThreadBlocking(()
                                       -> model.set(AssistantDetailsModel.DETAILS,
-                                              new AssistantDetails("title", 1, "image", "hint",
-                                                      null, false, "", "", "line 1", "", "", "",
-                                                      false, false, false, false, false, false)));
+                                              new AssistantDetails("title", "image", "hint", null,
+                                                      "", "", "line 1", "", "", "", false, false,
+                                                      false, false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mDescriptionLine1View))
                 .check(matches(allOf(withText("line 1"), isDisplayed())));
 
         /* Description line 1 is not set and should be invisible. */
         runOnUiThreadBlocking(()
                                       -> model.set(AssistantDetailsModel.DETAILS,
-                                              new AssistantDetails("title", 1, "image", "hint",
-                                                      null, false, "", "", "", "", "", "", false,
-                                                      false, false, false, false, false)));
+                                              new AssistantDetails("title", "image", "hint", null,
+                                                      "", "", "", "", "", "", false, false, false,
+                                                      false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mDescriptionLine1View)).check(matches(not(isDisplayed())));
     }
 
@@ -240,18 +297,18 @@
         /* Description line 2 is set and should be visible. */
         runOnUiThreadBlocking(()
                                       -> model.set(AssistantDetailsModel.DETAILS,
-                                              new AssistantDetails("title", 1, "image", "hint",
-                                                      null, false, "", "", "", "line 2", "", "",
-                                                      false, false, false, false, false, false)));
+                                              new AssistantDetails("title", "image", "hint", null,
+                                                      "", "", "", "line 2", "", "", false, false,
+                                                      false, false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mDescriptionLine2View))
                 .check(matches(allOf(withText("line 2"), isDisplayed())));
 
         /* Description line 2 is not set and should be invisible. */
         runOnUiThreadBlocking(()
                                       -> model.set(AssistantDetailsModel.DETAILS,
-                                              new AssistantDetails("title", 2, "image", "hint",
-                                                      null, false, "", "", "", "", "", "", false,
-                                                      false, false, false, false, false)));
+                                              new AssistantDetails("title", "image", "hint", null,
+                                                      "", "", "", "", "", "", false, false, false,
+                                                      false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mDescriptionLine2View)).check(matches(not(isDisplayed())));
     }
 
@@ -265,18 +322,18 @@
         /* Description line 3 is set and should be visible. */
         runOnUiThreadBlocking(()
                                       -> model.set(AssistantDetailsModel.DETAILS,
-                                              new AssistantDetails("title", 1, "image", "hint",
-                                                      null, false, "", "", "", "", "line 3", "",
-                                                      false, false, false, false, false, false)));
+                                              new AssistantDetails("title", "image", "hint", null,
+                                                      "", "", "", "", "line 3", "", false, false,
+                                                      false, false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mDescriptionLine3View))
                 .check(matches(allOf(withText("line 3"), isDisplayed())));
 
         /* Description line 3 is not set and should be invisible. */
         runOnUiThreadBlocking(()
                                       -> model.set(AssistantDetailsModel.DETAILS,
-                                              new AssistantDetails("title", 1, "image", "hint",
-                                                      null, false, "", "", "", "", "", "line 3",
-                                                      false, false, false, false, false, false)));
+                                              new AssistantDetails("title", "image", "hint", null,
+                                                      "", "", "", "", "", "line 3", false, false,
+                                                      false, false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mDescriptionLine3View)).check(matches(not(isDisplayed())));
     }
 
@@ -291,18 +348,18 @@
         runOnUiThreadBlocking(
                 ()
                         -> model.set(AssistantDetailsModel.DETAILS,
-                                new AssistantDetails("title", 1, "image", "hint", null, false,
-                                        "Total", "$12", "", "", "", "price attribution", false,
-                                        false, false, false, false, false)));
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "", "", "", "price attribution", false, false, false, false,
+                                        false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mPriceAttributionView))
                 .check(matches(allOf(withText("price attribution"), isDisplayed())));
 
         /* Price attribution is not set and should be invisible. */
         runOnUiThreadBlocking(()
                                       -> model.set(AssistantDetailsModel.DETAILS,
-                                              new AssistantDetails("title", 2, "image", "hint",
-                                                      null, false, "", "", "", "", "", "", false,
-                                                      false, false, false, false, false)));
+                                              new AssistantDetails("title", "image", "hint", null,
+                                                      "", "", "", "", "", "", false, false, false,
+                                                      false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mPriceAttributionView)).check(matches(not(isDisplayed())));
     }
 
@@ -317,9 +374,9 @@
         runOnUiThreadBlocking(
                 ()
                         -> model.set(AssistantDetailsModel.DETAILS,
-                                new AssistantDetails("title", 1, "image", "hint", null, false,
-                                        "Total", "$12", "line 1", "line 2", "line 3", "Est. total",
-                                        true, true, false, false, false, false)));
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "line 1", "line 2", "line 3", "Est. total", true, true,
+                                        false, false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mTitleView)).check(matches(hasTypefaceStyle(Typeface.BOLD_ITALIC)));
         onView(is(viewHolder.mDescriptionLine1View))
                 .check(matches(not(hasTypefaceStyle(Typeface.BOLD_ITALIC))));
@@ -334,9 +391,9 @@
         runOnUiThreadBlocking(
                 ()
                         -> model.set(AssistantDetailsModel.DETAILS,
-                                new AssistantDetails("title", 1, "image", "hint", null, false,
-                                        "Total", "$12", "line 1", "line 2", "line 3", "Est. total",
-                                        true, false, true, false, false, false)));
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "line 1", "line 2", "line 3", "Est. total", true, false,
+                                        true, false, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mTitleView))
                 .check(matches(not(hasTypefaceStyle(Typeface.BOLD_ITALIC))));
         onView(is(viewHolder.mDescriptionLine1View))
@@ -352,9 +409,9 @@
         runOnUiThreadBlocking(
                 ()
                         -> model.set(AssistantDetailsModel.DETAILS,
-                                new AssistantDetails("title", 1, "image", "hint", null, false,
-                                        "Total", "$12", "line 1", "line 2", "line 3", "Est. total",
-                                        true, false, false, true, false, false)));
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "line 1", "line 2", "line 3", "Est. total", true, false,
+                                        false, true, false, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mTitleView))
                 .check(matches(not(hasTypefaceStyle(Typeface.BOLD_ITALIC))));
         onView(is(viewHolder.mDescriptionLine1View))
@@ -370,9 +427,9 @@
         runOnUiThreadBlocking(
                 ()
                         -> model.set(AssistantDetailsModel.DETAILS,
-                                new AssistantDetails("title", 1, "image", "hint", null, false,
-                                        "Total", "$12", "line 1", "line 2", "line 3", "Est. total",
-                                        true, false, false, false, true, false)));
+                                new AssistantDetails("title", "image", "hint", null, "Total", "$12",
+                                        "line 1", "line 2", "line 3", "Est. total", true, false,
+                                        false, false, true, NO_PLACEHOLDERS)));
         onView(is(viewHolder.mTitleView))
                 .check(matches(not(hasTypefaceStyle(Typeface.BOLD_ITALIC))));
         onView(is(viewHolder.mDescriptionLine1View))
@@ -394,9 +451,9 @@
 
         runOnUiThreadBlocking(() -> {
             model.set(AssistantDetailsModel.DETAILS,
-                    new AssistantDetails("<b>title</b>", 1, "image", "hint", null, false,
-                            "<b>Total</b>", "<b>$12</b>", "<b>line 1</b>", "<b>line 2</b>", "",
-                            "<b>line 3</b>", false, false, false, false, false, false));
+                    new AssistantDetails("<b>title</b>", "image", "hint", null, "<b>Total</b>",
+                            "<b>$12</b>", "<b>line 1</b>", "<b>line 2</b>", "", "<b>line 3</b>",
+                            false, false, false, false, false, NO_PLACEHOLDERS));
         });
 
         onView(withText("title"))
@@ -412,4 +469,43 @@
         onView(withText("line 3"))
                 .check(matches(hasTypefaceSpan(0, "line 3".length() - 1, Typeface.BOLD)));
     }
+
+    @Test
+    @MediumTest
+    public void testPlaceholders() throws Exception {
+        AssistantDetailsModel model = new AssistantDetailsModel();
+        AssistantDetailsCoordinator coordinator = createCoordinator(model);
+        ViewHolder viewHolder = runOnUiThreadBlocking(() -> new ViewHolder(coordinator.getView()));
+
+        // Without placeholders, the image and descriptions are hidden. The title is always
+        // displayed.
+        runOnUiThreadBlocking(
+                ()
+                        -> model.set(AssistantDetailsModel.DETAILS,
+                                new AssistantDetails("title", "", "hint", null, "", "", "", "", "",
+                                        "", false, false, false, false, false, NO_PLACEHOLDERS)));
+        onView(is(viewHolder.mTitleView)).check(matches(isDisplayed()));
+        onView(is(viewHolder.mImageView)).check(matches(not(isDisplayed())));
+        onView(is(viewHolder.mDescriptionLine1View)).check(matches(not(isDisplayed())));
+        onView(is(viewHolder.mDescriptionLine2View)).check(matches(not(isDisplayed())));
+        onView(is(viewHolder.mDescriptionLine3View)).check(matches(not(isDisplayed())));
+
+        // With placeholders, the image and descriptions are displayed.
+        runOnUiThreadBlocking(
+                ()
+                        -> model.set(AssistantDetailsModel.DETAILS,
+                                new AssistantDetails("title", "", "hint", null, "", "", "", "", "",
+                                        "", false, false, false, false, false,
+                                        new AssistantPlaceholdersConfiguration(
+                                                /* showImagePlaceholder= */ true,
+                                                /* showTitlePlaceholder= */ true,
+                                                /* showDescriptionLine1Placeholder= */ true,
+                                                /* showDescriptionLine2Placeholder= */ true,
+                                                /* showDescriptionLine3Placeholder= */ true))));
+        onView(is(viewHolder.mTitleView)).check(matches(isDisplayed()));
+        onView(is(viewHolder.mImageView)).check(matches(isDisplayed()));
+        onView(is(viewHolder.mDescriptionLine1View)).check(matches(isDisplayed()));
+        onView(is(viewHolder.mDescriptionLine2View)).check(matches(isDisplayed()));
+        onView(is(viewHolder.mDescriptionLine3View)).check(matches(isDisplayed()));
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantTriggerScriptIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantTriggerScriptIntegrationTest.java
index 3127202e..ae0e668 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantTriggerScriptIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantTriggerScriptIntegrationTest.java
@@ -15,6 +15,8 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
 
 import static org.chromium.base.test.util.CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL;
 import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.tapElement;
@@ -35,6 +37,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.chrome.autofill_assistant.R;
@@ -63,7 +66,12 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.content_public.browser.GestureListenerManager;
+import org.chromium.content_public.browser.GestureStateListener;
+import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.ArrayList;
@@ -572,4 +580,80 @@
         Espresso.pressBack();
         waitUntilViewMatchesCondition(withText("Hello world"), isCompletelyDisplayed());
     }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({ChromeFeatureList.AUTOFILL_ASSISTANT_DISABLE_ONBOARDING_FLOW,
+            ChromeFeatureList.AUTOFILL_ASSISTANT_PROACTIVE_HELP})
+    public void
+    testScrollToHide() throws Exception {
+        GetTriggerScriptsResponseProto triggerScripts =
+                GetTriggerScriptsResponseProto.newBuilder()
+                        .addTriggerScripts(
+                                TriggerScriptProto
+                                        .newBuilder()
+                                        /* no trigger condition */
+                                        .setUserInterface(createDefaultUI("Trigger script",
+                                                /* bubbleMessage = */ "",
+                                                /* withProgressBar = */ false)
+                                                                  .setScrollToHide(true)))
+                        .build();
+
+        setupTriggerScripts(triggerScripts);
+        AutofillAssistantPreferencesUtil.setInitialPreferences(false);
+        startAutofillAssistantOnTab(TEST_PAGE_A);
+
+        waitUntilViewMatchesCondition(withText("Trigger script"), isCompletelyDisplayed());
+
+        BottomSheetController bottomSheetController =
+                AutofillAssistantUiTestUtil.getBottomSheetController(mTestRule.getActivity());
+
+        CallbackHelper waitForScroll = new CallbackHelper();
+        WebContents webContents = mTestRule.getWebContents();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            GestureListenerManager.fromWebContents(webContents)
+                    .addListener(new GestureStateListener() {
+                        @Override
+                        public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
+                            waitForScroll.notifyCalled();
+                        }
+                    });
+        });
+        int webContentX = TestThreadUtils.runOnUiThreadBlocking(
+                () -> webContents.getViewAndroidDelegate().getContainerView().getWidth() / 2);
+        int webContentY = TestThreadUtils.runOnUiThreadBlocking(
+                () -> webContents.getViewAndroidDelegate().getContainerView().getHeight() / 3);
+
+        int offsetBeforeScroll = TestThreadUtils.runOnUiThreadBlocking(
+                () -> bottomSheetController.getCurrentOffset());
+        assertThat(offsetBeforeScroll, greaterThan(0));
+
+        // Scroll more than the bottom sheet height, to be sure it's going to be completely hidden
+        // or shown due to the scroll.
+        int scrollDistance = (int) (bottomSheetController.getCurrentOffset() * 1.5f);
+        TouchCommon.performDrag(mTestRule.getActivity(), webContentX, webContentX, webContentY,
+                webContentY - scrollDistance,
+                /* stepCount*/ 10, /* duration in ms */ 250);
+        waitForScroll.waitForCallback("scroll down expected", /* currentCallCount= */ 0);
+
+        // After scroll down, the bottom sheet is completely hidden.
+        int offsetAfterScrollDown = TestThreadUtils.runOnUiThreadBlocking(
+                () -> bottomSheetController.getCurrentOffset());
+        Assert.assertEquals(0, offsetAfterScrollDown);
+
+        TouchCommon.performDrag(mTestRule.getActivity(), webContentX, webContentX, webContentY,
+                webContentY + scrollDistance, /* stepCount*/ 10, /* duration in ms */ 250);
+
+        waitForScroll.waitForCallback("scroll up expected", /* currentCallCount= */ 1);
+
+        // Wait until the bottom sheet is fully back on the screen again before capturing one last
+        // offset.
+        waitUntilViewMatchesCondition(withText("Trigger script"), isCompletelyDisplayed());
+
+        int offsetAfterScrollUp = TestThreadUtils.runOnUiThreadBlocking(
+                () -> bottomSheetController.getCurrentOffset());
+
+        // After scroll up, the bottom sheet is visible again.
+        Assert.assertEquals(offsetBeforeScroll, offsetAfterScrollUp);
+    }
 }
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
index 0ebc2c2..0f54bcd 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
@@ -182,11 +182,10 @@
                 ()
                         -> assistantCoordinator.getModel().getDetailsModel().set(
                                 AssistantDetailsModel.DETAILS,
-                                new AssistantDetails(movieTitle, /* titleMaxLines = */ 1,
+                                new AssistantDetails(movieTitle,
                                         /* imageUrl = */ "",
                                         /* imageAccessibilityHint = */ "",
                                         /* imageClickthroughData = */ null,
-                                        /* showImage = */ false,
                                         /* totalPriceLabel = */ "",
                                         /* totalPrice = */ "", descriptionLine1, descriptionLine2,
                                         descriptionLine3,
@@ -195,7 +194,7 @@
                                         /* highlightTitle= */ false, /* highlightLine1= */
                                         false, /* highlightLine2 = */ false,
                                         /* highlightLine3 = */ false,
-                                        /* animatePlaceholders= */ false)));
+                                        AutofillAssistantDetailsUiTest.NO_PLACEHOLDERS)));
         onView(withId(R.id.details_title))
                 .check(matches(allOf(withText(movieTitle), withEffectiveVisibility(VISIBLE))));
         onView(withId(R.id.details_line1))
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 0064f94..2a0abbae 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -30,6 +30,9 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleRegistry;
 
 import org.chromium.base.CallbackController;
 import org.chromium.base.CommandLine;
@@ -39,6 +42,7 @@
 import org.chromium.base.MemoryPressureListener;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
+import org.chromium.base.annotations.UsedByReflection;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
@@ -329,6 +333,32 @@
     };
 
     /**
+     * This class is used to warm up the chrome split ClassLoader. See SplitChromeApplication for
+     * more info
+     */
+    @UsedByReflection("SplitChromeApplication.java")
+    public static class Preload extends ChromeTabbedActivity {
+        private LifecycleRegistry mLifecycleRegistry;
+
+        @UsedByReflection("SplitChromeApplication.java")
+        public Preload() {}
+
+        @Override
+        public Lifecycle getLifecycle() {
+            if (mLifecycleRegistry == null) {
+                // LifecycleRegistry normally enforces it is called on the main thread, but this
+                // class will be preloaded in a background thread. The only method that gets called
+                // in the activity constructor is addObserver(), so just override that.
+                mLifecycleRegistry = new LifecycleRegistry(null) {
+                    @Override
+                    public void addObserver(LifecycleObserver observer) {}
+                };
+            }
+            return mLifecycleRegistry;
+        }
+    }
+
+    /**
      * Return whether the passed in component name matches any of the supported tabbed mode
      * activities.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java
index 02c41c9..c4d7280 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java
@@ -11,6 +11,8 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.SystemClock;
 
 import org.chromium.base.ActivityState;
@@ -92,18 +94,46 @@
         // If the chrome module is not enabled or isolated splits are not supported (e.g. in Android
         // N), the onComplete function will run immediately so it must handle the case where the
         // base context of the application has not been set yet.
-        sSplitPreloader.preload(CHROME_SPLIT_NAME, (chromeContext) -> {
-            // When installed, the vr module is always loaded on startup, so preload here.
-            sSplitPreloader.preload("vr", null);
-            // If the chrome module is not enabled or isolated splits are not supported,
-            // chromeContext will have the same ClassLoader as the base context, so no need to
-            // replace the ClassLoaders here.
-            if (!context.getClassLoader().equals(chromeContext.getClassLoader())) {
-                // Replace the application Context's ClassLoader with the chrome ClassLoader,
-                // because the application ClassLoader is expected to be able to access all chrome
-                // classes.
-                BundleUtils.replaceClassLoader(this, chromeContext.getClassLoader());
-                JNIUtils.setClassLoader(chromeContext.getClassLoader());
+        sSplitPreloader.preload(CHROME_SPLIT_NAME, new SplitPreloader.OnComplete() {
+            @Override
+            public void runImmediatelyInBackgroundThread(Context chromeContext) {
+                // A new thread is started here because we do not want to delay returning the chrome
+                // Context, since that slows down startup. This thread must be a HandlerThread
+                // because AsyncInitializationActivity (a base class of ChromeTabbedActivity)
+                // creates a Handler, so needs to have a Looper prepared.
+                HandlerThread thread = new HandlerThread("ActivityPreload");
+                thread.start();
+                new Handler(thread.getLooper()).post(() -> {
+                    try {
+                        // Create a throwaway instance of ChromeTabbedActivity. This will warm up
+                        // the chrome ClassLoader, and perform loading of classes used early in
+                        // startup in the background.
+                        chromeContext.getClassLoader()
+                                .loadClass(
+                                        "org.chromium.chrome.browser.ChromeTabbedActivity$Preload")
+                                .newInstance();
+                    } catch (ReflectiveOperationException e) {
+                        throw new RuntimeException(e);
+                    }
+                    thread.quit();
+                });
+            }
+
+            @Override
+            public void runInUiThread(Context chromeContext) {
+                // When installed, the vr module is always loaded on startup, so preload here.
+                sSplitPreloader.preload("vr", null);
+                // If the chrome module is not enabled or isolated splits are not supported,
+                // chromeContext will have the same ClassLoader as the base context, so no need to
+                // replace the ClassLoaders here.
+                if (!context.getClassLoader().equals(chromeContext.getClassLoader())) {
+                    // Replace the application Context's ClassLoader with the chrome ClassLoader,
+                    // because the application ClassLoader is expected to be able to access all
+                    // chrome classes.
+                    BundleUtils.replaceClassLoader(
+                            SplitChromeApplication.this, chromeContext.getClassLoader());
+                    JNIUtils.setClassLoader(chromeContext.getClassLoader());
+                }
             }
         });
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitPreloader.java b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitPreloader.java
index 004a89ec..ab4c626 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/base/SplitPreloader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/base/SplitPreloader.java
@@ -24,9 +24,23 @@
     private final SimpleArrayMap<String, PreloadTask> mPreloadTasks = new SimpleArrayMap<>();
     private final Context mContext;
 
-    /** Interface to run code after preload completion. */
+    /**
+     * Interface to run code after preload completion.
+     */
     public interface OnComplete {
-        void run(Context context);
+        /**
+         * Runs immediately on the background thread as soon as the split context is available.
+         * Note that normally runInUiThread() should be used instead because the context parameter
+         * here may have an incorrect ClassLoader due to b/172602571. This method should only be
+         * used for optimizations which need to run as soon as possible, and are safe throw away if
+         * a different ClassLoader ends up being used.
+         */
+        default void runImmediatelyInBackgroundThread(Context unsafeClassLoaderContext) {}
+
+        /**
+         * Guaranteed to run in the UI thread before {@link SplitPreloader#wait(String)} returns.
+         */
+        default void runInUiThread(Context context) {}
     }
 
     private class PreloadTask extends AsyncTask<Void> {
@@ -41,6 +55,9 @@
         @Override
         protected Void doInBackground() {
             Context context = createSplitContext();
+            if (mOnComplete != null) {
+                mOnComplete.runImmediatelyInBackgroundThread(context);
+            }
             return null;
         }
 
@@ -63,7 +80,7 @@
             if (mOnComplete != null) {
                 // Recreate the context here to make sure we have the latest version, in case there
                 // was a race to update the class loader cache, see b/172602571.
-                mOnComplete.run(createSplitContext());
+                mOnComplete.runInUiThread(createSplitContext());
                 mOnComplete = null;
             }
         }
@@ -82,10 +99,7 @@
 
     /** Starts preloading a split context on a background thread. */
     public void preload(String name, OnComplete onComplete) {
-        if (!BundleUtils.isIsolatedSplitInstalled(mContext, name)) {
-            if (onComplete != null) {
-                onComplete.run(mContext);
-            }
+        if (!BundleUtils.isIsolatedSplitInstalled(mContext, name) && onComplete == null) {
             return;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadActivity.java
index 1d45cc8..c921675 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadActivity.java
@@ -16,6 +16,9 @@
 import org.chromium.chrome.browser.download.home.DownloadManagerUiConfig;
 import org.chromium.chrome.browser.download.home.DownloadManagerUiConfigHelper;
 import org.chromium.chrome.browser.download.items.OfflineContentAggregatorNotificationBridgeUiFactory;
+import org.chromium.chrome.browser.incognito.IncognitoUtils;
+import org.chromium.chrome.browser.profiles.OTRProfileID;
+import org.chromium.chrome.browser.profiles.ProfileKey;
 import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.ui.base.ActivityAndroidPermissionDelegate;
@@ -46,6 +49,7 @@
                     mCurrentUrl = url;
                 }
             };
+    private OTRProfileID mOtrProfileID;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -63,6 +67,7 @@
                 getIntent(), IntentHandler.EXTRA_PARENT_COMPONENT);
         mPermissionDelegate =
                 new ActivityAndroidPermissionDelegate(new WeakReference<Activity>(this));
+        mOtrProfileID = DownloadUtils.getOTRProfileIDFromIntent(getIntent());
 
         DownloadManagerUiConfig config =
                 DownloadManagerUiConfigHelper.fromFlags()
@@ -91,7 +96,8 @@
     @Override
     public void onResume() {
         super.onResume();
-        DownloadUtils.checkForExternallyRemovedDownloads(mIsOffTheRecord);
+        ProfileKey profileKey = IncognitoUtils.getProfileKeyFromOTRProfileID(mOtrProfileID);
+        DownloadUtils.checkForExternallyRemovedDownloads(profileKey);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManagerImpl.java
index fc830c1..28d9d4f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastManagerImpl.java
@@ -39,6 +39,7 @@
 import org.chromium.chrome.browser.init.BrowserParts;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.init.EmptyBrowserParts;
+import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.components.offline_items_collection.ContentId;
 import org.chromium.components.offline_items_collection.LaunchLocation;
 import org.chromium.components.offline_items_collection.LegacyHelpers;
@@ -239,6 +240,7 @@
         boolean isOffTheRecord = entry == null
                 ? IntentUtils.safeGetBooleanExtra(intent, EXTRA_IS_OFF_THE_RECORD, false)
                 : entry.isOffTheRecord;
+        OTRProfileID otrProfileID = DownloadUtils.getOTRProfileIDFromIntent(intent);
         DownloadServiceDelegate downloadServiceDelegate = getServiceDelegate(id);
 
         checkNotNull(downloadServiceDelegate);
@@ -252,11 +254,11 @@
                         intent.getIntExtra(EXTRA_DOWNLOAD_STATE_AT_CANCEL, -1));
                 DownloadMetrics.recordDownloadCancel(
                         DownloadMetrics.CancelFrom.CANCEL_NOTIFICATION);
-                downloadServiceDelegate.cancelDownload(id, isOffTheRecord);
+                downloadServiceDelegate.cancelDownload(id, otrProfileID);
                 break;
 
             case ACTION_DOWNLOAD_PAUSE:
-                downloadServiceDelegate.pauseDownload(id, isOffTheRecord);
+                downloadServiceDelegate.pauseDownload(id, otrProfileID);
                 break;
 
             case ACTION_DOWNLOAD_RESUME:
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
index c3a79ac..9e83c66 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -40,10 +40,12 @@
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.incognito.IncognitoUtils;
 import org.chromium.chrome.browser.media.MediaViewerUtils;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileKey;
 import org.chromium.chrome.browser.profiles.ProfileManager;
@@ -402,7 +404,7 @@
             DownloadNotificationService.clearResumptionAttemptLeft();
 
             DownloadManagerService.getDownloadManagerService().checkForExternallyRemovedDownloads(
-                    /*isOffTheRecord=*/false);
+                    ProfileKey.getLastUsedRegularProfileKey());
             mActivityLaunched = true;
         }
     }
@@ -973,9 +975,10 @@
         // Downloads started from incognito mode should not be resumed in reduced mode.
         if (!ProfileManager.isInitialized() && item.getDownloadInfo().isOffTheRecord()) return;
 
+        OTRProfileID otrProfileID = item.getDownloadInfo().getOTRProfileId();
         DownloadManagerServiceJni.get().resumeDownload(getNativeDownloadManagerService(),
                 DownloadManagerService.this, item.getId(),
-                getProfileKey(item.getDownloadInfo().isOffTheRecord()), hasUserGesture);
+                IncognitoUtils.getProfileKeyFromOTRProfileID(otrProfileID), hasUserGesture);
     }
 
     /**
@@ -987,21 +990,23 @@
     // Deprecated after new download backend.
     // TODO(shaktisahu): Add retry to offline content provider or route it from resume call.
     public void retryDownload(ContentId id, DownloadItem item, boolean hasUserGesture) {
+        OTRProfileID otrProfileID = item.getDownloadInfo().getOTRProfileId();
         DownloadManagerServiceJni.get().retryDownload(getNativeDownloadManagerService(),
                 DownloadManagerService.this, item.getId(),
-                getProfileKey(item.getDownloadInfo().isOffTheRecord()), hasUserGesture);
+                IncognitoUtils.getProfileKeyFromOTRProfileID(otrProfileID), hasUserGesture);
     }
 
     /**
      * Called to cancel a download.
      * @param id The {@link ContentId} of the download to cancel.
-     * @param isOffTheRecord Whether the download is off the record.
+     * @param otrProfileID The {@link OTRProfileID} of the download. Null if in regular mode.
      */
     // Deprecated after new download backend.
     @Override
-    public void cancelDownload(ContentId id, boolean isOffTheRecord) {
+    public void cancelDownload(ContentId id, OTRProfileID otrProfileID) {
         DownloadManagerServiceJni.get().cancelDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, id.id, getProfileKey(isOffTheRecord));
+                DownloadManagerService.this, id.id,
+                IncognitoUtils.getProfileKeyFromOTRProfileID(otrProfileID));
         DownloadProgress progress = mDownloadProgressMap.get(id.id);
         if (progress != null) {
             DownloadInfo info =
@@ -1011,7 +1016,8 @@
             removeDownloadProgress(id.id);
         } else {
             mDownloadNotifier.notifyDownloadCanceled(id);
-            DownloadInfoBarController infoBarController = getInfoBarController(isOffTheRecord);
+            DownloadInfoBarController infoBarController =
+                    getInfoBarController(otrProfileID != null);
             if (infoBarController != null) infoBarController.onDownloadItemRemoved(id);
         }
     }
@@ -1019,13 +1025,14 @@
     /**
      * Called to pause a download.
      * @param id The {@link ContentId} of the download to pause.
-     * @param isOffTheRecord Whether the download is off the record.
+     * @param otrProfileID The {@link OTRProfileID} of the download. Null if in regular mode.
      */
     // Deprecated after new download backend.
     @Override
-    public void pauseDownload(ContentId id, boolean isOffTheRecord) {
+    public void pauseDownload(ContentId id, OTRProfileID otrProfileID) {
         DownloadManagerServiceJni.get().pauseDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, id.id, getProfileKey(isOffTheRecord));
+                DownloadManagerService.this, id.id,
+                IncognitoUtils.getProfileKeyFromOTRProfileID(otrProfileID));
         DownloadProgress progress = mDownloadProgressMap.get(id.id);
         // Calling pause will stop listening to the download item. Update its progress now.
         // If download is already completed, canceled or failed, there is no need to update the
@@ -1048,14 +1055,15 @@
     /**
      * Removes a download from the list.
      * @param downloadGuid GUID of the download.
-     * @param isOffTheRecord Whether the download is off the record.
+     * @param otrProfileID The {@link OTRProfileID} of the download. Null if in regular mode.
      * @param externallyRemoved If the file is externally removed by other applications.
      */
     public void removeDownload(
-            final String downloadGuid, boolean isOffTheRecord, boolean externallyRemoved) {
+            final String downloadGuid, OTRProfileID otrProfileID, boolean externallyRemoved) {
         mHandler.post(() -> {
             DownloadManagerServiceJni.get().removeDownload(getNativeDownloadManagerService(),
-                    DownloadManagerService.this, downloadGuid, getProfileKey(isOffTheRecord));
+                    DownloadManagerService.this, downloadGuid,
+                    IncognitoUtils.getProfileKeyFromOTRProfileID(otrProfileID));
             removeDownloadProgress(downloadGuid);
         });
 
@@ -1367,9 +1375,10 @@
 
     // Deprecated after new download backend.
     public void renameDownload(ContentId id, String name,
-            Callback<Integer /*RenameResult*/> callback, boolean isOffTheRecord) {
+            Callback<Integer /*RenameResult*/> callback, OTRProfileID otrProfileID) {
         DownloadManagerServiceJni.get().renameDownload(getNativeDownloadManagerService(),
-                DownloadManagerService.this, id.id, name, callback, getProfileKey(isOffTheRecord));
+                DownloadManagerService.this, id.id, name, callback,
+                IncognitoUtils.getProfileKeyFromOTRProfileID(otrProfileID));
     }
 
     /**
@@ -1416,12 +1425,11 @@
 
     /**
      * Checks if the files associated with any downloads have been removed by an external action.
-     * @param isOffTheRecord Whether or not to check downloads for the off the record profile.
+     * @param profileKey The {@link ProfileKey} to check the downloads for the the given profile.
      */
-    public void checkForExternallyRemovedDownloads(boolean isOffTheRecord) {
+    public void checkForExternallyRemovedDownloads(ProfileKey profileKey) {
         DownloadManagerServiceJni.get().checkForExternallyRemovedDownloads(
-                getNativeDownloadManagerService(), DownloadManagerService.this,
-                getProfileKey(isOffTheRecord));
+                getNativeDownloadManagerService(), DownloadManagerService.this, profileKey);
     }
 
     // Deprecated after new download backend.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
index 35e12a9..913a7736 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
@@ -32,6 +32,7 @@
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
@@ -717,7 +718,12 @@
             if (cancelActualDownload) {
                 DownloadServiceDelegate delegate = getServiceDelegate(id);
                 DownloadMetrics.recordDownloadCancel(DownloadMetrics.CancelFrom.CANCEL_SHUTDOWN);
-                delegate.cancelDownload(id, true);
+                // TODO(crbug.com/1164379): Pass OTRProfileID of the current OTR profile to cancel
+                // non-primary OTR downloads.
+                OTRProfileID otrProfileID = Profile.getLastUsedRegularProfile()
+                                                    .getPrimaryOTRProfile()
+                                                    .getOTRProfileID();
+                delegate.cancelDownload(id, otrProfileID);
                 delegate.destroyServiceDelegate();
             }
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadPage.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadPage.java
index c96222b..5ae524fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadPage.java
@@ -14,6 +14,8 @@
 import org.chromium.chrome.browser.download.home.DownloadManagerCoordinatorFactoryHelper;
 import org.chromium.chrome.browser.download.home.DownloadManagerUiConfig;
 import org.chromium.chrome.browser.download.home.DownloadManagerUiConfigHelper;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileKey;
 import org.chromium.chrome.browser.ui.native_page.BasicNativePage;
 import org.chromium.chrome.browser.ui.native_page.NativePageHost;
 import org.chromium.components.embedder_support.util.UrlConstants;
@@ -56,8 +58,10 @@
         // resumed.
         mActivityStateListener = (activity1, newState) -> {
             if (newState == ActivityState.RESUMED) {
-                DownloadUtils.checkForExternallyRemovedDownloads(
-                        activity.getCurrentTabModel().isIncognito());
+                Profile profile = activity.getCurrentTabModel().getProfile();
+                ProfileKey profileKey = profile == null ? ProfileKey.getLastUsedRegularProfileKey()
+                                                        : profile.getProfileKey();
+                DownloadUtils.checkForExternallyRemovedDownloads(profileKey);
             }
         };
         ApplicationStatus.registerStateListenerForActivity(mActivityStateListener, activity);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadServiceDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadServiceDelegate.java
index 40202e1..2c69487 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadServiceDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadServiceDelegate.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.download;
 
+import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.components.offline_items_collection.ContentId;
 
 /** Interface for classes implementing concrete implementation of UI behavior. */
@@ -11,16 +12,16 @@
     /**
      * Called to cancel a download.
      * @param id The {@link ContentId} of the download to cancel.
-     * @param isOffTheRecord Whether the download is off the record.
+     * @param otrProfileID The {@link OTRProfileID} of the download. Null if in regular mode.
      */
-    void cancelDownload(ContentId id, boolean isOffTheRecord);
+    void cancelDownload(ContentId id, OTRProfileID otrProfileID);
 
     /**
      * Called to pause a download.
      * @param id The {@link ContentId} of the download to pause.
-     * @param isOffTheRecord Whether the download is off the record.
+     * @param otrProfileID The {@link OTRProfileID} of the download. Null if in regular mode.
      */
-    void pauseDownload(ContentId id, boolean isOffTheRecord);
+    void pauseDownload(ContentId id, OTRProfileID otrProfileID);
 
     /**
      * Called to resume a paused download.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
index 3eac379..b9f0330 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
@@ -43,6 +43,7 @@
 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBridge;
 import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileKey;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
@@ -230,19 +231,19 @@
      * Issues a request to the {@link DownloadManagerService} associated to check for externally
      * removed downloads.
      * See {@link DownloadManagerService#checkForExternallyRemovedDownloads}.
-     * @param isOffTheRecord  Whether to check downloads for the off the record profile.
+     * @param profileKey  The {@link ProfileKey} to check downloads of the given profile.
      */
-    public static void checkForExternallyRemovedDownloads(boolean isOffTheRecord) {
+    public static void checkForExternallyRemovedDownloads(ProfileKey profileKey) {
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
             return;
         }
 
-        if (isOffTheRecord) {
+        if (profileKey.isOffTheRecord()) {
             DownloadManagerService.getDownloadManagerService().checkForExternallyRemovedDownloads(
-                    true);
+                    profileKey);
         }
         DownloadManagerService.getDownloadManagerService().checkForExternallyRemovedDownloads(
-                false);
+                ProfileKey.getLastUsedRegularProfileKey());
         RecordUserAction.record(
                 "Android.DownloadManager.CheckForExternallyRemovedItems");
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
index 43aa44b..0d78ae9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
@@ -326,7 +326,7 @@
                 OfflineContentAggregatorFactory.get().removeItem(mDownloadInfo.getContentId());
             } else {
                 DownloadManagerService.getDownloadManagerService().removeDownload(
-                        mDownloadInfo.getDownloadGuid(), mDownloadInfo.isOffTheRecord(),
+                        mDownloadInfo.getDownloadGuid(), mDownloadInfo.getOTRProfileId(),
                         false /* externallyRemoved */);
             }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/LegacyDownloadProviderImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/LegacyDownloadProviderImpl.java
index fcb8073..2e76dce 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/LegacyDownloadProviderImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/LegacyDownloadProviderImpl.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.download.DownloadMetrics;
 import org.chromium.chrome.browser.download.DownloadOpenSource;
 import org.chromium.chrome.browser.download.DownloadUtils;
+import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.components.offline_items_collection.ContentId;
 import org.chromium.components.offline_items_collection.LegacyHelpers;
 import org.chromium.components.offline_items_collection.OfflineContentProvider;
@@ -130,7 +131,7 @@
     @Override
     public void removeItem(OfflineItem item) {
         DownloadManagerService.getDownloadManagerService().removeDownload(
-                item.id.id, item.isOffTheRecord, item.externallyRemoved);
+                item.id.id, OTRProfileID.deserialize(item.otrProfileId), item.externallyRemoved);
         FileDeletionQueue.get().delete(item.filePath);
     }
 
@@ -138,13 +139,13 @@
     public void cancelDownload(OfflineItem item) {
         DownloadMetrics.recordDownloadCancel(DownloadMetrics.CancelFrom.CANCEL_DOWNLOAD_HOME);
         DownloadManagerService.getDownloadManagerService().cancelDownload(
-                item.id, item.isOffTheRecord);
+                item.id, OTRProfileID.deserialize(item.otrProfileId));
     }
 
     @Override
     public void pauseDownload(OfflineItem item) {
         DownloadManagerService.getDownloadManagerService().pauseDownload(
-                item.id, item.isOffTheRecord);
+                item.id, OTRProfileID.deserialize(item.otrProfileId));
     }
 
     @Override
@@ -200,7 +201,7 @@
     public void renameItem(
             OfflineItem item, String name, Callback</*RenameResult*/ Integer> callback) {
         DownloadManagerService.getDownloadManagerService().renameDownload(
-                item.id, name, callback, item.isOffTheRecord);
+                item.id, name, callback, OTRProfileID.deserialize(item.otrProfileId));
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUi.java b/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUi.java
index 4b1bb453..439ce90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUi.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUi.java
@@ -8,6 +8,7 @@
 import org.chromium.chrome.browser.download.DownloadItem;
 import org.chromium.chrome.browser.download.DownloadNotifier;
 import org.chromium.chrome.browser.download.DownloadServiceDelegate;
+import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.components.offline_items_collection.ContentId;
 import org.chromium.components.offline_items_collection.LegacyHelpers;
 import org.chromium.components.offline_items_collection.OfflineContentProvider;
@@ -105,12 +106,12 @@
 
     // DownloadServiceDelegate implementation.
     @Override
-    public void cancelDownload(ContentId id, boolean isOffTheRecord) {
+    public void cancelDownload(ContentId id, OTRProfileID otrProfileID) {
         mProvider.cancelDownload(id);
     }
 
     @Override
-    public void pauseDownload(ContentId id, boolean isOffTheRecord) {
+    public void pauseDownload(ContentId id, OTRProfileID otrProfileID) {
         mProvider.pauseDownload(id);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessor.java
index 9454f70..eb33839 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessor.java
@@ -4,9 +4,9 @@
 
 package org.chromium.chrome.browser.firstrun;
 
+import android.accounts.Account;
 import android.app.Activity;
 
-import org.chromium.base.Log;
 import org.chromium.chrome.browser.SyncFirstSetupCompleteSource;
 import org.chromium.chrome.browser.childaccounts.ChildAccountService;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -20,28 +20,28 @@
 import org.chromium.components.signin.ChildAccountStatus;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 
+import java.util.List;
+
 /**
  * A helper to perform all necessary steps for forced sign in.
  * The helper performs:
  * - necessary child account checks;
- * - automatic non-interactive forced sign in for child accounts; and
+ * - automatic non-interactive sign in for child accounts; and
  * The helper calls the observer's onSignInComplete() if
  * - nothing needs to be done, or when
  * - the sign in is complete.
  *
  * Usage:
- * ForcedSigninProcessor.start(appContext).
+ * ForcedSigninProcessor.start().
  */
 public final class ForcedSigninProcessor {
-    private static final String TAG = "ForcedSignin";
-
     /*
      * Only for static usage.
      */
     private ForcedSigninProcessor() {}
 
     /**
-     * Check whether a forced automatic signin is required and process it if it is.
+     * Check whether an automatic signin is required and process it if it is.
      * This is triggered once per Chrome Application lifetime and every time the Account state
      * changes with early exit if an account has already been signed in.
      */
@@ -50,37 +50,32 @@
             boolean hasChildAccount = ChildAccountStatus.isChild(status);
             AccountManagementFragment.setSignOutAllowedPreferenceValue(!hasChildAccount);
             if (hasChildAccount) {
-                processForcedSignIn();
+                // Account cache is already available when child account status is ready.
+                final List<Account> accounts =
+                        AccountManagerFacadeProvider.getInstance().tryGetGoogleAccounts();
+                assert accounts.size() == 1 : "Child account should be the only account on device!";
+                signinAndEnableSync(accounts.get(0));
             }
         });
     }
 
     /**
      * Processes the fully automatic non-FRE-related forced sign-in.
-     * This is used to enforce the environment for Android EDU and child accounts.
+     * This is used to enforce the environment for child accounts.
      */
-    private static void processForcedSignIn() {
+    private static void signinAndEnableSync(final Account childAccount) {
         final Profile profile = Profile.getLastUsedRegularProfile();
         if (FirstRunUtils.canAllowSync()
                 && IdentityServicesProvider.get().getIdentityManager(profile).hasPrimaryAccount()) {
             // TODO(https://crbug.com/1044206): Remove this.
             ProfileSyncService.get().setFirstSetupComplete(SyncFirstSetupCompleteSource.BASIC_FLOW);
         }
-
         final SigninManager signinManager =
                 IdentityServicesProvider.get().getSigninManager(profile);
         // By definition we have finished all the checks for first run.
         signinManager.onFirstRunCheckDone();
-        if (!FirstRunUtils.canAllowSync() || !signinManager.isSignInAllowed()) {
-            Log.d(TAG, "Sign in disallowed");
-            return;
-        }
-        AccountManagerFacadeProvider.getInstance().tryGetGoogleAccounts(accounts -> {
-            if (accounts.size() != 1) {
-                Log.d(TAG, "Incorrect number of accounts (%d)", accounts.size());
-                return;
-            }
-            signinManager.signinAndEnableSync(SigninAccessPoint.FORCED_SIGNIN, accounts.get(0),
+        if (FirstRunUtils.canAllowSync() && signinManager.isSignInAllowed()) {
+            signinManager.signinAndEnableSync(SigninAccessPoint.FORCED_SIGNIN, childAccount,
                     new SigninManager.SignInCallback() {
                         @Override
                         public void onSignInComplete() {
@@ -92,7 +87,7 @@
                         @Override
                         public void onSignInAborted() {}
                     });
-        });
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoUtils.java
index 1beeeec9..20ff3a8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoUtils.java
@@ -23,7 +23,9 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.customtabs.CustomTabIncognitoManager;
+import org.chromium.chrome.browser.profiles.OTRProfileID;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileKey;
 import org.chromium.chrome.browser.tab.TabStateFileManager;
 import org.chromium.chrome.browser.tabmodel.IncognitoTabHost;
 import org.chromium.chrome.browser.tabmodel.IncognitoTabHostRegistry;
@@ -234,6 +236,22 @@
         return customTabIncognitoManager.getProfile();
     }
 
+    /**
+     * Returns the {@link ProfileKey} from given {@link OTRProfileID}. If OTRProfileID is null, it
+     * is the key of regular profile.
+     *
+     * @param otrProfileID The {@link OTRProfileID} of the profile. Null for regular profile.
+     * @return The {@link ProfileKey} of the key.
+     */
+    public static ProfileKey getProfileKeyFromOTRProfileID(OTRProfileID otrProfileID) {
+        // If off-the-record is not requested, the request might be before native initialization.
+        if (otrProfileID == null) return ProfileKey.getLastUsedRegularProfileKey();
+
+        return Profile.getLastUsedRegularProfile()
+                .getOffTheRecordProfile(otrProfileID)
+                .getProfileKey();
+    }
+
     @VisibleForTesting
     public static void setEnabledForTesting(Boolean enabled) {
         sIsEnabledForTesting = enabled;
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 916d82b..8dce69dd 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
@@ -49,6 +49,7 @@
 import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.profiles.ProfileKey;
 import org.chromium.chrome.browser.query_tiles.QueryTileSection.QueryInfo;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.browser.suggestions.SuggestionsDependencyFactory;
@@ -357,7 +358,7 @@
         mBrowserControlsStateProvider.addObserver(this);
 
         DownloadManagerService.getDownloadManagerService().checkForExternallyRemovedDownloads(
-                /*isOffTheRecord=*/false);
+                ProfileKey.getLastUsedRegularProfileKey());
 
         mTabStripAndToolbarHeight =
                 activity.getResources().getDimensionPixelSize(R.dimen.tab_strip_and_toolbar_height);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninFragmentBase.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninFragmentBase.java
index 47a6a10..a73781c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninFragmentBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninFragmentBase.java
@@ -524,7 +524,7 @@
 
                     // AccountManagerFacade couldn't create intent, use SigninUtils to open settings
                     // instead.
-                    SigninUtils.openSettingsForAllAccounts(getContext());
+                    SigninUtils.openSettingsForAllAccounts(getActivity());
                 });
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
index c1d5e3ec..d08e5bf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninUtils.java
@@ -6,7 +6,6 @@
 
 import android.accounts.Account;
 import android.app.Activity;
-import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
 import android.provider.Settings;
@@ -47,30 +46,27 @@
 
     /**
      * Opens a Settings page to configure settings for a single account.
-     * @param context Context to use when starting the Activity.
+     * @param activity Activity to use when starting the Activity.
      * @param account The account for which the Settings page should be opened.
      * @return Whether or not Android accepted the Intent.
      */
-    public static boolean openSettingsForAccount(Context context, Account account) {
+    public static boolean openSettingsForAccount(Activity activity, Account account) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             // ACCOUNT_SETTINGS_ACTION no longer works on Android O+, always open all accounts page.
-            return openSettingsForAllAccounts(context);
+            return openSettingsForAllAccounts(activity);
         }
         Intent intent = new Intent(ACCOUNT_SETTINGS_ACTION);
         intent.putExtra(ACCOUNT_SETTINGS_ACCOUNT_KEY, account);
-        if (!(context instanceof Activity)) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return IntentUtils.safeStartActivity(context, intent);
+        return IntentUtils.safeStartActivity(activity, intent);
     }
 
     /**
      * Opens a Settings page with all accounts on the device.
-     * @param context Context to use when starting the Activity.
+     * @param activity Activity to use when starting the Activity.
      * @return Whether or not Android accepted the Intent.
      */
-    public static boolean openSettingsForAllAccounts(Context context) {
-        Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
-        if (!(context instanceof Activity)) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return IntentUtils.safeStartActivity(context, intent);
+    public static boolean openSettingsForAllAccounts(Activity activity) {
+        return IntentUtils.safeStartActivity(activity, new Intent(Settings.ACTION_SYNC_SETTINGS));
     }
 
     @CalledByNative
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java
index 9db80c9..487803e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java
@@ -159,13 +159,6 @@
     }
 
     /**
-     * @return Whether sync is enabled to sync urls or open tabs with a non custom passphrase.
-     */
-    public boolean isSyncingUrlsWithKeystorePassphrase() {
-        return mProfileSyncService.isSyncingUrlsWithKeystorePassphrase();
-    }
-
-    /**
      * Returns the SyncNotificationController.
      */
     public SyncNotificationController getSyncNotificationController() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java
index 727ef08..9e48cb7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java
@@ -7,7 +7,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import static org.chromium.base.test.util.CriteriaHelper.pollUiThread;
 import static org.chromium.chrome.browser.flags.ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE;
@@ -20,6 +23,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.Callback;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Restriction;
@@ -32,7 +36,9 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
+import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.ui.test.util.UiRestriction;
 
 import java.util.concurrent.ExecutionException;
@@ -246,6 +252,48 @@
                 mSheetController.getSheetState());
     }
 
+    @Test
+    @MediumTest
+    public void testOffsetController() {
+        mLowPriorityContent.setContentControlsOffset(true);
+
+        BottomSheetObserver forbidStateChanges = new EmptyBottomSheetObserver() {
+            @Override
+            public void onSheetOpened(@StateChangeReason int reason) {
+                fail("onSheetOpened unexpected");
+            }
+
+            @Override
+            public void onSheetClosed(@StateChangeReason int reason) {
+                fail("onSheetClosed unexpected");
+            }
+        };
+        runOnUiThreadBlocking(() -> {
+            assertTrue(mSheetController.requestShowContent(mLowPriorityContent, false));
+
+            Callback<Integer> offsetController = mLowPriorityContent.getOffsetController();
+            assertNotNull(offsetController);
+
+            mSheetController.addObserver(forbidStateChanges);
+
+            int startOffset = mSheetController.getCurrentOffset();
+            int modifiedOffset = startOffset / 2;
+            offsetController.onResult(modifiedOffset);
+            assertEquals(modifiedOffset, mSheetController.getCurrentOffset());
+
+            offsetController.onResult(0);
+            assertEquals(0, mSheetController.getCurrentOffset());
+
+            offsetController.onResult(startOffset);
+            assertEquals(startOffset, mSheetController.getCurrentOffset());
+
+            mSheetController.removeObserver(forbidStateChanges);
+
+            mSheetController.hideContent(mLowPriorityContent, false);
+            assertNull(mLowPriorityContent.getOffsetController());
+        });
+    }
+
     private void hideSheet() {
         runOnUiThreadBlocking(() -> mTestSupport.setSheetState(SheetState.HIDDEN, false));
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/TestBottomSheetContent.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/TestBottomSheetContent.java
index 98ea7146..7342abc1a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/TestBottomSheetContent.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/TestBottomSheetContent.java
@@ -12,6 +12,7 @@
 
 import androidx.annotation.Nullable;
 
+import org.chromium.base.Callback;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -54,6 +55,13 @@
     /** Whether this content intercepts back button presses. */
     private boolean mHandleBackPress;
 
+    /** Set to true to ask for an offset controller. */
+    private boolean mContentControlsOffset;
+
+    /** Current offset controller. */
+    @Nullable
+    private Callback<Integer> mOffsetController;
+
     /**
      * @param context A context to inflate views with.
      * @param priority The content's priority.
@@ -202,4 +210,22 @@
     public int getSheetClosedAccessibilityStringId() {
         return android.R.string.copy;
     }
+
+    @Override
+    public boolean contentControlsOffset() {
+        return mContentControlsOffset;
+    }
+
+    @Override
+    public void setOffsetController(Callback<Integer> offsetController) {
+        mOffsetController = offsetController;
+    }
+
+    public Callback<Integer> getOffsetController() {
+        return mOffsetController;
+    }
+
+    public void setContentControlsOffset(boolean value) {
+        mContentControlsOffset = value;
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/base/SplitPreloaderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/base/SplitPreloaderTest.java
index 5953d05..dc519c9 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/base/SplitPreloaderTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/base/SplitPreloaderTest.java
@@ -7,6 +7,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -23,6 +24,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.CallbackHelper;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -84,14 +86,25 @@
     }
 
     private static class OnCompleteTracker implements SplitPreloader.OnComplete {
+        private SplitContext mBackgroundContext;
         private SplitContext mUiContext;
 
         @Override
-        public void run(Context context) {
+        public void runImmediatelyInBackgroundThread(Context context) {
+            assertNull(mBackgroundContext);
+            mBackgroundContext = (SplitContext) context;
+        }
+
+        @Override
+        public void runInUiThread(Context context) {
             assertNull(mUiContext);
             mUiContext = (SplitContext) context;
         }
 
+        public SplitContext getBackgroundContext() {
+            return mBackgroundContext;
+        }
+
         public SplitContext getUiContext() {
             return mUiContext;
         }
@@ -129,6 +142,8 @@
         assertThat(mContext.getBackgroundThreadContextNames()).containsExactly(SPLIT_A);
         assertTrue(tracker.getUiContext().wasCreatedOnUiThread());
         assertEquals(tracker.getUiContext().getName(), SPLIT_A);
+        assertFalse(tracker.getBackgroundContext().wasCreatedOnUiThread());
+        assertEquals(tracker.getBackgroundContext().getName(), SPLIT_A);
     }
 
     @Test
@@ -178,13 +193,29 @@
     }
 
     @Test
-    public void testPreload_withOnComplete_splitNotInstalled() {
-        Context[] contextHolder = new Context[1];
-        mPreloader.preload(SPLIT_A, (Context context) -> contextHolder[0] = context);
+    public void testPreload_withOnComplete_splitNotInstalled() throws Exception {
+        Context[] backgroundContextHolder = new Context[1];
+        Context[] uiContextHolder = new Context[1];
+        CallbackHelper helper = new CallbackHelper();
+        mPreloader.preload(SPLIT_A, new SplitPreloader.OnComplete() {
+            @Override
+            public void runImmediatelyInBackgroundThread(Context context) {
+                backgroundContextHolder[0] = context;
+                helper.notifyCalled();
+            }
+
+            @Override
+            public void runInUiThread(Context context) {
+                uiContextHolder[0] = context;
+            }
+        });
+        helper.waitForFirst();
+        assertEquals(backgroundContextHolder[0], mContext);
+
         mPreloader.wait(SPLIT_A);
 
         assertThat(mContext.getUiThreadContextNames()).isEmpty();
         assertThat(mContext.getBackgroundThreadContextNames()).isEmpty();
-        assertEquals(contextHolder[0], mContext);
+        assertEquals(uiContextHolder[0], mContext);
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessorTest.java
new file mode 100644
index 0000000..542f588
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessorTest.java
@@ -0,0 +1,140 @@
+// 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.firstrun;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.SyncFirstSetupCompleteSource;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
+import org.chromium.chrome.browser.signin.services.SigninManager;
+import org.chromium.chrome.browser.signin.services.SigninManager.SignInCallback;
+import org.chromium.chrome.browser.sync.ProfileSyncService;
+import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule;
+import org.chromium.components.signin.AccountUtils;
+import org.chromium.components.signin.ChildAccountStatus;
+import org.chromium.components.signin.identitymanager.IdentityManager;
+import org.chromium.components.signin.metrics.SigninAccessPoint;
+import org.chromium.components.signin.test.util.FakeAccountManagerFacade;
+
+/**
+ * JUnit tests for {@link ForcedSigninProcessor}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class ForcedSigninProcessorTest {
+    private static final Account CHILD_ACCOUNT =
+            AccountUtils.createAccountFromName("child.account@gmail.com");
+
+    private final FakeAccountManagerFacade mFakeFacade = new FakeAccountManagerFacade(null) {
+        @Override
+        public void checkChildAccountStatus(Account account, ChildAccountStatusListener listener) {
+            listener.onStatusReady(account.equals(CHILD_ACCOUNT) ? ChildAccountStatus.REGULAR_CHILD
+                                                                 : ChildAccountStatus.NOT_CHILD);
+        }
+    };
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public final AccountManagerTestRule mAccountManagerTestRule =
+            new AccountManagerTestRule(mFakeFacade);
+
+    @Mock
+    private Profile mProfileMock;
+
+    @Mock
+    private ProfileSyncService mProfileSyncServiceMock;
+
+    @Mock
+    private SigninManager mSigninManagerMock;
+
+    @Mock
+    private IdentityManager mIdentityManagerMock;
+
+    @Before
+    public void setUp() {
+        Profile.setLastUsedProfileForTesting(mProfileMock);
+        ProfileSyncService.overrideForTests(mProfileSyncServiceMock);
+        IdentityServicesProvider.setInstanceForTests(mock(IdentityServicesProvider.class));
+
+        when(IdentityServicesProvider.get().getIdentityManager(mProfileMock))
+                .thenReturn(mIdentityManagerMock);
+        when(mIdentityManagerMock.hasPrimaryAccount()).thenReturn(false);
+
+        when(IdentityServicesProvider.get().getSigninManager(mProfileMock))
+                .thenReturn(mSigninManagerMock);
+        doAnswer(invocation -> {
+            SignInCallback callback = invocation.getArgument(2);
+            callback.onSignInComplete();
+            return null;
+        })
+                .when(mSigninManagerMock)
+                .signinAndEnableSync(anyInt(), any(Account.class), notNull());
+    }
+
+    @Test
+    public void testStartWhenMoreThanOneAccountsOnDevice() {
+        mAccountManagerTestRule.addAccount(CHILD_ACCOUNT);
+        mAccountManagerTestRule.addAccount("adult.account@gmail.com");
+
+        ForcedSigninProcessor.start();
+        verify(mSigninManagerMock, never()).onFirstRunCheckDone();
+    }
+
+    @Test
+    public void testStartWhenAdultAccountOnDevice() {
+        mAccountManagerTestRule.addAccount("adult.account@gmail.com");
+
+        ForcedSigninProcessor.start();
+        verify(mSigninManagerMock, never()).onFirstRunCheckDone();
+    }
+
+    @Test
+    public void testStartWhenSigninNotAllowed() {
+        mAccountManagerTestRule.addAccount(CHILD_ACCOUNT);
+        when(mSigninManagerMock.isSignInAllowed()).thenReturn(false);
+
+        ForcedSigninProcessor.start();
+        verify(mSigninManagerMock).onFirstRunCheckDone();
+        verify(mSigninManagerMock, never())
+                .signinAndEnableSync(anyInt(), any(Account.class), any());
+        verify(mProfileSyncServiceMock, never())
+                .setFirstSetupComplete(SyncFirstSetupCompleteSource.BASIC_FLOW);
+    }
+
+    @Test
+    public void testStartWhenSigninAllowed() {
+        mAccountManagerTestRule.addAccount(CHILD_ACCOUNT);
+        when(mSigninManagerMock.isSignInAllowed()).thenReturn(true);
+
+        ForcedSigninProcessor.start();
+        verify(mSigninManagerMock).onFirstRunCheckDone();
+        verify(mSigninManagerMock)
+                .signinAndEnableSync(
+                        eq(SigninAccessPoint.FORCED_SIGNIN), eq(CHILD_ACCOUNT), notNull());
+        verify(mProfileSyncServiceMock)
+                .setFirstSetupComplete(SyncFirstSetupCompleteSource.BASIC_FLOW);
+    }
+}
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 3ded6139..8388a92e 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -1187,20 +1187,14 @@
       <!-- Profile Picker -->
       <if expr="not chromeos and not is_android">
         <message name="IDS_PROFILE_PICKER_MAIN_VIEW_TITLE" desc="Profile picker main view title">
-          Who's using Chromium?
+          Welcome to Chromium profiles
         </message>
         <message name="IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE" desc="Profile picker main view subtitle">
-          Use different profiles in Chromium to separate work and personal browsing, or for different people who use this device
-        </message>
-        <message name="IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE" desc="Remove warning message shown for signed in profiles shown when the user selects remove from the 3 dotted menu">
-          This will permanently delete your browsing data from this device. To recover the data, sign in to Chromium as
+          With Chromium profiles you can separate all your Chromium stuff. Create profiles for friends and family, or split between work and fun.
         </message>
         <message name="IDS_PROFILE_PICKER_PROFILE_CREATION_FLOW_PROFILE_TYPE_CHOICE_TITLE" desc="Profile picker profile type choice title">
           Set up your new Chromium profile
         </message>
-        <message name="IDS_PROFILE_PICKER_ASK_ON_STARTUP" desc="Text for the checkbox on the profile picker main view">
-          Ask when Chromium opens
-        </message>
         <message name="IDS_PROFILE_PICKER_PROFILE_CREATION_FLOW_LOCAL_PROFILE_CREATION_TITLE" desc="Title for the local profile customiztion screen on the picker.">
           Customize your Chromium profile
         </message>
diff --git a/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1 b/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1
deleted file mode 100644
index e5670854..0000000
--- a/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4cb9c6b65bb30ae0d4391ab58ca3a0d184460e51
\ No newline at end of file
diff --git a/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE.png.sha1 b/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE.png.sha1
index 6dc2496..25a234d7 100644
--- a/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE.png.sha1
+++ b/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE.png.sha1
@@ -1 +1 @@
-630ac6a34bb1915cfee2dd9e8ce472a56e302874
\ No newline at end of file
+a064193913da1c2d06ab2a932d094c622e45a837
\ No newline at end of file
diff --git a/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_TITLE.png.sha1 b/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_TITLE.png.sha1
index 6dc2496..25a234d7 100644
--- a/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_TITLE.png.sha1
+++ b/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_TITLE.png.sha1
@@ -1 +1 @@
-630ac6a34bb1915cfee2dd9e8ce472a56e302874
\ No newline at end of file
+a064193913da1c2d06ab2a932d094c622e45a837
\ No newline at end of file
diff --git a/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1 b/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1
deleted file mode 100644
index 0715e41..0000000
--- a/chrome/app/chromium_strings_grd/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-de5d858671b12baa6ce008727ecbb34f2f2ec992
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 6eadb3b..f621942 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -1203,20 +1203,14 @@
       <!-- Profile Picker -->
       <if expr="not chromeos and not is_android">
         <message name="IDS_PROFILE_PICKER_MAIN_VIEW_TITLE" desc="Profile picker main view title">
-          Who's using Chrome?
+          Welcome to Chrome profiles
         </message>
         <message name="IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE" desc="Profile picker main view subtitle">
-          Use different profiles in Chrome to separate work and personal browsing, or for different people who use this device
-        </message>
-        <message name="IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE" desc="Remove warning message shown for signed in profiles shown when the user selects remove from the 3 dotted menu">
-          This will permanently delete your browsing data from this device. To recover the data, sign in to Chrome as
+          With Chrome profiles you can separate all your Chrome stuff. Create profiles for friends and family, or split between work and fun.
         </message>
         <message name="IDS_PROFILE_PICKER_PROFILE_CREATION_FLOW_PROFILE_TYPE_CHOICE_TITLE" desc="Profile picker profile type choice title">
           Set up your new Chrome profile
         </message>
-        <message name="IDS_PROFILE_PICKER_ASK_ON_STARTUP" desc="Text for the checkbox on the profile picker main view">
-          Ask when Chrome opens
-        </message>
         <message name="IDS_PROFILE_PICKER_PROFILE_CREATION_FLOW_LOCAL_PROFILE_CREATION_TITLE" desc="Title for the local profile customiztion screen on the picker.">
           Customize your Chrome profile
         </message>
diff --git a/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1
deleted file mode 100644
index e5670854..0000000
--- a/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4cb9c6b65bb30ae0d4391ab58ca3a0d184460e51
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE.png.sha1
index 6dc2496..25a234d7 100644
--- a/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE.png.sha1
+++ b/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_SUBTITLE.png.sha1
@@ -1 +1 @@
-630ac6a34bb1915cfee2dd9e8ce472a56e302874
\ No newline at end of file
+a064193913da1c2d06ab2a932d094c622e45a837
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_TITLE.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_TITLE.png.sha1
index 6dc2496..25a234d7 100644
--- a/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_TITLE.png.sha1
+++ b/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_MAIN_VIEW_TITLE.png.sha1
@@ -1 +1 @@
-630ac6a34bb1915cfee2dd9e8ce472a56e302874
\ No newline at end of file
+a064193913da1c2d06ab2a932d094c622e45a837
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1
deleted file mode 100644
index 0715e41..0000000
--- a/chrome/app/google_chrome_strings_grd/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-de5d858671b12baa6ce008727ecbb34f2f2ec992
\ No newline at end of file
diff --git a/chrome/app/profiles_strings.grdp b/chrome/app/profiles_strings.grdp
index c9e1d22..eff833b 100644
--- a/chrome/app/profiles_strings.grdp
+++ b/chrome/app/profiles_strings.grdp
@@ -708,7 +708,7 @@
       Add
     </message>
     <message name="IDS_PROFILE_PICKER_BROWSE_AS_GUEST_BUTTON" desc="Text for the profile picker main view button that launches guest session.">
-      Browse as Guest
+      Guest mode
     </message>
     <message name="IDS_PROFILE_PICKER_BACK_BUTTON_LABEL" desc="Label for a button that navigates user to the previous page">
       Back
@@ -723,17 +723,29 @@
       Customize your profile, including its name
     </message>
     <message name="IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_TEXT" desc="Text of the remove button in profile card menu.">
-      Remove
+      Delete
     </message>
     <message name="IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_CONFIRM" desc="Text of the remove button in the remove warning.">
-      Remove this profile
+      Delete
     </message>
     <message name="IDS_PROFILE_PICKER_PROFILE_MENU_CUSTOMIZE_TEXT" desc="Text of the customize button in profile card menu">
-      Customize
+      Edit
     </message>
     <message name="IDS_PROFILE_PICKER_PROFILE_MENU_INCOGNITO_TEXT" desc="Text of the incognito button in profile card menu">
       Incognito
     </message>
+    <message name="IDS_PROFILE_PICKER_ASK_ON_STARTUP" desc="Text for the checkbox on the profile picker main view">
+      Show on startup
+    </message>
+    <message name="IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE_TITLE" desc="Title of the remove warning message shown for signed in profiles, shown when the user selects remove from the 3 dotted menu">
+      Delete this profile?
+    </message>
+    <message name="IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE" desc="Remove warning message shown for signed in profiles shown when the user selects remove from the 3 dotted menu">
+      This will permanently delete your browsing data from this device. To recover the data, turn on sync as
+    </message>
+    <message name="IDS_PROFILE_PICKER_REMOVE_WARNING_LOCAL_PROFILE_TITLE" desc="Title of the remove warning message shown for local profiles, shown when the user selects remove from the 3 dotted menu.">
+      Delete this profile and its data?
+    </message>
     <message name="IDS_PROFILE_PICKER_REMOVE_WARNING_LOCAL_PROFILE" desc="Main text shown as a warning when attempting to remove an user.">
       This will permanently delete your browsing data from this device.
     </message>
diff --git a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1
new file mode 100644
index 0000000..25a234d7
--- /dev/null
+++ b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_ASK_ON_STARTUP.png.sha1
@@ -0,0 +1 @@
+a064193913da1c2d06ab2a932d094c622e45a837
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_BROWSE_AS_GUEST_BUTTON.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_BROWSE_AS_GUEST_BUTTON.png.sha1
index fbd2c11..25a234d7 100644
--- a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_BROWSE_AS_GUEST_BUTTON.png.sha1
+++ b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_BROWSE_AS_GUEST_BUTTON.png.sha1
@@ -1 +1 @@
-12998557c02a35e50d016c53c9ab9bc3a778c724
\ No newline at end of file
+a064193913da1c2d06ab2a932d094c622e45a837
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_CUSTOMIZE_TEXT.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_CUSTOMIZE_TEXT.png.sha1
index 84bc98d..30bfd4f 100644
--- a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_CUSTOMIZE_TEXT.png.sha1
+++ b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_CUSTOMIZE_TEXT.png.sha1
@@ -1 +1 @@
-0eb8b376ac429f0fc430c520e66c21845aeb307c
\ No newline at end of file
+cce701c74e713ef7261c237cb86d70425f50d285
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_CONFIRM.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_CONFIRM.png.sha1
index 051674e..d4f9de16 100644
--- a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_CONFIRM.png.sha1
+++ b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_CONFIRM.png.sha1
@@ -1 +1 @@
-ec9a79cdffe13f9f6adcfd406bad942b52994845
\ No newline at end of file
+2a3a3c3d5bf360187d890afbfe303e8c733403c5
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_TEXT.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_TEXT.png.sha1
index e22eff3..30bfd4f 100644
--- a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_TEXT.png.sha1
+++ b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_PROFILE_MENU_REMOVE_TEXT.png.sha1
@@ -1 +1 @@
-142eeb0b333995bb9e97f4e8deacb899fd9a3781
\ No newline at end of file
+cce701c74e713ef7261c237cb86d70425f50d285
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_LOCAL_PROFILE_TITLE.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_LOCAL_PROFILE_TITLE.png.sha1
new file mode 100644
index 0000000..c53cec3c
--- /dev/null
+++ b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_LOCAL_PROFILE_TITLE.png.sha1
@@ -0,0 +1 @@
+2bacc5f33ddef4d028243d6e97f85797d5792774
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1
new file mode 100644
index 0000000..d4f9de16
--- /dev/null
+++ b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE.png.sha1
@@ -0,0 +1 @@
+2a3a3c3d5bf360187d890afbfe303e8c733403c5
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE_TITLE.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE_TITLE.png.sha1
new file mode 100644
index 0000000..cc1db8b
--- /dev/null
+++ b/chrome/app/profiles_strings_grdp/IDS_PROFILE_PICKER_REMOVE_WARNING_SIGNED_IN_PROFILE_TITLE.png.sha1
@@ -0,0 +1 @@
+408fca9dc377d9224566b3b54d059f351c2b7673
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index e2012e9..19699f1 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1385,6 +1385,24 @@
         {"12 matches", kOmniboxUIMaxAutocompleteMatches12,
          base::size(kOmniboxUIMaxAutocompleteMatches12), nullptr}};
 
+const FeatureEntry::FeatureParam
+    kOmniboxDefaultTypedNavigationsToHttpsVariationsTimeout3s[] = {
+        {omnibox::kDefaultTypedNavigationsToHttpsTimeoutParam, "3s"}};
+const FeatureEntry::FeatureParam
+    kOmniboxDefaultTypedNavigationsToHttpsVariationsTimeout10s[] = {
+        {omnibox::kDefaultTypedNavigationsToHttpsTimeoutParam, "10s"}};
+const FeatureEntry::FeatureVariation
+    kOmniboxDefaultTypedNavigationsToHttpsVariations[] = {
+        {"3 second timeout",
+         kOmniboxDefaultTypedNavigationsToHttpsVariationsTimeout3s,
+         base::size(kOmniboxDefaultTypedNavigationsToHttpsVariationsTimeout3s),
+         nullptr},
+        {"10 second timeout",
+         kOmniboxDefaultTypedNavigationsToHttpsVariationsTimeout10s,
+         base::size(kOmniboxDefaultTypedNavigationsToHttpsVariationsTimeout10s),
+         nullptr},
+};
+
 const FeatureEntry::FeatureParam kOmniboxMaxURLMatches2[] = {
     {OmniboxFieldTrial::kOmniboxMaxURLMatchesParam, "2"}};
 const FeatureEntry::FeatureParam kOmniboxMaxURLMatches3[] = {
@@ -2499,6 +2517,7 @@
 
 const FeatureEntry::FeatureParam kPasswordsAccountStorage_ProfileStore[] = {
     {password_manager::features::kSaveToProfileStoreByDefault, "true"},
+    {password_manager::features::kSaveToAccountStoreOnOptIn, "true"},
 };
 
 const FeatureEntry::FeatureVariation kPasswordsAccountStorageVariations[] = {
@@ -4286,6 +4305,15 @@
      flag_descriptions::kMemlogStackModeDescription, kOsAll,
      MULTI_VALUE_TYPE(kMemlogStackModeChoices)},
 
+    {"omnibox-default-typed-navigations-to-https",
+     flag_descriptions::kOmniboxDefaultTypedNavigationsToHttpsName,
+     flag_descriptions::kOmniboxDefaultTypedNavigationsToHttpsDescription,
+     kOsAll,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(
+         omnibox::kDefaultTypedNavigationsToHttps,
+         kOmniboxDefaultTypedNavigationsToHttpsVariations,
+         "OmniboxDefaultTypedNavigationsToHttps")},
+
     {"omnibox-ui-sometimes-elide-to-registrable-domain",
      flag_descriptions::kOmniboxUIMaybeElideToRegistrableDomainName,
      flag_descriptions::kOmniboxUIMaybeElideToRegistrableDomainDescription,
@@ -6264,7 +6292,7 @@
      FEATURE_WITH_PARAMS_VALUE_TYPE(
          password_manager::features::kPasswordChangeInSettings,
          kPasswordChangeInSettingsFeatureVariations,
-         "PasswordChangeInSettingsFeatureVariations")},
+         "PasswordChangeInSettings")},
     {"password-scripts-fetching",
      flag_descriptions::kPasswordScriptsFetchingName,
      flag_descriptions::kPasswordScriptsFetchingDescription, kOsAndroid,
diff --git a/chrome/browser/android/autofill_assistant/trigger_script_bridge_android.cc b/chrome/browser/android/autofill_assistant/trigger_script_bridge_android.cc
index a6230d4..f8a77ea0 100644
--- a/chrome/browser/android/autofill_assistant/trigger_script_bridge_android.cc
+++ b/chrome/browser/android/autofill_assistant/trigger_script_bridge_android.cc
@@ -223,7 +223,7 @@
       ToJavaIntArray(env, cancel_popup_actions), jleft_aligned_chips,
       ToJavaIntArray(env, left_aligned_chip_actions), jright_aligned_chips,
       ToJavaIntArray(env, right_aligned_chip_actions),
-      proto.resize_visual_viewport());
+      proto.resize_visual_viewport(), proto.scroll_to_hide());
   trigger_script_coordinator_->OnTriggerScriptShown(success);
 }
 
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.cc b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
index 98f058f..0e33bbe5 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.cc
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
@@ -27,6 +27,7 @@
 #include "chrome/android/features/autofill_assistant/jni_headers/AssistantInfoBox_jni.h"
 #include "chrome/android/features/autofill_assistant/jni_headers/AssistantModel_jni.h"
 #include "chrome/android/features/autofill_assistant/jni_headers/AssistantOverlayModel_jni.h"
+#include "chrome/android/features/autofill_assistant/jni_headers/AssistantPlaceholdersConfiguration_jni.h"
 #include "chrome/android/features/autofill_assistant/jni_headers/AutofillAssistantUiController_jni.h"
 #include "chrome/browser/android/autofill_assistant/client_android.h"
 #include "chrome/browser/android/autofill_assistant/generic_ui_root_controller_android.h"
@@ -1779,16 +1780,30 @@
     jimage_accessibility_hint =
         ConvertUTF8ToJavaString(env, opt_image_accessibility_hint.value());
   }
+
+  // Create the placeholders configuration. We check here that the associated
+  // texts/urls are empty, so that on the Java side we can just check the
+  // placeholders configuration to know whether a placeholder should be shown or
+  // not.
+  auto placeholders = details->placeholders();
+  auto jplaceholders = Java_AssistantPlaceholdersConfiguration_Constructor(
+      env, placeholders.show_image_placeholder() && details->imageUrl().empty(),
+      placeholders.show_title_placeholder() && details->title().empty(),
+      placeholders.show_description_line_1_placeholder() &&
+          details->descriptionLine1().empty(),
+      placeholders.show_description_line_2_placeholder() &&
+          details->descriptionLine2().empty(),
+      placeholders.show_description_line_3_placeholder() &&
+          details->descriptionLine3().empty());
+
   auto jdetails = Java_AssistantDetails_create(
       env, ConvertUTF8ToJavaString(env, details->title()),
-      details->titleMaxLines(),
       ConvertUTF8ToJavaString(env, details->imageUrl()),
       jimage_accessibility_hint, details->imageAllowClickthrough(),
       ConvertUTF8ToJavaString(env, details->imageDescription()),
       ConvertUTF8ToJavaString(env, details->imagePositiveText()),
       ConvertUTF8ToJavaString(env, details->imageNegativeText()),
       ConvertUTF8ToJavaString(env, details->imageClickthroughUrl()),
-      details->showImagePlaceholder(),
       ConvertUTF8ToJavaString(env, details->totalPriceLabel()),
       ConvertUTF8ToJavaString(env, details->totalPrice()),
       ConvertUTF8ToJavaString(env, details->descriptionLine1()),
@@ -1797,7 +1812,7 @@
       ConvertUTF8ToJavaString(env, details->priceAttribution()),
       details->userApprovalRequired(), details->highlightTitle(),
       details->highlightLine1(), details->highlightLine2(),
-      details->highlightLine3(), details->animatePlaceholders());
+      details->highlightLine3(), jplaceholders);
   Java_AssistantDetailsModel_setDetails(env, jmodel, jdetails);
 }
 
diff --git a/chrome/browser/apps/app_service/extension_apps_chromeos.cc b/chrome/browser/apps/app_service/extension_apps_chromeos.cc
index 5091cd2b..ab3648b6 100644
--- a/chrome/browser/apps/app_service/extension_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/extension_apps_chromeos.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/apps/app_service/extension_apps_chromeos.h"
 
 #include <memory>
+#include <string>
 #include <utility>
 #include <vector>
 
@@ -62,6 +63,7 @@
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/management_policy.h"
 #include "extensions/browser/ui_util.h"
+#include "extensions/common/constants.h"
 #include "extensions/common/extension_urls.h"
 #include "extensions/common/manifest_handlers/options_page_info.h"
 #include "ui/message_center/public/cpp/notification.h"
@@ -502,36 +504,13 @@
     return;
   }
 
-  bool is_disabled =
-      base::Contains(*disabled_system_features_pref,
-                     base::Value(policy::SystemFeature::kCamera));
-  auto* app_id = extension_misc::kCameraAppId;
+  UpdateAppDisabledState(disabled_system_features_pref,
+                         policy::SystemFeature::kCamera,
+                         extension_misc::kCameraAppId);
 
-  // Sometimes the policy is updated before the app is installed, so this way
-  // the disabled_apps_ is updated regardless the Publish should happen or not
-  // and the app will be published with the correct readiness upon its
-  // installation.
-  bool should_publish = (base::Contains(disabled_apps_, app_id) != is_disabled);
-
-  if (is_disabled) {
-    disabled_apps_.insert(app_id);
-  } else {
-    disabled_apps_.erase(app_id);
-  }
-
-  if (!should_publish) {
-    return;
-  }
-
-  const auto* extension = MaybeGetExtension(app_id);
-  if (!extension) {
-    return;
-  }
-
-  Publish(
-      Convert(extension, is_disabled ? apps::mojom::Readiness::kDisabledByPolicy
-                                     : apps::mojom::Readiness::kReady),
-      subscribers());
+  UpdateAppDisabledState(disabled_system_features_pref,
+                         policy::SystemFeature::kWebStore,
+                         extensions::kWebStoreAppId);
 }
 
 bool ExtensionAppsChromeOs::Accepts(const extensions::Extension* extension) {
@@ -730,4 +709,38 @@
   return web_contents;
 }
 
+void ExtensionAppsChromeOs::UpdateAppDisabledState(
+    const base::ListValue* disabled_system_features_pref,
+    int feature,
+    const std::string& app_id) {
+  const bool is_disabled =
+      base::Contains(*disabled_system_features_pref, base::Value(feature));
+  // Sometimes the policy is updated before the app is installed, so this way
+  // the disabled_apps_ is updated regardless the Publish should happen or not
+  // and the app will be published with the correct readiness upon its
+  // installation.
+  const bool should_publish =
+      (base::Contains(disabled_apps_, app_id) != is_disabled);
+
+  if (is_disabled) {
+    disabled_apps_.insert(app_id);
+  } else {
+    disabled_apps_.erase(app_id);
+  }
+
+  if (!should_publish) {
+    return;
+  }
+
+  const auto* extension = MaybeGetExtension(app_id);
+  if (!extension) {
+    return;
+  }
+
+  Publish(
+      Convert(extension, is_disabled ? apps::mojom::Readiness::kDisabledByPolicy
+                                     : apps::mojom::Readiness::kReady),
+      subscribers());
+}
+
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/extension_apps_chromeos.h b/chrome/browser/apps/app_service/extension_apps_chromeos.h
index beacd54..371a45b 100644
--- a/chrome/browser/apps/app_service/extension_apps_chromeos.h
+++ b/chrome/browser/apps/app_service/extension_apps_chromeos.h
@@ -150,6 +150,11 @@
 
   content::WebContents* LaunchImpl(AppLaunchParams&& params) override;
 
+  void UpdateAppDisabledState(
+      const base::ListValue* disabled_system_features_pref,
+      int feature,
+      const std::string& app_id);
+
   apps::InstanceRegistry* instance_registry_;
   ScopedObserver<extensions::AppWindowRegistry,
                  extensions::AppWindowRegistry::Observer>
diff --git a/chrome/browser/apps/app_service/notifications_browsertest.cc b/chrome/browser/apps/app_service/notifications_browsertest.cc
index 68f4615..43acefb 100644
--- a/chrome/browser/apps/app_service/notifications_browsertest.cc
+++ b/chrome/browser/apps/app_service/notifications_browsertest.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/notifications/profile_notification.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/common/chrome_switches.h"
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/arc_util.h"
 #include "components/arc/session/arc_bridge_service.h"
@@ -265,6 +266,13 @@
     ASSERT_TRUE(https_server_.Start());
   }
 
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    extensions::PlatformAppBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitchASCII(
+        switches::kDesktopPWAsAttentionBadgingCrOS,
+        switches::kDesktopPWAsAttentionBadgingCrOSApiAndNotifications);
+  }
+
   std::string CreateWebApp(const GURL& url, const GURL& scope) const {
     auto web_app_info = std::make_unique<WebApplicationInfo>();
     web_app_info->start_url = url;
diff --git a/chrome/browser/apps/app_service/web_apps_chromeos.cc b/chrome/browser/apps/app_service/web_apps_chromeos.cc
index 8edb995..2d11921 100644
--- a/chrome/browser/apps/app_service/web_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/web_apps_chromeos.cc
@@ -70,7 +70,7 @@
     return cmdline->GetSwitchValueASCII(
         switches::kDesktopPWAsAttentionBadgingCrOS);
   }
-  return "";
+  return switches::kDesktopPWAsAttentionBadgingCrOSApiOnly;
 }
 
 }  // namespace
@@ -556,9 +556,8 @@
     // Show a badge only if a notification is showing.
     return has_notification;
   } else {
-    // When the flag is not set or set to "api-and-notifications" we show a
-    // badge if either a notification is showing or the Web Badging API has a
-    // badge set.
+    // When the flag is set to "api-and-notifications" we show a badge if either
+    // a notification is showing or the Web Badging API has a badge set.
     return badge_manager_ && badge_manager_->GetBadgeValue(app_id).has_value()
                ? apps::mojom::OptionalBool::kTrue
                : has_notification;
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
index b4b6e84..9f896b2 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
@@ -25,10 +25,10 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
-#include "chrome/browser/chromeos/login/screens/sync_consent_screen.h"
 #include "chrome/browser/chromeos/login/test/device_state_mixin.h"
 #include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
 #include "chrome/browser/chromeos/login/test/oobe_base_test.h"
+#include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
@@ -1141,7 +1141,7 @@
 // user) and announces the sync consent screen correctly.
 IN_PROC_BROWSER_TEST_F(SigninToUserProfileSwitchTest, LoginAsNewUser) {
   // Force sync screen.
-  auto reset = SyncConsentScreen::ForceBrandedBuildForTesting(true);
+  auto reset = WizardController::ForceBrandedBuildForTesting(true);
   AccessibilityManager::Get()->EnableSpokenFeedback(true);
   sm_.ExpectSpeechPattern("*");
 
diff --git a/chrome/browser/chromeos/crosapi/browser_manager.cc b/chrome/browser/chromeos/crosapi/browser_manager.cc
index 3a710b3..901bdc91 100644
--- a/chrome/browser/chromeos/crosapi/browser_manager.cc
+++ b/chrome/browser/chromeos/crosapi/browser_manager.cc
@@ -505,4 +505,8 @@
   lacros_chrome_service_version_ = version;
 }
 
+void BrowserManager::SetDeviceAccountPolicy(const std::string& policy_blob) {
+  environment_provider_->SetDeviceAccountPolicy(policy_blob);
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/browser_manager.h b/chrome/browser/chromeos/crosapi/browser_manager.h
index 11ac7f0..a9ccf2e79 100644
--- a/chrome/browser/chromeos/crosapi/browser_manager.h
+++ b/chrome/browser/chromeos/crosapi/browser_manager.h
@@ -106,6 +106,11 @@
     lacros_version_ = version;
   }
 
+  // Set the data of device account policy. It is the serialized blob of
+  // PolicyFetchResponse received from the server, or parsed from the file after
+  // is was validated by Ash.
+  void SetDeviceAccountPolicy(const std::string& policy_blob);
+
  protected:
   // Notifies Mojo connection to lacros-chrome has been disconnected.
   void NotifyMojoDisconnected();
diff --git a/chrome/browser/chromeos/crosapi/browser_util.cc b/chrome/browser/chromeos/crosapi/browser_util.cc
index f3c68b5a..c912c44 100644
--- a/chrome/browser/chromeos/crosapi/browser_util.cc
+++ b/chrome/browser/chromeos/crosapi/browser_util.cc
@@ -70,6 +70,23 @@
   }
 }
 
+// Returns the vector containing policy data of the device account. In case of
+// an error, returns nullopt.
+base::Optional<std::vector<uint8_t>> GetDeviceAccountPolicy(
+    EnvironmentProvider* environment_provider) {
+  if (!user_manager::UserManager::IsInitialized()) {
+    LOG(ERROR) << "User not initialized.";
+    return base::nullopt;
+  }
+  const auto* primary_user = user_manager::UserManager::Get()->GetPrimaryUser();
+  if (!primary_user) {
+    LOG(ERROR) << "No primary user.";
+    return base::nullopt;
+  }
+  std::string policy_data = environment_provider->GetDeviceAccountPolicy();
+  return std::vector<uint8_t>(policy_data.begin(), policy_data.end());
+}
+
 using InterfaceVersions = base::flat_map<base::Token, uint32_t>;
 template <typename T>
 void AddVersion(InterfaceVersions* map) {
@@ -102,6 +119,7 @@
       crosapi::mojom::ExoImeSupport::kConsumedByImeWorkaround;
   params->cros_user_id_hash = chromeos::ProfileHelper::GetUserIdHashFromProfile(
       ProfileManager::GetPrimaryUserProfile());
+  params->device_account_policy = GetDeviceAccountPolicy(environment_provider);
 
   return params;
 }
@@ -239,8 +257,8 @@
       std::move(mojo_disconnected_callback));
 
   // This is for backward compatibility.
-  // TODO(crbug.com/1156033): Remove InitDeperecated() invocation when lacros
-  // becomes new enough.
+  // TODO(crbug.com/1156033): Remove InitDeprecated() invocation when lacros
+  // becomes mature enough.
   lacros_chrome_service->InitDeprecated(
       GetLacrosInitParams(environment_provider));
 
diff --git a/chrome/browser/chromeos/crosapi/environment_provider.cc b/chrome/browser/chromeos/crosapi/environment_provider.cc
index be0969a..4b36cae 100644
--- a/chrome/browser/chromeos/crosapi/environment_provider.cc
+++ b/chrome/browser/chromeos/crosapi/environment_provider.cc
@@ -98,4 +98,13 @@
   return account_id.GetGaiaId();
 }
 
+void EnvironmentProvider::SetDeviceAccountPolicy(
+    const std::string& policy_blob) {
+  device_account_policy_blob_ = policy_blob;
+}
+
+std::string EnvironmentProvider::GetDeviceAccountPolicy() {
+  return device_account_policy_blob_;
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/environment_provider.h b/chrome/browser/chromeos/crosapi/environment_provider.h
index 0417191..284cf453 100644
--- a/chrome/browser/chromeos/crosapi/environment_provider.h
+++ b/chrome/browser/chromeos/crosapi/environment_provider.h
@@ -28,6 +28,17 @@
   // not the Lacros profile.
   virtual crosapi::mojom::DefaultPathsPtr GetDefaultPaths();
   virtual std::string GetDeviceAccountGaiaId();
+
+  // Getter and setter for device account policy data. Used to pass data from
+  // Ash to Lacros. The format is serialized PolicyFetchResponse object. See
+  // components/policy/proto/device_management_backend.proto for details.
+  virtual std::string GetDeviceAccountPolicy();
+  virtual void SetDeviceAccountPolicy(const std::string& policy_blob);
+
+ private:
+  // The serialized PolicyFetchResponse object corresponding to the policy of
+  // device account. Used to pass the data from Ash to Lacros.
+  std::string device_account_policy_blob_;
 };
 
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
index de39c29..5dc1a875 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
@@ -83,7 +83,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileManagerDialogBaseTest) {
-  RunTestURL("foreground/js/ui/file_manager_dialog_base_unittest_gen.html");
+  RunTestURL("foreground/js/ui/file_manager_dialog_base_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileOperationHandlerTest) {
@@ -145,7 +145,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTypeFiltersController) {
-  RunTestURL("foreground/js/file_type_filters_controller_unittest_gen.html");
+  RunTestURL("foreground/js/file_type_filters_controller_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileType) {
@@ -209,7 +209,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, MultiMenu) {
-  RunTestURL("foreground/js/ui/multi_menu_unittest_gen.html");
+  RunTestURL("foreground/js/ui/multi_menu_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, MultiMetadataProvider) {
@@ -222,11 +222,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ProvidersModel) {
-  RunTestURL("foreground/js/providers_model_unittest_gen.html");
+  RunTestURL("foreground/js/providers_model_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, SpinnerController) {
-  RunTestURL("foreground/js/spinner_controller_unittest_gen.html");
+  RunTestURL("foreground/js/spinner_controller_unittest.m_gen.html");
 }
 
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, TaskController) {
diff --git a/chrome/browser/chromeos/login/configuration_based_oobe_browsertest.cc b/chrome/browser/chromeos/login/configuration_based_oobe_browsertest.cc
index a6557c9..99ac3d1 100644
--- a/chrome/browser/chromeos/login/configuration_based_oobe_browsertest.cc
+++ b/chrome/browser/chromeos/login/configuration_based_oobe_browsertest.cc
@@ -128,7 +128,8 @@
     LoadConfiguration();
 
     // Make sure that OOBE is run as an "official" build.
-    branded_build_override_ = WizardController::ForceBrandedBuildForTesting();
+    branded_build_override_ =
+        WizardController::ForceBrandedBuildForTesting(true);
 
     // Clear portal list (as it is by default in OOBE).
     NetworkHandler::Get()->network_state_handler()->SetCheckPortalList("");
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc b/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
index 4d92f1af..fcf1536 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
@@ -148,7 +148,8 @@
     update_engine_client()->set_update_check_result(
         UpdateEngineClient::UPDATE_RESULT_FAILED);
     DisableConfirmationDialogAnimations();
-    branded_build_override_ = WizardController::ForceBrandedBuildForTesting();
+    branded_build_override_ =
+        WizardController::ForceBrandedBuildForTesting(true);
     DisconnectAllNetworks();
   }
 
diff --git a/chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper_impl.cc b/chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper_impl.cc
index eb8f16bd..225609c 100644
--- a/chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper_impl.cc
+++ b/chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper_impl.cc
@@ -259,7 +259,13 @@
   policy::BrowserPolicyConnectorChromeOS* connector =
       g_browser_process->platform_part()->browser_policy_connector_chromeos();
   // Re-enrollment is not implemented for Active Directory.
-  if (connector->IsCloudManaged() &&
+  // If an enrollment domain is already fixed in install attributes and
+  // re-enrollment happens via login, domains need to be equal.
+  // If there is a mismatch between domain set in install attributes and
+  // auto re-enrollment domain provided by the server, policy validation will
+  // fail later in the process.
+  if (connector->IsCloudManaged() && !enrolling_user_domain_.empty() &&
+      !enrollment_config_.is_mode_attestation() &&
       connector->GetEnterpriseEnrollmentDomain() != enrolling_user_domain_) {
     LOG(ERROR) << "Trying to re-enroll to a different domain than "
                << connector->GetEnterpriseEnrollmentDomain();
diff --git a/chrome/browser/chromeos/login/enrollment/hands_off_enrollment_browsertest.cc b/chrome/browser/chromeos/login/enrollment/hands_off_enrollment_browsertest.cc
index 5e2700f..8c25854 100644
--- a/chrome/browser/chromeos/login/enrollment/hands_off_enrollment_browsertest.cc
+++ b/chrome/browser/chromeos/login/enrollment/hands_off_enrollment_browsertest.cc
@@ -52,7 +52,8 @@
     MixinBasedInProcessBrowserTest::SetUpOnMainThread();
 
     // Set official build so EULA screen is not skipped by default.
-    branded_build_override_ = WizardController::ForceBrandedBuildForTesting();
+    branded_build_override_ =
+        WizardController::ForceBrandedBuildForTesting(true);
 
     // Sets all network services into idle state to simulate disconnected state.
     NetworkStateHandler::NetworkStateList networks;
diff --git a/chrome/browser/chromeos/login/login_ui_shelf_visibility_browsertest.cc b/chrome/browser/chromeos/login/login_ui_shelf_visibility_browsertest.cc
index a6fbc8e..044d06b 100644
--- a/chrome/browser/chromeos/login/login_ui_shelf_visibility_browsertest.cc
+++ b/chrome/browser/chromeos/login/login_ui_shelf_visibility_browsertest.cc
@@ -67,7 +67,7 @@
 // Verifies that guest button and add user button are hidden on post-login
 // screens, after a user session is started.
 IN_PROC_BROWSER_TEST_F(LoginUIShelfVisibilityTest, PostLoginScreen) {
-  auto autoreset = SyncConsentScreen::ForceBrandedBuildForTesting(true);
+  auto autoreset = WizardController::ForceBrandedBuildForTesting(true);
   EXPECT_TRUE(ash::LoginScreenTestApi::ClickAddUserButton());
   test::OobeGaiaPageWaiter().WaitUntilReady();
   LoginDisplayHost::default_host()
diff --git a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
index 44c3ad65..a63e1aa1 100644
--- a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
+++ b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
@@ -652,10 +652,8 @@
                                   std::tuple<bool, bool, bool, ArcState>> {
  public:
   OobeInteractiveUITest() {
-    // TODO(https://crbug.com/1130502): Merge these functions into one.
-    branded_build_override_ = WizardController::ForceBrandedBuildForTesting();
-    sync_branded_build_override_ =
-        SyncConsentScreen::ForceBrandedBuildForTesting(true);
+    branded_build_override_ =
+        WizardController::ForceBrandedBuildForTesting(true);
   }
   ~OobeInteractiveUITest() override = default;
 
@@ -693,7 +691,6 @@
 
  private:
   std::unique_ptr<base::AutoReset<bool>> branded_build_override_;
-  std::unique_ptr<base::AutoReset<bool>> sync_branded_build_override_;
   FakeGaiaMixin fake_gaia_{&mixin_host_, embedded_test_server()};
   FakeEulaMixin fake_eula_{&mixin_host_, embedded_test_server()};
 
diff --git a/chrome/browser/chromeos/login/screens/gaia_screen.cc b/chrome/browser/chromeos/login/screens/gaia_screen.cc
index 2654f27..f231ac0 100644
--- a/chrome/browser/chromeos/login/screens/gaia_screen.cc
+++ b/chrome/browser/chromeos/login/screens/gaia_screen.cc
@@ -25,8 +25,8 @@
   switch (result) {
     case Result::BACK:
       return "Back";
-    case Result::CLOSE_DIALOG:
-      return "CloseDialog";
+    case Result::CANCEL:
+      return "Cancel";
     case Result::ENTERPRISE_ENROLL:
       return "EnterpriseEnroll";
     case Result::START_CONSUMER_KIOSK:
@@ -83,21 +83,15 @@
 }
 
 void GaiaScreen::OnUserAction(const std::string& action_id) {
-  if (action_id == kUserActionBack || action_id == kUserActionCancel) {
-    // `kUserActionBack` will go back to user creation screen if possible.
-    // `kUserActionCancel` will stay at the gaia screen and reload the screen.
-    if (action_id == kUserActionBack && context()->is_user_creation_enabled) {
-      exit_callback_.Run(Result::BACK);
-    } else {
-      HandleCancel();
-    }
-    return;
-  }
-  if (action_id == kUserActionStartEnrollment) {
+  if (action_id == kUserActionBack) {
+    exit_callback_.Run(Result::BACK);
+  } else if (action_id == kUserActionCancel) {
+    exit_callback_.Run(Result::CANCEL);
+  } else if (action_id == kUserActionStartEnrollment) {
     exit_callback_.Run(Result::ENTERPRISE_ENROLL);
-    return;
+  } else {
+    BaseScreen::OnUserAction(action_id);
   }
-  BaseScreen::OnUserAction(action_id);
 }
 
 bool GaiaScreen::HandleAccelerator(ash::LoginAcceleratorAction action) {
@@ -112,15 +106,4 @@
   return false;
 }
 
-void GaiaScreen::HandleCancel() {
-  // Close the user pod if it exists and gaia is the first screen, or reload
-  // the page.
-  if (LoginDisplayHost::default_host()->HasUserPods() &&
-      !context()->is_user_creation_enabled) {
-    exit_callback_.Run(Result::CLOSE_DIALOG);
-  } else {
-    LoadOnline(EmptyAccountId());
-  }
-}
-
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/gaia_screen.h b/chrome/browser/chromeos/login/screens/gaia_screen.h
index 8ad51606..31ba35f 100644
--- a/chrome/browser/chromeos/login/screens/gaia_screen.h
+++ b/chrome/browser/chromeos/login/screens/gaia_screen.h
@@ -24,7 +24,7 @@
 
   enum class Result {
     BACK,
-    CLOSE_DIALOG,
+    CANCEL,
     ENTERPRISE_ENROLL,
     START_CONSUMER_KIOSK,
   };
@@ -51,8 +51,6 @@
   void OnUserAction(const std::string& action_id) override;
   bool HandleAccelerator(ash::LoginAcceleratorAction action) override;
 
-  void HandleCancel();
-
   GaiaView* view_ = nullptr;
 
   ScreenExitCallback exit_callback_;
diff --git a/chrome/browser/chromeos/login/screens/parental_handoff_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/parental_handoff_screen_browsertest.cc
index 1ce0dd0f..2a21f1c 100644
--- a/chrome/browser/chromeos/login/screens/parental_handoff_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/parental_handoff_screen_browsertest.cc
@@ -14,7 +14,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h"
 #include "chrome/browser/chromeos/login/screens/edu_coexistence_login_screen.h"
-#include "chrome/browser/chromeos/login/screens/sync_consent_screen.h"
 #include "chrome/browser/chromeos/login/test/fake_gaia_mixin.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
@@ -95,7 +94,7 @@
 
   base::HistogramTester histogram_tester_;
 
-  std::unique_ptr<base::AutoReset<bool>> chrome_sync_is_google_branded_build_;
+  std::unique_ptr<base::AutoReset<bool>> is_google_branded_build_;
 
   std::unique_ptr<base::AutoReset<bool>> assistant_is_enabled_;
 
@@ -108,8 +107,8 @@
 }
 
 void ParentalHandoffScreenBrowserTest::SetUpOnMainThread() {
-  chrome_sync_is_google_branded_build_ =
-      SyncConsentScreen::ForceBrandedBuildForTesting(true);
+  is_google_branded_build_ =
+      WizardController::ForceBrandedBuildForTesting(true);
   assistant_is_enabled_ =
       AssistantOptInFlowScreen::ForceLibAssistantEnabledForTesting(false);
   ParentalHandoffScreen* screen = GetParentalHandoffScreen();
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc
index 4cc2163..e7404e41 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h"
 #include "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_delegate.h"
 #include "chrome/browser/chromeos/login/screens/recommend_apps/scoped_test_recommend_apps_fetcher_factory.h"
-#include "chrome/browser/chromeos/login/screens/sync_consent_screen.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
 #include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
@@ -554,7 +553,7 @@
 IN_PROC_BROWSER_TEST_F(RecommendAppsScreenManagedTest, SkipDueToManagedUser) {
   // Force the sync screen to be shown so that OOBE isn't destroyed
   // right after login due to all screens being skipped.
-  auto autoreset = SyncConsentScreen::ForceBrandedBuildForTesting(true);
+  auto autoreset = WizardController::ForceBrandedBuildForTesting(true);
 
   // Mark user as managed.
   user_policy_mixin_.RequestPolicyUpdate();
diff --git a/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc b/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
index 2c81f72..4e42e86 100644
--- a/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
@@ -129,7 +129,7 @@
  public:
   SyncConsentTest()
       : force_branded_build_(
-            SyncConsentScreen::ForceBrandedBuildForTesting(true)) {}
+            WizardController::ForceBrandedBuildForTesting(true)) {}
   ~SyncConsentTest() override = default;
 
   void SetUpOnMainThread() override {
@@ -252,7 +252,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(SyncConsentTest, SkippedNotBrandedBuild) {
-  auto autoreset = SyncConsentScreen::ForceBrandedBuildForTesting(false);
+  auto autoreset = WizardController::ForceBrandedBuildForTesting(false);
   LoginToSyncConsentScreen();
 
   WaitForScreenExit();
@@ -264,7 +264,7 @@
 
 IN_PROC_BROWSER_TEST_F(SyncConsentTest, SkippedSyncDisabledByPolicy) {
   // Set up screen and policy.
-  auto autoreset = SyncConsentScreen::ForceBrandedBuildForTesting(true);
+  auto autoreset = WizardController::ForceBrandedBuildForTesting(true);
   SyncConsentScreen* screen = GetSyncConsentScreen();
   screen->SetProfileSyncDisabledByPolicyForTesting(true);
 
@@ -584,7 +584,7 @@
 
 IN_PROC_BROWSER_TEST_F(SyncConsentSplitSettingsSyncTest,
                        SkippedNotBrandedBuild) {
-  auto autoreset = SyncConsentScreen::ForceBrandedBuildForTesting(false);
+  auto autoreset = WizardController::ForceBrandedBuildForTesting(false);
   LoginToSyncConsentScreen();
   WaitForScreenExit();
   EXPECT_EQ(screen_result_.value(), SyncConsentScreen::Result::NOT_APPLICABLE);
diff --git a/chrome/browser/chromeos/login/screens/sync_consent_screen.cc b/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
index d460cf8c..fe37e6401f 100644
--- a/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
+++ b/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/metrics/histogram_functions.h"
+#include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/consent_auditor/consent_auditor_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -54,13 +55,6 @@
 }  // namespace
 
 // static
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-bool g_is_branded_build = true;
-#else
-bool g_is_branded_build = false;
-#endif
-
-// static
 std::string SyncConsentScreen::GetResultString(Result result) {
   switch (result) {
     case Result::NEXT:
@@ -273,17 +267,6 @@
   }
 }
 
-// static
-std::unique_ptr<base::AutoReset<bool>>
-SyncConsentScreen::ForceBrandedBuildForTesting(bool value) {
-  return std::make_unique<base::AutoReset<bool>>(&g_is_branded_build, value);
-}
-
-// static
-bool SyncConsentScreen::IsBrandedBuildForTesting() {
-  return g_is_branded_build;
-}
-
 void SyncConsentScreen::SetDelegateForTesting(
     SyncConsentScreen::SyncConsentScreenTestDelegate* delegate) {
   test_delegate_ = delegate;
@@ -307,7 +290,7 @@
   // Skip for non-branded (e.g. developer) builds. Check this after the account
   // type checks so we don't try to enable sync in browser_tests for those
   // account types.
-  if (!g_is_branded_build)
+  if (!WizardController::IsBrandedBuild())
     return SyncScreenBehavior::kSkipAndEnableNonBrandedBuild;
 
   const user_manager::UserManager* user_manager =
diff --git a/chrome/browser/chromeos/login/screens/sync_consent_screen.h b/chrome/browser/chromeos/login/screens/sync_consent_screen.h
index a486e91..be499ce 100644
--- a/chrome/browser/chromeos/login/screens/sync_consent_screen.h
+++ b/chrome/browser/chromeos/login/screens/sync_consent_screen.h
@@ -104,11 +104,6 @@
   // Enables sync if required when skipping the dialog.
   void MaybeEnableSyncForSkip();
 
-  static std::unique_ptr<base::AutoReset<bool>> ForceBrandedBuildForTesting(
-      bool value);
-
-  static bool IsBrandedBuildForTesting();
-
   // Sets internal condition "Sync disabled by policy" for tests.
   void SetProfileSyncDisabledByPolicyForTesting(bool value);
 
diff --git a/chrome/browser/chromeos/login/test/oobe_screens_utils.cc b/chrome/browser/chromeos/login/test/oobe_screens_utils.cc
index af442a3b..50f020a 100644
--- a/chrome/browser/chromeos/login/test/oobe_screens_utils.cc
+++ b/chrome/browser/chromeos/login/test/oobe_screens_utils.cc
@@ -144,25 +144,25 @@
 }
 
 void WaitForEulaScreen() {
-  if (!WizardController::IsBrandedBuildForTesting())
+  if (!WizardController::IsBrandedBuild())
     return;
   WaitFor(EulaView::kScreenId);
 }
 
 void TapEulaAccept() {
-  if (!WizardController::IsBrandedBuildForTesting())
+  if (!WizardController::IsBrandedBuild())
     return;
   test::OobeJS().TapOnPath({"oobe-eula-md", "acceptButton"});
 }
 
 void WaitForSyncConsentScreen() {
-  if (!SyncConsentScreen::IsBrandedBuildForTesting())
+  if (!WizardController::IsBrandedBuild())
     return;
   WaitFor(SyncConsentScreenView::kScreenId);
 }
 
 void ExitScreenSyncConsent() {
-  if (!SyncConsentScreen::IsBrandedBuildForTesting())
+  if (!WizardController::IsBrandedBuild())
     return;
   SyncConsentScreen* screen = static_cast<SyncConsentScreen*>(
       WizardController::default_controller()->GetScreen(
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 7613700..7c162da9 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -957,10 +957,22 @@
   OnScreenExit(GaiaView::kScreenId, GaiaScreen::GetResultString(result));
   switch (result) {
     case GaiaScreen::Result::BACK:
-      AdvanceToScreen(UserCreationView::kScreenId);
-      break;
-    case GaiaScreen::Result::CLOSE_DIALOG:
-      LoginDisplayHost::default_host()->HideOobeDialog();
+    case GaiaScreen::Result::CANCEL:
+      if (result == GaiaScreen::Result::BACK &&
+          wizard_context_->is_user_creation_enabled) {
+        // `Result::BACK` is only triggered when pressing back button. It goes
+        // back to UserCreationScreen if screen is enabled; otherwise, it
+        // behaves the same as `Result::CANCEL` which is triggered by pressing
+        // ESC key.
+        AdvanceToScreen(UserCreationView::kScreenId);
+        break;
+      }
+      if (LoginDisplayHost::default_host()->HasUserPods() &&
+          !wizard_context_->is_user_creation_enabled) {
+        LoginDisplayHost::default_host()->HideOobeDialog();
+      } else {
+        GetScreen<GaiaScreen>()->LoadOnline(EmptyAccountId());
+      }
       break;
     case GaiaScreen::Result::ENTERPRISE_ENROLL:
       ShowEnrollmentScreenIfEligible();
@@ -2023,8 +2035,8 @@
 
 // static
 std::unique_ptr<base::AutoReset<bool>>
-WizardController::ForceBrandedBuildForTesting() {
-  return std::make_unique<base::AutoReset<bool>>(&is_branded_build_, true);
+WizardController::ForceBrandedBuildForTesting(bool value) {
+  return std::make_unique<base::AutoReset<bool>>(&is_branded_build_, value);
 }
 
 // static
diff --git a/chrome/browser/chromeos/login/wizard_controller.h b/chrome/browser/chromeos/login/wizard_controller.h
index 199d17dc..7910275 100644
--- a/chrome/browser/chromeos/login/wizard_controller.h
+++ b/chrome/browser/chromeos/login/wizard_controller.h
@@ -111,13 +111,16 @@
   static void SkipEnrollmentPromptsForTesting();
 
   // Forces screens that should only appear in chrome branded builds to show.
-  static std::unique_ptr<base::AutoReset<bool>> ForceBrandedBuildForTesting();
+  static std::unique_ptr<base::AutoReset<bool>> ForceBrandedBuildForTesting(
+      bool value);
 
   // Returns true if OOBE is operating under the
   // Zero-Touch Hands-Off Enrollment Flow.
   static bool UsingHandsOffEnrollment();
 
-  static bool IsBrandedBuildForTesting() { return is_branded_build_; }
+  // Returns true if this is a branded build, value could be overwritten by
+  // `ForceBrandedBuildForTesting`.
+  static bool IsBrandedBuild() { return is_branded_build_; }
 
   bool is_initialized() { return is_initialized_; }
 
diff --git a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
index 06eaa0c..bb0fee0 100644
--- a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
@@ -484,7 +484,8 @@
     WizardControllerTest::SetUpOnMainThread();
 
     // Make sure that OOBE is run as an "official" build.
-    branded_build_override_ = WizardController::ForceBrandedBuildForTesting();
+    branded_build_override_ =
+        WizardController::ForceBrandedBuildForTesting(true);
 
     WizardController* wizard_controller =
         WizardController::default_controller();
@@ -2723,7 +2724,8 @@
     WizardControllerTest::SetUpOnMainThread();
 
     // Make sure that OOBE is run as an "official" build.
-    branded_build_override_ = WizardController::ForceBrandedBuildForTesting();
+    branded_build_override_ =
+        WizardController::ForceBrandedBuildForTesting(true);
 
     WizardController* wizard_controller =
         WizardController::default_controller();
diff --git a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.cc b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.cc
index 25eca10..1bc3858 100644
--- a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.cc
+++ b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.cc
@@ -18,6 +18,7 @@
 const char kBrowserSettingsFeature[] = "browser_settings";
 const char kOsSettingsFeature[] = "os_settings";
 const char kScanningFeature[] = "scanning";
+const char kWebStoreFeature[] = "web_store";
 
 const char kBlockedDisableMode[] = "blocked";
 const char kHiddenDisableMode[] = "hidden";
@@ -78,6 +79,8 @@
     return SystemFeature::kBrowserSettings;
   if (system_feature == kScanningFeature)
     return SystemFeature::kScanning;
+  if (system_feature == kWebStoreFeature)
+    return SystemFeature::kWebStore;
 
   LOG(ERROR) << "Unsupported system feature: " << system_feature;
   return kUnknownSystemFeature;
diff --git a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h
index 2d3070c..8708af2 100644
--- a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h
+++ b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h
@@ -18,13 +18,14 @@
 // A system feature that can be disabled by SystemFeaturesDisableList policy.
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
-enum SystemFeature {
+enum SystemFeature : int {
   kUnknownSystemFeature = 0,
   kCamera = 1,           // The camera chrome app on Chrome OS.
   kBrowserSettings = 2,  // Browser settings.
   kOsSettings = 3,       // The settings feature on Chrome OS.
   kScanning = 4,         // The scan SWA on Chrome OS.
-  kMaxValue = kScanning
+  kWebStore = 5,         // The web store chrome app on Chrome OS.
+  kMaxValue = kWebStore
 };
 
 // A disabling mode that decides the user experience when a system feature is
@@ -40,6 +41,7 @@
 extern const char kBrowserSettingsFeature[];
 extern const char kOsSettingsFeature[];
 extern const char kScanningFeature[];
+extern const char kWebStoreFeature[];
 
 extern const char kBlockedDisableMode[];
 extern const char kHiddenDisableMode[];
diff --git a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler_unittest.cc b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler_unittest.cc
index ecaa003..970e308b 100644
--- a/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler_unittest.cc
+++ b/chrome/browser/chromeos/policy/system_features_disable_list_policy_handler_unittest.cc
@@ -67,12 +67,16 @@
   histogram_tester_.ExpectBucketCount(kSystemFeaturesDisableListHistogram,
                                       SystemFeature::kUnknownSystemFeature,
                                       /*amount*/ 0);
+  histogram_tester_.ExpectBucketCount(kSystemFeaturesDisableListHistogram,
+                                      SystemFeature::kWebStore,
+                                      /*amount*/ 0);
 
   features_list.ClearList();
   features_list.Append("camera");
   features_list.Append("os_settings");
   features_list.Append("scanning");
   features_list.Append("gallery");
+  features_list.Append("web_store");
 
   policy_map.Set(policy::key::kSystemFeaturesDisableList,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
@@ -88,11 +92,12 @@
   expected_list.Append(SystemFeature::kOsSettings);
   expected_list.Append(SystemFeature::kScanning);
   expected_list.Append(SystemFeature::kUnknownSystemFeature);
+  expected_list.Append(SystemFeature::kWebStore);
 
   EXPECT_TRUE(prefs.GetValue(policy_prefs::kSystemFeaturesDisableList, &value));
   EXPECT_EQ(expected_list, *value);
 
-  histogram_tester_.ExpectTotalCount(kSystemFeaturesDisableListHistogram, 5);
+  histogram_tester_.ExpectTotalCount(kSystemFeaturesDisableListHistogram, 6);
   histogram_tester_.ExpectBucketCount(kSystemFeaturesDisableListHistogram,
                                       SystemFeature::kCamera,
                                       /*amount*/ 1);
@@ -108,5 +113,8 @@
   histogram_tester_.ExpectBucketCount(kSystemFeaturesDisableListHistogram,
                                       SystemFeature::kUnknownSystemFeature,
                                       /*amount*/ 1);
+  histogram_tester_.ExpectBucketCount(kSystemFeaturesDisableListHistogram,
+                                      SystemFeature::kWebStore,
+                                      /*amount*/ 1);
 }
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.cc b/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.cc
index a2904b2..b2ff2f90 100644
--- a/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.cc
+++ b/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.cc
@@ -13,6 +13,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
+#include "chrome/browser/chromeos/crosapi/browser_manager.h"
 #include "chrome/browser/chromeos/policy/cached_policy_key_loader_chromeos.h"
 #include "chrome/browser/chromeos/policy/value_validation/onc_user_policy_value_validator.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
@@ -282,6 +283,17 @@
                 cached_policy_key_loader_->cached_policy_key());
   status_ = STATUS_OK;
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (crosapi::BrowserManager::Get()) {
+    std::string policy_blob;
+    // Since the policy have passed all the validations, the serialization must
+    // succeed.
+    bool success = validator->policy()->SerializeToString(&policy_blob);
+    DCHECK(success);
+    crosapi::BrowserManager::Get()->SetDeviceAccountPolicy(policy_blob);
+  }
+#endif
+
   NotifyStoreLoaded();
 }
 
diff --git a/chrome/browser/chromeos/system/automatic_reboot_manager_unittest.cc b/chrome/browser/chromeos/system/automatic_reboot_manager_unittest.cc
index 58f4e26..0ac70ec 100644
--- a/chrome/browser/chromeos/system/automatic_reboot_manager_unittest.cc
+++ b/chrome/browser/chromeos/system/automatic_reboot_manager_unittest.cc
@@ -205,7 +205,8 @@
   bool reboot_after_update_ = false;
 
   base::test::TaskEnvironment task_environment_;
-  base::ScopedClosureRunner reset_main_thread_task_runner_;
+  base::ThreadTaskRunnerHandleOverrideForTesting
+      thread_task_runner_handle_override_;
 
   TestingPrefServiceSimple local_state_;
   MockUserManager* mock_user_manager_;  // Not owned.
@@ -308,8 +309,7 @@
 
 AutomaticRebootManagerBasicTest::AutomaticRebootManagerBasicTest()
     : task_runner_(new TestAutomaticRebootManagerTaskRunner),
-      reset_main_thread_task_runner_(
-          base::ThreadTaskRunnerHandle::OverrideForTesting(task_runner_)),
+      thread_task_runner_handle_override_(task_runner_),
       mock_user_manager_(new MockUserManager),
       user_manager_enabler_(base::WrapUnique(mock_user_manager_)) {}
 
diff --git a/chrome/browser/chromeos/web_applications/chrome_camera_app_ui_delegate.cc b/chrome/browser/chromeos/web_applications/chrome_camera_app_ui_delegate.cc
index aad16cd2..5415cddb 100644
--- a/chrome/browser/chromeos/web_applications/chrome_camera_app_ui_delegate.cc
+++ b/chrome/browser/chromeos/web_applications/chrome_camera_app_ui_delegate.cc
@@ -6,6 +6,7 @@
 
 #include <vector>
 
+#include "ash/public/cpp/tablet_mode.h"
 #include "base/files/file_path.h"
 #include "base/system/sys_info.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
@@ -53,7 +54,7 @@
 }
 
 bool ChromeCameraAppUIDelegate::CameraAppDialog::CanMaximizeDialog() const {
-  return true;
+  return !ash::TabletMode::Get()->InTabletMode();
 }
 
 void ChromeCameraAppUIDelegate::CameraAppDialog::GetDialogSize(
diff --git a/chrome/browser/data_saver/subresource_redirect_login_robots_browsertest.cc b/chrome/browser/data_saver/subresource_redirect_login_robots_browsertest.cc
index a3a5640..ba39cfd 100644
--- a/chrome/browser/data_saver/subresource_redirect_login_robots_browsertest.cc
+++ b/chrome/browser/data_saver/subresource_redirect_login_robots_browsertest.cc
@@ -14,6 +14,8 @@
 #include "build/build_config.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
+#include "chrome/browser/login_detection/login_detection_type.h"
+#include "chrome/browser/login_detection/login_detection_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/subresource_redirect/https_image_compression_infobar_decider.h"
 #include "chrome/browser/ui/browser.h"
@@ -301,6 +303,8 @@
       params["robots_rules_receive_timeout"] = "1000";
       enabled_features.emplace_back(blink::features::kSubresourceRedirect,
                                     params);
+      enabled_features.emplace_back(login_detection::kLoginDetection,
+                                    base::FieldTrialParams());
     }
     scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
     InProcessBrowserTest::SetUp();
@@ -748,4 +752,50 @@
       {"/load_image/image.png"});
 }
 
+IN_PROC_BROWSER_TEST_F(
+    SubresourceRedirectLoginRobotsBrowserTest,
+    DISABLE_ON_WIN_MAC_CHROMEOS(TestNoCompressionOnLoggedInPage)) {
+  robots_rules_server_.AddRobotsRules(GetHttpsTestURL("/"),
+                                      {{kRuleTypeAllow, "*"}});
+  // Trigger OAuth login by triggering OAuth start and complete.
+  ui_test_utils::NavigateToURL(browser(),
+                               GetHttpsTestURL("/simple.html?initial"));
+  histogram_tester_.ExpectUniqueSample(
+      "Login.PageLoad.DetectionType",
+      login_detection::LoginDetectionType::kNoLogin, 1);
+  ui_test_utils::NavigateToURL(
+      browser(), https_test_server_.GetURL("oauth_server.com",
+                                           "/simple.html?client_id=user"));
+  histogram_tester_.ExpectBucketCount(
+      "Login.PageLoad.DetectionType",
+      login_detection::LoginDetectionType::kNoLogin, 2);
+
+  ui_test_utils::NavigateToURL(browser(),
+                               GetHttpsTestURL("/simple.html?code=123"));
+  histogram_tester_.ExpectBucketCount(
+      "Login.PageLoad.DetectionType",
+      login_detection::LoginDetectionType::kOauthFirstTimeLoginFlow, 1);
+
+  // The next navigation will be treated as logged-in.
+  NavigateAndWaitForLoad(browser(), GetHttpsTestURL("/load_image/image.html"));
+  histogram_tester_.ExpectBucketCount(
+      "Login.PageLoad.DetectionType",
+      login_detection::LoginDetectionType::kOauthLogin, 1);
+
+  // No image compression will be triggered.
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.CompressionAttempt.ResponseCode", 0);
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.CompressionAttempt.ServerResponded", 0);
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.RobotsRulesFetcher.ResponseCode", 0);
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.RobotsRules.Browser.InMemoryCacheHit", 0);
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.ImageCompressionNotificationInfoBar", 0);
+
+  robots_rules_server_.VerifyRequestedOrigins({});
+  image_compression_server_.VerifyRequestedImagePaths({});
+}
+
 }  // namespace subresource_redirect
diff --git a/chrome/browser/enterprise/reporting/browser_report_generator_desktop.cc b/chrome/browser/enterprise/reporting/browser_report_generator_desktop.cc
index 5d9594e..98de7db 100644
--- a/chrome/browser/enterprise/reporting/browser_report_generator_desktop.cc
+++ b/chrome/browser/enterprise/reporting/browser_report_generator_desktop.cc
@@ -76,12 +76,10 @@
   base::flat_set<base::FilePath> extension_request_profile_paths =
       throttler->GetProfiles();
 
-  for (const auto* entry : g_browser_process->profile_manager()
-                               ->GetProfileAttributesStorage()
-                               .GetAllProfilesAttributes()) {
-    // Exclude Guest profiles.
-    if (entry->IsGuest())
-      continue;
+  for (const auto* entry :
+       g_browser_process->profile_manager()
+           ->GetProfileAttributesStorage()
+           .GetAllProfilesAttributes(/*include_guest_profile=*/false)) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     // Skip sign-in and lock screen app profile on Chrome OS.
     if (!chromeos::ProfileHelper::IsRegularProfilePath(
diff --git a/chrome/browser/extensions/api/alarms/alarms_apitest.cc b/chrome/browser/extensions/api/alarms/alarms_apitest.cc
index 64a4ea24..eef7d65 100644
--- a/chrome/browser/extensions/api/alarms/alarms_apitest.cc
+++ b/chrome/browser/extensions/api/alarms/alarms_apitest.cc
@@ -6,7 +6,6 @@
 #include "content/public/test/browser_test.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/common/api/test.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "net/dns/mock_host_resolver.h"
@@ -25,11 +24,6 @@
     ExtensionApiTest::SetUpOnMainThread();
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(StartEmbeddedTestServer());
-
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
   }
 
   static std::unique_ptr<base::ListValue> BuildEventArguments(
@@ -48,9 +42,6 @@
     return LoadExtensionWithFlags(
         test_data_dir_.AppendASCII("alarms").AppendASCII(path), flags);
   }
-
- private:
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(EventPage,
diff --git a/chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc b/chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc
index 5b2b6e1..09c46fdc 100644
--- a/chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc
+++ b/chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc
@@ -19,7 +19,6 @@
 #include "components/bookmarks/test/bookmark_test_helpers.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_test.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 
 using bookmarks::BookmarkModel;
 
@@ -29,16 +28,6 @@
 
 class BookmarksApiTest : public ExtensionApiTest,
                          public testing::WithParamInterface<ContextType> {
- public:
-  BookmarksApiTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
- private:
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(EventPage,
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
index b84fdaf..686ad11 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
@@ -41,7 +41,6 @@
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/test_extension_registry_observer.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 
 namespace extensions {
 
@@ -222,14 +221,6 @@
 class ExtensionContentSettingsApiLazyTest
     : public ExtensionContentSettingsApiTest,
       public testing::WithParamInterface<ContextType> {
- public:
-  ExtensionContentSettingsApiLazyTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
  protected:
   bool RunLazyTest(const std::string& extension_name) {
     int browser_test_flags = kFlagNone;
@@ -239,8 +230,6 @@
     return RunExtensionTestWithFlagsAndArg(extension_name, nullptr,
                                            browser_test_flags, kFlagNone);
   }
-
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(EventPage,
diff --git a/chrome/browser/extensions/api/context_menus/context_menu_apitest.cc b/chrome/browser/extensions/api/context_menus/context_menu_apitest.cc
index 9a1a6a3..43682fb 100644
--- a/chrome/browser/extensions/api/context_menus/context_menu_apitest.cc
+++ b/chrome/browser/extensions/api/context_menus/context_menu_apitest.cc
@@ -20,7 +20,6 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "extensions/browser/extension_action.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/result_catcher.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "ui/base/models/menu_model.h"
@@ -33,7 +32,6 @@
 };
 
 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ServiceWorkerContextMenus) {
-  ScopedWorkerBasedExtensionsChannel worker_channel_override;
   ASSERT_TRUE(RunExtensionTestWithFlags("context_menus/event_page",
                                         kFlagRunAsServiceWorkerBasedExtension,
                                         kFlagNone))
diff --git a/chrome/browser/extensions/api/cookies/cookies_apitest.cc b/chrome/browser/extensions/api/cookies/cookies_apitest.cc
index 9e8b0fe..4a7cd854 100644
--- a/chrome/browser/extensions/api/cookies/cookies_apitest.cc
+++ b/chrome/browser/extensions/api/cookies/cookies_apitest.cc
@@ -6,7 +6,6 @@
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/test/browser_test.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "net/cookies/cookie_util.h"
 
 namespace extensions {
@@ -21,14 +20,6 @@
 
 class CookiesApiTest : public ExtensionApiTest,
                        public testing::WithParamInterface<ContextType> {
- public:
-  CookiesApiTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
  protected:
   bool RunTest(const std::string& extension_name) {
     return RunTestWithFlags(extension_name, kFlagNone);
@@ -56,8 +47,6 @@
     return RunExtensionTestWithFlags(extension_name, browser_test_flags,
                                      kFlagNone);
   }
-
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(EventPage,
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
index c4aa4201..7eef0ce 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
@@ -58,7 +58,6 @@
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/common/feature_switch.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -153,14 +152,7 @@
 class BrowserActionApiLazyTest
     : public BrowserActionApiTest,
       public testing::WithParamInterface<ContextType> {
- public:
-  BrowserActionApiLazyTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
+ protected:
   const extensions::Extension* LoadExtensionWithParamFlags(
       const base::FilePath& path) {
     int flags = kFlagEnableFileAccess;
@@ -237,8 +229,6 @@
 
  private:
   base::test::ScopedFeatureList feature_list_;
-  std::unique_ptr<extensions::ScopedWorkerBasedExtensionsChannel>
-      current_channel_;
 };
 
 IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, Basic) {
diff --git a/chrome/browser/extensions/api/management/management_api_browsertest.cc b/chrome/browser/extensions/api/management/management_api_browsertest.cc
index 1cab1a0..b12525b 100644
--- a/chrome/browser/extensions/api/management/management_api_browsertest.cc
+++ b/chrome/browser/extensions/api/management/management_api_browsertest.cc
@@ -31,7 +31,6 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/common/extension_builder.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/extension_test_message_listener.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -64,24 +63,13 @@
 class ExtensionManagementApiTestWithBackgroundType
     : public ExtensionManagementApiBrowserTest,
       public testing::WithParamInterface<ContextType> {
- public:
-  ExtensionManagementApiTestWithBackgroundType() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
+ protected:
   const Extension* LoadExtensionWithParamFlags(const base::FilePath& path) {
     int flags = kFlagEnableFileAccess;
     if (GetParam() == ContextType::kServiceWorker)
       flags |= ExtensionBrowserTest::kFlagRunAsServiceWorkerBasedExtension;
     return LoadExtensionWithFlags(path, flags);
   }
-
- private:
-  std::unique_ptr<extensions::ScopedWorkerBasedExtensionsChannel>
-      current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(PersistentBackground,
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
index 8fef91e..6244a7c 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
@@ -25,7 +25,6 @@
 #include "components/keep_alive_registry/keep_alive_types.h"
 #include "content/public/test/browser_test.h"
 #include "extensions/browser/process_manager.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/result_catcher.h"
 
 namespace extensions {
@@ -52,14 +51,6 @@
 class NativeMessagingLazyApiTest
     : public NativeMessagingApiTest,
       public testing::WithParamInterface<ContextType> {
- public:
-  NativeMessagingLazyApiTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
  protected:
   bool RunLazyTest(const std::string& extension_name) {
     if (GetParam() == ContextType::kEventPage) {
@@ -68,8 +59,6 @@
     return RunExtensionTestWithFlags(
         extension_name, kFlagRunAsServiceWorkerBasedExtension, kFlagNone);
   }
-
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(EventPage,
diff --git a/chrome/browser/extensions/api/metrics_private/metrics_apitest.cc b/chrome/browser/extensions/api/metrics_private/metrics_apitest.cc
index 66e7e0d..54e4e43 100644
--- a/chrome/browser/extensions/api/metrics_private/metrics_apitest.cc
+++ b/chrome/browser/extensions/api/metrics_private/metrics_apitest.cc
@@ -14,7 +14,6 @@
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "components/variations/variations_associated_data.h"
 #include "content/public/test/browser_test.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 
 namespace extensions {
 
@@ -138,13 +137,6 @@
     : public ExtensionApiTest,
       public testing::WithParamInterface<ContextType> {
  public:
-  ExtensionMetricsApiTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
   bool RunComponentTestWithParamFlag(const std::string& extension_name) {
     int flags = kFlagNone;
     if (GetParam() == ContextType::kServiceWorker)
@@ -153,9 +145,6 @@
     return RunExtensionTestWithFlags(extension_name, flags,
                                      kFlagLoadAsComponent);
   }
-
- private:
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(PersistentBackground,
diff --git a/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc b/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc
index 86cd394e..fad5fe6 100644
--- a/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc
+++ b/chrome/browser/extensions/api/page_capture/page_capture_apitest.cc
@@ -25,7 +25,6 @@
 #include "extensions/browser/extension_dialog_auto_confirm.h"
 #include "extensions/common/permissions/permission_set.h"
 #include "extensions/common/permissions/permissions_data.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/common/url_pattern_set.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
@@ -79,14 +78,7 @@
 class ExtensionPageCaptureApiTest
     : public ExtensionApiTest,
       public testing::WithParamInterface<ContextType> {
- public:
-  ExtensionPageCaptureApiTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
+ protected:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     ExtensionApiTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
@@ -123,9 +115,6 @@
     if (GetParam() != ContextType::kServiceWorker)
       delegate->WaitForFinalRelease();
   }
-
- private:
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(PersistentBackground,
diff --git a/chrome/browser/extensions/api/permissions/permissions_apitest.cc b/chrome/browser/extensions/api/permissions/permissions_apitest.cc
index a311cb4c..ea47a37 100644
--- a/chrome/browser/extensions/api/permissions/permissions_apitest.cc
+++ b/chrome/browser/extensions/api/permissions/permissions_apitest.cc
@@ -16,7 +16,6 @@
 #include "content/public/test/browser_test.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/common/permissions/permission_set.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/common/switches.h"
 #include "net/dns/mock_host_resolver.h"
 
@@ -52,15 +51,6 @@
 class PermissionsApiTestWithContextType
     : public PermissionsApiTest,
       public testing::WithParamInterface<ContextType> {
- public:
-  PermissionsApiTestWithContextType() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker) {
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-    }
-  }
-
  protected:
   bool RunTest(const std::string& extension_name) {
     int browser_test_flags = kFlagNone;
@@ -69,10 +59,6 @@
     return RunExtensionTestWithFlags(extension_name, browser_test_flags,
                                      kFlagNone);
   }
-
- private:
-  std::unique_ptr<extensions::ScopedWorkerBasedExtensionsChannel>
-      current_channel_;
 };
 
 IN_PROC_BROWSER_TEST_F(PermissionsApiTest, PermissionsFail) {
diff --git a/chrome/browser/extensions/api/runtime/runtime_apitest.cc b/chrome/browser/extensions/api/runtime/runtime_apitest.cc
index 02dee5d..4720055 100644
--- a/chrome/browser/extensions/api/runtime/runtime_apitest.cc
+++ b/chrome/browser/extensions/api/runtime/runtime_apitest.cc
@@ -19,7 +19,6 @@
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/test_extension_registry_observer.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "extensions/test/test_extension_dir.h"
@@ -33,13 +32,7 @@
 class RuntimeApiTest : public ExtensionApiTest,
                        public testing::WithParamInterface<ContextType> {
  public:
-  RuntimeApiTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
+  RuntimeApiTest() = default;
   RuntimeApiTest(const RuntimeApiTest&) = delete;
   RuntimeApiTest& operator=(const RuntimeApiTest&) = delete;
 
@@ -58,10 +51,6 @@
 
     return RunExtensionTestWithFlags(extension_name, flags, kFlagNone);
   }
-
- private:
-  std::unique_ptr<extensions::ScopedWorkerBasedExtensionsChannel>
-      current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(PersistentBackground,
diff --git a/chrome/browser/extensions/api/test/apitest_apitest.cc b/chrome/browser/extensions/api/test/apitest_apitest.cc
index ea4df5af..e6de56cd 100644
--- a/chrome/browser/extensions/api/test/apitest_apitest.cc
+++ b/chrome/browser/extensions/api/test/apitest_apitest.cc
@@ -6,7 +6,6 @@
 
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "content/public/test/browser_test.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/result_catcher.h"
 #include "extensions/test/test_extension_dir.h"
 
@@ -24,16 +23,8 @@
 
 }  // namespace
 
-class TestAPITest
-    : public ExtensionApiTest,
-      public testing::WithParamInterface<ExtensionApiTest::ContextType> {
+class TestAPITest : public ExtensionApiTest {
  public:
-  TestAPITest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
   ~TestAPITest() override = default;
 
   // Loads and returns an extension with the given |background_script|.
@@ -42,7 +33,6 @@
 
  private:
   std::vector<std::unique_ptr<TestExtensionDir>> test_dirs_;
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 const Extension* TestAPITest::LoadExtensionWithBackgroundScript(
@@ -56,13 +46,13 @@
 }
 
 // TODO(devlin): This test name should be more descriptive.
-IN_PROC_BROWSER_TEST_P(TestAPITest, ApiTest) {
+IN_PROC_BROWSER_TEST_F(TestAPITest, ApiTest) {
   ASSERT_TRUE(RunExtensionTest("apitest")) << message_;
 }
 
 // Verifies that failing an assert in a promise will properly fail and end the
 // test.
-IN_PROC_BROWSER_TEST_P(TestAPITest, FailedAssertsInPromises) {
+IN_PROC_BROWSER_TEST_F(TestAPITest, FailedAssertsInPromises) {
   ResultCatcher result_catcher;
   constexpr char kBackgroundJs[] =
       R"(chrome.test.runTests([
@@ -80,7 +70,7 @@
 }
 
 // Verifies that using await and assert'ing aspects of the results succeeds.
-IN_PROC_BROWSER_TEST_P(TestAPITest, AsyncAwaitAssertions_Succeed) {
+IN_PROC_BROWSER_TEST_F(TestAPITest, AsyncAwaitAssertions_Succeed) {
   ResultCatcher result_catcher;
   constexpr char kBackgroundJs[] =
       R"(chrome.test.runTests([
@@ -98,7 +88,7 @@
 
 // Verifies that using await and having failed assertions properly fails the
 // test.
-IN_PROC_BROWSER_TEST_P(TestAPITest, AsyncAwaitAssertions_Failed) {
+IN_PROC_BROWSER_TEST_F(TestAPITest, AsyncAwaitAssertions_Failed) {
   ResultCatcher result_catcher;
   constexpr char kBackgroundJs[] =
       R"(chrome.test.runTests([
@@ -117,7 +107,7 @@
 
 // Verifies that we can assert values on chrome.runtime.lastError after using
 // await with an API call.
-IN_PROC_BROWSER_TEST_P(TestAPITest, AsyncAwaitAssertions_LastError) {
+IN_PROC_BROWSER_TEST_F(TestAPITest, AsyncAwaitAssertions_LastError) {
   ResultCatcher result_catcher;
   constexpr char kBackgroundJs[] =
       R"(chrome.test.runTests([
@@ -135,14 +125,4 @@
   EXPECT_TRUE(result_catcher.GetNextResult());
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    EventPage,
-    TestAPITest,
-    ::testing::Values(ExtensionApiTest::ContextType::kEventPage));
-
-INSTANTIATE_TEST_SUITE_P(
-    ServiceWorker,
-    TestAPITest,
-    ::testing::Values(ExtensionApiTest::ContextType::kServiceWorker));
-
 }  // namespace extensions
diff --git a/chrome/browser/extensions/execute_script_apitest.cc b/chrome/browser/extensions/execute_script_apitest.cc
index 2824714..7c34ee2 100644
--- a/chrome/browser/extensions/execute_script_apitest.cc
+++ b/chrome/browser/extensions/execute_script_apitest.cc
@@ -9,7 +9,6 @@
 #include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/test/browser_test.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "net/base/filename_util.h"
 #include "net/dns/mock_host_resolver.h"
 
@@ -31,13 +30,6 @@
 class ExecuteScriptApiTest : public ExecuteScriptApiTestBase,
                              public testing::WithParamInterface<ContextType> {
  protected:
-  ExecuteScriptApiTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker)
-      current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
-  }
-
   bool RunTest(const std::string& extension_name) {
     int browser_test_flags = kFlagNone;
     if (GetParam() == ContextType::kServiceWorker)
@@ -55,9 +47,6 @@
     return RunExtensionTestWithFlags(extension_name, browser_test_flags,
                                      kFlagNone);
   }
-
- private:
-  std::unique_ptr<ScopedWorkerBasedExtensionsChannel> current_channel_;
 };
 
 INSTANTIATE_TEST_SUITE_P(PersistentBackground,
diff --git a/chrome/browser/extensions/extension_context_menu_browsertest.cc b/chrome/browser/extensions/extension_context_menu_browsertest.cc
index 091201aa..d6dc890 100644
--- a/chrome/browser/extensions/extension_context_menu_browsertest.cc
+++ b/chrome/browser/extensions/extension_context_menu_browsertest.cc
@@ -37,7 +37,6 @@
 #include "extensions/browser/test_management_policy.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/features/feature_channel.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "net/dns/mock_host_resolver.h"
@@ -260,15 +259,6 @@
     : public ExtensionContextMenuBrowserTest,
       public testing::WithParamInterface<ContextType> {
  public:
-  ExtensionContextMenuLazyTest() {
-    // Service Workers are currently only available on certain channels, so set
-    // the channel for those tests.
-    if (GetParam() == ContextType::kServiceWorker) {
-      current_channel_ =
-          std::make_unique<extensions::ScopedWorkerBasedExtensionsChannel>();
-    }
-  }
-
   void SetUpOnMainThread() override {
     ExtensionContextMenuBrowserTest::SetUpOnMainThread();
     // Set shorter delays to prevent test timeouts.
@@ -349,10 +339,6 @@
     ASSERT_TRUE(update.WaitUntilSatisfied());
     ASSERT_EQ(!enabled, menu->IsCommandIdEnabled(command_id));
   }
-
- private:
-  std::unique_ptr<extensions::ScopedWorkerBasedExtensionsChannel>
-      current_channel_;
 };
 
 class ExtensionContextMenuPersistentTest
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 6f3f709b..7c12f6e 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -70,7 +70,6 @@
 #include "extensions/common/extensions_client.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/common/permissions/permissions_data.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/common/value_builder.h"
 #include "extensions/common/verifier_formats.h"
 #include "extensions/test/background_page_watcher.h"
@@ -153,19 +152,15 @@
 };
 
 class ServiceWorkerTest : public ExtensionApiTest {
- public:
-  ServiceWorkerTest() : current_channel_(version_info::Channel::STABLE) {}
-  explicit ServiceWorkerTest(version_info::Channel channel)
-      : current_channel_(channel) {}
-
-  ~ServiceWorkerTest() override {}
+ protected:
+  ServiceWorkerTest() = default;
+  ~ServiceWorkerTest() override = default;
 
   void SetUpOnMainThread() override {
     ExtensionApiTest::SetUpOnMainThread();
     host_resolver()->AddRule("*", "127.0.0.1");
   }
 
- protected:
   // Returns the ProcessManager for the test's profile.
   ProcessManager* process_manager() { return ProcessManager::Get(profile()); }
 
@@ -238,27 +233,10 @@
         content::BrowserContext::GetDefaultStoragePartition(
             browser()->profile())
             ->GetServiceWorkerContext();
-    base::RunLoop run_loop;
-    size_t ref_count = 0;
-    auto set_ref_count = [](size_t* ref_count, base::RunLoop* run_loop,
-                            size_t external_request_count) {
-      *ref_count = external_request_count;
-      run_loop->Quit();
-    };
-    sw_context->CountExternalRequestsForTest(
-        origin, base::BindOnce(set_ref_count, &ref_count, &run_loop));
-    run_loop.Run();
-    return ref_count;
+    return sw_context->CountExternalRequestsForTest(origin);
   }
 
  private:
-  // Sets the channel to "stable".
-  // Not useful after we've opened extension Service Workers to stable
-  // channel.
-  // TODO(lazyboy): Remove this when ExtensionServiceWorkersEnabled() is
-  // removed.
-  ScopedCurrentChannel current_channel_;
-
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerTest);
 };
 
@@ -306,8 +284,6 @@
   }
 
  private:
-  ScopedWorkerBasedExtensionsChannel channel_;
-
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerBasedBackgroundTest);
 };
 
@@ -903,8 +879,6 @@
   }
 
  private:
-  ScopedWorkerBasedExtensionsChannel channel_;
-
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerLazyBackgroundTest);
 };
 
@@ -2512,8 +2486,8 @@
     : public ServiceWorkerTest,
       public testing::WithParamInterface<version_info::Channel> {
  public:
-  ServiceWorkerCheckBindingsTest() : ServiceWorkerTest(GetParam()) {}
-  ~ServiceWorkerCheckBindingsTest() override {}
+  ServiceWorkerCheckBindingsTest() = default;
+  ~ServiceWorkerCheckBindingsTest() override = default;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerCheckBindingsTest);
diff --git a/chrome/browser/extensions/service_worker_messaging_apitest.cc b/chrome/browser/extensions/service_worker_messaging_apitest.cc
index 0704e71..3f77b5a 100644
--- a/chrome/browser/extensions/service_worker_messaging_apitest.cc
+++ b/chrome/browser/extensions/service_worker_messaging_apitest.cc
@@ -11,7 +11,6 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/service_worker_test_helpers.h"
 #include "extensions/browser/service_worker/service_worker_test_utils.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "extensions/test/test_extension_dir.h"
@@ -94,8 +93,6 @@
   extensions::ScopedTestNativeMessagingHost test_host_;
 
  private:
-  ScopedWorkerBasedExtensionsChannel current_channel_;
-
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerMessagingTest);
 };
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index f1df1d25..faf4cfc 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3573,6 +3573,11 @@
     "expiry_milestone": 90
   },
   {
+    "name": "omnibox-default-typed-navigations-to-https",
+    "owners": ["meacer", "chrome-omnibox-team@google.com"],
+    "expiry_milestone": 93
+  },
+  {
     "name": "omnibox-disable-cgi-param-matching",
     "owners": [ "yoangela", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 90
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 34cf6b2..5a84604 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1529,6 +1529,16 @@
     "the current page is provided as the first suggestion without a title. "
     "Enabling this flag causes the title to be displayed.";
 
+const char kOmniboxDefaultTypedNavigationsToHttpsName[] =
+    "Omnibox - Use HTTPS as the default protocol for navigations";
+const char kOmniboxDefaultTypedNavigationsToHttpsDescription[] =
+    "Use HTTPS as the default protocol when the user types a URL without "
+    "a protocol in the omnibox such as 'example.com'. Presently, such an entry "
+    "navigates to http://example.com. When this feature is enabled, it will "
+    "navigate to https://example.com if the HTTPS URL is available. If Chrome "
+    "can't determine the availability of the HTTPS URL within the timeout, it "
+    "will fall back to the HTTP URL.";
+
 const char kOmniboxExperimentalSuggestScoringName[] =
     "Omnibox Experimental Suggest Scoring";
 const char kOmniboxExperimentalSuggestScoringDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index ea8bf99..f4ae4db 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -904,6 +904,9 @@
 extern const char kOmniboxDisplayTitleForCurrentUrlName[];
 extern const char kOmniboxDisplayTitleForCurrentUrlDescription[];
 
+extern const char kOmniboxDefaultTypedNavigationsToHttpsName[];
+extern const char kOmniboxDefaultTypedNavigationsToHttpsDescription[];
+
 extern const char kOmniboxExperimentalSuggestScoringName[];
 extern const char kOmniboxExperimentalSuggestScoringDescription[];
 
diff --git a/chrome/browser/installable/installable_manager_browsertest.cc b/chrome/browser/installable/installable_manager_browsertest.cc
index e76d448f..ec020f6 100644
--- a/chrome/browser/installable/installable_manager_browsertest.cc
+++ b/chrome/browser/installable/installable_manager_browsertest.cc
@@ -1425,7 +1425,6 @@
                                        "/banners/manifest_data_url_icon.json"));
   run_loop.Run();
 
-
   EXPECT_FALSE(tester->manifest().IsEmpty());
   EXPECT_FALSE(tester->manifest_url().is_empty());
 
@@ -1508,6 +1507,56 @@
   }
 }
 
+// The case that a service worker doesn't return an offline response for the
+// start_url, but does for the manifest scope.
+// - manifest's scope: /banners/
+// - manifest's start_url: /banners/manifest_test_page.html?ignore
+// - service worker's scope: /banners/
+IN_PROC_BROWSER_TEST_P(InstallableManagerOfflineCapabilityBrowserTest,
+                       CheckNotOfflineCapableStartUrl) {
+  // This test wants to check the service worker that doesn't support offline
+  // pages, so ignore the cases when `is_service_worker_offline_supported_` is
+  // true.
+  if (IsServiceWorkerOfflineSupported())
+    return;
+
+  base::RunLoop run_loop;
+  std::unique_ptr<CallbackTester> tester(
+      new CallbackTester(run_loop.QuitClosure()));
+
+  NavigateAndRunInstallableManager(
+      browser(), tester.get(), GetWebAppParams(),
+      GetURLOfPageWithServiceWorkerAndManifest(
+          "/banners/manifest_not_offline_capable_url.json"));
+
+  run_loop.Run();
+
+  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(tester->manifest_url().is_empty());
+  EXPECT_FALSE(tester->primary_icon_url().is_empty());
+  EXPECT_NE(nullptr, tester->primary_icon());
+  EXPECT_TRUE(tester->valid_manifest());
+  EXPECT_TRUE(tester->splash_icon_url().is_empty());
+  EXPECT_EQ(nullptr, tester->splash_icon());
+  CheckServiceWorkerForTester(tester.get());
+
+  InstallableManager* manager = GetManager(browser());
+
+  EXPECT_FALSE(manager->manifest().IsEmpty());
+  EXPECT_FALSE(manager->manifest_url().is_empty());
+  EXPECT_TRUE(manager->valid_manifest());
+  EXPECT_EQ(1u, manager->icons_.size());
+  EXPECT_FALSE(
+      (manager->icon_url(InstallableManager::IconUsage::kPrimary).is_empty()));
+  EXPECT_NE(nullptr, (manager->icon(InstallableManager::IconUsage::kPrimary)));
+  EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
+  EXPECT_EQ(NO_ERROR_DETECTED, manager->valid_manifest_error());
+  EXPECT_EQ(NO_ERROR_DETECTED,
+            (manager->icon_error(InstallableManager::IconUsage::kPrimary)));
+  EXPECT_TRUE(!manager->task_queue_.HasCurrent());
+  CheckServiceWorkerForInstallableManager(manager);
+}
+
 IN_PROC_BROWSER_TEST_F(InstallableManagerBrowserTest,
                        CheckNestedCallsToGetData) {
   // Verify that we can call GetData while in a callback from GetData.
diff --git a/chrome/browser/metrics/extensions_metrics_provider_unittest.cc b/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
index f5509ff..0304504 100644
--- a/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
+++ b/chrome/browser/metrics/extensions_metrics_provider_unittest.cc
@@ -28,7 +28,6 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_set.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/common/value_builder.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/metrics_proto/extension_install.pb.h"
@@ -367,7 +366,6 @@
   }
   {
     // Test that service worker scripts are reported correctly.
-    extensions::ScopedWorkerBasedExtensionsChannel worker_channel_override;
     scoped_refptr<const Extension> extension =
         ExtensionBuilder("service worker")
             .SetBackgroundContext(
diff --git a/chrome/browser/policy/chrome_browser_policy_connector.cc b/chrome/browser/policy/chrome_browser_policy_connector.cc
index f76d437..68551a2 100644
--- a/chrome/browser/policy/chrome_browser_policy_connector.cc
+++ b/chrome/browser/policy/chrome_browser_policy_connector.cc
@@ -15,6 +15,7 @@
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "build/branding_buildflags.h"
+#include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/policy/configuration_policy_handler_list_factory.h"
@@ -62,6 +63,10 @@
 #include "chrome/browser/browser_switcher/browser_switcher_policy_migrator.h"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "components/policy/core/common/policy_loader_lacros.h"
+#endif
+
 namespace policy {
 namespace {
 bool command_line_enabled_for_testing = false;
@@ -205,6 +210,12 @@
       new MacPreferences(), bundle_id);
   return std::make_unique<AsyncPolicyProvider>(GetSchemaRegistry(),
                                                std::move(loader));
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  auto loader = std::make_unique<PolicyLoaderLacros>(
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
+  return std::make_unique<AsyncPolicyProvider>(GetSchemaRegistry(),
+                                               std::move(loader));
 #elif defined(OS_POSIX) && !defined(OS_ANDROID)
   base::FilePath config_dir_path;
   if (base::PathService::Get(chrome::DIR_POLICY_FILES, &config_dir_path)) {
diff --git a/chrome/browser/policy/system_features_policy_browsertest.cc b/chrome/browser/policy/system_features_policy_browsertest.cc
index 6d3b75d..1773c147 100644
--- a/chrome/browser/policy/system_features_policy_browsertest.cc
+++ b/chrome/browser/policy/system_features_policy_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/values.h"
 #include "chrome/browser/apps/app_service/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -43,126 +44,139 @@
     return web_contents->GetTitle();
   }
 
+  void EnableExtensions(bool skip_session_components) {
+    auto* profile = browser()->profile();
+    extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
+    extensions::ExtensionSystem::Get(profile)
+        ->extension_service()
+        ->component_loader()
+        ->AddDefaultComponentExtensions(skip_session_components);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  // Disables specified system features or enables all if system_features is
+  // empty.
+  void UpdateSystemFeaturesDisableList(base::Value system_features) {
+    PolicyMap policies;
+    policies.Set(key::kSystemFeaturesDisableList, POLICY_LEVEL_MANDATORY,
+                 POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+                 std::move(system_features), nullptr);
+    UpdateProviderPolicy(policies);
+  }
+
+  void VerifyAppState(const char* app_id,
+                      apps::mojom::Readiness expected_readiness,
+                      bool blocked_icon) {
+    auto* profile = browser()->profile();
+    extensions::ExtensionRegistry* registry =
+        extensions::ExtensionRegistry::Get(profile);
+    ASSERT_TRUE(registry->enabled_extensions().GetByID(app_id));
+
+    apps::AppServiceProxy* proxy =
+        apps::AppServiceProxyFactory::GetForProfile(profile);
+    proxy->FlushMojoCallsForTesting();
+
+    proxy->AppRegistryCache().ForOneApp(
+        app_id,
+        [&expected_readiness, &blocked_icon](const apps::AppUpdate& update) {
+          EXPECT_EQ(expected_readiness, update.Readiness());
+          if (blocked_icon) {
+            EXPECT_TRUE(apps::IconEffects::kBlocked &
+                        update.IconKey()->icon_effects);
+          } else {
+            EXPECT_FALSE(apps::IconEffects::kBlocked &
+                         update.IconKey()->icon_effects);
+          }
+        });
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(SystemFeaturesPolicyTest, DisableCameraBeforeInstall) {
-  PolicyMap policies;
   base::Value system_features(base::Value::Type::LIST);
   system_features.Append(kCameraFeature);
-  policies.Set(key::kSystemFeaturesDisableList, POLICY_LEVEL_MANDATORY,
-               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               std::move(system_features), nullptr);
-  UpdateProviderPolicy(policies);
+  UpdateSystemFeaturesDisableList(std::move(system_features));
+  EnableExtensions(false);
+  VerifyAppState(extension_misc::kCameraAppId,
+                 apps::mojom::Readiness::kDisabledByPolicy, true);
 
-  auto* profile = browser()->profile();
-  extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
-  extensions::ExtensionSystem::Get(profile)
-      ->extension_service()
-      ->component_loader()
-      ->AddDefaultComponentExtensions(false);
-  base::RunLoop().RunUntilIdle();
-
-  extensions::ExtensionRegistry* registry =
-      extensions::ExtensionRegistry::Get(profile);
-  ASSERT_TRUE(
-      registry->enabled_extensions().GetByID(extension_misc::kCameraAppId));
-
-  apps::AppServiceProxy* proxy =
-      apps::AppServiceProxyFactory::GetForProfile(profile);
-  proxy->FlushMojoCallsForTesting();
-
-  proxy->AppRegistryCache().ForOneApp(
-      extension_misc::kCameraAppId, [](const apps::AppUpdate& update) {
-        EXPECT_EQ(apps::mojom::Readiness::kDisabledByPolicy,
-                  update.Readiness());
-        EXPECT_TRUE(apps::IconEffects::kBlocked &
-                    update.IconKey()->icon_effects);
-      });
-
-  policies.Set(key::kSystemFeaturesDisableList, POLICY_LEVEL_MANDATORY,
-               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(), nullptr);
-  UpdateProviderPolicy(policies);
-
-  ASSERT_TRUE(
-      registry->enabled_extensions().GetByID(extension_misc::kCameraAppId));
-
-  proxy->FlushMojoCallsForTesting();
-  proxy->AppRegistryCache().ForOneApp(
-      extension_misc::kCameraAppId, [](const apps::AppUpdate& update) {
-        EXPECT_EQ(apps::mojom::Readiness::kReady, update.Readiness());
-        EXPECT_FALSE(apps::IconEffects::kBlocked &
-                     update.IconKey()->icon_effects);
-      });
+  UpdateSystemFeaturesDisableList(base::Value());
+  VerifyAppState(extension_misc::kCameraAppId, apps::mojom::Readiness::kReady,
+                 false);
 }
 
 IN_PROC_BROWSER_TEST_F(SystemFeaturesPolicyTest, DisableCameraAfterInstall) {
-  auto* profile = browser()->profile();
-  extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
-  extensions::ExtensionSystem::Get(profile)
-      ->extension_service()
-      ->component_loader()
-      ->AddDefaultComponentExtensions(false);
-  base::RunLoop().RunUntilIdle();
-
-  PolicyMap policies;
+  EnableExtensions(false);
   base::Value system_features(base::Value::Type::LIST);
   system_features.Append(kCameraFeature);
-  policies.Set(key::kSystemFeaturesDisableList, POLICY_LEVEL_MANDATORY,
-               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               std::move(system_features), nullptr);
-  UpdateProviderPolicy(policies);
+  UpdateSystemFeaturesDisableList(std::move(system_features));
 
-  extensions::ExtensionRegistry* registry =
-      extensions::ExtensionRegistry::Get(profile);
-  ASSERT_TRUE(
-      registry->enabled_extensions().GetByID(extension_misc::kCameraAppId));
+  VerifyAppState(extension_misc::kCameraAppId,
+                 apps::mojom::Readiness::kDisabledByPolicy, true);
 
-  apps::AppServiceProxy* proxy =
-      apps::AppServiceProxyFactory::GetForProfile(profile);
-  proxy->FlushMojoCallsForTesting();
-  proxy->AppRegistryCache().ForOneApp(
-      extension_misc::kCameraAppId, [](const apps::AppUpdate& update) {
-        EXPECT_EQ(apps::mojom::Readiness::kDisabledByPolicy,
-                  update.Readiness());
-        EXPECT_TRUE(apps::IconEffects::kBlocked &
-                    update.IconKey()->icon_effects);
-      });
+  UpdateSystemFeaturesDisableList(base::Value());
+  VerifyAppState(extension_misc::kCameraAppId, apps::mojom::Readiness::kReady,
+                 false);
+}
 
-  policies.Set(key::kSystemFeaturesDisableList, POLICY_LEVEL_MANDATORY,
-               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(), nullptr);
-  UpdateProviderPolicy(policies);
+IN_PROC_BROWSER_TEST_F(SystemFeaturesPolicyTest, DisableWebStoreBeforeInstall) {
+  base::Value system_features(base::Value::Type::LIST);
+  system_features.Append(kWebStoreFeature);
+  UpdateSystemFeaturesDisableList(std::move(system_features));
+  EnableExtensions(true);
+  VerifyAppState(extensions::kWebStoreAppId,
+                 apps::mojom::Readiness::kDisabledByPolicy, true);
 
-  ASSERT_TRUE(
-      registry->enabled_extensions().GetByID(extension_misc::kCameraAppId));
+  UpdateSystemFeaturesDisableList(base::Value());
+  VerifyAppState(extensions::kWebStoreAppId, apps::mojom::Readiness::kReady,
+                 false);
+}
 
-  proxy->FlushMojoCallsForTesting();
-  proxy->AppRegistryCache().ForOneApp(
-      extension_misc::kCameraAppId, [](const apps::AppUpdate& update) {
-        EXPECT_EQ(apps::mojom::Readiness::kReady, update.Readiness());
-        EXPECT_FALSE(apps::IconEffects::kBlocked &
-                     update.IconKey()->icon_effects);
-      });
+IN_PROC_BROWSER_TEST_F(SystemFeaturesPolicyTest, DisableWebStoreAfterInstall) {
+  EnableExtensions(false);
+  base::Value system_features(base::Value::Type::LIST);
+  system_features.Append(kWebStoreFeature);
+  UpdateSystemFeaturesDisableList(std::move(system_features));
+
+  VerifyAppState(extensions::kWebStoreAppId,
+                 apps::mojom::Readiness::kDisabledByPolicy, true);
+
+  UpdateSystemFeaturesDisableList(base::Value());
+  VerifyAppState(extensions::kWebStoreAppId, apps::mojom::Readiness::kReady,
+                 false);
+}
+
+IN_PROC_BROWSER_TEST_F(SystemFeaturesPolicyTest,
+                       DisableCameraAndWebStoreAfterInstall) {
+  EnableExtensions(false);
+  base::Value system_features(base::Value::Type::LIST);
+  system_features.Append(kWebStoreFeature);
+  system_features.Append(kCameraFeature);
+  UpdateSystemFeaturesDisableList(std::move(system_features));
+
+  VerifyAppState(extensions::kWebStoreAppId,
+                 apps::mojom::Readiness::kDisabledByPolicy, true);
+  VerifyAppState(extension_misc::kCameraAppId,
+                 apps::mojom::Readiness::kDisabledByPolicy, true);
+
+  UpdateSystemFeaturesDisableList(base::Value());
+  VerifyAppState(extensions::kWebStoreAppId, apps::mojom::Readiness::kReady,
+                 false);
 }
 
 IN_PROC_BROWSER_TEST_F(SystemFeaturesPolicyTest, RedirectChromeSettingsURL) {
   PolicyMap policies;
   base::Value system_features(base::Value::Type::LIST);
   system_features.Append(kBrowserSettingsFeature);
-  policies.Set(key::kSystemFeaturesDisableList, POLICY_LEVEL_MANDATORY,
-               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               std::move(system_features), nullptr);
-  UpdateProviderPolicy(policies);
+  UpdateSystemFeaturesDisableList(std::move(system_features));
 
   GURL settings_url = GURL(chrome::kChromeUISettingsURL);
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_CHROME_URLS_DISABLED_PAGE_HEADER),
             GetWebUITitle(settings_url));
 
-  policies.Set(key::kSystemFeaturesDisableList, POLICY_LEVEL_MANDATORY,
-               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(), nullptr);
-  UpdateProviderPolicy(policies);
-
+  UpdateSystemFeaturesDisableList(base::Value());
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_SETTINGS_SETTINGS),
             GetWebUITitle(settings_url));
 }
diff --git a/chrome/browser/profiles/profile_attributes_storage.cc b/chrome/browser/profiles/profile_attributes_storage.cc
index ed9eb11..b8047cc4 100644
--- a/chrome/browser/profiles/profile_attributes_storage.cc
+++ b/chrome/browser/profiles/profile_attributes_storage.cc
@@ -245,7 +245,7 @@
 }
 
 std::vector<ProfileAttributesEntry*>
-ProfileAttributesStorage::GetAllProfilesAttributes() {
+ProfileAttributesStorage::GetAllProfilesAttributes(bool include_guest_profile) {
   std::vector<ProfileAttributesEntry*> ret;
   for (const auto& path_and_entry : profile_attributes_entries_) {
     ProfileAttributesEntry* entry;
@@ -253,7 +253,8 @@
     bool success = GetProfileAttributesWithPath(
         base::FilePath(path_and_entry.first), &entry);
     DCHECK(success);
-    ret.push_back(entry);
+    if (!entry->IsGuest() || include_guest_profile)
+      ret.push_back(entry);
   }
   return ret;
 }
@@ -261,7 +262,8 @@
 std::vector<ProfileAttributesEntry*>
 ProfileAttributesStorage::GetAllProfilesAttributesSorted(
     bool use_local_profile_name) {
-  std::vector<ProfileAttributesEntry*> ret = GetAllProfilesAttributes();
+  std::vector<ProfileAttributesEntry*> ret =
+      GetAllProfilesAttributes(/*include_guest_profile=*/false);
   // Do not allocate the collator and sort if it is not necessary.
   if (ret.size() < 2)
     return ret;
@@ -317,7 +319,8 @@
 
     // Loop through previously named profiles to ensure we're not duplicating.
     std::vector<ProfileAttributesEntry*> entries =
-        const_cast<ProfileAttributesStorage*>(this)->GetAllProfilesAttributes();
+        const_cast<ProfileAttributesStorage*>(this)->GetAllProfilesAttributes(
+            /*include_guest_profile=*/false);
 
     if (std::none_of(entries.begin(), entries.end(),
                      [name](ProfileAttributesEntry* entry) {
@@ -364,7 +367,8 @@
   std::unordered_set<size_t> used_icon_indices;
 
   std::vector<ProfileAttributesEntry*> entries =
-      const_cast<ProfileAttributesStorage*>(this)->GetAllProfilesAttributes();
+      const_cast<ProfileAttributesStorage*>(this)->GetAllProfilesAttributes(
+          /*include_guest_profile=*/false);
   for (const ProfileAttributesEntry* entry : entries)
     used_icon_indices.insert(entry->GetAvatarIconIndex());
 
@@ -423,15 +427,14 @@
 #endif
 
 void ProfileAttributesStorage::RecordProfilesState() {
-  std::vector<ProfileAttributesEntry*> entries = GetAllProfilesAttributes();
+  std::vector<ProfileAttributesEntry*> entries =
+      GetAllProfilesAttributes(/*include_guest_profile=*/false);
   if (entries.size() == 0)
     return;
 
   MultiProfileUserType type = GetMultiProfileUserType(entries);
 
   for (ProfileAttributesEntry* entry : entries) {
-    if (entry->IsGuest())
-      continue;
     RecordProfileState(entry, profile_metrics::StateSuffix::kAll);
 
     switch (type) {
diff --git a/chrome/browser/profiles/profile_attributes_storage.h b/chrome/browser/profiles/profile_attributes_storage.h
index 416fe39..67759b3 100644
--- a/chrome/browser/profiles/profile_attributes_storage.h
+++ b/chrome/browser/profiles/profile_attributes_storage.h
@@ -69,9 +69,13 @@
 
   // Returns a vector containing one attributes entry per known profile. They
   // are not sorted in any particular order.
-  std::vector<ProfileAttributesEntry*> GetAllProfilesAttributes();
+  std::vector<ProfileAttributesEntry*> GetAllProfilesAttributes(
+      bool include_guest_profile = false);
 
+  // Returns all non-Guest profile attributes sorted by name.
   std::vector<ProfileAttributesEntry*> GetAllProfilesAttributesSortedByName();
+
+  // Returns all non-Guest profile attributes sorted by local profile name.
   std::vector<ProfileAttributesEntry*>
   GetAllProfilesAttributesSortedByLocalProfilName();
 
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
index 8386804..bc4d9f31 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.html
@@ -67,6 +67,11 @@
         margin-inline-start: 0;
       }
 
+      network-list {
+        --cr-network-row-padding-bottom: 8px;
+        --cr-network-row-padding-top: 8px;
+        --cr-network-row-height: auto;
+      }
     </style>
     <template is="dom-if" if="[[!!euicc_]]" restamp>
       <div class="cellular-network-list-header esim-list-header flex">
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
index d92899e..4caf1172 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
@@ -85,6 +85,7 @@
 
 js_library("multidevice_notification_access_setup_dialog") {
   deps = [
+    ":multidevice_constants",
     "//ui/webui/resources/js:i18n_behavior",
     "//ui/webui/resources/js:web_ui_listener_behavior",
   ]
@@ -284,6 +285,7 @@
 js_library("multidevice_notification_access_setup_dialog.m") {
   sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.m.js" ]
   deps = [
+    ":multidevice_constants.m",
     "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
index 41a5a46..a02d654 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
@@ -8,6 +8,7 @@
 <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="multidevice_browser_proxy.html">
+<link rel="import" href="multidevice_constants.html">
 <link rel="import" href="../localized_link/localized_link.html">
 <link rel="import" href="../os_icons.html">
 <link rel="import" href="../../settings_shared_css.html">
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js
index 75b1d04..cba28d1 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.js
@@ -120,6 +120,11 @@
    */
   onSetupStateChanged_(setupState) {
     this.setupState_ = setupState;
+    if (this.setupState_ ===
+        NotificationAccessSetupOperationStatus.COMPLETED_SUCCESSFULLY) {
+      this.browserProxy_.setFeatureEnabledState(
+          settings.MultiDeviceFeature.PHONE_HUB_NOTIFICATIONS, true);
+    }
   },
 
   /**
diff --git a/chrome/browser/resources/settings/metrics_browser_proxy.js b/chrome/browser/resources/settings/metrics_browser_proxy.js
index 47c054ec..5d8f5f1 100644
--- a/chrome/browser/resources/settings/metrics_browser_proxy.js
+++ b/chrome/browser/resources/settings/metrics_browser_proxy.js
@@ -54,19 +54,20 @@
  * @enum {number}
  */
 export const SafetyCheckInteractions = {
-  SAFETY_CHECK_START: 0,
-  SAFETY_CHECK_UPDATES_RELAUNCH: 1,
-  SAFETY_CHECK_PASSWORDS_MANAGE: 2,
-  SAFETY_CHECK_SAFE_BROWSING_MANAGE: 3,
-  SAFETY_CHECK_EXTENSIONS_REVIEW: 4,
-  SAFETY_CHECK_CHROME_CLEANER_REBOOT: 5,
-  SAFETY_CHECK_CHROME_CLEANER_REVIEW_INFECTED_STATE: 6,
-  SAFETY_CHECK_PASSWORDS_MANAGE_THROUGH_CARET_NAVIGATION: 7,
-  SAFETY_CHECK_SAFE_BROWSING_MANAGE_THROUGH_CARET_NAVIGATION: 8,
-  SAFETY_CHECK_EXTENSIONS_REVIEW_THROUGH_CARET_NAVIGATION: 9,
-  SAFETY_CHECK_CHROME_CLEANER_CARET_NAVIGATION: 10,
+  RUN_SAFETY_CHECK: 0,
+  UPDATES_RELAUNCH: 1,
+  PASSWORDS_MANAGE_COMPROMISED_PASSWORDS: 2,
+  SAFE_BROWSING_MANAGE: 3,
+  EXTENSIONS_REVIEW: 4,
+  CHROME_CLEANER_REBOOT: 5,
+  CHROME_CLEANER_REVIEW_INFECTED_STATE: 6,
+  PASSWORDS_CARET_NAVIGATION: 7,
+  SAFE_BROWSING_CARET_NAVIGATION: 8,
+  EXTENSIONS_CARET_NAVIGATION: 9,
+  CHROME_CLEANER_CARET_NAVIGATION: 10,
+  PASSWORDS_MANAGE_WEAK_PASSWORDS: 11,
   // Leave this at the end.
-  COUNT: 11,
+  COUNT: 12,
 };
 
 /**
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_chrome_cleaner_child.js b/chrome/browser/resources/settings/safety_check_page/safety_check_chrome_cleaner_child.js
index a1b2ad5..7b5bd5a 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_chrome_cleaner_child.js
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_chrome_cleaner_child.js
@@ -196,15 +196,14 @@
     switch (this.status_) {
       case SafetyCheckChromeCleanerStatus.INFECTED:
         this.logUserInteraction_(
-            SafetyCheckInteractions
-                .SAFETY_CHECK_CHROME_CLEANER_REVIEW_INFECTED_STATE,
+            SafetyCheckInteractions.CHROME_CLEANER_REVIEW_INFECTED_STATE,
             'Settings.SafetyCheck.ChromeCleanerReviewInfectedState');
         // Navigate to Chrome cleaner UI.
         this.navigateToFoilPage_();
         break;
       case SafetyCheckChromeCleanerStatus.REBOOT_REQUIRED:
         this.logUserInteraction_(
-            SafetyCheckInteractions.SAFETY_CHECK_CHROME_CLEANER_REBOOT,
+            SafetyCheckInteractions.CHROME_CLEANER_REBOOT,
             'Settings.SafetyCheck.ChromeCleanerReboot');
         this.chromeCleanupBrowserProxy_.restartComputer();
         break;
@@ -239,7 +238,7 @@
   onRowClick_: function() {
     if (this.isRowClickable_()) {
       this.logUserInteraction_(
-          SafetyCheckInteractions.SAFETY_CHECK_CHROME_CLEANER_CARET_NAVIGATION,
+          SafetyCheckInteractions.CHROME_CLEANER_CARET_NAVIGATION,
           'Settings.SafetyCheck.ChromeCleanerCaretNavigation');
       this.navigateToFoilPage_();
     }
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_extensions_child.js b/chrome/browser/resources/settings/safety_check_page/safety_check_extensions_child.js
index c482261b..aa116d0 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_extensions_child.js
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_extensions_child.js
@@ -131,7 +131,7 @@
   onButtonClick_: function() {
     // Log click both in action and histogram.
     this.metricsBrowserProxy_.recordSafetyCheckInteractionHistogram(
-        SafetyCheckInteractions.SAFETY_CHECK_EXTENSIONS_REVIEW);
+        SafetyCheckInteractions.EXTENSIONS_REVIEW);
     this.metricsBrowserProxy_.recordAction(
         'Settings.SafetyCheck.ReviewExtensions');
     this.openExtensionsPage_();
@@ -163,8 +163,7 @@
     if (this.isRowClickable_()) {
       // Log click both in action and histogram.
       this.metricsBrowserProxy_.recordSafetyCheckInteractionHistogram(
-          SafetyCheckInteractions
-              .SAFETY_CHECK_EXTENSIONS_REVIEW_THROUGH_CARET_NAVIGATION);
+          SafetyCheckInteractions.EXTENSIONS_CARET_NAVIGATION);
       this.metricsBrowserProxy_.recordAction(
           'Settings.SafetyCheck.ReviewExtensionsThroughCaretNavigation');
       this.openExtensionsPage_();
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_page.js b/chrome/browser/resources/settings/safety_check_page/safety_check_page.js
index cb7e141e..24f7499 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_page.js
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_page.js
@@ -105,7 +105,7 @@
   runSafetyCheck_: function() {
     // Log click both in action and histogram.
     this.metricsBrowserProxy_.recordSafetyCheckInteractionHistogram(
-        SafetyCheckInteractions.SAFETY_CHECK_START);
+        SafetyCheckInteractions.RUN_SAFETY_CHECK);
     this.metricsBrowserProxy_.recordAction('Settings.SafetyCheck.Start');
 
     // Trigger safety check.
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_passwords_child.js b/chrome/browser/resources/settings/safety_check_page/safety_check_passwords_child.js
index c4337ce9a..60337a7 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_passwords_child.js
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_passwords_child.js
@@ -135,7 +135,7 @@
   onButtonClick_: function() {
     // Log click both in action and histogram.
     this.metricsBrowserProxy_.recordSafetyCheckInteractionHistogram(
-        SafetyCheckInteractions.SAFETY_CHECK_PASSWORDS_MANAGE);
+        SafetyCheckInteractions.PASSWORDS_MANAGE_COMPROMISED_PASSWORDS);
     this.metricsBrowserProxy_.recordAction(
         'Settings.SafetyCheck.ManagePasswords');
     this.openPasswordCheckPage_();
@@ -154,10 +154,13 @@
     if (this.isRowClickable_()) {
       // Log click both in action and histogram.
       this.metricsBrowserProxy_.recordSafetyCheckInteractionHistogram(
-          SafetyCheckInteractions
-              .SAFETY_CHECK_PASSWORDS_MANAGE_THROUGH_CARET_NAVIGATION);
+          this.status_ === SafetyCheckPasswordsStatus.WEAK_PASSWORDS_EXIST ?
+              SafetyCheckInteractions.PASSWORDS_MANAGE_WEAK_PASSWORDS :
+              SafetyCheckInteractions.PASSWORDS_CARET_NAVIGATION);
       this.metricsBrowserProxy_.recordAction(
-          'Settings.SafetyCheck.ManagePasswordsThroughCaretNavigation');
+          this.status_ === SafetyCheckPasswordsStatus.WEAK_PASSWORDS_EXIST ?
+              'Settings.SafetyCheck.ManageWeakPasswords' :
+              'Settings.SafetyCheck.ManagePasswordsThroughCaretNavigation');
       this.openPasswordCheckPage_();
     }
   },
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_safe_browsing_child.js b/chrome/browser/resources/settings/safety_check_page/safety_check_safe_browsing_child.js
index 28667ec..2f4630c1 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_safe_browsing_child.js
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_safe_browsing_child.js
@@ -134,7 +134,7 @@
   onButtonClick_: function() {
     // Log click both in action and histogram.
     this.metricsBrowserProxy_.recordSafetyCheckInteractionHistogram(
-        SafetyCheckInteractions.SAFETY_CHECK_SAFE_BROWSING_MANAGE);
+        SafetyCheckInteractions.SAFE_BROWSING_MANAGE);
     this.metricsBrowserProxy_.recordAction(
         'Settings.SafetyCheck.ManageSafeBrowsing');
     this.openSecurityPage_();
@@ -168,8 +168,7 @@
     if (this.isRowClickable_()) {
       // Log click both in action and histogram.
       this.metricsBrowserProxy_.recordSafetyCheckInteractionHistogram(
-          SafetyCheckInteractions
-              .SAFETY_CHECK_SAFE_BROWSING_MANAGE_THROUGH_CARET_NAVIGATION);
+          SafetyCheckInteractions.SAFE_BROWSING_CARET_NAVIGATION);
       this.metricsBrowserProxy_.recordAction(
           'Settings.SafetyCheck.ManageSafeBrowsingThroughCaretNavigation');
       this.openSecurityPage_();
diff --git a/chrome/browser/resources/settings/safety_check_page/safety_check_updates_child.js b/chrome/browser/resources/settings/safety_check_page/safety_check_updates_child.js
index cddfc3c8..a2cad3fc 100644
--- a/chrome/browser/resources/settings/safety_check_page/safety_check_updates_child.js
+++ b/chrome/browser/resources/settings/safety_check_page/safety_check_updates_child.js
@@ -119,7 +119,7 @@
   onButtonClick_: function() {
     // Log click both in action and histogram.
     this.metricsBrowserProxy_.recordSafetyCheckInteractionHistogram(
-        SafetyCheckInteractions.SAFETY_CHECK_UPDATES_RELAUNCH);
+        SafetyCheckInteractions.UPDATES_RELAUNCH);
     this.metricsBrowserProxy_.recordAction(
         'Settings.SafetyCheck.RelaunchAfterUpdates');
 
diff --git a/chrome/browser/ssl/typed_navigation_upgrade_throttle.cc b/chrome/browser/ssl/typed_navigation_upgrade_throttle.cc
index b3c60e6..86cbdf1 100644
--- a/chrome/browser/ssl/typed_navigation_upgrade_throttle.cc
+++ b/chrome/browser/ssl/typed_navigation_upgrade_throttle.cc
@@ -25,13 +25,14 @@
 
 namespace {
 
-// Delay in milliseconds before falling back to the HTTP URL.
+// Delay before falling back to the HTTP URL.
 // This can be changed in tests.
 // - If the HTTPS load finishes successfully during this time, the timer is
 //   cleared and no more work is done.
 // - Otherwise, a new navigation to the the fallback HTTP URL is started.
 constexpr base::FeatureParam<base::TimeDelta> kFallbackDelay{
-    &omnibox::kDefaultTypedNavigationsToHttps, "timeout",
+    &omnibox::kDefaultTypedNavigationsToHttps,
+    omnibox::kDefaultTypedNavigationsToHttpsTimeoutParam,
     base::TimeDelta::FromSeconds(3)};
 
 bool IsNavigationUsingHttpsAsDefaultScheme(content::NavigationHandle* handle) {
diff --git a/chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc b/chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc
index 025696b..6e8d295 100644
--- a/chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc
+++ b/chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc
@@ -92,7 +92,7 @@
     std::vector<base::Feature> disabled_features;
     if (IsFeatureEnabled()) {
       base::FieldTrialParams params;
-      params["timeout"] =
+      params[omnibox::kDefaultTypedNavigationsToHttpsTimeoutParam] =
           base::NumberToString(fallback_delay.InMilliseconds()) + "ms";
       enabled_features.emplace_back(omnibox::kDefaultTypedNavigationsToHttps,
                                     params);
diff --git a/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc b/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
index a6f8c85..9b9af29b 100644
--- a/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
+++ b/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
@@ -29,7 +29,6 @@
 #include "components/subresource_filter/core/common/activation_decision.h"
 #include "components/subresource_filter/core/common/activation_scope.h"
 #include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
-#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
@@ -98,18 +97,6 @@
   }
 }
 
-subresource_filter::mojom::ActivationLevel
-ChromeSubresourceFilterClient::OnPageActivationComputed(
-    content::NavigationHandle* navigation_handle,
-    subresource_filter::mojom::ActivationLevel initial_activation_level,
-    subresource_filter::ActivationDecision* decision) {
-  // TODO(crbug.com/1116095): Once SafeBrowsingActivationThrottle knows about
-  // ProfileInteractionManager, it can invoke ProfileInteractionManager directly
-  // and SubresourceFilterClient::OnPageActivationComputed() can be eliminated.
-  return profile_interaction_manager_->OnPageActivationComputed(
-      navigation_handle, initial_activation_level, decision);
-}
-
 void ChromeSubresourceFilterClient::OnAdsViolationTriggered(
     content::RenderFrameHost* rfh,
     subresource_filter::mojom::AdsViolation triggered_violation) {
@@ -129,6 +116,11 @@
                                : nullptr;
 }
 
+subresource_filter::ProfileInteractionManager*
+ChromeSubresourceFilterClient::GetProfileInteractionManager() {
+  return profile_interaction_manager_.get();
+}
+
 void ChromeSubresourceFilterClient::ShowUI(const GURL& url) {
 #if defined(OS_ANDROID)
   InfoBarService* infobar_service =
diff --git a/chrome/browser/subresource_filter/chrome_subresource_filter_client.h b/chrome/browser/subresource_filter/chrome_subresource_filter_client.h
index 0ef6322..72ee4a3 100644
--- a/chrome/browser/subresource_filter/chrome_subresource_filter_client.h
+++ b/chrome/browser/subresource_filter/chrome_subresource_filter_client.h
@@ -15,13 +15,11 @@
 class GURL;
 
 namespace content {
-class NavigationHandle;
 class WebContents;
 }  // namespace content
 
 namespace subresource_filter {
 class ContentSubresourceFilterThrottleManager;
-class ProfileInteractionManager;
 class SubresourceFilterProfileContext;
 }  // namespace subresource_filter
 
@@ -47,15 +45,13 @@
 
   // SubresourceFilterClient:
   void ShowNotification() override;
-  subresource_filter::mojom::ActivationLevel OnPageActivationComputed(
-      content::NavigationHandle* navigation_handle,
-      subresource_filter::mojom::ActivationLevel initial_activation_level,
-      subresource_filter::ActivationDecision* decision) override;
   void OnAdsViolationTriggered(
       content::RenderFrameHost* rfh,
       subresource_filter::mojom::AdsViolation triggered_violation) override;
   const scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
   GetSafeBrowsingDatabaseManager() override;
+  subresource_filter::ProfileInteractionManager* GetProfileInteractionManager()
+      override;
   void OnReloadRequested() override;
 
  private:
diff --git a/chrome/browser/subresource_redirect/subresource_redirect_observer.cc b/chrome/browser/subresource_redirect/subresource_redirect_observer.cc
index b3aed23..f3aa2e7c 100644
--- a/chrome/browser/subresource_redirect/subresource_redirect_observer.cc
+++ b/chrome/browser/subresource_redirect/subresource_redirect_observer.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/subresource_redirect/subresource_redirect_observer.h"
 
+#include "chrome/browser/login_detection/login_detection_keyed_service.h"
+#include "chrome/browser/login_detection/login_detection_keyed_service_factory.h"
+#include "chrome/browser/login_detection/login_detection_type.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -145,13 +148,33 @@
                                               navigation_handle)) {
     return;
   }
+  content::RenderFrameHost* render_frame_host =
+      navigation_handle->GetRenderFrameHost();
 
   // Handle login robots based compression mode.
   if (ShouldEnableLoginRobotsCheckedCompression()) {
+    auto* login_detection_keyed_service =
+        login_detection::LoginDetectionKeyedServiceFactory::GetForProfile(
+            Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
+    if (!login_detection_keyed_service)
+      return;
+
+    is_https_image_compression_applied_ =
+        login_detection_keyed_service->GetPersistentLoginDetection(
+            navigation_handle->GetURL()) ==
+        login_detection::LoginDetectionType::kNoLogin;
+
+    mojo::AssociatedRemote<mojom::SubresourceRedirectHintsReceiver>
+        hints_receiver;
+    render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
+        &hints_receiver);
+    hints_receiver->SetLoggedInState(!is_https_image_compression_applied_);
+
+    if (!is_https_image_compression_applied_)
+      return;
+
     SubresourceRedirectDocumentHost::GetOrCreateForCurrentDocument(
         navigation_handle->GetRenderFrameHost());
-    // TODO(1149853): Handle whether page is logged-in and disable compression.
-    is_https_image_compression_applied_ = true;
     return;
   }
 
@@ -163,8 +186,6 @@
   if (!optimization_guide_decider)
     return;
 
-  content::RenderFrameHost* render_frame_host =
-      navigation_handle->GetRenderFrameHost();
   optimization_guide_decider->CanApplyOptimizationAsync(
       navigation_handle, optimization_guide::proto::COMPRESS_PUBLIC_IMAGES,
       base::BindOnce(
diff --git a/chrome/browser/ui/extensions/extensions_container.h b/chrome/browser/ui/extensions/extensions_container.h
index f00cfe1..1e3b45d 100644
--- a/chrome/browser/ui/extensions/extensions_container.h
+++ b/chrome/browser/ui/extensions/extensions_container.h
@@ -76,7 +76,11 @@
   virtual void ShowToolbarActionBubbleAsync(
       std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) = 0;
 
+  // Toggle the Extensions menu (as if the user clicked the puzzle piece icon).
   virtual void ToggleExtensionsMenu() = 0;
+
+  // Whether there are any Extensions registered with the ExtensionsContainer.
+  virtual bool HasAnyExtensions() const = 0;
 };
 
 #endif  // CHROME_BROWSER_UI_EXTENSIONS_EXTENSIONS_CONTAINER_H_
diff --git a/chrome/browser/ui/profile_picker.cc b/chrome/browser/ui/profile_picker.cc
index 6460acd..d5800f2 100644
--- a/chrome/browser/ui/profile_picker.cc
+++ b/chrome/browser/ui/profile_picker.cc
@@ -71,13 +71,13 @@
     return false;
 
   std::vector<ProfileAttributesEntry*> profile_attributes =
-      profile_manager->GetProfileAttributesStorage().GetAllProfilesAttributes();
+      profile_manager->GetProfileAttributesStorage().GetAllProfilesAttributes(
+          /*include_guest_profile=*/false);
   int number_of_active_profiles =
       std::count_if(profile_attributes.begin(), profile_attributes.end(),
                     [](ProfileAttributesEntry* entry) {
                       return (base::Time::Now() - entry->GetActiveTime() <
-                              kActiveTimeThreshold) &&
-                             !entry->IsGuest();
+                              kActiveTimeThreshold);
                     });
   // Don't show the profile picker at launch if the user has less than two
   // active profiles. However, if the user has already seen the profile picker
diff --git a/chrome/browser/ui/thumbnails/OWNERS b/chrome/browser/ui/thumbnails/OWNERS
index 3a92f21..f71a866 100644
--- a/chrome/browser/ui/thumbnails/OWNERS
+++ b/chrome/browser/ui/thumbnails/OWNERS
@@ -1,2 +1,6 @@
+# Primary
 dfried@chromium.org
+collinbaker@chromium.org
+
+# Backup
 pbos@chromium.org
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
index 77768513..4ca4d53 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
@@ -608,10 +608,17 @@
 
 void ToolbarActionsBar::ToggleExtensionsMenu() {
   // This is only implemented by |ExtensionsToolbarContainer|.
-  // TODO(crbug.com/943702): Remove this entire class.
+  // TODO(crbug.com/1165609): Remove this entire class.
   NOTREACHED();
 }
 
+bool ToolbarActionsBar::HasAnyExtensions() const {
+  // This is only implemented by |ExtensionsToolbarContainer|.
+  // TODO(crbug.com/1165609): Remove this entire class.
+  NOTREACHED();
+  return false;
+}
+
 bool ToolbarActionsBar::CloseOverflowMenuIfOpen() {
   return delegate_->CloseOverflowMenuIfOpen();
 }
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.h b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
index ccf320e..2797c65 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
@@ -249,6 +249,7 @@
   void ShowToolbarActionBubbleAsync(
       std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
   void ToggleExtensionsMenu() override;
+  bool HasAnyExtensions() const override;
 
  private:
   // Returns the insets by which the icon area bounds (See GetIconAreaRect())
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_button.h b/chrome/browser/ui/views/extensions/extensions_toolbar_button.h
index 3351f7e..e31bafa28 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_button.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_button.h
@@ -26,8 +26,8 @@
   ExtensionsToolbarButton& operator=(const ExtensionsToolbarButton&) = delete;
   ~ExtensionsToolbarButton() override;
 
-  // Activate the Extensions menu. If the ExtensionsToolbarContainer is in
-  // kAutoHide mode this will cause it to show.
+  // Toggle the Extensions menu. If the ExtensionsToolbarContainer is in
+  // kAutoHide mode and hidden this will cause it to show.
   void ToggleExtensionsMenu();
 
   bool IsExtensionsMenuShowing() const;
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 84d6c8b..b7bad1f 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -391,6 +391,10 @@
   extensions_button_->ToggleExtensionsMenu();
 }
 
+bool ExtensionsToolbarContainer::HasAnyExtensions() const {
+  return !actions_.empty();
+}
+
 void ExtensionsToolbarContainer::OnTabStripModelChanged(
     TabStripModel* tab_strip_model,
     const TabStripModelChange& change,
@@ -407,7 +411,11 @@
     int index) {
   CreateActionForId(action_id);
   ReorderViews();
-  UpdateContainerVisibility();
+
+  // Auto hide mode should not become visible due to extensions being added,
+  // only due to user interaction.
+  if (display_mode_ != DisplayMode::kAutoHide)
+    UpdateContainerVisibility();
 }
 
 void ExtensionsToolbarContainer::OnToolbarActionRemoved(
@@ -712,7 +720,7 @@
 bool ExtensionsToolbarContainer::ShouldContainerBeVisible() const {
   // The container (and extensions-menu button) should not be visible if we have
   // no extensions.
-  if (actions_.empty())
+  if (!HasAnyExtensions())
     return false;
 
   // All other display modes are constantly visible.
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index c2f0bc5..2a9d6b62 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -54,13 +54,13 @@
     // be hidden if the available space does not allow for them. Compact mode is
     // used in smaller windows (e.g. web apps) where
     // there may not be enough space to display the buttons.
+    // TODO(crbug.com/1155421): Remove kCompact in favour of kAutoHide once the
+    // |kDesktopPWAsElidedExtensionsMenu| flag is removed.
     kCompact,
     // In auto hide mode the menu icon is hidden until
     // extensions_button()->ToggleExtensionsMenu() is called by the embedder.
-    // This
-    // is used for windows that want to minimize the number of visible icons in
-    // their
-    // toolbar (e.g. web apps).
+    // This is used for windows that want to minimize the number of visible
+    // icons in their toolbar (e.g. web apps).
     kAutoHide,
   };
 
@@ -141,6 +141,7 @@
   void ShowToolbarActionBubbleAsync(
       std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
   void ToggleExtensionsMenu() override;
+  bool HasAnyExtensions() const override;
 
   // ToolbarActionView::Delegate:
   content::WebContents* GetCurrentWebContents() override;
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view.cc b/chrome/browser/ui/views/profiles/profile_menu_view.cc
index ae0f53c..b667b84 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view.cc
@@ -695,11 +695,9 @@
                              ->GetProfileAttributesStorage()
                              .GetAllProfilesAttributesSortedByName();
   for (ProfileAttributesEntry* profile_entry : profile_entries) {
-    // Guest profile and the current profile are excluded.
-    if (profile_entry->IsGuest() ||
-        profile_entry->GetPath() == browser()->profile()->GetPath()) {
+    // The current profile is excluded.
+    if (profile_entry->GetPath() == browser()->profile()->GetPath())
       continue;
-    }
 
     AddSelectableProfile(
         ui::ImageModel::FromImage(
diff --git a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc
index 4589dba..2af1946d 100644
--- a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc
@@ -97,6 +97,19 @@
     return &web_app_frame_toolbar_helper_;
   }
 
+  bool IsMenuCommandEnabled(int command_id) {
+    auto app_menu_model = std::make_unique<WebAppMenuModel>(
+        /*provider=*/nullptr, helper()->app_browser());
+    app_menu_model->Init();
+    ui::MenuModel* model = app_menu_model.get();
+    int index = -1;
+    if (!app_menu_model->GetModelAndIndexForCommandId(command_id, &model,
+                                                      &index)) {
+      return false;
+    }
+    return model->IsEnabledAt(index);
+  }
+
  private:
   net::EmbeddedTestServer https_server_;
   WebAppFrameToolbarTestHelper web_app_frame_toolbar_helper_;
@@ -311,26 +324,27 @@
 
 IN_PROC_BROWSER_TEST_F(WebAppFrameToolbarBrowserTest_ElidedExtensionsMenu,
                        Test) {
-  LoadTestPopUpExtension(browser()->profile());
   helper()->InstallAndLaunchWebApp(browser(), GURL("https://test.org"));
 
-  WebAppToolbarButtonContainer* toolbar_button_container =
-      helper()->web_app_frame_toolbar()->get_right_container_for_testing();
+  // There should be no menu entry for opening the Extensions menu prior to
+  // installing Extensions.
+  EXPECT_FALSE(IsMenuCommandEnabled(WebAppMenuModel::kExtensionsMenuCommandId));
+
+  // Install test Extension.
+  LoadTestPopUpExtension(browser()->profile());
 
   // There should be no visible Extensions icon.
+  WebAppToolbarButtonContainer* toolbar_button_container =
+      helper()->web_app_frame_toolbar()->get_right_container_for_testing();
   EXPECT_FALSE(toolbar_button_container->extensions_container()->GetVisible());
 
   // There should be a menu entry for opening the Extensions menu.
+  EXPECT_TRUE(IsMenuCommandEnabled(WebAppMenuModel::kExtensionsMenuCommandId));
+
+  // Trigger the Extensions menu entry.
   auto app_menu_model = std::make_unique<WebAppMenuModel>(
       /*provider=*/nullptr, helper()->app_browser());
   app_menu_model->Init();
-  ui::MenuModel* model = app_menu_model.get();
-  int index = -1;
-  const bool found = app_menu_model->GetModelAndIndexForCommandId(
-      WebAppMenuModel::kExtensionsMenuCommandId, &model, &index);
-  EXPECT_TRUE(found);
-  EXPECT_TRUE(model->IsEnabledAt(index));
-
   app_menu_model->ExecuteCommand(WebAppMenuModel::kExtensionsMenuCommandId,
                                  /*event_flags=*/0);
 
@@ -371,12 +385,5 @@
   EXPECT_TRUE(toolbar_button_container->extensions_container()->GetVisible());
 
   // There should be no menu entry for opening the Extensions menu.
-  auto app_menu_model = std::make_unique<WebAppMenuModel>(
-      /*provider=*/nullptr, helper()->app_browser());
-  app_menu_model->Init();
-  ui::MenuModel* model = app_menu_model.get();
-  int index = -1;
-  const bool found = app_menu_model->GetModelAndIndexForCommandId(
-      WebAppMenuModel::kExtensionsMenuCommandId, &model, &index);
-  EXPECT_FALSE(found);
+  EXPECT_FALSE(IsMenuCommandEnabled(WebAppMenuModel::kExtensionsMenuCommandId));
 }
diff --git a/chrome/browser/ui/web_applications/web_app_menu_model.cc b/chrome/browser/ui/web_applications/web_app_menu_model.cc
index 0e00a55..0b94013 100644
--- a/chrome/browser/ui/web_applications/web_app_menu_model.cc
+++ b/chrome/browser/ui/web_applications/web_app_menu_model.cc
@@ -47,7 +47,8 @@
     case kExtensionsMenuCommandId:
       return base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu) &&
              base::FeatureList::IsEnabled(
-                 features::kDesktopPWAsElidedExtensionsMenu);
+                 features::kDesktopPWAsElidedExtensionsMenu) &&
+             browser()->window()->GetExtensionsContainer()->HasAnyExtensions();
     default:
       return AppMenuModel::IsCommandIdEnabled(command_id);
   }
diff --git a/chrome/browser/ui/webui/read_later/read_later_ui.cc b/chrome/browser/ui/webui/read_later/read_later_ui.cc
index 87ad0a0..b43cd929 100644
--- a/chrome/browser/ui/webui/read_later/read_later_ui.cc
+++ b/chrome/browser/ui/webui/read_later/read_later_ui.cc
@@ -34,7 +34,10 @@
 }  // namespace
 
 ReadLaterUI::ReadLaterUI(content::WebUI* web_ui)
-    : ui::MojoBubbleWebUIController(web_ui) {
+    : ui::MojoBubbleWebUIController(web_ui),
+      webui_load_timer_(web_ui->GetWebContents(),
+                        "ReadingList.WebUI.LoadDocumentTime",
+                        "ReadingList.WebUI.LoadCompletedTime") {
   content::WebUIDataSource* source =
       content::WebUIDataSource::Create(chrome::kChromeUIReadLaterHost);
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
diff --git a/chrome/browser/ui/webui/read_later/read_later_ui.h b/chrome/browser/ui/webui/read_later/read_later_ui.h
index 5532f286..86ddb50 100644
--- a/chrome/browser/ui/webui/read_later/read_later_ui.h
+++ b/chrome/browser/ui/webui/read_later/read_later_ui.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "chrome/browser/ui/webui/read_later/read_later.mojom.h"
+#include "chrome/browser/ui/webui/webui_load_timer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -40,6 +41,8 @@
   mojo::Receiver<read_later::mojom::PageHandlerFactory> page_factory_receiver_{
       this};
 
+  WebuiLoadTimer webui_load_timer_;
+
   WEB_UI_CONTROLLER_TYPE_DECL();
 };
 
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.cc b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
index b33a898..0432221 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
@@ -637,9 +637,6 @@
   // Vector of nullptr entries.
   std::vector<ProfileAttributesEntry*> entries(number_of_profiles);
   for (ProfileAttributesEntry* entry : ordered_entries) {
-    if (entry->IsGuest())
-      continue;
-
     DCHECK(profiles_order_.find(entry->GetPath()) != profiles_order_.end());
     size_t index = profiles_order_[entry->GetPath()];
     DCHECK_LT(index, number_of_profiles);
@@ -655,8 +652,6 @@
   const int avatar_icon_size =
       kProfileCardAvatarSize * web_ui()->GetDeviceScaleFactor();
   for (const ProfileAttributesEntry* entry : entries) {
-    if (entry->IsGuest())
-      continue;
     auto profile_entry = std::make_unique<base::DictionaryValue>();
     profile_entry->SetKey("profilePath",
                           util::FilePathToValue(entry->GetPath()));
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.h b/chrome/browser/ui/webui/signin/profile_picker_handler.h
index 61e1eb9..1966cb08 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.h
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.h
@@ -91,7 +91,7 @@
   void SetProfilesOrder(const std::vector<ProfileAttributesEntry*>& entries);
 
   // Returns the list of profiles in the same order as when the picker
-  // was first shown.
+  // was first shown. Guest profile is not included here.
   std::vector<ProfileAttributesEntry*> GetProfileAttributes();
 
   // Creation time of the handler, to measure performance on startup. Only set
diff --git a/chrome/browser/vr/base_scheduler_delegate_unittest.cc b/chrome/browser/vr/base_scheduler_delegate_unittest.cc
index 363d56c..903c99249 100644
--- a/chrome/browser/vr/base_scheduler_delegate_unittest.cc
+++ b/chrome/browser/vr/base_scheduler_delegate_unittest.cc
@@ -54,8 +54,7 @@
   void SetUp() override {
     test_task_runner_ =
         base::WrapRefCounted(new base::TestMockTimeTaskRunner());
-    on_destroy_ =
-        base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner_);
+    task_runner_handle_override_.emplace(test_task_runner_);
   }
 
   void FastForwardBy(base::TimeDelta delta) {
@@ -64,7 +63,8 @@
 
  private:
   scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
-  base::ScopedClosureRunner on_destroy_;
+  base::Optional<base::ThreadTaskRunnerHandleOverrideForTesting>
+      task_runner_handle_override_;
 };
 
 TEST_F(SchedulerDelegateTest, NoTimeoutWhenWebXrFrameArrivesFast) {
diff --git a/chrome/browser/web_applications/components/external_install_options.cc b/chrome/browser/web_applications/components/external_install_options.cc
index 434ecaa..833d7b5 100644
--- a/chrome/browser/web_applications/components/external_install_options.cc
+++ b/chrome/browser/web_applications/components/external_install_options.cc
@@ -61,6 +61,7 @@
         options.bypass_service_worker_check,
         options.require_manifest,
         options.force_reinstall,
+        options.force_reinstall_for_milestone,
         options.wait_for_windows_closed,
         options.install_placeholder,
         options.reinstall_placeholder,
@@ -133,6 +134,8 @@
          << install_options.bypass_service_worker_check
          << "\n require_manifest: " << install_options.require_manifest
          << "\n force_reinstall: " << install_options.force_reinstall
+         << "\n force_reinstall_for_milestone: "
+         << install_options.force_reinstall_for_milestone
          << "\n wait_for_windows_closed: "
          << install_options.wait_for_windows_closed
          << "\n install_placeholder: " << install_options.install_placeholder
diff --git a/chrome/browser/web_applications/components/external_install_options.h b/chrome/browser/web_applications/components/external_install_options.h
index 7031fc9a..836b0f9 100644
--- a/chrome/browser/web_applications/components/external_install_options.h
+++ b/chrome/browser/web_applications/components/external_install_options.h
@@ -109,6 +109,12 @@
   // Whether the app should be reinstalled even if it is already installed.
   bool force_reinstall = false;
 
+  // Whether we should update the app if the browser's binary milestone number
+  // goes from less the milestone specified to greater or equal than the
+  // milestone specified. For example, if this value is 89 then we update the
+  // app on all browser upgrades from <89 to >=89. The update happens only once.
+  base::Optional<int> force_reinstall_for_milestone;
+
   // Whether we should wait for all app windows being closed before reinstalling
   // the placeholder.
   bool wait_for_windows_closed = false;
diff --git a/chrome/browser/web_applications/external_web_app_manager.cc b/chrome/browser/web_applications/external_web_app_manager.cc
index 4d102e6..5caea6046 100644
--- a/chrome/browser/web_applications/external_web_app_manager.cc
+++ b/chrome/browser/web_applications/external_web_app_manager.cc
@@ -392,6 +392,16 @@
     debug_info_->enabled_configs = parsed_configs.options_list;
   }
 
+  // Triggers |force_reinstall| in PendingAppManager if milestone increments
+  // across |force_reinstall_for_milestone|.
+  for (ExternalInstallOptions& options : parsed_configs.options_list) {
+    if (options.force_reinstall_for_milestone &&
+        IsReinstallPastMilestoneNeededSinceLastSync(
+            options.force_reinstall_for_milestone.value())) {
+      options.force_reinstall = true;
+    }
+  }
+
   UMA_HISTOGRAM_COUNTS_100(kHistogramEnabledCount,
                            parsed_configs.options_list.size());
   UMA_HISTOGRAM_COUNTS_100(kHistogramDisabledCount, disabled_count);
@@ -417,8 +427,8 @@
     PendingAppManager::SynchronizeCallback callback,
     std::map<GURL, PendingAppManager::InstallResult> install_results,
     std::map<GURL, bool> uninstall_results) {
-  // Note that we are storing the Chrome version instead of a "has synchronised"
-  // bool in order to do version update specific logic in the future.
+  // Note that we are storing the Chrome version (milestone number) instead of a
+  // "has synchronised" bool in order to do version update specific logic.
   profile_->GetPrefs()->SetString(
       prefs::kWebAppsLastPreinstallSynchronizeVersion,
       version_info::GetMajorVersionNumber());
@@ -491,6 +501,17 @@
   return ExternallyInstalledWebAppPrefs(prefs).HasNoApps();
 }
 
+bool ExternalWebAppManager::IsReinstallPastMilestoneNeededSinceLastSync(
+    int force_reinstall_for_milestone) {
+  PrefService* prefs = profile_->GetPrefs();
+  std::string last_preinstall_synchronize_milestone =
+      prefs->GetString(prefs::kWebAppsLastPreinstallSynchronizeVersion);
+
+  return IsReinstallPastMilestoneNeeded(last_preinstall_synchronize_milestone,
+                                        version_info::GetMajorVersionNumber(),
+                                        force_reinstall_for_milestone);
+}
+
 ExternalWebAppManager::DebugInfo::DebugInfo() = default;
 
 ExternalWebAppManager::DebugInfo::~DebugInfo() = default;
diff --git a/chrome/browser/web_applications/external_web_app_manager.h b/chrome/browser/web_applications/external_web_app_manager.h
index f8837f5f..0f74073 100644
--- a/chrome/browser/web_applications/external_web_app_manager.h
+++ b/chrome/browser/web_applications/external_web_app_manager.h
@@ -119,6 +119,11 @@
   // profile.
   bool IsNewUser();
 
+  // |force_reinstall_for_milestone| is a major version number. See
+  // components/version_info/version_info.h.
+  bool IsReinstallPastMilestoneNeededSinceLastSync(
+      int force_reinstall_for_milestone);
+
   PendingAppManager* pending_app_manager_ = nullptr;
   Profile* const profile_;
 
diff --git a/chrome/browser/web_applications/external_web_app_utils.cc b/chrome/browser/web_applications/external_web_app_utils.cc
index 643b6c53..2032be7 100644
--- a/chrome/browser/web_applications/external_web_app_utils.cc
+++ b/chrome/browser/web_applications/external_web_app_utils.cc
@@ -148,6 +148,10 @@
 //   "theme_color": "red"
 constexpr char kOfflineManifestThemeColorArgbHex[] = "theme_color_argb_hex";
 
+// Contains numeric milestone number M like 89 (the Chrome version). The app
+// gets updated if browser's binary milestone number goes from <M to >=M.
+constexpr char kForceReinstallForMilestone[] = "force_reinstall_for_milestone";
+
 }  // namespace
 
 OptionsOrError ParseConfig(FileUtilsWrapper& file_utils,
@@ -356,6 +360,16 @@
                          " set with no ", kOfflineManifest, " available"});
   }
 
+  // force_reinstall_for_milestone
+  value = app_config.FindKey(kForceReinstallForMilestone);
+  if (value) {
+    if (!value->is_int()) {
+      return base::StrCat({file.AsUTF8Unsafe(), " had an invalid ",
+                           kForceReinstallForMilestone});
+    }
+    options.force_reinstall_for_milestone = value->GetInt();
+  }
+
   return options;
 }
 
@@ -495,4 +509,23 @@
       std::move(app_info));
 }
 
+bool IsReinstallPastMilestoneNeeded(
+    base::StringPiece last_preinstall_synchronize_milestone_str,
+    base::StringPiece current_milestone_str,
+    int force_reinstall_for_milestone) {
+  int last_preinstall_synchronize_milestone = 0;
+  if (!base::StringToInt(last_preinstall_synchronize_milestone_str,
+                         &last_preinstall_synchronize_milestone)) {
+    return false;
+  }
+
+  int current_milestone = 0;
+  if (!base::StringToInt(current_milestone_str, &current_milestone))
+    return false;
+
+  return last_preinstall_synchronize_milestone <
+             force_reinstall_for_milestone &&
+         current_milestone >= force_reinstall_for_milestone;
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/external_web_app_utils.h b/chrome/browser/web_applications/external_web_app_utils.h
index dd735f4..9d689be 100644
--- a/chrome/browser/web_applications/external_web_app_utils.h
+++ b/chrome/browser/web_applications/external_web_app_utils.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/optional.h"
+#include "base/strings/string_piece.h"
 #include "chrome/browser/web_applications/components/external_install_options.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 
@@ -36,6 +37,16 @@
     const base::FilePath& file,
     const base::Value& offline_manifest);
 
+// Returns true if we need to update the app. |current_milestone_str| is the
+// browser's binary milestone number.
+// |last_preinstall_synchronize_milestone_str| is a previous milestone for the
+// user's data state. For example, if |force_reinstall_for_milestone| value is
+// 89 then we need to update the app on all browser upgrades from <89 to >=89.
+bool IsReinstallPastMilestoneNeeded(
+    base::StringPiece last_preinstall_synchronize_milestone_str,
+    base::StringPiece current_milestone_str,
+    int force_reinstall_for_milestone);
+
 }  // namespace web_app
 
 #endif  // CHROME_BROWSER_WEB_APPLICATIONS_EXTERNAL_WEB_APP_UTILS_H_
diff --git a/chrome/browser/web_applications/external_web_app_utils_unittest.cc b/chrome/browser/web_applications/external_web_app_utils_unittest.cc
index 6e8a832..d4bf053 100644
--- a/chrome/browser/web_applications/external_web_app_utils_unittest.cc
+++ b/chrome/browser/web_applications/external_web_app_utils_unittest.cc
@@ -443,4 +443,51 @@
   )")) << "theme_color_argb_hex is valid";
 }
 
+TEST_F(ExternalWebAppUtilsTest, ForceReinstallForMilestone) {
+  base::Optional<ExternalInstallOptions> non_number = ParseConfig(R"(
+    {
+      "app_url": "https://test.org",
+      "launch_container": "window",
+      "force_reinstall_for_milestone": "error",
+      "user_type": ["test"]
+    }
+  )");
+  EXPECT_FALSE(non_number.has_value());
+
+  base::Optional<ExternalInstallOptions> number = ParseConfig(R"(
+    {
+      "app_url": "https://test.org",
+      "launch_container": "window",
+      "force_reinstall_for_milestone": 89,
+      "user_type": ["test"]
+    }
+  )");
+  EXPECT_TRUE(number.has_value());
+  EXPECT_EQ(89, number->force_reinstall_for_milestone);
+}
+
+TEST_F(ExternalWebAppUtilsTest, IsReinstallPastMilestoneNeeded) {
+  // Arguments: last_preinstall_synchronize_milestone, current_milestone,
+  // force_reinstall_for_milestone.
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("87", "87", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("87", "88", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("88", "88", 89));
+  EXPECT_TRUE(IsReinstallPastMilestoneNeeded("88", "89", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("89", "89", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("89", "90", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("90", "90", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("90", "91", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("91", "91", 89));
+
+  // Long jumps:
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("80", "85", 89));
+  EXPECT_TRUE(IsReinstallPastMilestoneNeeded("80", "100", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("90", "95", 89));
+
+  // Wrong input:
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("error", "90", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("88", "error", 89));
+  EXPECT_FALSE(IsReinstallPastMilestoneNeeded("error", "error", 0));
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_mover.h b/chrome/browser/web_applications/web_app_mover.h
index 04d8685..b0ff80a5 100644
--- a/chrome/browser/web_applications/web_app_mover.h
+++ b/chrome/browser/web_applications/web_app_mover.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/callback.h"
+#include "base/callback_helpers.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
@@ -107,4 +108,4 @@
 
 }  // namespace web_app
 
-#endif  // CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_MOVER_H_
\ No newline at end of file
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_MOVER_H_
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 05e0781..6124f62 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-master-1610408110-1b25c741a858a17df9d643531610232aedd2e5da.profdata
+chrome-linux-master-1610431066-b046cf295bac84ed9eefc440cb75a6df945c21fe.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 2f4b4b7..ffd6067 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1610408110-5a618b6e0bdb5b4a60796f5aa8c6f6759f78cdd3.profdata
+chrome-mac-master-1610431066-5d7d40ce59ba8e505b376b46e02fe57205cb3e46.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 648021c..724313c 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1610398040-59038019b78fe7d9277311f636fda97275a69be4.profdata
+chrome-win32-master-1610420273-f729673981a14d4f8cc70d6513013e1d16474c41.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index bf52c0a..cd7d99d 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1610387594-34ec7be13acbaa9470ba5363f8af27b0c42697d0.profdata
+chrome-win64-master-1610431066-60b154442ebb054827e182b9c834d6f36e05d2b3.profdata
diff --git a/chrome/common/subresource_redirect_service.mojom b/chrome/common/subresource_redirect_service.mojom
index bd242ef..300c46c 100644
--- a/chrome/common/subresource_redirect_service.mojom
+++ b/chrome/common/subresource_redirect_service.mojom
@@ -36,4 +36,10 @@
 interface SubresourceRedirectHintsReceiver {
   // Sends the public image URL hints from the browser to renderers.
   SetCompressPublicImagesHints(CompressPublicImagesHints images_hints);
+
+
+  // Sets whether the current page is determined to be logged in. This guides
+  // whether subresource redirect based compression will be enabled. Sent from
+  // the browser to renderers on navigation commit.
+  SetLoggedInState(bool is_logged_in);
 };
diff --git a/chrome/renderer/subresource_redirect/login_robots_decider_agent.cc b/chrome/renderer/subresource_redirect/login_robots_decider_agent.cc
index 8e28e97..dea688c 100644
--- a/chrome/renderer/subresource_redirect/login_robots_decider_agent.cc
+++ b/chrome/renderer/subresource_redirect/login_robots_decider_agent.cc
@@ -32,6 +32,7 @@
     case RobotsRulesParser::CheckResult::kAllowed:
       return RedirectResult::kRedirectable;
     case RobotsRulesParser::CheckResult::kDisallowed:
+    case RobotsRulesParser::CheckResult::kInvalidated:
       return RedirectResult::kIneligibleRobotsDisallowed;
     case RobotsRulesParser::CheckResult::kTimedout:
     case RobotsRulesParser::CheckResult::kDisallowedAfterTimeout:
@@ -39,12 +40,10 @@
   }
 }
 
-// Converts the robots rules CheckResult to RedirectResult and passes to the
-// callback.
-void SendRedirectResultToCallback(
-    LoginRobotsDeciderAgent::ShouldRedirectDecisionCallback callback,
-    RobotsRulesParser::CheckResult check_result) {
-  std::move(callback).Run(ConvertToRedirectResult(check_result));
+void RecordRedirectResultMetric(RedirectResult redirect_result) {
+  LOCAL_HISTOGRAM_ENUMERATION(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
+      redirect_result);
 }
 
 }  // namespace
@@ -65,6 +64,8 @@
     ShouldRedirectDecisionCallback callback) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(url.is_valid());
+  if (redirect_result_ != RedirectResult::kRedirectable)
+    return redirect_result_;
   if (!render_frame()->IsMainFrame())
     return RedirectResult::kIneligibleSubframeResource;
 
@@ -83,21 +84,51 @@
 
   base::Optional<RobotsRulesParser::CheckResult> result =
       robots_rules_parser_cache.CheckRobotsRules(
-          url,
-          base::BindOnce(&SendRedirectResultToCallback, std::move(callback)));
-  if (result)
-    return ConvertToRedirectResult(*result);
+          routing_id(), url,
+          base::BindOnce(
+              &LoginRobotsDeciderAgent::OnShouldRedirectSubresourceResult,
+              weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+  if (result) {
+    RedirectResult redirect_result = ConvertToRedirectResult(*result);
+    RecordRedirectResultMetric(redirect_result);
+    return redirect_result;
+  }
 
   return base::nullopt;
 }
 
+void LoginRobotsDeciderAgent::OnShouldRedirectSubresourceResult(
+    LoginRobotsDeciderAgent::ShouldRedirectDecisionCallback callback,
+    RobotsRulesParser::CheckResult check_result) {
+  // Verify if the navigation is still allowed to redirect.
+  if (redirect_result_ != RedirectResult::kRedirectable) {
+    RecordRedirectResultMetric(redirect_result_);
+    std::move(callback).Run(redirect_result_);
+    return;
+  }
+  RedirectResult redirect_result = ConvertToRedirectResult(check_result);
+  RecordRedirectResultMetric(redirect_result);
+  std::move(callback).Run(redirect_result);
+}
+
+void LoginRobotsDeciderAgent::ReadyToCommitNavigation(
+    blink::WebDocumentLoader* document_loader) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  redirect_result_ = RedirectResult::kUnknown;
+  GetRobotsRulesParserCache().InvalidatePendingRequests(routing_id());
+}
+
+void LoginRobotsDeciderAgent::SetLoggedInState(bool is_logged_in) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  redirect_result_ = is_logged_in ? RedirectResult::kIneligibleLoginDetected
+                                  : RedirectResult::kRedirectable;
+}
+
 void LoginRobotsDeciderAgent::RecordMetricsOnLoadFinished(
     const GURL& url,
     int64_t content_length,
     RedirectResult redirect_result) {
-  LOCAL_HISTOGRAM_ENUMERATION(
-      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
-      redirect_result);
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // TODO(crbug.com/1148980): Record coverage metrics
 }
 
diff --git a/chrome/renderer/subresource_redirect/login_robots_decider_agent.h b/chrome/renderer/subresource_redirect/login_robots_decider_agent.h
index 72b0b8e..774cc15 100644
--- a/chrome/renderer/subresource_redirect/login_robots_decider_agent.h
+++ b/chrome/renderer/subresource_redirect/login_robots_decider_agent.h
@@ -6,6 +6,7 @@
 #define CHROME_RENDERER_SUBRESOURCE_REDIRECT_LOGIN_ROBOTS_DECIDER_AGENT_H_
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
 #include "base/timer/timer.h"
 #include "chrome/renderer/subresource_redirect/public_resource_decider_agent.h"
@@ -37,9 +38,14 @@
   void UpdateRobotsRulesForTesting(const url::Origin& origin,
                                    const base::Optional<std::string>& rules);
 
+  // content::RenderFrameObserver:
+  void ReadyToCommitNavigation(
+      blink::WebDocumentLoader* document_loader) override;
+
   // mojom::SubresourceRedirectHintsReceiver:
   void SetCompressPublicImagesHints(
       mojom::CompressPublicImagesHintsPtr images_hints) override;
+  void SetLoggedInState(bool is_logged_in) override;
 
   // PublicResourceDeciderAgent:
   base::Optional<RedirectResult> ShouldRedirectSubresource(
@@ -49,9 +55,21 @@
                                    int64_t content_length,
                                    RedirectResult redirect_result) override;
 
+  // Callback invoked when should redirect check result is available.
+  void OnShouldRedirectSubresourceResult(
+      ShouldRedirectDecisionCallback callback,
+      RobotsRulesParser::CheckResult check_result);
+
   bool IsMainFrame() const;
 
+  // Current state of the redirect compression that should be used for the
+  // current navigation.
+  RedirectResult redirect_result_ = RedirectResult::kUnknown;
+
   THREAD_CHECKER(thread_checker_);
+
+  // Used to get a weak pointer to |this|.
+  base::WeakPtrFactory<LoginRobotsDeciderAgent> weak_ptr_factory_{this};
 };
 
 }  // namespace subresource_redirect
diff --git a/chrome/renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc b/chrome/renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc
index ab6c89a..9fdd1b46 100644
--- a/chrome/renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc
+++ b/chrome/renderer/subresource_redirect/login_robots_decider_agent_browsertest.cc
@@ -73,6 +73,10 @@
            result_receiver.redirect_result() == RedirectResult::kRedirectable;
   }
 
+  void SetLoggedInState(bool is_logged_in) {
+    login_robots_decider_agent_->SetLoggedInState(is_logged_in);
+  }
+
  protected:
   void SetUp() override {
     ChromeRenderViewTest::SetUp();
@@ -91,6 +95,7 @@
 
 TEST_F(SubresourceRedirectLoginRobotsDeciderAgentTest,
        TestAllowDisallowSingleOrigin) {
+  SetLoggedInState(false);
   SetUpRobotsRules("https://foo.com", {{kRuleTypeAllow, "/public"},
                                        {kRuleTypeDisallow, "/private"}});
   EXPECT_TRUE(ShouldRedirectSubresource("https://foo.com/public.jpg"));
@@ -103,6 +108,7 @@
 
 TEST_F(SubresourceRedirectLoginRobotsDeciderAgentTest,
        TestHTTPRulesAreSeparate) {
+  SetLoggedInState(false);
   SetUpRobotsRules("https://foo.com", {{kRuleTypeAllow, "/public"},
                                        {kRuleTypeDisallow, "/private"}});
   EXPECT_FALSE(ShouldRedirectSubresource("http://foo.com/public.jpg"));
@@ -116,6 +122,7 @@
 }
 
 TEST_F(SubresourceRedirectLoginRobotsDeciderAgentTest, TestURLWithArguments) {
+  SetLoggedInState(false);
   SetUpRobotsRules("https://foo.com",
                    {{kRuleTypeAllow, "/*.jpg$"},
                     {kRuleTypeDisallow, "/*.png?*arg_disallowed"},
@@ -140,6 +147,7 @@
 
 TEST_F(SubresourceRedirectLoginRobotsDeciderAgentTest,
        TestRulesAreCaseSensitive) {
+  SetLoggedInState(false);
   SetUpRobotsRules("https://foo.com", {{kRuleTypeAllow, "/allowed"},
                                        {kRuleTypeAllow, "/CamelCase"},
                                        {kRuleTypeAllow, "/CAPITALIZE"},
@@ -152,4 +160,13 @@
   EXPECT_FALSE(ShouldRedirectSubresource("https://foo.com/capitalize.jpg"));
 }
 
+TEST_F(SubresourceRedirectLoginRobotsDeciderAgentTest,
+       TestDisabledWhenLoggedIn) {
+  SetLoggedInState(true);
+  SetUpRobotsRules("https://foo.com", {{kRuleTypeAllow, "/public"},
+                                       {kRuleTypeDisallow, "/private"}});
+  EXPECT_FALSE(ShouldRedirectSubresource("https://foo.com/public.jpg"));
+  EXPECT_FALSE(ShouldRedirectSubresource("https://foo.com/private.jpg"));
+}
+
 }  // namespace subresource_redirect
diff --git a/chrome/renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc b/chrome/renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc
index 99f8fa6..b9e5fd7 100644
--- a/chrome/renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc
+++ b/chrome/renderer/subresource_redirect/login_robots_url_loader_throttle_browsertest.cc
@@ -84,17 +84,13 @@
     NOTIMPLEMENTED();
   }
 
-  void VerifyRedirectResult(RedirectResult expected_result) {
-    base::HistogramTester histogram_tester;
+  void VerifyWillProcessResponse() {
     network::mojom::URLResponseHeadPtr head =
         network::CreateURLResponseHead(net::HTTP_OK);
     head->headers->SetHeader("Content-Length", "1024");
     bool defer = false;
     throttle_->WillProcessResponse(GURL("https://foo.com/img.jpg"), head.get(),
                                    &defer);
-    histogram_tester.ExpectUniqueSample(
-        "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
-        expected_result, 1);
     EXPECT_FALSE(defer);
   }
 
@@ -150,6 +146,10 @@
                  blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON));
   }
 
+  void SetLoggedInState(bool is_logged_in) {
+    login_robots_decider_agent_->SetLoggedInState(is_logged_in);
+  }
+
  protected:
   void SetUp() override {
     ChromeRenderViewTest::SetUp();
@@ -163,9 +163,10 @@
         &associated_interfaces_, view_->GetMainRenderFrame());
   }
 
- private:
+ protected:
   LoginRobotsDeciderAgent* login_robots_decider_agent_;
   base::test::ScopedFeatureList scoped_feature_list_;
+  base::HistogramTester histogram_tester_;
 };
 
 TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
@@ -221,6 +222,7 @@
        TestGetSubresourceURL) {
   struct TestCase {
     int previews_state;
+    bool is_logged_in;
     std::string original_url;
     GURL redirected_subresource_url;  // Empty URL means there will be no
                                       // redirect.
@@ -229,17 +231,20 @@
   const TestCase kTestCases[]{
       {
           blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
+          false,
           "https://www.test.com/public_img.jpg",
           GetSubresourceURLForURL(GURL("https://www.test.com/public_img.jpg")),
       },
       {
           blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
+          false,
           "https://www.test.com/public_img.jpg#anchor",
           GetSubresourceURLForURL(
               GURL("https://www.test.com/public_img.jpg#anchor")),
       },
       {
           blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
+          false,
           "https://www.test.com/public_img.jpg?public_arg1=bar&public_arg2",
           GetSubresourceURLForURL(
               GURL("https://www.test.com/"
@@ -248,14 +253,23 @@
       // Private images will not be redirected.
       {
           blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
+          false,
           "https://www.test.com/private_img.jpg",
           GURL(),
       },
       {
           blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
+          false,
           "https://www.test.com/public_img.jpg&private_arg1=foo",
           GURL(),
       },
+      // No redirection when logged-in
+      {
+          blink::PreviewsTypes::SUBRESOURCE_REDIRECT_ON,
+          true,
+          "https://www.test.com/public_img.jpg",
+          GURL(),
+      },
   };
   blink::WebNetworkStateNotifier::SetSaveDataEnabled(true);
 
@@ -264,6 +278,7 @@
                                             {kRuleTypeDisallow, ""}});
 
   for (const TestCase& test_case : kTestCases) {
+    SetLoggedInState(test_case.is_logged_in);
     auto throttle = CreateLoginRobotsDecider(
         test_case.original_url, network::mojom::RequestDestination::kImage,
         test_case.previews_state);
@@ -288,6 +303,7 @@
 TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
        TestRobotsRulesSentBeforeThrottle) {
   blink::WebNetworkStateNotifier::SetSaveDataEnabled(true);
+  SetLoggedInState(false);
 
   SetUpRobotsRules("https://www.test.com",
                    {{kRuleTypeAllow, "/public"}, {kRuleTypeDisallow, ""}});
@@ -296,8 +312,6 @@
       CreateURLLoaderThrottleInfo("https://www.test.com/public.jpg");
   auto throttle_info2 =
       CreateURLLoaderThrottleInfo("https://www.test.com/private.jpg");
-  throttle_info1->VerifyRedirectResult(RedirectResult::kRedirectable);
-  throttle_info2->VerifyRedirectResult(RedirectResult::kRedirectable);
 
   throttle_info1->SendStartRequestAndVerifyDeferral(
       WillStartRequestDeferralState::kRedirected);
@@ -307,9 +321,14 @@
   EXPECT_FALSE(throttle_info2->did_resume());
   EXPECT_FALSE(throttle_info1->did_restart_with_url_reset_and_flags());
   EXPECT_FALSE(throttle_info2->did_restart_with_url_reset_and_flags());
-  throttle_info1->VerifyRedirectResult(RedirectResult::kRedirectable);
-  throttle_info2->VerifyRedirectResult(
-      RedirectResult::kIneligibleRobotsDisallowed);
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult", 2);
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
+      RedirectResult::kRedirectable, 1);
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
+      RedirectResult::kIneligibleRobotsDisallowed, 1);
 }
 
 // Tests the cases when robots rules are sent, after throttles are
@@ -317,6 +336,7 @@
 TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
        TestRobotsRulesSentAfterThrottle) {
   blink::WebNetworkStateNotifier::SetSaveDataEnabled(true);
+  SetLoggedInState(false);
 
   auto throttle_info1 =
       CreateURLLoaderThrottleInfo("https://www.test.com/public.jpg");
@@ -344,15 +364,23 @@
   EXPECT_TRUE(throttle_info2->did_restart_with_url_reset_and_flags());
   EXPECT_TRUE(throttle_info2->did_resume());
 
-  throttle_info1->VerifyRedirectResult(RedirectResult::kRedirectable);
-  throttle_info2->VerifyRedirectResult(
-      RedirectResult::kIneligibleRobotsDisallowed);
+  throttle_info1->VerifyWillProcessResponse();
+  throttle_info2->VerifyWillProcessResponse();
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult", 2);
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
+      RedirectResult::kRedirectable, 1);
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
+      RedirectResult::kIneligibleRobotsDisallowed, 1);
 }
 
 // Tests the cases when robots rules retrieval timesout.
 TEST_F(SubresourceRedirectLoginRobotsURLLoaderThrottleTest,
        TestRobotsRulesTimeout) {
   blink::WebNetworkStateNotifier::SetSaveDataEnabled(true);
+  SetLoggedInState(false);
 
   auto throttle_info1 =
       CreateURLLoaderThrottleInfo("https://www.test.com/public.jpg");
@@ -377,10 +405,13 @@
   EXPECT_TRUE(throttle_info2->did_restart_with_url_reset_and_flags());
   EXPECT_TRUE(throttle_info2->did_resume());
 
-  throttle_info1->VerifyRedirectResult(
-      RedirectResult::kIneligibleRobotsTimeout);
-  throttle_info2->VerifyRedirectResult(
-      RedirectResult::kIneligibleRobotsTimeout);
+  throttle_info1->VerifyWillProcessResponse();
+  throttle_info2->VerifyWillProcessResponse();
+  histogram_tester_.ExpectTotalCount(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult", 2);
+  histogram_tester_.ExpectBucketCount(
+      "SubresourceRedirect.LoginRobotsDeciderAgent.RedirectResult",
+      RedirectResult::kIneligibleRobotsTimeout, 2);
 }
 
 }  // namespace subresource_redirect
diff --git a/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.cc b/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.cc
index a78fb22..39eee6f 100644
--- a/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.cc
+++ b/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.cc
@@ -160,6 +160,7 @@
       break;
     case RedirectResult::kIneligibleRobotsDisallowed:
     case RedirectResult::kIneligibleRobotsTimeout:
+    case RedirectResult::kIneligibleLoginDetected:
       NOTREACHED();
   }
   mojo::PendingRemote<ukm::mojom::UkmRecorderInterface> recorder;
@@ -198,4 +199,10 @@
   ClearImageHints();
 }
 
+void PublicImageHintsDeciderAgent::SetLoggedInState(bool is_logged_in) {
+  // This mojo from browser process should not be called for public image hints
+  // based compression.
+  NOTIMPLEMENTED();
+}
+
 }  // namespace subresource_redirect
diff --git a/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.h b/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.h
index 27c592a..7c05277 100644
--- a/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.h
+++ b/chrome/renderer/subresource_redirect/public_image_hints_decider_agent.h
@@ -42,6 +42,7 @@
   // mojom::SubresourceRedirectHintsReceiver:
   void SetCompressPublicImagesHints(
       mojom::CompressPublicImagesHintsPtr images_hints) override;
+  void SetLoggedInState(bool is_logged_in) override;
 
   // PublicResourceDeciderAgent:
   base::Optional<RedirectResult> ShouldRedirectSubresource(
diff --git a/chrome/renderer/subresource_redirect/redirect_result.h b/chrome/renderer/subresource_redirect/redirect_result.h
index c0a2b766..b41d438 100644
--- a/chrome/renderer/subresource_redirect/redirect_result.h
+++ b/chrome/renderer/subresource_redirect/redirect_result.h
@@ -57,7 +57,10 @@
   // Because the robots rules fetch timedout.
   kIneligibleRobotsTimeout,
 
-  kMaxValue = RedirectResult::kIneligibleRobotsTimeout
+  // Because the page was detected to be logged-in.
+  kIneligibleLoginDetected,
+
+  kMaxValue = RedirectResult::kIneligibleLoginDetected
 };
 
 }  // namespace subresource_redirect
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser.cc b/chrome/renderer/subresource_redirect/robots_rules_parser.cc
index 0658232a..f8aa4d81 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser.cc
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser.cc
@@ -126,21 +126,27 @@
   }
 
   // Respond to the pending requests, even if robots proto parse failed.
-  for (auto& request : pending_check_requests_) {
-    std::move(request.first).Run(CheckRobotsRulesImmediate(request.second));
+  for (auto& requests : pending_check_requests_) {
+    for (auto& request : requests.second) {
+      std::move(request.first).Run(CheckRobotsRulesImmediate(request.second));
+    }
   }
   pending_check_requests_.clear();
 }
 
 base::Optional<RobotsRulesParser::CheckResult>
-RobotsRulesParser::CheckRobotsRules(const GURL& url,
+RobotsRulesParser::CheckRobotsRules(int routing_id,
+                                    const GURL& url,
                                     CheckResultCallback callback) {
   std::string path_with_query = url.path();
   if (url.has_query())
     base::StrAppend(&path_with_query, {"?", url.query()});
   if (rules_receive_state_ == RulesReceiveState::kTimerRunning) {
     DCHECK(rules_receive_timeout_timer_.IsRunning());
-    pending_check_requests_.emplace_back(
+    auto it = pending_check_requests_.insert(std::make_pair(
+        routing_id,
+        std::vector<std::pair<CheckResultCallback, std::string>>()));
+    it.first->second.emplace_back(
         std::make_pair(std::move(callback), path_with_query));
     return base::nullopt;
   }
@@ -172,11 +178,24 @@
 void RobotsRulesParser::OnRulesReceiveTimeout() {
   DCHECK(!rules_receive_timeout_timer_.IsRunning());
   rules_receive_state_ = RulesReceiveState::kTimeout;
-  for (auto& request : pending_check_requests_)
-    std::move(request.first).Run(CheckResult::kTimedout);
+  for (auto& requests : pending_check_requests_) {
+    for (auto& request : requests.second) {
+      std::move(request.first).Run(CheckResult::kTimedout);
+    }
+  }
   pending_check_requests_.clear();
   RecordRobotsRulesReceiveResultHistogram(
       SubresourceRedirectRobotsRulesReceiveResult::kTimeout);
 }
 
+void RobotsRulesParser::InvalidatePendingRequests(int routing_id) {
+  auto it = pending_check_requests_.find(routing_id);
+  if (it == pending_check_requests_.end())
+    return;
+  for (auto& request : it->second) {
+    std::move(request.first).Run(CheckResult::kInvalidated);
+  }
+  pending_check_requests_.erase(it);
+}
+
 }  // namespace subresource_redirect
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser.h b/chrome/renderer/subresource_redirect/robots_rules_parser.h
index 5ffa5c7..a350a197 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser.h
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser.h
@@ -37,6 +37,8 @@
     kTimedout,                // Timeout in retrieving the robots rules
     kDisallowedAfterTimeout,  // Timeout got triggered already, and the resource
                               // was disallowed
+    kInvalidated,  // The result check was invalidated, before robots rules are
+                   // received or timeout triggered.
   };
 
   enum class RulesReceiveState {
@@ -73,10 +75,16 @@
   // added to |pending_check_requests_| and called when a decision can be made
   // like when rules are retrieved, or rule fetch timeout, etc.
   // The robots rules check will make use of the |url| path and query
-  // parameters.The |url| origin, ref fragment, etc are immaterial.
-  base::Optional<CheckResult> CheckRobotsRules(const GURL& url,
+  // parameters.The |url| origin, ref fragment, etc are immaterial. |routing_id|
+  // is the render frame ID for which this URL is requested for.
+  base::Optional<CheckResult> CheckRobotsRules(int routing_id,
+                                               const GURL& url,
                                                CheckResultCallback callback);
 
+  // Invalidate and cancel the pending requests that were added for
+  // |routing_id|.
+  void InvalidatePendingRequests(int routing_id);
+
  private:
   friend class SubresourceRedirectRobotsRulesParserTest;
 
@@ -107,9 +115,10 @@
   // Ordered list of robots rules from longest to shortest.
   std::vector<RobotsRule> robots_rules_;
 
-  // Contains the requests that are pending for robots rules to be received.
-  // Holds the URL path and the callback.
-  std::vector<std::pair<CheckResultCallback, std::string>>
+  // Contains the requests that are pending for robots rules to be received,
+  // keyed by routing ID. Key is the rouging ID and the value holds the URL path
+  // and the callback.
+  std::map<int, std::vector<std::pair<CheckResultCallback, std::string>>>
       pending_check_requests_;
 
   // To trigger the timeout for the robots rules to be received.
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser_cache.cc b/chrome/renderer/subresource_redirect/robots_rules_parser_cache.cc
index e40dd4d..e7d77be5 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser_cache.cc
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser_cache.cc
@@ -25,10 +25,16 @@
 
 base::Optional<RobotsRulesParser::CheckResult>
 RobotsRulesParserCache::CheckRobotsRules(
+    int routing_id,
     const GURL& url,
     RobotsRulesParser::CheckResultCallback callback) {
   return GetRobotsRulesParserForOrigin(url::Origin::Create(url))
-      .CheckRobotsRules(url, std::move(callback));
+      .CheckRobotsRules(routing_id, url, std::move(callback));
+}
+
+void RobotsRulesParserCache::InvalidatePendingRequests(int routing_id) {
+  for (auto& entry : parsers_cache_)
+    entry.second->InvalidatePendingRequests(routing_id);
 }
 
 RobotsRulesParser& RobotsRulesParserCache::GetRobotsRulesParserForOrigin(
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser_cache.h b/chrome/renderer/subresource_redirect/robots_rules_parser_cache.h
index acf5a1e50..1f0d985 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser_cache.h
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser_cache.h
@@ -34,9 +34,13 @@
   // should be returned and the |callback| will be invoked when the decision was
   // made.
   base::Optional<RobotsRulesParser::CheckResult> CheckRobotsRules(
+      int routing_id,
       const GURL& url,
       RobotsRulesParser::CheckResultCallback callback);
 
+  // Invalidate and cancel the pending requests for the robots rules parser.
+  void InvalidatePendingRequests(int routing_id);
+
  private:
   // Returns a reference to the robots rules parser for the |origin| from the
   // cache. An entry is created if it does not exist.
diff --git a/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc b/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
index 3565f3a..8df3d73 100644
--- a/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
+++ b/chrome/renderer/subresource_redirect/robots_rules_parser_unittest.cc
@@ -39,6 +39,8 @@
   base::WeakPtrFactory<CheckResultReceiver> weak_ptr_factory_{this};
 };
 
+const int kRenderFrameID = 1;
+
 class SubresourceRedirectRobotsRulesParserTest : public testing::Test {
  public:
   SubresourceRedirectRobotsRulesParserTest()
@@ -56,10 +58,12 @@
   // Verify robots rules check result is received synchronously with the
   // expected result.
   void CheckRobotsRules(const std::string& url_path_with_query,
-                        RobotsRulesParser::CheckResult expected_result) {
+                        RobotsRulesParser::CheckResult expected_result,
+                        int render_frame_id = kRenderFrameID) {
     CheckResultReceiver result_receiver;
     auto result = robots_rules_parser_.CheckRobotsRules(
-        GURL(kTestOrigin + url_path_with_query), result_receiver.GetCallback());
+        render_frame_id, GURL(kTestOrigin + url_path_with_query),
+        result_receiver.GetCallback());
     EXPECT_FALSE(result_receiver.did_receive_result());
     EXPECT_EQ(expected_result, result);
   }
@@ -67,10 +71,11 @@
   // Verify robots rules check result is received asynchronously, and returns
   // the receiver that can be used to check the result.
   std::unique_ptr<CheckResultReceiver> CheckRobotsRulesAsync(
-      const std::string& url_path_with_query) {
+      const std::string& url_path_with_query,
+      int render_frame_id = kRenderFrameID) {
     auto result_receiver = std::make_unique<CheckResultReceiver>();
     EXPECT_FALSE(robots_rules_parser_.CheckRobotsRules(
-        GURL(kTestOrigin + url_path_with_query),
+        render_frame_id, GURL(kTestOrigin + url_path_with_query),
         result_receiver->GetCallback()));
     return result_receiver;
   }
@@ -196,10 +201,11 @@
   auto receiver1 = std::make_unique<CheckResultReceiver>();
   auto receiver2 = std::make_unique<CheckResultReceiver>();
 
-  robots_rules_parser->CheckRobotsRules(GURL("https://test.com/foo.jpg"),
+  robots_rules_parser->CheckRobotsRules(kRenderFrameID,
+                                        GURL("https://test.com/foo.jpg"),
                                         receiver1->GetCallback());
-  robots_rules_parser->CheckRobotsRules(GURL("https://test.com/bar"),
-                                        receiver2->GetCallback());
+  robots_rules_parser->CheckRobotsRules(
+      kRenderFrameID, GURL("https://test.com/bar"), receiver2->GetCallback());
   EXPECT_FALSE(receiver1->did_receive_result());
   EXPECT_FALSE(receiver2->did_receive_result());
   VerifyTotalRobotsRulesApplyHistograms(0);
@@ -372,4 +378,41 @@
   VerifyTotalRobotsRulesApplyHistograms(6);
 }
 
+TEST_F(SubresourceRedirectRobotsRulesParserTest,
+       TestInvalidatePendingRequests) {
+  auto receiver1 = CheckRobotsRulesAsync("/allowed.jpg", 1 /*render_frame_id*/);
+  auto receiver2 =
+      CheckRobotsRulesAsync("/disallowed.jpg", 1 /*render_frame_id*/);
+  auto receiver3 = CheckRobotsRulesAsync("/allowed.jpg", 2 /*render_frame_id*/);
+  auto receiver4 =
+      CheckRobotsRulesAsync("/disallowed.jpg", 2 /*render_frame_id*/);
+
+  // Invalidate should cancel requests for that render frame ID.
+  robots_rules_parser_.InvalidatePendingRequests(1 /*render_frame_id*/);
+  EXPECT_TRUE(receiver1->did_receive_result());
+  EXPECT_TRUE(receiver2->did_receive_result());
+  EXPECT_EQ(RobotsRulesParser::CheckResult::kInvalidated,
+            receiver1->check_result());
+  EXPECT_EQ(RobotsRulesParser::CheckResult::kInvalidated,
+            receiver2->check_result());
+  EXPECT_FALSE(receiver3->did_receive_result());
+  EXPECT_FALSE(receiver4->did_receive_result());
+  VerifyTotalRobotsRulesApplyHistograms(0);
+
+  // When robots rules are retrieved, the
+  SetUpRobotsRules({{kRuleTypeAllow, "/allow*"}, {kRuleTypeDisallow, "/*"}});
+  EXPECT_TRUE(receiver3->did_receive_result());
+  EXPECT_TRUE(receiver4->did_receive_result());
+  EXPECT_EQ(RobotsRulesParser::CheckResult::kAllowed,
+            receiver3->check_result());
+  EXPECT_EQ(RobotsRulesParser::CheckResult::kDisallowed,
+            receiver4->check_result());
+  VerifyTotalRobotsRulesApplyHistograms(2);
+
+  CheckRobotsRules("/allowed.jpg", RobotsRulesParser::CheckResult::kAllowed);
+  CheckRobotsRules("/disallowed.jpg",
+                   RobotsRulesParser::CheckResult::kDisallowed);
+  VerifyTotalRobotsRulesApplyHistograms(4);
+}
+
 }  // namespace subresource_redirect
diff --git a/chrome/test/base/extension_js_browser_test.cc b/chrome/test/base/extension_js_browser_test.cc
index 2c0dedf..8e985fd 100644
--- a/chrome/test/base/extension_js_browser_test.cc
+++ b/chrome/test/base/extension_js_browser_test.cc
@@ -19,9 +19,9 @@
 ExtensionJSBrowserTest::~ExtensionJSBrowserTest() {}
 
 void ExtensionJSBrowserTest::WaitForExtension(const char* extension_id,
-                                              const base::Closure& load_cb) {
+                                              base::OnceClosure load_cb) {
   load_waiter_.reset(new ExtensionLoadWaiterOneShot());
-  load_waiter_->WaitForExtension(extension_id, load_cb);
+  load_waiter_->WaitForExtension(extension_id, std::move(load_cb));
 }
 
 bool ExtensionJSBrowserTest::RunJavascriptTestF(bool is_async,
diff --git a/chrome/test/base/extension_js_browser_test.h b/chrome/test/base/extension_js_browser_test.h
index 9380d234..e050e58 100644
--- a/chrome/test/base/extension_js_browser_test.h
+++ b/chrome/test/base/extension_js_browser_test.h
@@ -22,7 +22,7 @@
 
  protected:
   // Waits for an extension to load; returns immediately if already loaded.
-  void WaitForExtension(const char* extension_id, const base::Closure& load_cb);
+  void WaitForExtension(const char* extension_id, base::OnceClosure load_cb);
 
   // Method required for js2gtest.
   // Runs |test_fixture|.|test_name| using the framework in test_api.js.
diff --git a/chrome/test/base/extension_load_waiter_one_shot.cc b/chrome/test/base/extension_load_waiter_one_shot.cc
index 6f33d7b..e421f59e 100644
--- a/chrome/test/base/extension_load_waiter_one_shot.cc
+++ b/chrome/test/base/extension_load_waiter_one_shot.cc
@@ -16,7 +16,7 @@
 ExtensionLoadWaiterOneShot::~ExtensionLoadWaiterOneShot() = default;
 
 void ExtensionLoadWaiterOneShot::WaitForExtension(const char* extension_id,
-                                  const base::Closure& load_cb) {
+                                                  base::OnceClosure load_cb) {
   CHECK(!extension_id_) <<
       "ExtensionLoadWaiterOneShot should only be used once.";
   extension_id_ = extension_id;
@@ -24,7 +24,7 @@
   registrar_.Add(this,
                  extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD,
                  content::NotificationService::AllSources());
-  load_cb.Run();
+  std::move(load_cb).Run();
   load_looper_->Run();
 }
 
diff --git a/chrome/test/base/extension_load_waiter_one_shot.h b/chrome/test/base/extension_load_waiter_one_shot.h
index 7ab89154..cc2646d8 100644
--- a/chrome/test/base/extension_load_waiter_one_shot.h
+++ b/chrome/test/base/extension_load_waiter_one_shot.h
@@ -26,7 +26,7 @@
 
   // Waits for extension with |extension_id| to load. The id should be a pointer
   // to a static char array.
-  void WaitForExtension(const char* extension_id, const base::Closure& load_cb);
+  void WaitForExtension(const char* extension_id, base::OnceClosure load_cb);
 
   // content::NotificationObserver overrides.
   void Observe(int type,
diff --git a/chrome/test/data/banners/manifest_not_offline_capable_url.json b/chrome/test/data/banners/manifest_not_offline_capable_url.json
new file mode 100644
index 0000000..62a45df
--- /dev/null
+++ b/chrome/test/data/banners/manifest_not_offline_capable_url.json
@@ -0,0 +1,39 @@
+{
+  "name": "Manifest test app with not offline capable start_url",
+  "icons": [
+    {
+      "src": "launcher-icon-1x.png",
+      "sizes": "48x48",
+      "type": "image/png"
+    },
+    {
+      "src": "launcher-icon-1-5x.png",
+      "sizes": "72x72",
+      "type": "image/png"
+    },
+    {
+      "src": "launcher-icon-2x.png",
+      "sizes": "96x96",
+      "type": "image/png",
+      "purpose": "any monochrome"
+    },
+    {
+      "src": "launcher-icon-3x.png",
+      "sizes": "144x144",
+      "type": "image/png"
+    },
+    {
+      "src": "launcher-icon-4x.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    },
+    {
+      "src": "image-512px.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "manifest_test_page.html?ignore",
+  "display": "standalone",
+  "orientation": "landscape"
+}
diff --git a/chrome/test/data/pdf/BUILD.gn b/chrome/test/data/pdf/BUILD.gn
index de7f52cb..cb5e25e 100644
--- a/chrome/test/data/pdf/BUILD.gn
+++ b/chrome/test/data/pdf/BUILD.gn
@@ -146,6 +146,7 @@
 js_library("navigator_test") {
   deps = [
     ":test_util",
+    "../webui:test_browser_proxy.m",
     "//chrome/browser/resources/pdf:navigator",
     "//chrome/browser/resources/pdf:open_pdf_params_parser",
     "//chrome/browser/resources/pdf:pdf_scripting_api",
diff --git a/chrome/test/data/pdf/navigator_test.js b/chrome/test/data/pdf/navigator_test.js
index 76cda3f..9d70f39 100644
--- a/chrome/test/data/pdf/navigator_test.js
+++ b/chrome/test/data/pdf/navigator_test.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {TestBrowserProxy} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/_test_resources/webui/test_browser_proxy.m.js';
 import {NavigatorDelegate, PdfNavigator, WindowOpenDisposition} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/navigator.js';
 import {OpenPdfParamsParser} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/open_pdf_params_parser.js';
 import {PDFScriptingAPI} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_scripting_api.js';
@@ -9,37 +10,28 @@
 import {getZoomableViewport, MockDocumentDimensions, MockElement, MockSizer, MockViewportChangedCallback} from './test_util.js';
 
 /** @implements {NavigatorDelegate} */
-class MockNavigatorDelegate {
+class MockNavigatorDelegate extends TestBrowserProxy {
   constructor() {
-    this.navigateInCurrentTabCalled = false;
-    this.navigateInNewTabCalled = false;
-    this.navigateInNewWindowCalled = false;
-    this.url = undefined;
+    super([
+      'navigateInCurrentTab',
+      'navigateInNewTab',
+      'navigateInNewWindow',
+    ]);
   }
 
   /** @override */
   navigateInCurrentTab(url) {
-    this.navigateInCurrentTabCalled = true;
-    this.url = url || '<called, but no url set>';
+    this.methodCalled('navigateInCurrentTab', url);
   }
 
   /** @override */
   navigateInNewTab(url) {
-    this.navigateInNewTabCalled = true;
-    this.url = url || '<called, but no url set>';
+    this.methodCalled('navigateInNewTab', url);
   }
 
   /** @override */
   navigateInNewWindow(url) {
-    this.navigateInNewWindowCalled = true;
-    this.url = url || '<called, but no url set>';
-  }
-
-  reset() {
-    this.navigateInCurrentTabCalled = false;
-    this.navigateInNewTabCalled = false;
-    this.navigateInNewWindowCalled = false;
-    this.url = undefined;
+    this.methodCalled('navigateInNewWindow', url);
   }
 }
 
@@ -62,23 +54,26 @@
   navigatorDelegate.reset();
   await navigator.navigate(url, disposition);
   chrome.test.assertFalse(viewportChangedCallback.wasCalled);
-  chrome.test.assertEq(expectedResultUrl, navigatorDelegate.url);
   if (expectedResultUrl === undefined) {
     return;
   }
+
+  let actualUrl = null;
   switch (disposition) {
     case WindowOpenDisposition.CURRENT_TAB:
-      chrome.test.assertTrue(navigatorDelegate.navigateInCurrentTabCalled);
+      actualUrl = await navigatorDelegate.whenCalled('navigateInCurrentTab');
       break;
     case WindowOpenDisposition.NEW_BACKGROUND_TAB:
-      chrome.test.assertTrue(navigatorDelegate.navigateInNewTabCalled);
+      actualUrl = await navigatorDelegate.whenCalled('navigateInNewTab');
       break;
     case WindowOpenDisposition.NEW_WINDOW:
-      chrome.test.assertTrue(navigatorDelegate.navigateInNewWindowCalled);
+      actualUrl = await navigatorDelegate.whenCalled('navigateInNewWindow');
       break;
     default:
       break;
   }
+
+  chrome.test.assertEq(expectedResultUrl, actualUrl);
 }
 
 /**
@@ -166,7 +161,7 @@
     await navigator.navigate(
         url + '#US', WindowOpenDisposition.NEW_BACKGROUND_TAB);
     chrome.test.assertFalse(mockCallback.wasCalled);
-    chrome.test.assertTrue(navigatorDelegate.navigateInNewTabCalled);
+    await navigatorDelegate.whenCalled('navigateInNewTab');
     chrome.test.assertEq(0, viewport.position.x);
     chrome.test.assertEq(0, viewport.position.y);
 
@@ -184,7 +179,7 @@
     // navigating results, as this link will open in the same tab.
     await navigator.navigate(url + '#ABC', WindowOpenDisposition.CURRENT_TAB);
     chrome.test.assertFalse(mockCallback.wasCalled);
-    chrome.test.assertTrue(navigatorDelegate.navigateInCurrentTabCalled);
+    await navigatorDelegate.whenCalled('navigateInCurrentTab');
     chrome.test.assertEq(0, viewport.position.x);
     chrome.test.assertEq(300, viewport.position.y);
     chrome.test.succeed();
diff --git a/chrome/test/data/webui/new_tab_page/modules/module_descriptor_test.js b/chrome/test/data/webui/new_tab_page/modules/module_descriptor_test.js
new file mode 100644
index 0000000..522288f
--- /dev/null
+++ b/chrome/test/data/webui/new_tab_page/modules/module_descriptor_test.js
@@ -0,0 +1,51 @@
+// 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.
+
+import {BrowserProxy, ModuleDescriptor} from 'chrome://new-tab-page/new_tab_page.js';
+import {createTestProxy} from 'chrome://test/new_tab_page/test_support.js';
+
+suite('NewTabPageModulesModuleDescriptorTest', () => {
+  /**
+   * @implements {BrowserProxy}
+   * @extends {TestBrowserProxy}
+   */
+  let testProxy;
+
+  setup(() => {
+    PolymerTest.clearBody();
+    testProxy = createTestProxy();
+    BrowserProxy.instance_ = testProxy;
+  });
+
+  test('instantiate module with data', async () => {
+    // Arrange.
+    const element = document.createElement('div');
+    const moduleDescriptor =
+        new ModuleDescriptor('foo', 100, () => Promise.resolve(element));
+    testProxy.setResultFor('now', 123);
+
+    // Act.
+    await moduleDescriptor.initialize();
+
+    // Assert.
+    assertEquals(element, moduleDescriptor.element);
+    const [id, now] = await testProxy.handler.whenCalled('onModuleLoaded');
+    assertEquals(1, testProxy.handler.getCallCount('onModuleLoaded'));
+    assertEquals('foo', id);
+    assertEquals(123, now);
+  });
+
+  test('instantiate module without data', async () => {
+    // Arrange.
+    const moduleDescriptor =
+        new ModuleDescriptor('foo', 100, () => Promise.resolve(null));
+
+    // Act.
+    await moduleDescriptor.initialize();
+
+    // Assert.
+    assertEquals(null, moduleDescriptor.element);
+    assertEquals(0, testProxy.handler.getCallCount('onModuleLoaded'));
+  });
+});
diff --git a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
index 6719105..5af4594 100644
--- a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
+++ b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
@@ -196,6 +196,19 @@
 });
 
 // eslint-disable-next-line no-var
+var NewTabPageModulesModuleDescriptorTest =
+    class extends NewTabPageBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://new-tab-page/test_loader.html?module=new_tab_page/modules/module_descriptor_test.js';
+  }
+};
+
+TEST_F('NewTabPageModulesModuleDescriptorTest', 'All', function() {
+  mocha.run();
+});
+
+// eslint-disable-next-line no-var
 var NewTabPageModulesModuleRegistryTest = class extends NewTabPageBrowserTest {
   /** @override */
   get browsePreload() {
diff --git a/chrome/test/data/webui/settings/chromeos/multidevice_notification_access_setup_dialog_tests.js b/chrome/test/data/webui/settings/chromeos/multidevice_notification_access_setup_dialog_tests.js
index b8bef55..b96e135 100644
--- a/chrome/test/data/webui/settings/chromeos/multidevice_notification_access_setup_dialog_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/multidevice_notification_access_setup_dialog_tests.js
@@ -82,6 +82,7 @@
     assertFalse(!!buttonContainer.querySelector('#doneButton'));
     assertFalse(!!buttonContainer.querySelector('#tryAgainButton'));
 
+    assertEquals(browserProxy.getCallCount('setFeatureEnabledState'), 0);
     simulateStatusChanged(
         NotificationAccessSetupOperationStatus.COMPLETED_SUCCESSFULLY);
     assertFalse(isSetupInstructionsShownSeparately());
@@ -90,6 +91,10 @@
     assertTrue(!!buttonContainer.querySelector('#doneButton'));
     assertFalse(!!buttonContainer.querySelector('#tryAgainButton'));
 
+    // The feature becomes enabled when the status becomes
+    // NotificationAccessSetupOperationStatus.COMPLETED_SUCCESSFULLY.
+    assertEquals(browserProxy.getCallCount('setFeatureEnabledState'), 1);
+
     assertTrue(notificationAccessSetupDialog.$$('#dialog').open);
     buttonContainer.querySelector('#doneButton').click();
     assertFalse(notificationAccessSetupDialog.$$('#dialog').open);
diff --git a/chrome/test/data/webui/settings/safety_check_chrome_cleaner_test.js b/chrome/test/data/webui/settings/safety_check_chrome_cleaner_test.js
index e27e367..fa4b3cc2 100644
--- a/chrome/test/data/webui/settings/safety_check_chrome_cleaner_test.js
+++ b/chrome/test/data/webui/settings/safety_check_chrome_cleaner_test.js
@@ -168,8 +168,7 @@
     // User clicks the button.
     page.$$('#safetyCheckChild').$$('#button').click();
     await expectLogging(
-        SafetyCheckInteractions
-            .SAFETY_CHECK_CHROME_CLEANER_REVIEW_INFECTED_STATE,
+        SafetyCheckInteractions.CHROME_CLEANER_REVIEW_INFECTED_STATE,
         'Settings.SafetyCheck.ChromeCleanerReviewInfectedState');
     // Ensure the correct Settings page is shown.
     assertEquals(routes.CHROME_CLEANUP, Router.getInstance().getCurrentRoute());
@@ -190,7 +189,7 @@
     // User clicks the button.
     page.$$('#safetyCheckChild').$$('#button').click();
     await expectLogging(
-        SafetyCheckInteractions.SAFETY_CHECK_CHROME_CLEANER_REBOOT,
+        SafetyCheckInteractions.CHROME_CLEANER_REBOOT,
         'Settings.SafetyCheck.ChromeCleanerReboot');
     // Ensure the browser proxy call is done.
     return chromeCleanupBrowserProxy.whenCalled('restartComputer');
@@ -210,7 +209,7 @@
     page.$$('#safetyCheckChild').click();
     // Ensure UMA is logged.
     await expectLogging(
-        SafetyCheckInteractions.SAFETY_CHECK_CHROME_CLEANER_CARET_NAVIGATION,
+        SafetyCheckInteractions.CHROME_CLEANER_CARET_NAVIGATION,
         'Settings.SafetyCheck.ChromeCleanerCaretNavigation');
     // Ensure the correct Settings page is shown.
     assertEquals(routes.CHROME_CLEANUP, Router.getInstance().getCurrentRoute());
@@ -230,7 +229,7 @@
     page.$$('#safetyCheckChild').click();
     // Ensure UMA is logged.
     await expectLogging(
-        SafetyCheckInteractions.SAFETY_CHECK_CHROME_CLEANER_CARET_NAVIGATION,
+        SafetyCheckInteractions.CHROME_CLEANER_CARET_NAVIGATION,
         'Settings.SafetyCheck.ChromeCleanerCaretNavigation');
     // Ensure the correct Settings page is shown.
     assertEquals(routes.CHROME_CLEANUP, Router.getInstance().getCurrentRoute());
@@ -261,7 +260,7 @@
     page.$$('#safetyCheckChild').click();
     // Ensure UMA is logged.
     await expectLogging(
-        SafetyCheckInteractions.SAFETY_CHECK_CHROME_CLEANER_CARET_NAVIGATION,
+        SafetyCheckInteractions.CHROME_CLEANER_CARET_NAVIGATION,
         'Settings.SafetyCheck.ChromeCleanerCaretNavigation');
     // Ensure the correct Settings page is shown.
     assertEquals(routes.CHROME_CLEANUP, Router.getInstance().getCurrentRoute());
@@ -281,7 +280,7 @@
     page.$$('#safetyCheckChild').click();
     // Ensure UMA is logged.
     await expectLogging(
-        SafetyCheckInteractions.SAFETY_CHECK_CHROME_CLEANER_CARET_NAVIGATION,
+        SafetyCheckInteractions.CHROME_CLEANER_CARET_NAVIGATION,
         'Settings.SafetyCheck.ChromeCleanerCaretNavigation');
     // Ensure the correct Settings page is shown.
     assertEquals(routes.CHROME_CLEANUP, Router.getInstance().getCurrentRoute());
@@ -301,7 +300,7 @@
     page.$$('#safetyCheckChild').click();
     // Ensure UMA is logged.
     await expectLogging(
-        SafetyCheckInteractions.SAFETY_CHECK_CHROME_CLEANER_CARET_NAVIGATION,
+        SafetyCheckInteractions.CHROME_CLEANER_CARET_NAVIGATION,
         'Settings.SafetyCheck.ChromeCleanerCaretNavigation');
     // Ensure the correct Settings page is shown.
     assertEquals(routes.CHROME_CLEANUP, Router.getInstance().getCurrentRoute());
diff --git a/chrome/test/data/webui/settings/safety_check_page_test.js b/chrome/test/data/webui/settings/safety_check_page_test.js
index 9746a906..d269550 100644
--- a/chrome/test/data/webui/settings/safety_check_page_test.js
+++ b/chrome/test/data/webui/settings/safety_check_page_test.js
@@ -204,7 +204,7 @@
     page.$$('#safetyCheckParentButton').click();
     // Ensure UMA is logged.
     assertEquals(
-        SafetyCheckInteractions.SAFETY_CHECK_START,
+        SafetyCheckInteractions.RUN_SAFETY_CHECK,
         await metricsBrowserProxy.whenCalled(
             'recordSafetyCheckInteractionHistogram'));
     assertEquals(
@@ -472,7 +472,7 @@
     page.$$('#safetyCheckChild').$$('#button').click();
     // Ensure UMA is logged.
     assertEquals(
-        SafetyCheckInteractions.SAFETY_CHECK_UPDATES_RELAUNCH,
+        SafetyCheckInteractions.UPDATES_RELAUNCH,
         await metricsBrowserProxy.whenCalled(
             'recordSafetyCheckInteractionHistogram'));
     assertEquals(
@@ -571,8 +571,7 @@
     page.$$('#safetyCheckChild').click();
     // Ensure UMA is logged.
     assertEquals(
-        SafetyCheckInteractions
-            .SAFETY_CHECK_PASSWORDS_MANAGE_THROUGH_CARET_NAVIGATION,
+        SafetyCheckInteractions.PASSWORDS_CARET_NAVIGATION,
         await metricsBrowserProxy.whenCalled(
             'recordSafetyCheckInteractionHistogram'));
     assertEquals(
@@ -602,7 +601,7 @@
     page.$$('#safetyCheckChild').$$('#button').click();
     // Ensure UMA is logged.
     assertEquals(
-        SafetyCheckInteractions.SAFETY_CHECK_PASSWORDS_MANAGE,
+        SafetyCheckInteractions.PASSWORDS_MANAGE_COMPROMISED_PASSWORDS,
         await metricsBrowserProxy.whenCalled(
             'recordSafetyCheckInteractionHistogram'));
     assertEquals(
@@ -619,6 +618,32 @@
         PasswordManagerProxy.PasswordCheckReferrer.SAFETY_CHECK, referrer);
   });
 
+  test('passwordWeakUiTest', async function() {
+    fireSafetyCheckPasswordsEvent(
+        SafetyCheckPasswordsStatus.WEAK_PASSWORDS_EXIST);
+    flush();
+    assertSafetyCheckChild({
+      page: page,
+      iconStatus: SafetyCheckIconStatus.INFO,
+      label: 'Passwords',
+      rowClickable: true,
+    });
+
+    // User clicks the manage passwords button.
+    page.$$('#safetyCheckChild').click();
+    // Ensure UMA is logged.
+    assertEquals(
+        SafetyCheckInteractions.PASSWORDS_MANAGE_WEAK_PASSWORDS,
+        await metricsBrowserProxy.whenCalled(
+            'recordSafetyCheckInteractionHistogram'));
+    assertEquals(
+        'Settings.SafetyCheck.ManageWeakPasswords',
+        await metricsBrowserProxy.whenCalled('recordAction'));
+    // Ensure the correct Settings page is shown.
+    assertEquals(
+        routes.CHECK_PASSWORDS, Router.getInstance().getCurrentRoute());
+  });
+
   test('passwordInfoStatesUiTest', function() {
     // Iterate over all states
     for (const state of Object.values(SafetyCheckPasswordsStatus)) {
@@ -639,7 +664,6 @@
           break;
         case SafetyCheckPasswordsStatus.QUOTA_LIMIT:
         case SafetyCheckPasswordsStatus.ERROR:
-        case SafetyCheckPasswordsStatus.WEAK_PASSWORDS_EXIST:
           assertSafetyCheckChild({
             page: page,
             iconStatus: SafetyCheckIconStatus.INFO,
@@ -703,8 +727,7 @@
     page.$$('#safetyCheckChild').click();
     // Ensure UMA is logged.
     assertEquals(
-        SafetyCheckInteractions
-            .SAFETY_CHECK_SAFE_BROWSING_MANAGE_THROUGH_CARET_NAVIGATION,
+        SafetyCheckInteractions.SAFE_BROWSING_CARET_NAVIGATION,
         await metricsBrowserProxy.whenCalled(
             'recordSafetyCheckInteractionHistogram'));
     assertEquals(
@@ -754,7 +777,7 @@
     page.$$('#safetyCheckChild').$$('#button').click();
     // Ensure UMA is logged.
     assertEquals(
-        SafetyCheckInteractions.SAFETY_CHECK_SAFE_BROWSING_MANAGE,
+        SafetyCheckInteractions.SAFE_BROWSING_MANAGE,
         await metricsBrowserProxy.whenCalled(
             'recordSafetyCheckInteractionHistogram'));
     assertEquals(
@@ -824,7 +847,7 @@
     page.$$('#safetyCheckChild').$$('#button').click();
     // Ensure UMA is logged.
     assertEquals(
-        SafetyCheckInteractions.SAFETY_CHECK_EXTENSIONS_REVIEW,
+        SafetyCheckInteractions.EXTENSIONS_REVIEW,
         await metricsBrowserProxy.whenCalled(
             'recordSafetyCheckInteractionHistogram'));
     assertEquals(
@@ -871,8 +894,7 @@
     page.$$('#safetyCheckChild').click();
     // Ensure UMA is logged.
     assertEquals(
-        SafetyCheckInteractions
-            .SAFETY_CHECK_EXTENSIONS_REVIEW_THROUGH_CARET_NAVIGATION,
+        SafetyCheckInteractions.EXTENSIONS_CARET_NAVIGATION,
         await metricsBrowserProxy.whenCalled(
             'recordSafetyCheckInteractionHistogram'));
     assertEquals(
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 7aef9b5..1ff8f7a 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-13718.0.0
\ No newline at end of file
+13719.0.0
\ No newline at end of file
diff --git a/chromeos/components/help_app_ui/BUILD.gn b/chromeos/components/help_app_ui/BUILD.gn
index b888e97..e6392cf 100644
--- a/chromeos/components/help_app_ui/BUILD.gn
+++ b/chromeos/components/help_app_ui/BUILD.gn
@@ -133,6 +133,7 @@
 js_library("test_driver_js") {
   testonly = true
   sources = [ "test/driver.js" ]
+  externs_list = [ "//third_party/chaijs/externs/chai-3.5.js" ]
   deps = [
     ":test_driver_api_js",
     "//chromeos/components/help_app_ui/resources:browser_proxy",
diff --git a/chromeos/components/help_app_ui/resources/browser_proxy.js b/chromeos/components/help_app_ui/resources/browser_proxy.js
index ca0b96b..32ff8bc 100644
--- a/chromeos/components/help_app_ui/resources/browser_proxy.js
+++ b/chromeos/components/help_app_ui/resources/browser_proxy.js
@@ -37,6 +37,7 @@
 const TITLE_ID = 'title';
 const BODY_ID = 'body';
 const CATEGORY_ID = 'main-category';
+const SUBCATEGORY_ID = 'subcategory';
 const SUBHEADING_ID = 'subheading';
 
 /**
@@ -79,9 +80,17 @@
             weight: 0.1,
           },
         ];
+        if (searchable_item.subcategoryNames) {
+          for (let i = 0; i < searchable_item.subcategoryNames.length; ++i) {
+            contents.push({
+              id: SUBCATEGORY_ID + i,
+              content: toString16(searchable_item.subcategoryNames[i]),
+              weight: 0.1,
+            });
+          }
+        }
         // If there are subheadings, use those instead of the body.
-        if (searchable_item.subheadings
-            && searchable_item.subheadings.length > 0) {
+        if (searchable_item.subheadings) {
           for (let i = 0; i < searchable_item.subheadings.length; ++i) {
             contents.push({
               id: SUBHEADING_ID + i,
diff --git a/chromeos/components/help_app_ui/test/help_app_guest_ui_browsertest.js b/chromeos/components/help_app_ui/test/help_app_guest_ui_browsertest.js
index d4a1725a..244f094 100644
--- a/chromeos/components/help_app_ui/test/help_app_guest_ui_browsertest.js
+++ b/chromeos/components/help_app_ui/test/help_app_guest_ui_browsertest.js
@@ -98,6 +98,72 @@
   ]);
 });
 
+// Test that search works for the categories and subcategories of searchable
+// items.
+GUEST_TEST('GuestCanSearchWithCategories', async () => {
+  const delegate = await waitForInitialIndexUpdate();
+
+  await delegate.addOrUpdateSearchIndex([{
+    // Main category match. No subcategories.
+    id: 'test-id-1',
+    title: 'Title with of article',
+    body: 'Body text',
+    mainCategoryName: 'Verycomplicatedsearchtoken',
+    locale: 'en-US',
+  },{
+    // Subcategory match.
+    id: 'test-id-2',
+    title: 'Title 2',
+    subcategoryNames: [
+      'Subcategory 1',
+      'verycomplicatedsearchtoken in subcategory. Verycomplicatedsearchtoken',
+      'Another subcategory with verycomplicatedsearchtoken',
+    ],
+    body: 'Body text',
+    mainCategoryName: 'Help',
+    locale: 'en-US',
+  },{
+    // Should not appear in the results.
+    id: 'test-id-3',
+    title: 'Title of irrelevant article',
+    body: 'Body text',
+    mainCategoryName: 'Help',
+    locale: 'en-US',
+  }]);
+
+  // Keep polling until the index finishes updating or too much time has passed.
+  /** @type {?helpApp.FindResponse} */
+  let response = null;
+  for (let numTries = 0; numTries < 50; numTries++) {
+    // This search query was chosen because it is unlikely to show any search
+    // results for the real app's data.
+    response = await delegate.findInSearchIndex('verycomplicatedsearchtoken');
+    if (response && response.results && response.results.length > 0) break;
+    await new Promise(resolve => {setTimeout(resolve, 50)});
+  }
+
+  // Don't test the ordering of search results because they should have similar
+  // relevance.
+  chai.expect(response.results).to.have.deep.members([
+      // This result only matches on the main category.
+      {
+        id: 'test-id-1',
+        titlePositions: [],
+        subheadingIndex: null,
+        subheadingPositions: null,
+        bodyPositions: [],
+      },
+      // This result only matches on the second and third subcategories.
+      {
+        id: 'test-id-2',
+        titlePositions: [],
+        subheadingIndex: null,
+        subheadingPositions: null,
+        bodyPositions: [],
+      },
+    ]);
+});
+
 // Test that the guest frame can clear the search index.
 GUEST_TEST('GuestCanClearSearchIndex', async () => {
   const delegate = await waitForInitialIndexUpdate();
diff --git a/chromeos/components/help_app_ui/test/help_app_ui_browsertest.js b/chromeos/components/help_app_ui/test/help_app_ui_browsertest.js
index e219c01..aadfe4b5 100644
--- a/chromeos/components/help_app_ui/test/help_app_ui_browsertest.js
+++ b/chromeos/components/help_app_ui/test/help_app_ui_browsertest.js
@@ -84,6 +84,11 @@
   testDone();
 });
 
+TEST_F('HelpAppUIBrowserTest', 'GuestCanSearchWithCategories', async () => {
+  await runTestInGuest('GuestCanSearchWithCategories');
+  testDone();
+});
+
 TEST_F('HelpAppUIBrowserTest', 'GuestCanClearSearchIndex', async () => {
   await runTestInGuest('GuestCanClearSearchIndex');
   testDone();
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index ebdb116..cdc6005 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -215,7 +215,7 @@
 // If ash-chrome is newer than lacros-chrome, then some fields may not be
 // processed by lacros-chrome.
 //
-// Next version: 11
+// Next version: 12
 [Stable]
 struct LacrosInitParams {
   // This is ash-chrome's version of the AshChromeService interface. This is
@@ -282,6 +282,14 @@
   // Do not use this to construct paths, use DefaultPaths for that purpose.
   [MinVersion=10]
   string? cros_user_id_hash@10;
+
+  // Policy blob of the device account. If present, it's a managed account with
+  // policy data. If empty, it's unmanaged account. If absent, an error occurred
+  // while loading policy data. The format is serialized PolicyFetchResponse
+  // object. See components/policy/proto/device_management_backend.proto for
+  // details.
+  [MinVersion=11]
+  array<uint8>? device_account_policy@11;
 };
 
 // LacrosChromeService defines the APIs that live in lacros-chrome and
diff --git a/chromeos/dbus/hermes/fake_hermes_manager_client.cc b/chromeos/dbus/hermes/fake_hermes_manager_client.cc
index 991f8a7..610a7fd 100644
--- a/chromeos/dbus/hermes/fake_hermes_manager_client.cc
+++ b/chromeos/dbus/hermes/fake_hermes_manager_client.cc
@@ -88,6 +88,9 @@
   euicc_client_test->AddFakeCarrierProfile(dbus::ObjectPath(kDefaultEuiccPath),
                                            hermes::profile::State::kInactive,
                                            "");
+  euicc_client_test->AddFakeCarrierProfile(dbus::ObjectPath(kDefaultEuiccPath),
+                                           hermes::profile::State::kPending,
+                                           "");
 }
 
 void FakeHermesManagerClient::NotifyAvailableEuiccListChanged() {
diff --git a/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java b/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java
index 21215e7c..63ecfa7 100644
--- a/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java
+++ b/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java
@@ -5,6 +5,7 @@
 package org.chromium.components.autofill;
 
 import android.annotation.TargetApi;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Build;
@@ -29,7 +30,8 @@
     // NOTE: As a result of the above, the tag below still references the name of this class from
     // when it was originally developed specifically for Android WebView.
     public static final String TAG = "AwAutofillManager";
-
+    private static final String AWG_COMPONENT_NAME =
+            "com.google.android.gms/com.google.android.gms.autofill.service.AutofillService";
     /**
      * The observer of suggestion window.
      */
@@ -58,17 +60,32 @@
     private boolean mDestroyed;
     private boolean mDisabled;
     private ArrayList<WeakReference<InputUIObserver>> mInputUIObservers;
+    // Indicates if AwG is the current Android autofill service.
+    private final boolean mIsAwGCurrentAutofillService;
 
     public AutofillManagerWrapper(Context context) {
         updateLogStat();
         if (isLoggable()) log("constructor");
         mAutofillManager = context.getSystemService(AutofillManager.class);
         mDisabled = mAutofillManager == null || !mAutofillManager.isEnabled();
+
         if (mDisabled) {
+            mIsAwGCurrentAutofillService = false;
             if (isLoggable()) log("disabled");
             return;
         }
 
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            ComponentName componentName = mAutofillManager.getAutofillServiceComponentName();
+            if (componentName != null) {
+                mIsAwGCurrentAutofillService =
+                        AWG_COMPONENT_NAME.equals(componentName.flattenToString());
+            } else {
+                mIsAwGCurrentAutofillService = false;
+            }
+        } else {
+            mIsAwGCurrentAutofillService = false;
+        }
         mMonitor = new AutofillInputUIMonitor(this);
         mAutofillManager.registerCallback(mMonitor);
     }
@@ -142,6 +159,14 @@
         return mDisabled;
     }
 
+    /**
+     * Only work for Android P and beyond. Always return false for Android O.
+     * @return if the Autofill with Google is the current autofill service.
+     */
+    public boolean isAwGCurrentAutofillService() {
+        return mIsAwGCurrentAutofillService;
+    }
+
     private boolean checkAndWarnIfDestroyed() {
         if (mDestroyed) {
             Log.w(TAG, "Application attempted to call on a destroyed AutofillManagerWrapper",
diff --git a/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java b/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java
index 6326536..df54e7c 100644
--- a/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java
+++ b/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java
@@ -319,7 +319,7 @@
             assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
             mAutofillManager = manager;
             mContainerView = containerView;
-            mAutofillUMA = new AutofillProviderUMA(context);
+            mAutofillUMA = new AutofillProviderUMA(context, manager.isAwGCurrentAutofillService());
             mInputUIObserver = new AutofillManagerWrapper.InputUIObserver() {
                 @Override
                 public void onInputUIShown() {
@@ -440,6 +440,9 @@
         int virtualId = mRequest.getVirtualId((short) focus);
         notifyVirtualViewEntered(mContainerView, virtualId, absBound);
         mAutofillUMA.onSessionStarted(mAutofillManager.isDisabled());
+        if (hasServerPrediction) {
+            mAutofillUMA.onServerTypeAvailable(formData, /*afterSessionStarted=*/false);
+        }
         mAutofillTriggeredTimeMillis = System.currentTimeMillis();
 
         mAutofillManager.notifyNewSessionStarted();
@@ -756,10 +759,12 @@
     @CalledByNative
     private void onQueryDone(boolean success) {
         mRequest.onQueryDone(success);
+        mAutofillUMA.onServerTypeAvailable(
+                success ? mRequest.mFormData : null, /*afterSessionStarted*/ true);
         mAutofillManager.onQueryDone(success);
     }
 
-    private static boolean isQueryServerFieldTypesEnabled() {
+    public static boolean isQueryServerFieldTypesEnabled() {
         if (sIsQueryServerFieldTypesEnabled == null) {
             sIsQueryServerFieldTypesEnabled =
                     AutofillProviderJni.get().isQueryServerFieldTypesEnabled();
diff --git a/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java b/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java
index 394cd84..3f4ea07 100644
--- a/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java
+++ b/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java
@@ -5,6 +5,7 @@
 package org.chromium.components.autofill;
 
 import android.content.Context;
+import android.os.Build;
 
 import org.chromium.autofill.mojom.SubmissionSource;
 import org.chromium.base.ContextUtils;
@@ -26,6 +27,10 @@
     public static final String UMA_AUTOFILL_CREATED_BY_ACTIVITY_CONTEXT =
             "Autofill.WebView.CreatedByActivityContext";
 
+    // Records whether the current autofill service is AwG.
+    public static final String UMA_AUTOFILL_AWG_IS_CURRENT_SERVICE =
+            "Autofill.WebView.AwGIsCurrentService";
+
     // Records what happened in an autofill session.
     public static final String UMA_AUTOFILL_AUTOFILL_SESSION = "Autofill.WebView.AutofillSession";
     // The possible value of UMA_AUTOFILL_AUTOFILL_SESSION.
@@ -45,6 +50,25 @@
     public static final int USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED = 13;
     public static final int AUTOFILL_SESSION_HISTOGRAM_COUNT = 14;
 
+    // The possible values for the server prediction availability.
+    public static final String UMA_AUTOFILL_SERVER_PREDICTION_AVAILABILITY =
+            "Autofill.WebView.ServerPredicton.PredictionAvailability";
+    public static final int SERVER_PREDICTION_NOT_AVAILABLE = 0;
+    public static final int SERVER_PREDICTION_AVAILABLE_ON_SESSION_STARTS = 1;
+    public static final int SERVER_PREDICTION_AVAILABLE_AFTER_SESSION_STARTS = 2;
+    public static final int SERVER_PREDICTION_AVAILABLE_COUNT = 3;
+
+    // The possible values for the AwG suggestion availability.
+    public static final String UMA_AUTOFILL_AWG_SUGGSTION_AVAILABILITY =
+            "Autofill.WebView.ServerPrediction.AwGSuggestionAvailability";
+    public static final int AWG_NO_SUGGESTION = 0;
+    public static final int AWG_HAS_SUGGESTION_NO_AUTOFILL = 1;
+    public static final int AWG_HAS_SUGGESTION_AUTOFILLED = 2;
+    public static final int AWG_SUGGSTION_AVAILABLE_COUNT = 3;
+
+    public static final String UMA_AUTOFILL_VALID_SERVER_PREDICTION =
+            "Autofill.WebView.ServerPredicton.HasValidServerPrediction";
+
     // Records whether user changed autofilled field if user ever changed the form. The action isn't
     // recorded if user didn't change form at all.
     public static final String UMA_AUTOFILL_USER_CHANGED_AUTOFILLED_FIELD =
@@ -120,6 +144,31 @@
             if (mSuggestionTimeMillis != null) {
                 recordTimesHistogram(UMA_AUTOFILL_SUGGESTION_TIME, mSuggestionTimeMillis);
             }
+            if (!mServerPredictionAvailable && AutofillProvider.isQueryServerFieldTypesEnabled()) {
+                RecordHistogram.recordEnumeratedHistogram(
+                        UMA_AUTOFILL_SERVER_PREDICTION_AVAILABILITY,
+                        SERVER_PREDICTION_NOT_AVAILABLE, SERVER_PREDICTION_AVAILABLE_COUNT);
+            }
+        }
+
+        public void onServerTypeAvailable(FormData formData, boolean afterSessionStarted) {
+            if (!AutofillProvider.isQueryServerFieldTypesEnabled()) return;
+            mServerPredictionAvailable = true;
+            RecordHistogram.recordEnumeratedHistogram(UMA_AUTOFILL_SERVER_PREDICTION_AVAILABILITY,
+                    afterSessionStarted ? SERVER_PREDICTION_AVAILABLE_AFTER_SESSION_STARTS
+                                        : SERVER_PREDICTION_AVAILABLE_ON_SESSION_STARTS,
+                    SERVER_PREDICTION_AVAILABLE_COUNT);
+            if (formData != null) {
+                boolean hasValidServerData = false;
+                for (FormFieldData fieldData : formData.mFields) {
+                    if (!fieldData.getServerType().equals("NO_SERVER_DATA")) {
+                        hasValidServerData = true;
+                        break;
+                    }
+                }
+                RecordHistogram.recordBooleanHistogram(
+                        UMA_AUTOFILL_VALID_SERVER_PREDICTION, hasValidServerData);
+            }
         }
 
         private int toUMAAutofillSessionValue() {
@@ -174,19 +223,62 @@
 
         private int mState;
         private Boolean mUserChangedAutofilledField;
+
+        // Indicates whether the server prediction arrives.
+        private boolean mServerPredictionAvailable;
+    }
+
+    /**
+     * The class to record Autofill.WebView.ServerPrediction.AwGSuggestion, is only instantiated
+     * when the Android platform AutofillServcie is AwG, This will give us more actual result in
+     * A/B experiment while only AwG supports the server prediction.
+     */
+    private static class ServerPredictionRecorder {
+        private boolean mHasSuggestions;
+        private boolean mAutofilled;
+        private boolean mRecorded;
+
+        public void onSuggestionDisplayed() {
+            mHasSuggestions = true;
+        }
+
+        public void onAutofill() {
+            mAutofilled = true;
+        }
+
+        public void recordHistograms() {
+            if (mRecorded) return;
+            mRecorded = true;
+            int sample = AWG_NO_SUGGESTION;
+            if (mHasSuggestions) {
+                sample = mAutofilled ? AWG_HAS_SUGGESTION_AUTOFILLED
+                                     : AWG_HAS_SUGGESTION_NO_AUTOFILL;
+            }
+            RecordHistogram.recordEnumeratedHistogram(
+                    UMA_AUTOFILL_AWG_SUGGSTION_AVAILABILITY, sample, AWG_SUGGSTION_AVAILABLE_COUNT);
+        }
     }
 
     private SessionRecorder mRecorder;
     private Boolean mAutofillDisabled;
 
-    public AutofillProviderUMA(Context context) {
+    private final boolean mIsAwGCurrentAutofillService;
+    private ServerPredictionRecorder mServerPredictionRecorder;
+
+    public AutofillProviderUMA(Context context, boolean isAwGCurrentAutofillService) {
         RecordHistogram.recordBooleanHistogram(UMA_AUTOFILL_CREATED_BY_ACTIVITY_CONTEXT,
                 ContextUtils.activityFromContext(context) != null);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            RecordHistogram.recordBooleanHistogram(
+                    UMA_AUTOFILL_AWG_IS_CURRENT_SERVICE, isAwGCurrentAutofillService);
+        }
+        mIsAwGCurrentAutofillService = isAwGCurrentAutofillService;
     }
 
     public void onFormSubmitted(int submissionSource) {
         if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_FORM_SUBMITTED);
         recordSession();
+        if (mServerPredictionRecorder != null) mServerPredictionRecorder.recordHistograms();
         // We record this no matter autofill service is disabled or not.
         RecordHistogram.recordEnumeratedHistogram(UMA_AUTOFILL_SUBMISSION_SOURCE,
                 toUMASubmissionSource(submissionSource), SUBMISSION_SOURCE_HISTOGRAM_COUNT);
@@ -201,6 +293,9 @@
 
         if (mRecorder != null) recordSession();
         mRecorder = new SessionRecorder();
+        if (mIsAwGCurrentAutofillService) {
+            mServerPredictionRecorder = new ServerPredictionRecorder();
+        }
     }
 
     public void onVirtualStructureProvided() {
@@ -212,10 +307,12 @@
             mRecorder.record(SessionRecorder.EVENT_SUGGESTION_DISPLAYED);
             mRecorder.setSuggestionTimeMillis(suggestionTimeMillis);
         }
+        if (mServerPredictionRecorder != null) mServerPredictionRecorder.onSuggestionDisplayed();
     }
 
     public void onAutofill() {
         if (mRecorder != null) mRecorder.record(SessionRecorder.EVENT_FORM_AUTOFILLED);
+        if (mServerPredictionRecorder != null) mServerPredictionRecorder.onAutofill();
     }
 
     public void onUserChangeFieldValue(boolean isPreviouslyAutofilled) {
@@ -227,6 +324,17 @@
         }
     }
 
+    /**
+     * Invoked when the server query was done or has arrived when the autofill sension starts.
+     *
+     * @param formData the form of the current session, is null if the query failed.
+     * @param afterSessionStarted true if the server type predication arrive after the session
+     *         starts.
+     */
+    public void onServerTypeAvailable(FormData formData, boolean afterSessionStarted) {
+        mRecorder.onServerTypeAvailable(formData, afterSessionStarted);
+    }
+
     private void recordSession() {
         if (mAutofillDisabled != null && !mAutofillDisabled.booleanValue() && mRecorder != null) {
             mRecorder.recordHistogram();
diff --git a/components/autofill/android/provider/test_support/autofill_provider_test_helper.cc b/components/autofill/android/provider/test_support/autofill_provider_test_helper.cc
index 080ee9a..a5d2e44d 100644
--- a/components/autofill/android/provider/test_support/autofill_provider_test_helper.cc
+++ b/components/autofill/android/provider/test_support/autofill_provider_test_helper.cc
@@ -71,9 +71,10 @@
     }
     if (found_fields_count > 0) {
       signatures = autofill::test::GetEncodedSignatures(*(j.second));
-      CHECK(found_fields_count == field_ids.size());
+      break;
     }
   }
+  CHECK(found_fields_count == field_ids.size());
 
   std::string response_string;
   CHECK(response.SerializeToString(&response_string));
diff --git a/components/autofill_assistant/browser/details.cc b/components/autofill_assistant/browser/details.cc
index d864f63..bf5ba0c 100644
--- a/components/autofill_assistant/browser/details.cc
+++ b/components/autofill_assistant/browser/details.cc
@@ -253,10 +253,9 @@
   if (show_initial.value_or("true") == "false") {
     return false;
   }
-  // Whenever details are updated from parameters we want to animate missing
-  // data.
-  proto_.set_animate_placeholders(true);
-  proto_.set_show_image_placeholder(true);
+  // Whenever details are updated from parameters we want to show a placeholder
+  // for the image.
+  proto_.mutable_placeholders()->set_show_image_placeholder(true);
   if (MaybeUpdateFromDetailsParameters(context)) {
     Update();
     return true;
@@ -370,10 +369,6 @@
   return proto_.title();
 }
 
-int Details::titleMaxLines() const {
-  return title_max_lines_;
-}
-
 const std::string Details::imageUrl() const {
   return proto_.image_url();
 }
@@ -405,10 +400,6 @@
   return proto_.image_clickthrough_data().clickthrough_url();
 }
 
-bool Details::showImagePlaceholder() const {
-  return proto_.show_image_placeholder();
-}
-
 const std::string Details::totalPriceLabel() const {
   return proto_.total_price_label();
 }
@@ -453,8 +444,8 @@
   return change_flags_.highlight_line3();
 }
 
-bool Details::animatePlaceholders() const {
-  return proto_.animate_placeholders();
+DetailsProto::PlaceholdersConfiguration Details::placeholders() const {
+  return proto_.placeholders();
 }
 
 void Details::ClearChanges() {
@@ -473,16 +464,6 @@
   price_attribution_content_.assign(proto_.total_price().empty()
                                         ? std::string()
                                         : proto_.description_line_3());
-
-  bool isDescriptionLine1Empty = descriptionLine1().empty();
-  bool isDescriptionLine2Empty = descriptionLine2().empty();
-  if (isDescriptionLine1Empty && isDescriptionLine2Empty) {
-    title_max_lines_ = 3;
-  } else if (isDescriptionLine1Empty || isDescriptionLine2Empty) {
-    title_max_lines_ = 2;
-  } else {
-    title_max_lines_ = 1;
-  }
 }
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/details.h b/components/autofill_assistant/browser/details.h
index ec48966..c43a7f5 100644
--- a/components/autofill_assistant/browser/details.h
+++ b/components/autofill_assistant/browser/details.h
@@ -57,7 +57,6 @@
                                            Details* details);
 
   const std::string title() const;
-  int titleMaxLines() const;
   const std::string imageUrl() const;
   const base::Optional<std::string> imageAccessibilityHint() const;
   bool imageAllowClickthrough() const;
@@ -65,7 +64,6 @@
   const std::string imagePositiveText() const;
   const std::string imageNegativeText() const;
   const std::string imageClickthroughUrl() const;
-  bool showImagePlaceholder() const;
   const std::string totalPriceLabel() const;
   const std::string totalPrice() const;
   const std::string descriptionLine1() const;
@@ -77,7 +75,7 @@
   bool highlightLine1() const;
   bool highlightLine2() const;
   bool highlightLine3() const;
-  bool animatePlaceholders() const;
+  DetailsProto::PlaceholdersConfiguration placeholders() const;
 
   // Clears all change flags.
   void ClearChanges();
@@ -98,9 +96,6 @@
   DetailsProto proto_;
   DetailsChangesProto change_flags_;
 
-  // Maximum of lines for the title.
-  int title_max_lines_ = 1;
-
   // Content to be shown in description line 1 in the UI.
   std::string description_line_1_content_;
 
diff --git a/components/autofill_assistant/browser/details_unittest.cc b/components/autofill_assistant/browser/details_unittest.cc
index cdcfbe0f..046bf58a 100644
--- a/components/autofill_assistant/browser/details_unittest.cc
+++ b/components/autofill_assistant/browser/details_unittest.cc
@@ -87,8 +87,7 @@
   Details details;
   details.UpdateFromParameters(*context);
 
-  EXPECT_TRUE(details.animatePlaceholders());
-  EXPECT_TRUE(details.showImagePlaceholder());
+  EXPECT_TRUE(details.placeholders().show_image_placeholder());
 }
 
 TEST_F(DetailsTest, UpdateFromParametersUpdateFromDetails) {
@@ -109,7 +108,7 @@
   Details details;
   EXPECT_TRUE(details.UpdateFromParameters(*context));
 
-  EXPECT_TRUE(details.animatePlaceholders());
+  EXPECT_TRUE(details.placeholders().show_image_placeholder());
   EXPECT_THAT(details.title(), Eq("title"));
   EXPECT_THAT(details.descriptionLine1(), Eq("line1"));
   EXPECT_THAT(details.descriptionLine2(), Eq("line2"));
@@ -137,8 +136,7 @@
   Details details;
   EXPECT_TRUE(details.UpdateFromParameters(*context));
 
-  EXPECT_TRUE(details.animatePlaceholders());
-  EXPECT_TRUE(details.showImagePlaceholder());
+  EXPECT_TRUE(details.placeholders().show_image_placeholder());
   EXPECT_THAT(details.title(), Eq("movie_name"));
   EXPECT_THAT(details.descriptionLine2(), Eq("movie_theater"));
   EXPECT_THAT(details.descriptionLine1(),
@@ -287,42 +285,6 @@
   EXPECT_FALSE(details.descriptionLine1().empty());
 }
 
-TEST_F(DetailsTest, GetTitleMaxLines) {
-  Details details;
-
-  ShowDetailsProto proto_no_description;
-  proto_no_description.mutable_details()->set_title("title");
-  EXPECT_TRUE(Details::UpdateFromProto(proto_no_description, &details));
-  EXPECT_THAT(details.titleMaxLines(), Eq(3));
-
-  ShowDetailsProto proto_description1;
-  proto_description1.mutable_details()->set_title("title");
-  proto_description1.mutable_details()->set_description_line_1("line 1");
-  EXPECT_TRUE(Details::UpdateFromProto(proto_description1, &details));
-  EXPECT_THAT(details.titleMaxLines(), Eq(2));
-
-  ShowDetailsProto proto_description2;
-  proto_description2.mutable_details()->set_title("title");
-  proto_description2.mutable_details()->set_description_line_2("line 2");
-  EXPECT_TRUE(Details::UpdateFromProto(proto_description2, &details));
-  EXPECT_THAT(details.titleMaxLines(), Eq(2));
-
-  ShowDetailsProto proto_description1_date;
-  proto_description1_date.mutable_details()->set_title("title");
-  SetDateTimeProto(
-      proto_description1_date.mutable_details()->mutable_datetime(), 2019, 9,
-      26, 16, 40, 2);
-  EXPECT_TRUE(Details::UpdateFromProto(proto_description1_date, &details));
-  EXPECT_THAT(details.titleMaxLines(), Eq(2));
-
-  ShowDetailsProto proto_both_descriptions;
-  proto_both_descriptions.mutable_details()->set_title("title");
-  proto_both_descriptions.mutable_details()->set_description_line_1("line 1");
-  proto_both_descriptions.mutable_details()->set_description_line_2("line 2");
-  EXPECT_TRUE(Details::UpdateFromProto(proto_both_descriptions, &details));
-  EXPECT_THAT(details.titleMaxLines(), Eq(1));
-}
-
 TEST_F(DetailsTest, GetDescriptionLine1) {
   base::test::ScopedRestoreICUDefaultLocale restore_locale;
 
@@ -443,14 +405,13 @@
   EXPECT_THAT(details.imageClickthroughUrl(), Eq("url"));
 }
 
-TEST_F(DetailsTest, GetPlaceholderFlags) {
+TEST_F(DetailsTest, GetPlaceholderConfiguration) {
   Details details;
   ShowDetailsProto proto;
-  proto.mutable_details()->set_show_image_placeholder(true);
-  proto.mutable_details()->set_animate_placeholders(true);
+  proto.mutable_details()->mutable_placeholders()->set_show_image_placeholder(
+      true);
   EXPECT_TRUE(Details::UpdateFromProto(proto, &details));
-  EXPECT_TRUE(details.showImagePlaceholder());
-  EXPECT_TRUE(details.animatePlaceholders());
+  EXPECT_TRUE(details.placeholders().show_image_placeholder());
 }
 
 TEST_F(DetailsTest, GetTotalPrice) {
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto
index ef836189..d5fdfc9 100644
--- a/components/autofill_assistant/browser/service.proto
+++ b/components/autofill_assistant/browser/service.proto
@@ -596,6 +596,13 @@
   // Whether the visual viewport should be resized to allow scrolling to the
   // bottom of the page while the trigger script is being displayed.
   optional bool resize_visual_viewport = 8;
+  // Whether the bottom sheet should temporarily disappear when scrolling down
+  // the website, to move out of the way.
+  //
+  // Avoid setting both resize_visual_viewport and scroll_to_hide to true, as
+  // the resulting behavior is confusing: the bottom sheet can pop back up after
+  // a scroll down instead of staying hidden in some situations.
+  optional bool scroll_to_hide = 9;
 }
 
 // An action could be performed.
@@ -2274,11 +2281,8 @@
 message DetailsProto {
   optional string title = 1;
 
-  oneof image {
-    string image_url = 2;
-    // When set to true shows placeholder in place of an image.
-    bool show_image_placeholder = 10;
-  }
+  optional string image_url = 2;
+
   // Specifies the hint for accessibility. If set to empty, the image will not
   // be announced by accessibility.
   optional string image_accessibility_hint = 14;
@@ -2317,17 +2321,30 @@
   optional DateTimeProto datetime = 3;
   optional string description = 4;
 
-  // Asks the UI to show animated placeholders for missing fields.
-  // The placeholder will be shown on effectively missing:
-  // * title
-  // * image
-  // * description line (1, 2 or 3)
-  // TODO(crbug.com/806868): Make the fields for displaying placeholders
-  // configurable by the server.
-  optional bool animate_placeholders = 11;
+  // The configuration for the placeholders.
+  message PlaceholdersConfiguration {
+    // Show a placeholder if |DetailsProto.image_url| is empty.
+    optional bool show_image_placeholder = 1;
+
+    // Show a placeholder if |DetailsProto.title| is empty.
+    optional bool show_title_placeholder = 2;
+
+    // Show a placeholder if |DetailsProto.description_line_1| is empty.
+    optional bool show_description_line_1_placeholder = 3;
+
+    // Show a placeholder if |DetailsProto.description_line_2| is empty.
+    optional bool show_description_line_2_placeholder = 4;
+
+    // Show a placeholder if |DetailsProto.description_line_3| is empty.
+    optional bool show_description_line_3_placeholder = 5;
+  }
+
+  // The placeholders configuration. Note that a placeholder will be shown only
+  // if the associated data is empty.
+  optional PlaceholdersConfiguration placeholders = 15;
 
   // Deprecated, no longer supported.
-  reserved 5;
+  reserved 5, 10, 11;
 }
 
 // Show contextual information.
diff --git a/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc b/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc
index 6fc8c69..5611c9d 100644
--- a/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc
+++ b/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc
@@ -22,7 +22,6 @@
 #include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/subresource_filter/content/browser/fake_safe_browsing_database_manager.h"
-#include "components/subresource_filter/content/browser/subresource_filter_client.h"
 #include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
 #include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h"
 #include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_client.h"
@@ -48,31 +47,13 @@
     "ContentSettings.Popups.StrongBlocker.NumBlocked";
 
 class SafeBrowsingTriggeredPopupBlockerTest
-    : public content::RenderViewHostTestHarness,
-      public subresource_filter::SubresourceFilterClient {
+    : public content::RenderViewHostTestHarness {
  public:
   SafeBrowsingTriggeredPopupBlockerTest() = default;
   ~SafeBrowsingTriggeredPopupBlockerTest() override {
     settings_map_->ShutdownOnUIThread();
   }
 
-  // subresource_filter::SubresourceFilterClient:
-  void ShowNotification() override {}
-  subresource_filter::mojom::ActivationLevel OnPageActivationComputed(
-      content::NavigationHandle* navigation_handle,
-      subresource_filter::mojom::ActivationLevel initial_activation_level,
-      subresource_filter::ActivationDecision* decision) override {
-    return initial_activation_level;
-  }
-  void OnAdsViolationTriggered(
-      content::RenderFrameHost*,
-      subresource_filter::mojom::AdsViolation) override {}
-  const scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
-  GetSafeBrowsingDatabaseManager() override {
-    return nullptr;
-  }
-  void OnReloadRequested() override {}
-
   // content::RenderViewHostTestHarness:
   void SetUp() override {
     content::RenderViewHostTestHarness::SetUp();
@@ -161,7 +142,7 @@
       content::NavigationHandle* handle) {
     return std::make_unique<
         subresource_filter::SubresourceFilterSafeBrowsingActivationThrottle>(
-        handle, this, content::GetIOThreadTaskRunner({}),
+        handle, /*delegate=*/nullptr, content::GetIOThreadTaskRunner({}),
         fake_safe_browsing_database_);
   }
 
diff --git a/components/browser_ui/android/bottomsheet/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java b/components/browser_ui/android/bottomsheet/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
index 4614ff6..19657ea 100644
--- a/components/browser_ui/android/bottomsheet/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
+++ b/components/browser_ui/android/bottomsheet/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
@@ -180,6 +180,12 @@
      */
     private int mScrollableHeight;
 
+    /**
+     * The instance passed to the current content that is allowed to
+     * change the sheet offset.
+     */
+    private Callback<Integer> mOffsetController;
+
     @Override
     public boolean shouldGestureMoveSheet(MotionEvent initialEvent, MotionEvent currentEvent) {
         // If the sheet is scrolling off-screen or in the process of hiding, gestures should not
@@ -526,6 +532,10 @@
         if (mSheetContent != null) {
             mSheetContent.setContentSizeListener(null);
             mSheetContent.getContentView().removeOnLayoutChangeListener(this);
+            if (mOffsetController != null) {
+                mSheetContent.setOffsetController(null);
+                mOffsetController = null;
+            }
         }
 
         if (content != null && getParent() == null) {
@@ -667,8 +677,21 @@
     /**
      * Sets the sheet's offset relative to the bottom of the screen.
      * @param offset The offset that the sheet should be.
+     * @param reason The reason for the sheet offset to change to report to listeners.
      */
     void setSheetOffsetFromBottom(float offset, @StateChangeReason int reason) {
+        setSheetOffsetFromBottom(offset, reason, /* reportOpenClosed= */ true);
+    }
+
+    /**
+     * Sets the sheet's offset relative to the bottom of the screen.
+     * @param offset The offset that the sheet should be.
+     * @param reason The reason for the sheet offset to change to report to listeners.
+     * @param reportOpenClosed {@code true} to allow reporting the sheet opened or closed as a
+     *         result of this change. {@code reason} is never used when this is {@code false}.
+     */
+    void setSheetOffsetFromBottom(
+            float offset, @StateChangeReason int reason, boolean reportOpenClosed) {
         mCurrentOffsetPx = offset;
 
         // The browser controls offset is added here so that the sheet's toolbar behaves like the
@@ -679,28 +702,32 @@
 
         setTranslationY(translationY);
 
-        // Do open/close computation based on the minimum allowed state by the sheet's content.
-        // Note that when transitioning from hidden to peek, even dismissable sheets may want
-        // to have a peek state.
-        @SheetState
-        int minSwipableState = getMinSwipableSheetState();
-        if (isPeekStateEnabled() && (!isSheetOpen() || mTargetState == SheetState.PEEK)) {
-            minSwipableState = SheetState.PEEK;
-        }
+        if (reportOpenClosed) {
+            // Do open/close computation based on the minimum allowed state by the sheet's content.
+            // Note that when transitioning from hidden to peek, even dismissable sheets may want
+            // to have a peek state.
+            @SheetState
+            int minSwipableState = getMinSwipableSheetState();
+            if (isPeekStateEnabled() && (!isSheetOpen() || mTargetState == SheetState.PEEK)) {
+                minSwipableState = SheetState.PEEK;
+            }
 
-        float minScrollableHeight = getSheetHeightForState(minSwipableState);
-        boolean isAtMinHeight = MathUtils.areFloatsEqual(getCurrentOffsetPx(), minScrollableHeight);
-        boolean heightLessThanPeek = getCurrentOffsetPx() < minScrollableHeight;
-        // Trigger the onSheetClosed event when the sheet is moving toward the hidden state if peek
-        // is disabled. This should be fine since touch is disabled when the sheet's target is
-        // hidden.
-        boolean triggerCloseWithHidden = !isPeekStateEnabled() && mTargetState == SheetState.HIDDEN;
+            float minScrollableHeight = getSheetHeightForState(minSwipableState);
+            boolean isAtMinHeight =
+                    MathUtils.areFloatsEqual(getCurrentOffsetPx(), minScrollableHeight);
+            boolean heightLessThanPeek = getCurrentOffsetPx() < minScrollableHeight;
+            // Trigger the onSheetClosed event when the sheet is moving toward the hidden state if
+            // peek is disabled. This should be fine since touch is disabled when the sheet's target
+            // is hidden.
+            boolean triggerCloseWithHidden =
+                    !isPeekStateEnabled() && mTargetState == SheetState.HIDDEN;
 
-        if (isSheetOpen() && (heightLessThanPeek || isAtMinHeight || triggerCloseWithHidden)) {
-            onSheetClosed(reason);
-        } else if (!isSheetOpen() && mTargetState != SheetState.HIDDEN
-                && getCurrentOffsetPx() > minScrollableHeight) {
-            onSheetOpened(reason);
+            if (isSheetOpen() && (heightLessThanPeek || isAtMinHeight || triggerCloseWithHidden)) {
+                onSheetClosed(reason);
+            } else if (!isSheetOpen() && mTargetState != SheetState.HIDDEN
+                    && getCurrentOffsetPx() > minScrollableHeight) {
+                onSheetOpened(reason);
+            }
         }
 
         sendOffsetChangeEvents();
@@ -1228,18 +1255,34 @@
     protected void onSheetContentChanged(@Nullable final BottomSheetContent content) {
         mSheetContent = content;
 
-        if (content != null && isFullHeightWrapContent()) {
-            // Listen for layout/size changes.
-            if (!content.setContentSizeListener(this::onContentSizeChanged)) {
-                content.getContentView().addOnLayoutChangeListener(this);
+        if (content != null) {
+            if (isFullHeightWrapContent()) {
+                // Listen for layout/size changes.
+                if (!content.setContentSizeListener(this::onContentSizeChanged)) {
+                    content.getContentView().addOnLayoutChangeListener(this);
+                }
+
+                invalidateContentDesiredHeight();
+                ensureContentIsWrapped(/* animate= */ true);
+
+                // HALF state is forbidden when wrapping the content.
+                if (mCurrentState == SheetState.HALF) {
+                    setSheetState(SheetState.FULL, /* animate= */ true);
+                }
             }
+            if (content.contentControlsOffset()) {
+                mOffsetController = new Callback<Integer>() {
+                    @Override
+                    public void onResult(Integer offsetPx) {
+                        if (this != mOffsetController) return;
 
-            invalidateContentDesiredHeight();
-            ensureContentIsWrapped(/* animate= */ true);
-
-            // HALF state is forbidden when wrapping the content.
-            if (mCurrentState == SheetState.HALF) {
-                setSheetState(SheetState.FULL, /* animate= */ true);
+                        cancelAnimation();
+                        setSheetOffsetFromBottom(
+                                MathUtils.clamp(offsetPx, 0, (int) getMaxOffsetPx()),
+                                StateChangeReason.NONE, /* reportOpenClosed=*/false);
+                    }
+                };
+                content.setOffsetController(mOffsetController);
             }
         }
 
@@ -1267,6 +1310,12 @@
      */
     private void onContentSizeChanged(int width, int height, int oldWidth, int oldHeight) {
         boolean heightChanged = mContentDesiredHeight != height;
+        boolean widthChanged = mContentWidth != width;
+
+        // onContentSizeChanged() is sometimes called when there's no size change, because of
+        // animations running in the content. Ignore these calls.
+        if (!heightChanged && !widthChanged) return;
+
         mContentDesiredHeight = height;
         mContentWidth = width;
 
diff --git a/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetContent.java b/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetContent.java
index 554352d..9b0c55e7 100644
--- a/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetContent.java
+++ b/components/browser_ui/android/bottomsheet/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetContent.java
@@ -9,6 +9,8 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import org.chromium.base.Callback;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -205,4 +207,23 @@
      *         typically the name of your feature followed by 'closed'.
      */
     int getSheetClosedAccessibilityStringId();
+
+    /**
+     * Return {@code true} if the content expects {@link #setOffsetController} to be called.
+     *
+     * This is an experimental feature. Use it at your own risks. TODO(b/177037825): Remove or
+     * cleanup.
+     */
+    default boolean contentControlsOffset() {
+        return false;
+    }
+
+    /**
+     * Set or reset the set offset callback.
+     *
+     * The active content can use this callback to move the sheet to the given offset.
+     *
+     * Only called if {@link #contentControlsOffset} returns {@code true}.
+     */
+    default void setOffsetController(@Nullable Callback<Integer> setOffset) {}
 }
diff --git a/components/full_restore/BUILD.gn b/components/full_restore/BUILD.gn
index f1c2363..3276bb6 100644
--- a/components/full_restore/BUILD.gn
+++ b/components/full_restore/BUILD.gn
@@ -52,5 +52,6 @@
     ":full_restore",
     "//content/test:test_support",
     "//testing/gtest",
+    "//ui/aura:test_support",
   ]
 }
diff --git a/components/full_restore/full_restore_read_and_save_unittest.cc b/components/full_restore/full_restore_read_and_save_unittest.cc
index f14404d..5e71b5e4 100644
--- a/components/full_restore/full_restore_read_and_save_unittest.cc
+++ b/components/full_restore/full_restore_read_and_save_unittest.cc
@@ -17,17 +17,23 @@
 #include "components/full_restore/full_restore_save_handler.h"
 #include "components/full_restore/full_restore_utils.h"
 #include "components/full_restore/restore_data.h"
+#include "components/full_restore/window_info.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
 
 namespace full_restore {
 
 namespace {
 
-const char kAppId[] = "aaa";
+constexpr char kAppId[] = "aaa";
 
-const int32_t kId1 = 100;
-const int32_t kId2 = 200;
+constexpr int32_t kId1 = 100;
+constexpr int32_t kId2 = 200;
+
+constexpr int32_t kActivationIndex1 = 100;
+constexpr int32_t kActivationIndex2 = 101;
 
 }  // namespace
 
@@ -71,7 +77,19 @@
         file_path, std::make_unique<full_restore::AppLaunchInfo>(kAppId, id));
   }
 
-  void VerifyRestoreData(const base::FilePath& file_path, int32_t id) {
+  void CreateWindowInfo(int32_t id, int32_t index) {
+    std::unique_ptr<aura::Window> window(
+        aura::test::CreateTestWindowWithId(id, nullptr));
+    WindowInfo window_info;
+    window_info.window = window.get();
+    window->SetProperty(full_restore::kWindowIdKey, id);
+    window_info.activation_index = index;
+    full_restore::SaveWindowInfo(window_info);
+  }
+
+  void VerifyRestoreData(const base::FilePath& file_path,
+                         int32_t id,
+                         int32_t index) {
     ReadFromFile(file_path);
 
     const auto* restore_data = GetRestoreData(file_path);
@@ -88,6 +106,10 @@
     // Verify for |id|
     const auto app_restore_data_it = launch_list_it->second.find(id);
     EXPECT_TRUE(app_restore_data_it != launch_list_it->second.end());
+
+    const auto& data = app_restore_data_it->second;
+    EXPECT_TRUE(data->activation_index.has_value());
+    EXPECT_EQ(index, data->activation_index.value());
   }
 
   content::BrowserTaskEnvironment& task_environment() {
@@ -112,15 +134,23 @@
   FullRestoreSaveHandler* save_handler = FullRestoreSaveHandler::GetInstance();
   base::OneShotTimer* timer = save_handler->GetTimerForTesting();
 
-  // Add app launch info, and verity the timer starts.
+  // Add app launch info, and verify the timer starts.
   AddAppLaunchInfo(GetPath(), kId1);
   EXPECT_TRUE(timer->IsRunning());
 
-  // Add one more app launch info, and verity the timer is still running.
+  // Add one more app launch info, and verify the timer is still running.
   AddAppLaunchInfo(GetPath(), kId2);
   EXPECT_TRUE(timer->IsRunning());
 
-  // Simulate timeout, and verity the timer stops.
+  // Simulate timeout, and verify the timer stops.
+  timer->FireNow();
+  CreateWindowInfo(kId2, kActivationIndex2);
+  task_environment().RunUntilIdle();
+  EXPECT_FALSE(timer->IsRunning());
+
+  // Modify the window info, and verify the timer starts.
+  CreateWindowInfo(kId1, kActivationIndex1);
+  EXPECT_TRUE(timer->IsRunning());
   timer->FireNow();
   task_environment().RunUntilIdle();
 
@@ -142,9 +172,17 @@
   const auto app_restore_data_it1 = launch_list_it->second.find(kId1);
   EXPECT_TRUE(app_restore_data_it1 != launch_list_it->second.end());
 
+  const auto& data1 = app_restore_data_it1->second;
+  EXPECT_TRUE(data1->activation_index.has_value());
+  EXPECT_EQ(kActivationIndex1, data1->activation_index.value());
+
   // Verify for |kId2|
   const auto app_restore_data_it2 = launch_list_it->second.find(kId2);
   EXPECT_TRUE(app_restore_data_it2 != launch_list_it->second.end());
+
+  const auto& data2 = app_restore_data_it2->second;
+  EXPECT_TRUE(data2->activation_index.has_value());
+  EXPECT_EQ(kActivationIndex2, data2->activation_index.value());
 }
 
 TEST_F(FullRestoreReadAndSaveTest, MultipleFilePaths) {
@@ -156,20 +194,28 @@
   ASSERT_TRUE(tmp_dir1.CreateUniqueTempDir());
   ASSERT_TRUE(tmp_dir2.CreateUniqueTempDir());
 
-  // Add app launch info for |tmp_dir1|, and verity the timer starts.
+  // Add app launch info for |tmp_dir1|, and verify the timer starts.
   AddAppLaunchInfo(tmp_dir1.GetPath(), kId1);
   EXPECT_TRUE(timer->IsRunning());
 
-  // Add app launch info for |tmp_dir2|, and verity the timer is still running.
+  // Add app launch info for |tmp_dir2|, and verify the timer is still running.
   AddAppLaunchInfo(tmp_dir2.GetPath(), kId2);
   EXPECT_TRUE(timer->IsRunning());
 
-  // Simulate timeout, and verity the timer stops.
+  // Simulate timeout, and verify the timer stops.
+  timer->FireNow();
+  CreateWindowInfo(kId2, kActivationIndex2);
+  task_environment().RunUntilIdle();
+  EXPECT_FALSE(timer->IsRunning());
+
+  // Modify the window info, and verify the timer starts.
+  CreateWindowInfo(kId1, kActivationIndex1);
+  EXPECT_TRUE(timer->IsRunning());
   timer->FireNow();
   task_environment().RunUntilIdle();
 
-  VerifyRestoreData(tmp_dir1.GetPath(), kId1);
-  VerifyRestoreData(tmp_dir2.GetPath(), kId2);
+  VerifyRestoreData(tmp_dir1.GetPath(), kId1, kActivationIndex1);
+  VerifyRestoreData(tmp_dir2.GetPath(), kId2, kActivationIndex2);
 }
 
 }  // namespace full_restore
diff --git a/components/full_restore/full_restore_save_handler.cc b/components/full_restore/full_restore_save_handler.cc
index 30686585..6b8adc3 100644
--- a/components/full_restore/full_restore_save_handler.cc
+++ b/components/full_restore/full_restore_save_handler.cc
@@ -124,6 +124,10 @@
 
   profile_path_to_restore_data_[it->second.first].ModifyWindowInfo(
       it->second.second, window_id, window_info);
+
+  pending_save_profile_paths_.insert(it->second.first);
+
+  MaybeStartSaveTimer();
 }
 
 void FullRestoreSaveHandler::Flush(const base::FilePath& profile_path) {
@@ -173,23 +177,24 @@
   pending_save_profile_paths_.clear();
 }
 
-void FullRestoreSaveHandler::OnSaveFinished(const base::FilePath& file_path) {
-  save_running_.erase(file_path);
+void FullRestoreSaveHandler::OnSaveFinished(
+    const base::FilePath& profile_path) {
+  save_running_.erase(profile_path);
 }
 
 FullRestoreFileHandler* FullRestoreSaveHandler::GetFileHandler(
-    const base::FilePath& file_path) {
-  if (profile_path_to_file_handler_.find(file_path) ==
+    const base::FilePath& profile_path) {
+  if (profile_path_to_file_handler_.find(profile_path) ==
       profile_path_to_file_handler_.end()) {
-    profile_path_to_file_handler_[file_path] =
-        base::MakeRefCounted<FullRestoreFileHandler>(file_path);
+    profile_path_to_file_handler_[profile_path] =
+        base::MakeRefCounted<FullRestoreFileHandler>(profile_path);
   }
-  return profile_path_to_file_handler_[file_path].get();
+  return profile_path_to_file_handler_[profile_path].get();
 }
 
 base::SequencedTaskRunner* FullRestoreSaveHandler::BackendTaskRunner(
-    const base::FilePath& file_path) {
-  return GetFileHandler(file_path)->owning_task_runner();
+    const base::FilePath& profile_path) {
+  return GetFileHandler(profile_path)->owning_task_runner();
 }
 
 }  // namespace full_restore
diff --git a/components/full_restore/full_restore_save_handler.h b/components/full_restore/full_restore_save_handler.h
index b0f3ebd..67f7ddc 100644
--- a/components/full_restore/full_restore_save_handler.h
+++ b/components/full_restore/full_restore_save_handler.h
@@ -54,11 +54,11 @@
   // aura::WindowObserver:
   void OnWindowDestroyed(aura::Window* window) override;
 
-  // Save |app_launch_info| to the full restore file in |profile_path|.
+  // Saves |app_launch_info| to the full restore file in |profile_path|.
   void SaveAppLaunchInfo(const base::FilePath& profile_path,
                          std::unique_ptr<AppLaunchInfo> app_launch_info);
 
-  // Save |window_info| to |profile_path_to_restore_data_|.
+  // Saves |window_info| to |profile_path_to_restore_data_|.
   void SaveWindowInfo(const WindowInfo& window_info);
 
   // Flushes the full restore file in |profile_path| with the current restore
@@ -78,12 +78,13 @@
   // Passes |profile_path_to_restore_data_| to the backend for saving.
   void Save();
 
-  // Invoked when write to file operation for |file_path| is finished.
-  void OnSaveFinished(const base::FilePath& file_path);
+  // Invoked when write to file operation for |profile_path| is finished.
+  void OnSaveFinished(const base::FilePath& profile_path);
 
-  FullRestoreFileHandler* GetFileHandler(const base::FilePath& file_path);
+  FullRestoreFileHandler* GetFileHandler(const base::FilePath& profile_path);
 
-  base::SequencedTaskRunner* BackendTaskRunner(const base::FilePath& file_path);
+  base::SequencedTaskRunner* BackendTaskRunner(
+      const base::FilePath& profile_path);
 
   // Records whether there are new updates for saving between each saving delay.
   // |pending_save_profile_paths_| is cleared when Save is invoked.
diff --git a/components/full_restore/full_restore_utils.cc b/components/full_restore/full_restore_utils.cc
index ca329658..ac9177a 100644
--- a/components/full_restore/full_restore_utils.cc
+++ b/components/full_restore/full_restore_utils.cc
@@ -17,13 +17,13 @@
 DEFINE_UI_CLASS_PROPERTY_KEY(int32_t, kWindowIdKey, 0)
 DEFINE_UI_CLASS_PROPERTY_KEY(int32_t, kRestoreWindowIdKey, 0)
 
-void SaveAppLaunchInfo(const base::FilePath& profile_dir,
+void SaveAppLaunchInfo(const base::FilePath& profile_path,
                        std::unique_ptr<AppLaunchInfo> app_launch_info) {
   if (!ash::features::IsFullRestoreEnabled() || !app_launch_info)
     return;
 
   FullRestoreSaveHandler::GetInstance()->SaveAppLaunchInfo(
-      profile_dir, std::move(app_launch_info));
+      profile_path, std::move(app_launch_info));
 }
 
 void SaveWindowInfo(const WindowInfo& window_info) {
diff --git a/components/full_restore/full_restore_utils.h b/components/full_restore/full_restore_utils.h
index 0fcf698..88bd7aa 100644
--- a/components/full_restore/full_restore_utils.h
+++ b/components/full_restore/full_restore_utils.h
@@ -35,7 +35,7 @@
 
 // Saves the app launch parameters to the full restore file.
 COMPONENT_EXPORT(FULL_RESTORE)
-void SaveAppLaunchInfo(const base::FilePath& profile_dir,
+void SaveAppLaunchInfo(const base::FilePath& profile_path,
                        std::unique_ptr<AppLaunchInfo> app_launch_info);
 
 // Saves the window information to the full restore file.
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index f8cd664..8c0d11a 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -373,6 +373,13 @@
 // necessary.
 const base::Feature kDefaultTypedNavigationsToHttps{
     "OmniboxDefaultTypedNavigationsToHttps", base::FEATURE_DISABLED_BY_DEFAULT};
+// Parameter name used to look up the delay before falling back to the HTTP URL
+// while trying an HTTPS URL. The parameter is treated as a TimeDelta, so the
+// unit must be included in the value as well (e.g. 3s for 3 seconds).
+// - If the HTTPS load finishes successfully during this time, the timer is
+//   cleared and no more work is done.
+// - Otherwise, a new navigation to the the fallback HTTP URL is started.
+const char kDefaultTypedNavigationsToHttpsTimeoutParam[] = "timeout";
 
 // NOTE: while this is enabled by default, CCT visits are only tagged with the
 // necessary transition type if the intent launching CCT supplies the
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index beeaa54..9f49392 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -99,6 +99,7 @@
 
 // Navigation experiments.
 extern const base::Feature kDefaultTypedNavigationsToHttps;
+extern const char kDefaultTypedNavigationsToHttpsTimeoutParam[];
 
 // Experiment to control whether visits from CCT are hidden.
 // TODO(https://crbug.com/1141501): this is for an experiment, and will be
diff --git a/components/password_manager/core/browser/password_form.cc b/components/password_manager/core/browser/password_form.cc
index f0c74354..6c719c8 100644
--- a/components/password_manager/core/browser/password_form.cc
+++ b/components/password_manager/core/browser/password_form.cc
@@ -173,11 +173,12 @@
 PasswordForm& PasswordForm::operator=(PasswordForm&& form) = default;
 
 bool PasswordForm::IsPossibleChangePasswordForm() const {
-  return !new_password_element.empty();
+  return !new_password_element_renderer_id.is_null();
 }
 
 bool PasswordForm::IsPossibleChangePasswordFormWithoutUsername() const {
-  return IsPossibleChangePasswordForm() && username_element.empty();
+  return IsPossibleChangePasswordForm() &&
+         username_element_renderer_id.is_null();
 }
 
 bool PasswordForm::HasUsernameElement() const {
diff --git a/components/password_manager/core/browser/password_manager_features_util.cc b/components/password_manager/core/browser/password_manager_features_util.cc
index 981b3e41..db658c4 100644
--- a/components/password_manager/core/browser/password_manager_features_util.cc
+++ b/components/password_manager/core/browser/password_manager_features_util.cc
@@ -259,6 +259,18 @@
                                      GaiaIdHash::FromGaiaId(gaia_id))
       .SetOptedIn();
 
+  // Potentially also set the default store to the account one, based on a
+  // feature param.
+  bool save_to_account_store = base::GetFieldTrialParamByFeatureAsBool(
+      features::kEnablePasswordsAccountStorage,
+      features::kSaveToAccountStoreOnOptIn,
+      features::kSaveToAccountStoreOnOptInDefaultValue);
+  if (save_to_account_store) {
+    ScopedAccountStorageSettingsUpdate(pref_service,
+                                       GaiaIdHash::FromGaiaId(gaia_id))
+        .SetDefaultStore(PasswordForm::Store::kAccountStore);
+  }
+
   // Record the total number of (now) opted-in accounts.
   base::UmaHistogramExactLinear(
       "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptIn",
diff --git a/components/password_manager/core/browser/password_manager_features_util_unittest.cc b/components/password_manager/core/browser/password_manager_features_util_unittest.cc
index 7ca81e1..3000825 100644
--- a/components/password_manager/core/browser/password_manager_features_util_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_features_util_unittest.cc
@@ -289,6 +289,53 @@
   EXPECT_TRUE(ShouldShowAccountStorageOptIn(&pref_service, &sync_service));
   EXPECT_EQ(GetDefaultPasswordStore(&pref_service, &sync_service),
             PasswordForm::Store::kProfileStore);
+
+  // After the user opts in, the default store should still be the profile one.
+  OptInToAccountStorage(&pref_service, &sync_service);
+  ASSERT_TRUE(IsOptedInForAccountStorage(&pref_service, &sync_service));
+  EXPECT_EQ(GetDefaultPasswordStore(&pref_service, &sync_service),
+            PasswordForm::Store::kProfileStore);
+}
+
+TEST(PasswordFeatureManagerUtil, SaveToAccountStoreOnOptInParam) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeatureWithParameters(
+      features::kEnablePasswordsAccountStorage,
+      {{features::kSaveToProfileStoreByDefault, "true"},
+       {features::kSaveToAccountStoreOnOptIn, "true"}});
+
+  TestingPrefServiceSimple pref_service;
+  pref_service.registry()->RegisterDictionaryPref(
+      prefs::kAccountStoragePerAccountSettings);
+
+  CoreAccountInfo account;
+  account.email = "name@account.com";
+  account.gaia = "name";
+  account.account_id = CoreAccountId::FromGaiaId(account.gaia);
+
+  // SyncService is running in transport mode.
+  syncer::TestSyncService sync_service;
+  sync_service.SetAuthenticatedAccountInfo(account);
+  sync_service.SetIsAuthenticatedAccountPrimary(false);
+  sync_service.SetDisableReasons({});
+  sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE);
+  ASSERT_FALSE(sync_service.IsSyncFeatureEnabled());
+
+  // By default, the user is not opted in. Since the
+  // |kSaveToProfileStoreByDefault| parameter is set, the default store should
+  // be the *profile* one. The opt-in for the account store should still show
+  // up, though.
+  ASSERT_FALSE(IsOptedInForAccountStorage(&pref_service, &sync_service));
+  ASSERT_TRUE(ShouldShowAccountStorageOptIn(&pref_service, &sync_service));
+  ASSERT_EQ(GetDefaultPasswordStore(&pref_service, &sync_service),
+            PasswordForm::Store::kProfileStore);
+
+  // After the user opts in, the default store should change to the account one,
+  // based on the |kSaveToAccountStoreOnOptIn| param.
+  OptInToAccountStorage(&pref_service, &sync_service);
+  ASSERT_TRUE(IsOptedInForAccountStorage(&pref_service, &sync_service));
+  EXPECT_EQ(GetDefaultPasswordStore(&pref_service, &sync_service),
+            PasswordForm::Store::kAccountStore);
 }
 
 TEST(PasswordFeatureManagerUtil, AccountStorageKeepSettingsOnlyForUsers) {
diff --git a/components/password_manager/core/browser/password_manager_unittest.cc b/components/password_manager/core/browser/password_manager_unittest.cc
index 39061cb..511dd4ac 100644
--- a/components/password_manager/core/browser/password_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_unittest.cc
@@ -3768,6 +3768,47 @@
   manager()->OnPasswordFormCleared(&driver_, form_data);
 }
 
+// Similar test as above with fields that have empty names.
+TEST_P(PasswordManagerTest, SubmissionDetectedOnClearedNamelessForm) {
+  constexpr base::char16 kEmptyName[] = STRING16_LITERAL("");
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kDetectFormSubmissionOnFormClear);
+  EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true));
+  PasswordForm saved_match(MakeSavedForm());
+  EXPECT_CALL(*store_, GetLogins)
+      .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), saved_match)));
+
+  FormData form_data;
+  form_data.unique_renderer_id = FormRendererId(0);
+  form_data.url = GURL("http://www.google.com/a/LoginAuth");
+
+  FormFieldData old_password_field;
+  old_password_field.form_control_type = "password";
+  old_password_field.unique_renderer_id = FieldRendererId(1);
+  old_password_field.name = kEmptyName;
+  old_password_field.value = ASCIIToUTF16("oldpass");
+  form_data.fields.push_back(old_password_field);
+
+  FormFieldData new_password_field;
+  new_password_field.form_control_type = "password";
+  new_password_field.unique_renderer_id = FieldRendererId(2);
+  new_password_field.name = kEmptyName;
+  new_password_field.autocomplete_attribute = "new-password";
+  form_data.fields.push_back(new_password_field);
+
+  manager()->OnPasswordFormsParsed(&driver_, {form_data});
+
+  form_data.fields[0].value = ASCIIToUTF16("oldpass");
+  form_data.fields[1].value = ASCIIToUTF16("newpass");
+
+  manager()->OnInformAboutUserInput(&driver_, form_data);
+
+  std::unique_ptr<PasswordFormManagerForUI> form_manager_to_save;
+  EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr)
+      .WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save)));
+  manager()->OnPasswordFormCleared(&driver_, form_data);
+}
+
 TEST_P(PasswordManagerTest, SubmissionDetectedOnClearedFormlessFields) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(features::kDetectFormSubmissionOnFormClear);
@@ -3830,6 +3871,63 @@
     manager()->OnPasswordFormCleared(&driver_, form_data);
   }
 }
+
+// Similar test as above with fields that have empty names.
+TEST_P(PasswordManagerTest, SubmissionDetectedOnClearedNameAndFormlessFields) {
+  constexpr base::char16 kEmptyName[] = STRING16_LITERAL("");
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kDetectFormSubmissionOnFormClear);
+  EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true));
+  PasswordForm saved_match(MakeSavedForm());
+  EXPECT_CALL(*store_, GetLogins)
+      .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), saved_match)));
+
+  for (bool new_password_field_was_cleared : {true, false}) {
+    SCOPED_TRACE(testing::Message("#new password field was cleared = ")
+                 << new_password_field_was_cleared);
+
+    // Create FormData for a form with 1 password field and process it.
+    FormData form_data;
+    form_data.is_form_tag = false;
+    form_data.unique_renderer_id = FormRendererId(0);
+    form_data.url = GURL("http://www.google.com/a/LoginAuth");
+
+    FormFieldData old_password_field;
+    old_password_field.form_control_type = "password";
+    old_password_field.unique_renderer_id = FieldRendererId(1);
+    old_password_field.name = kEmptyName;
+    old_password_field.value = ASCIIToUTF16("oldpass");
+    form_data.fields.push_back(old_password_field);
+
+    FormFieldData new_password_field;
+    new_password_field.form_control_type = "password";
+    new_password_field.unique_renderer_id = FieldRendererId(2);
+    new_password_field.name = kEmptyName;
+    new_password_field.autocomplete_attribute = "new-password";
+    form_data.fields.push_back(new_password_field);
+
+    manager()->OnPasswordFormsParsed(&driver_, {form_data});
+
+    form_data.fields[0].value = ASCIIToUTF16("oldpass");
+    form_data.fields[1].value = ASCIIToUTF16("newpass");
+
+    manager()->OnInformAboutUserInput(&driver_, form_data);
+
+    form_data.fields[0].value.clear();
+    if (new_password_field_was_cleared)
+      form_data.fields[1].value.clear();
+
+    std::unique_ptr<PasswordFormManagerForUI> form_manager_to_save;
+    if (new_password_field_was_cleared) {
+      EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr)
+          .WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save)));
+    } else {
+      EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr).Times(0);
+    }
+
+    manager()->OnPasswordFormCleared(&driver_, form_data);
+  }
+}
 #endif  // !defined(OS_IOS)
 
 TEST_P(PasswordManagerTest, IsFormManagerPendingPasswordUpdate) {
diff --git a/components/password_manager/core/common/password_manager_features.cc b/components/password_manager/core/common/password_manager_features.cc
index 3142d87..f573051 100644
--- a/components/password_manager/core/common/password_manager_features.cc
+++ b/components/password_manager/core/common/password_manager_features.cc
@@ -162,6 +162,13 @@
 
 const bool kSaveToProfileStoreByDefaultDefaultValue = false;
 
+// If set to true, Chrome will set the default store to the account store when
+// the user opts in. This is mostly meaningful together with
+// |kSaveToProfileStoreByDefault|.
+const char kSaveToAccountStoreOnOptIn[] = "save_to_account_store_on_optin";
+
+const bool kSaveToAccountStoreOnOptInDefaultValue = false;
+
 }  // namespace features
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/common/password_manager_features.h b/components/password_manager/core/common/password_manager_features.h
index eb9623d..de830de 100644
--- a/components/password_manager/core/common/password_manager_features.h
+++ b/components/password_manager/core/common/password_manager_features.h
@@ -62,6 +62,8 @@
 extern const int kMaxMoveToAccountOffersForNonOptedInUserDefaultValue;
 extern const char kSaveToProfileStoreByDefault[];
 extern const bool kSaveToProfileStoreByDefaultDefaultValue;
+extern const char kSaveToAccountStoreOnOptIn[];
+extern const bool kSaveToAccountStoreOnOptInDefaultValue;
 
 }  // namespace features
 
diff --git a/components/performance_manager/graph/process_node_impl.cc b/components/performance_manager/graph/process_node_impl.cc
index 6f9fa066..d48a4e5 100644
--- a/components/performance_manager/graph/process_node_impl.cc
+++ b/components/performance_manager/graph/process_node_impl.cc
@@ -10,6 +10,7 @@
 #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/graph/worker_node_impl.h"
 #include "components/performance_manager/public/execution_context/execution_context_registry.h"
 #include "components/performance_manager/v8_memory/v8_context_tracker.h"
 
@@ -249,6 +250,16 @@
   return frames;
 }
 
+base::flat_set<const WorkerNode*> ProcessNodeImpl::GetWorkerNodes() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::flat_set<const WorkerNode*> workers;
+  for (auto* worker_impl : worker_nodes_) {
+    const WorkerNode* worker = worker_impl;
+    workers.insert(worker);
+  }
+  return workers;
+}
+
 bool ProcessNodeImpl::GetMainThreadTaskLoadIsLow() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return main_thread_task_load_is_low();
diff --git a/components/performance_manager/graph/process_node_impl.h b/components/performance_manager/graph/process_node_impl.h
index b801bbf..ea016dc4 100644
--- a/components/performance_manager/graph/process_node_impl.h
+++ b/components/performance_manager/graph/process_node_impl.h
@@ -158,6 +158,7 @@
   base::Optional<int32_t> GetExitStatus() const override;
   bool VisitFrameNodes(const FrameNodeVisitor& visitor) const override;
   base::flat_set<const FrameNode*> GetFrameNodes() const override;
+  base::flat_set<const WorkerNode*> GetWorkerNodes() const override;
   bool GetMainThreadTaskLoadIsLow() const override;
   uint64_t GetPrivateFootprintKb() const override;
   uint64_t GetResidentSetKb() const override;
diff --git a/components/performance_manager/public/graph/process_node.h b/components/performance_manager/public/graph/process_node.h
index d48beda9..3375c39 100644
--- a/components/performance_manager/public/graph/process_node.h
+++ b/components/performance_manager/public/graph/process_node.h
@@ -21,6 +21,7 @@
 namespace performance_manager {
 
 class FrameNode;
+class WorkerNode;
 class ProcessNodeObserver;
 class RenderProcessHostProxy;
 
@@ -79,6 +80,10 @@
   // calling this causes the set of nodes to be generated.
   virtual base::flat_set<const FrameNode*> GetFrameNodes() const = 0;
 
+  // Returns the set of worker nodes that are hosted in this process. Note that
+  // calling this causes the set of nodes to be generated.
+  virtual base::flat_set<const WorkerNode*> GetWorkerNodes() const = 0;
+
   // Returns true if the main thread task load is low (below some threshold
   // of usage). See ProcessNodeObserver::OnMainThreadTaskLoadIsLow.
   virtual bool GetMainThreadTaskLoadIsLow() const = 0;
diff --git a/components/performance_manager/service_worker_context_adapter.cc b/components/performance_manager/service_worker_context_adapter.cc
index c70d49c..f84c732 100644
--- a/components/performance_manager/service_worker_context_adapter.cc
+++ b/components/performance_manager/service_worker_context_adapter.cc
@@ -131,10 +131,10 @@
   return content::ServiceWorkerExternalRequestResult::kOk;
 }
 
-void ServiceWorkerContextAdapter::CountExternalRequestsForTest(
-    const url::Origin& origin,
-    CountExternalRequestsCallback callback) {
+size_t ServiceWorkerContextAdapter::CountExternalRequestsForTest(
+    const url::Origin& origin) {
   NOTIMPLEMENTED();
+  return 0u;
 }
 
 bool ServiceWorkerContextAdapter::MaybeHasRegistrationForOrigin(
diff --git a/components/performance_manager/service_worker_context_adapter.h b/components/performance_manager/service_worker_context_adapter.h
index 619bc3b..e7f0f900 100644
--- a/components/performance_manager/service_worker_context_adapter.h
+++ b/components/performance_manager/service_worker_context_adapter.h
@@ -58,9 +58,7 @@
   content::ServiceWorkerExternalRequestResult FinishedExternalRequest(
       int64_t service_worker_version_id,
       const std::string& request_uuid) override;
-  void CountExternalRequestsForTest(
-      const url::Origin& origin,
-      CountExternalRequestsCallback callback) override;
+  size_t CountExternalRequestsForTest(const url::Origin& origin) override;
   bool MaybeHasRegistrationForOrigin(const url::Origin& origin) override;
   void GetInstalledRegistrationOrigins(
       base::Optional<std::string> host_filter,
diff --git a/components/performance_manager/v8_memory/v8_detailed_memory.cc b/components/performance_manager/v8_memory/v8_detailed_memory.cc
index 0ccfabe..be416e4 100644
--- a/components/performance_manager/v8_memory/v8_detailed_memory.cc
+++ b/components/performance_manager/v8_memory/v8_detailed_memory.cc
@@ -469,43 +469,48 @@
   // existing frame is likewise accured to unassociated usage.
   uint64_t unassociated_v8_bytes_used = 0;
 
-  // Create a mapping from token to per-frame usage for the merge below.
-  std::vector<std::pair<blink::LocalFrameToken,
+  // Create a mapping from token to execution context usage for the merge below.
+  std::vector<std::pair<blink::ExecutionContextToken,
                         blink::mojom::PerContextV8MemoryUsagePtr>>
       tmp;
   for (auto& isolate : result->isolates) {
     for (auto& entry : isolate->contexts) {
-      if (entry->token.Is<blink::LocalFrameToken>()) {
-        tmp.emplace_back(entry->token.GetAs<blink::LocalFrameToken>(),
-                         std::move(entry));
-      }
-      // TODO(ulan): Handle WorkerFrameTokens here.
+      tmp.emplace_back(entry->token, std::move(entry));
     }
     unassociated_v8_bytes_used += isolate->unassociated_bytes_used;
   }
 
   size_t found_frame_count = tmp.size();
 
-  base::flat_map<blink::LocalFrameToken,
+  base::flat_map<blink::ExecutionContextToken,
                  blink::mojom::PerContextV8MemoryUsagePtr>
       associated_memory(std::move(tmp));
   // Validate that the frame tokens were all unique. If there are duplicates,
   // the map will arbitrarily drop all but one record per unique token.
   DCHECK_EQ(associated_memory.size(), found_frame_count);
 
-  base::flat_set<const FrameNode*> frame_nodes = process_node_->GetFrameNodes();
-  for (const FrameNode* frame_node : frame_nodes) {
-    auto it = associated_memory.find(frame_node->GetFrameToken());
+  std::vector<const execution_context::ExecutionContext*> execution_contexts;
+  for (auto* node : process_node_->GetFrameNodes()) {
+    execution_contexts.push_back(
+        execution_context::ExecutionContext::From(node));
+  }
+  for (auto* node : process_node_->GetWorkerNodes()) {
+    execution_contexts.push_back(
+        execution_context::ExecutionContext::From(node));
+  }
+
+  for (const execution_context::ExecutionContext* ec : execution_contexts) {
+    auto it = associated_memory.find(ec->GetToken());
     if (it == associated_memory.end()) {
       // No data for this node, clear any data associated with it.
-      ExecutionContextAttachedData::Destroy(frame_node);
+      ExecutionContextAttachedData::Destroy(ec);
     } else {
-      ExecutionContextAttachedData* frame_data =
-          ExecutionContextAttachedData::GetOrCreate(frame_node);
-      DCHECK_CALLED_ON_VALID_SEQUENCE(frame_data->sequence_checker_);
+      ExecutionContextAttachedData* ec_data =
+          ExecutionContextAttachedData::GetOrCreate(ec);
+      DCHECK_CALLED_ON_VALID_SEQUENCE(ec_data->sequence_checker_);
 
-      frame_data->data_available_ = true;
-      frame_data->data_.set_v8_bytes_used(it->second->bytes_used);
+      ec_data->data_available_ = true;
+      ec_data->data_.set_v8_bytes_used(it->second->bytes_used);
       // Zero out this datum as its usage has been consumed.
       // We avoid erase() here because it may take O(n) time.
       it->second.reset();
@@ -514,7 +519,7 @@
 
   for (const auto& it : associated_memory) {
     if (it.second.is_null()) {
-      // Frame was already consumed.
+      // Execution context was already consumed.
       continue;
     }
     // Accrue the data for non-existent frames to unassociated bytes.
diff --git a/components/performance_manager/v8_memory/v8_detailed_memory_unittest.cc b/components/performance_manager/v8_memory/v8_detailed_memory_unittest.cc
index 4b6e8289..b6892a7 100644
--- a/components/performance_manager/v8_memory/v8_detailed_memory_unittest.cc
+++ b/components/performance_manager/v8_memory/v8_detailed_memory_unittest.cc
@@ -21,6 +21,7 @@
 #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"
+#include "components/performance_manager/graph/worker_node_impl.h"
 #include "components/performance_manager/public/performance_manager.h"
 #include "components/performance_manager/public/render_process_host_id.h"
 #include "components/performance_manager/public/render_process_host_proxy.h"
@@ -1920,6 +1921,50 @@
   EXPECT_FALSE(weak_lifetime_test);
 }
 
+TEST_F(V8DetailedMemoryDecoratorTest, DedicatedWorkers) {
+  V8DetailedMemoryRequest memory_request(kMinTimeBetweenRequests, graph());
+
+  MockV8DetailedMemoryReporter reporter;
+
+  auto process = CreateNode<ProcessNodeImpl>(
+      content::PROCESS_TYPE_RENDERER,
+      RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+  // Create a couple of frames with specified IDs.
+  auto page = CreateNode<PageNodeImpl>();
+
+  blink::LocalFrameToken frame_id = blink::LocalFrameToken();
+  auto frame = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
+                                         2, frame_id);
+
+  blink::DedicatedWorkerToken worker_id = blink::DedicatedWorkerToken();
+  auto worker = CreateNode<WorkerNodeImpl>(
+      WorkerNode::WorkerType::kDedicated, process.get(),
+      page->browser_context_id(), worker_id);
+
+  worker->AddClientFrame(frame.get());
+  {
+    auto data = NewPerProcessV8MemoryUsage(2);
+    AddIsolateMemoryUsage(frame_id, 1001u, data->isolates[0].get());
+    AddIsolateMemoryUsage(worker_id, 1002u, data->isolates[1].get());
+    ExpectBindAndRespondToQuery(&reporter, std::move(data));
+  }
+
+  task_env().RunUntilIdle();
+  Mock::VerifyAndClearExpectations(&reporter);
+
+  ASSERT_TRUE(V8DetailedMemoryExecutionContextData::ForFrameNode(frame.get()));
+  EXPECT_EQ(1001u,
+            V8DetailedMemoryExecutionContextData::ForFrameNode(frame.get())
+                ->v8_bytes_used());
+  ASSERT_TRUE(
+      V8DetailedMemoryExecutionContextData::ForWorkerNode(worker.get()));
+  EXPECT_EQ(1002u,
+            V8DetailedMemoryExecutionContextData::ForWorkerNode(worker.get())
+                ->v8_bytes_used());
+  worker->RemoveClientFrame(frame.get());
+}
+
 }  // namespace v8_memory
 
 }  // namespace performance_manager
diff --git a/components/performance_manager/v8_memory/v8_memory_test_helpers.cc b/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
index cc48a97..eed1019 100644
--- a/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
+++ b/components/performance_manager/v8_memory/v8_memory_test_helpers.cc
@@ -353,18 +353,18 @@
   return data;
 }
 
-void AddIsolateMemoryUsage(const blink::LocalFrameToken& frame_token,
+void AddIsolateMemoryUsage(blink::ExecutionContextToken token,
                            uint64_t bytes_used,
                            blink::mojom::PerIsolateV8MemoryUsage* isolate) {
   for (auto& entry : isolate->contexts) {
-    if (entry->token == blink::ExecutionContextToken(frame_token)) {
+    if (entry->token == token) {
       entry->bytes_used = bytes_used;
       return;
     }
   }
 
   auto context = blink::mojom::PerContextV8MemoryUsage::New();
-  context->token = blink::ExecutionContextToken(frame_token);
+  context->token = token;
   context->bytes_used = bytes_used;
   isolate->contexts.push_back(std::move(context));
 }
diff --git a/components/performance_manager/v8_memory/v8_memory_test_helpers.h b/components/performance_manager/v8_memory/v8_memory_test_helpers.h
index 000865b..39271ae 100644
--- a/components/performance_manager/v8_memory/v8_memory_test_helpers.h
+++ b/components/performance_manager/v8_memory/v8_memory_test_helpers.h
@@ -332,9 +332,9 @@
 blink::mojom::PerProcessV8MemoryUsagePtr NewPerProcessV8MemoryUsage(
     size_t number_of_isolates);
 
-// Finds the PerContextV8MemoryUsage in |isolate| whose token is |frame_token|,
+// Finds the PerContextV8MemoryUsage in |isolate| whose token is |token|,
 // or creates it if it does not exist, and sets its bytes_used to |bytes_used|.
-void AddIsolateMemoryUsage(const blink::LocalFrameToken& frame_token,
+void AddIsolateMemoryUsage(blink::ExecutionContextToken token,
                            uint64_t bytes_used,
                            blink::mojom::PerIsolateV8MemoryUsage* isolate);
 
diff --git a/components/policy/core/common/BUILD.gn b/components/policy/core/common/BUILD.gn
index ba65baa8..4c99709 100644
--- a/components/policy/core/common/BUILD.gn
+++ b/components/policy/core/common/BUILD.gn
@@ -189,6 +189,18 @@
     "//url",
   ]
 
+  if (chromeos_is_browser_only) {
+    sources += [
+      "policy_loader_lacros.cc",
+      "policy_loader_lacros.h",
+    ]
+    deps += [
+      "//chromeos/crosapi/mojom",
+      "//chromeos/lacros",
+      "//chromeos/startup:startup",
+    ]
+  }
+
   allow_circular_includes_from = [
     # Generated files use policy_details.h and policy_map.h from this target.
     # TODO(https://crbug.com/945868) - refactor so we don't have dep cycles.
diff --git a/components/policy/core/common/DEPS b/components/policy/core/common/DEPS
index 0c358062..ef4d3b8c 100644
--- a/components/policy/core/common/DEPS
+++ b/components/policy/core/common/DEPS
@@ -1,4 +1,7 @@
 include_rules = [
+  "+chromeos/crosapi",
+  "+chromeos/lacros",
+  "+chromeos/startup",
   "+chromeos/system",
   "+components/account_id",
   "-components/policy/core/browser",
diff --git a/components/policy/core/common/policy_loader_lacros.cc b/components/policy/core/common/policy_loader_lacros.cc
new file mode 100644
index 0000000..3afd3cf0c
--- /dev/null
+++ b/components/policy/core/common/policy_loader_lacros.cc
@@ -0,0 +1,111 @@
+// 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 "components/policy/core/common/policy_loader_lacros.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/time/time.h"
+#include "chromeos/lacros/lacros_chrome_service_impl.h"
+#include "chromeos/startup/startup.h"
+#include "components/policy/core/common/cloud/cloud_policy_validator.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_proto_decoders.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+PolicyLoaderLacros::PolicyLoaderLacros(
+    scoped_refptr<base::SequencedTaskRunner> task_runner)
+    : AsyncPolicyLoader(task_runner), task_runner_(task_runner) {}
+
+PolicyLoaderLacros::~PolicyLoaderLacros() = default;
+
+void PolicyLoaderLacros::InitOnBackgroundThread() {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+}
+
+std::unique_ptr<PolicyBundle> PolicyLoaderLacros::Load() {
+  std::unique_ptr<PolicyBundle> bundle = std::make_unique<PolicyBundle>();
+
+  crosapi::mojom::LacrosInitParamsPtr result;
+  const crosapi::mojom::LacrosInitParams* init_params;
+  auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
+  if (lacros_chrome_service) {
+    init_params = lacros_chrome_service->init_params();
+  } else {
+    // On the first start of Lacros browser, the lacros_chrome_service is
+    // not initialized yet, so take the data directly from the file. This always
+    // happens on first start after user login, because policy data is loaded
+    // before the service is initialized. We cannot do other way, since there
+    // are other dependencies that create a cycle. The in-memory file is used
+    // to break the cycle. After that, if user reloads the policy the service
+    // is present.
+    // TODO(crbug.com/1114069): This code is duplicated in
+    // LacrosChromeServiceImpl. We could store the data in a static variable
+    // inside LacrosChromeServiceImpl and make a single static function to call.
+    base::Optional<std::string> content = chromeos::ReadStartupData();
+    if (!content) {
+      LOG(ERROR) << "No content in file for init params";
+      return bundle;
+    }
+
+    if (!crosapi::mojom::LacrosInitParams::Deserialize(
+            content->data(), content->size(), &result)) {
+      LOG(ERROR) << "Failed to parse startup data";
+      return bundle;
+    }
+
+    init_params = result.get();
+  }
+
+  if (!init_params) {
+    LOG(ERROR) << "No init params";
+    return bundle;
+  }
+
+  if (!init_params->device_account_policy) {
+    LOG(ERROR) << "No policy data";
+    return bundle;
+  }
+
+  std::vector<uint8_t> data = init_params->device_account_policy.value();
+  if (data.empty()) {
+    return bundle;
+  }
+
+  auto policy = std::make_unique<enterprise_management::PolicyFetchResponse>();
+  if (!policy->ParseFromString(std::string(data.begin(), data.end()))) {
+    LOG(ERROR) << "Failed to parse policy data";
+    return bundle;
+  }
+  UserCloudPolicyValidator validator(std::move(policy), task_runner_);
+  validator.ValidatePayload();
+  validator.RunValidation();
+
+  PolicyMap policy_map;
+  base::WeakPtr<CloudExternalDataManager> external_data_manager;
+  DecodeProtoFields(*(validator.payload()), external_data_manager,
+                    PolicySource::POLICY_SOURCE_CLOUD,
+                    PolicyScope::POLICY_SCOPE_USER, &policy_map);
+  bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
+      .MergeFrom(policy_map);
+  last_modification_ = base::Time::Now();
+  return bundle;
+}
+
+base::Time PolicyLoaderLacros::LastModificationTime() {
+  return last_modification_;
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_loader_lacros.h b/components/policy/core/common/policy_loader_lacros.h
new file mode 100644
index 0000000..67ec66b
--- /dev/null
+++ b/components/policy/core/common/policy_loader_lacros.h
@@ -0,0 +1,48 @@
+// 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 COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LACROS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LACROS_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "components/policy/core/common/async_policy_loader.h"
+
+namespace policy {
+
+// A policy loader for Lacros. The data is taken from Ash and the validatity of
+// data is trusted, since they have been validated by Ash.
+class POLICY_EXPORT PolicyLoaderLacros : public AsyncPolicyLoader {
+ public:
+  // Creates the policy loader, saving the task_runner internally. Later
+  // task_runner is used to have in sequence the process of policy parsing and
+  // validation.
+  explicit PolicyLoaderLacros(
+      scoped_refptr<base::SequencedTaskRunner> task_runner);
+  // Not copyable or movable
+  PolicyLoaderLacros(const PolicyLoaderLacros&) = delete;
+  PolicyLoaderLacros& operator=(const PolicyLoaderLacros&) = delete;
+  ~PolicyLoaderLacros() override;
+
+  // AsyncPolicyLoader implementation.
+  // Verifies that it runs on correct thread.
+  void InitOnBackgroundThread() override;
+  // Loads the policy data from LacrosInitParams and populates it in the bundle
+  // that is returned.
+  std::unique_ptr<PolicyBundle> Load() override;
+  // Returns the last time the policy successfully loaded.
+  base::Time LastModificationTime() override;
+
+ private:
+  // Task runner for running background jobs.
+  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+  // The time of last modification.
+  base::Time last_modification_;
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LACROS_H_
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index eb46a56..de803a35 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -10939,6 +10939,7 @@
             'os_settings',
             'camera',
             'scanning',
+            'web_store',
           ],
         },
       },
@@ -10963,6 +10964,11 @@
           'value': 'scanning',
           'caption': '''Scanning (supported since version 87)''',
         },
+        {
+          'name': 'web_store',
+          'value': 'web_store',
+          'caption': '''Web Store (supported since version 89)''',
+        },
       ],
       'supported_on': ['chrome_os:84-'],
       'features': {
@@ -10970,9 +10976,9 @@
         'dynamic_refresh': True,
         'per_profile': False,
       },
-      'example_value': ['camera', 'browser_settings', 'os_settings', 'scanning'],
+      'example_value': ['camera', 'browser_settings', 'os_settings', 'scanning', 'web_store'],
       'id': 689,
-      'caption': '''Configure the camera, browser settings, os settings, and scanning features to be disabled''',
+      'caption': '''Configure the camera, browser settings, os settings, scanning and web store features to be disabled''',
       'tags': [],
       'desc': '''Allows you to set a list of <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph> features to be disabled.
 
diff --git a/components/safe_browsing/core/browser/sync/BUILD.gn b/components/safe_browsing/core/browser/sync/BUILD.gn
index 68a0503..60778a4 100644
--- a/components/safe_browsing/core/browser/sync/BUILD.gn
+++ b/components/safe_browsing/core/browser/sync/BUILD.gn
@@ -8,6 +8,8 @@
   sources = [
     "safe_browsing_primary_account_token_fetcher.cc",
     "safe_browsing_primary_account_token_fetcher.h",
+    "sync_utils.cc",
+    "sync_utils.h",
   ]
 
   deps = [
@@ -15,19 +17,24 @@
     "//components/safe_browsing/core/browser:token_fetcher",
     "//components/safe_browsing/core/common:thread_utils",
     "//components/signin/public/identity_manager",
+    "//components/sync/driver",
     "//google_apis",
   ]
 }
 
 source_set("unittests") {
   testonly = true
-  sources = [ "safe_browsing_primary_account_token_fetcher_unittest.cc" ]
+  sources = [
+    "safe_browsing_primary_account_token_fetcher_unittest.cc",
+    "sync_utils_unittest.cc",
+  ]
 
   deps = [
     ":sync",
     "//base/test:test_support",
     "//components/safe_browsing/core/common:test_support",
     "//components/signin/public/identity_manager:test_support",
+    "//components/sync/driver:test_support",
     "//testing/gtest",
   ]
 }
diff --git a/components/safe_browsing/core/browser/sync/sync_utils.cc b/components/safe_browsing/core/browser/sync/sync_utils.cc
new file mode 100644
index 0000000..ad28be4
--- /dev/null
+++ b/components/safe_browsing/core/browser/sync/sync_utils.cc
@@ -0,0 +1,50 @@
+// 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/safe_browsing/core/browser/sync/sync_utils.h"
+
+#include "components/signin/public/identity_manager/account_info.h"
+#include "components/signin/public/identity_manager/consent_level.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/sync/base/user_selectable_type.h"
+#include "components/sync/driver/sync_service.h"
+#include "components/sync/driver/sync_service_utils.h"
+#include "components/sync/driver/sync_user_settings.h"
+
+namespace safe_browsing {
+
+// static
+bool SyncUtils::IsPrimaryAccountSignedIn(
+    signin::IdentityManager* identity_manager) {
+  CoreAccountInfo primary_account_info =
+      identity_manager->GetPrimaryAccountInfo(
+          signin::ConsentLevel::kNotRequired);
+  return !primary_account_info.account_id.empty();
+}
+
+// static
+bool SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+    syncer::SyncService* sync_service,
+    signin::IdentityManager* identity_manager,
+    bool user_has_enabled_enhanced_protection) {
+  // If the user has explicitly enabled enhanced protection and the primary
+  // account is available, no further conditions are needed.
+  if (user_has_enabled_enhanced_protection &&
+      IsPrimaryAccountSignedIn(identity_manager)) {
+    return true;
+  }
+
+  // Otherwise, check the status of sync: Safe browsing token fetches are
+  // enabled when the user is syncing their browsing history without a custom
+  // passphrase.
+  // NOTE: |sync_service| can be null in Incognito, and can also be set to null
+  // by a cmdline param.
+  return sync_service &&
+         (syncer::GetUploadToGoogleState(
+              sync_service, syncer::ModelType::HISTORY_DELETE_DIRECTIVES) ==
+          syncer::UploadState::ACTIVE) &&
+         !sync_service->GetUserSettings()->IsUsingSecondaryPassphrase();
+}
+
+}  // namespace safe_browsing
diff --git a/components/safe_browsing/core/browser/sync/sync_utils.h b/components/safe_browsing/core/browser/sync/sync_utils.h
new file mode 100644
index 0000000..e0bd6917
--- /dev/null
+++ b/components/safe_browsing/core/browser/sync/sync_utils.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 COMPONENTS_SAFE_BROWSING_CORE_BROWSER_SYNC_SYNC_UTILS_H_
+#define COMPONENTS_SAFE_BROWSING_CORE_BROWSER_SYNC_SYNC_UTILS_H_
+
+namespace syncer {
+class SyncService;
+}
+
+namespace signin {
+class IdentityManager;
+}
+
+namespace safe_browsing {
+
+// This class implements sync and signin-related safe browsing utilities.
+class SyncUtils {
+ public:
+  SyncUtils() = delete;
+  ~SyncUtils() = delete;
+
+  // Returns true if signin and sync are configured such that access token
+  // fetches for safe browsing can be enabled.
+  static bool AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      syncer::SyncService* sync_service,
+      signin::IdentityManager* identity_manager,
+      bool user_has_enabled_enhanced_protection);
+
+ private:
+  // Whether the primary account is signed in. Sync is not required.
+  static bool IsPrimaryAccountSignedIn(
+      signin::IdentityManager* identity_manager);
+};  // class SyncUtils
+
+}  // namespace safe_browsing
+
+#endif  // COMPONENTS_SAFE_BROWSING_CORE_BROWSER_SYNC_SYNC_UTILS_H_
diff --git a/components/safe_browsing/core/browser/sync/sync_utils_unittest.cc b/components/safe_browsing/core/browser/sync/sync_utils_unittest.cc
new file mode 100644
index 0000000..4d971e57
--- /dev/null
+++ b/components/safe_browsing/core/browser/sync/sync_utils_unittest.cc
@@ -0,0 +1,113 @@
+// 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/safe_browsing/core/browser/sync/sync_utils.h"
+
+#include "components/safe_browsing/core/common/test_task_environment.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "components/sync/driver/test_sync_service.h"
+#include "testing/platform_test.h"
+
+namespace safe_browsing {
+
+class SyncUtilsTest : public PlatformTest {
+ public:
+  SyncUtilsTest() : task_environment_(CreateTestTaskEnvironment()) {}
+
+  std::unique_ptr<base::test::TaskEnvironment> task_environment_;
+};
+
+TEST_F(SyncUtilsTest, AreSigninAndSyncSetUpForSafeBrowsingTokenFetches_Sync) {
+  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env =
+      std::make_unique<signin::IdentityTestEnvironment>();
+  signin::IdentityManager* identity_manager =
+      identity_test_env->identity_manager();
+  syncer::TestSyncService sync_service;
+
+  // For the purposes of this test, IdentityManager has no primary account.
+
+  // Sync is disabled.
+  sync_service.SetDisableReasons(
+      {syncer::SyncService::DISABLE_REASON_USER_CHOICE});
+  sync_service.SetTransportState(syncer::SyncService::TransportState::DISABLED);
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/true));
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/false));
+
+  // Sync is enabled.
+  sync_service.SetDisableReasons({});
+  sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE);
+  EXPECT_TRUE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/true));
+  EXPECT_TRUE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/false));
+
+  // History sync is disabled.
+  sync_service.GetUserSettings()->SetSelectedTypes(
+      /* sync_everything */ false, {});
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/true));
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/false));
+
+  // Custom passphrase is enabled.
+  sync_service.GetUserSettings()->SetSelectedTypes(
+      false, {syncer::UserSelectableType::kHistory});
+  sync_service.SetIsUsingSecondaryPassphrase(true);
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/true));
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/false));
+}
+
+TEST_F(SyncUtilsTest,
+       AreSigninAndSyncSetUpForSafeBrowsingTokenFetches_IdentityManager) {
+  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env =
+      std::make_unique<signin::IdentityTestEnvironment>();
+  signin::IdentityManager* identity_manager =
+      identity_test_env->identity_manager();
+  syncer::TestSyncService sync_service;
+
+  // For the purposes of this test, disable sync.
+  sync_service.SetDisableReasons(
+      {syncer::SyncService::DISABLE_REASON_USER_CHOICE});
+  sync_service.SetTransportState(syncer::SyncService::TransportState::DISABLED);
+  sync_service.GetUserSettings()->SetSelectedTypes(
+      /* sync_everything */ false, {});
+
+  // If the user is not signed in, it should not be
+  // possible to perform URL lookups with tokens.
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/true));
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/false));
+
+  // Enhanced protection is on and the user is signed in: it should be possible
+  // to perform URL lookups with tokens (even though the
+  // kRealTimeLookupEnabledWithToken feature and sync/history sync are
+  // disabled).
+  identity_test_env->MakeUnconsentedPrimaryAccountAvailable("test@example.com");
+  EXPECT_TRUE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/true));
+
+  // Enhanced protection is *off* and the user is signed in: it should not be
+  // possible to perform URL lookups with tokens without sync being enabled.
+  EXPECT_FALSE(SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches(
+      &sync_service, identity_manager,
+      /* user_has_enabled_enhanced_protection=*/false));
+}
+
+}  // namespace safe_browsing
diff --git a/components/safe_browsing/core/realtime/BUILD.gn b/components/safe_browsing/core/realtime/BUILD.gn
index f600bd8c..6a58ffb0 100644
--- a/components/safe_browsing/core/realtime/BUILD.gn
+++ b/components/safe_browsing/core/realtime/BUILD.gn
@@ -15,8 +15,6 @@
     "//components/safe_browsing/core:realtimeapi_proto",
     "//components/safe_browsing/core/common",
     "//components/safe_browsing/core/common:safe_browsing_prefs",
-    "//components/signin/public/identity_manager",
-    "//components/sync",
     "//components/unified_consent",
     "//components/user_prefs",
     "//components/variations/service",
diff --git a/components/safe_browsing/core/realtime/policy_engine.cc b/components/safe_browsing/core/realtime/policy_engine.cc
index cffaf46..6666b75 100644
--- a/components/safe_browsing/core/realtime/policy_engine.cc
+++ b/components/safe_browsing/core/realtime/policy_engine.cc
@@ -12,13 +12,6 @@
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/core/common/safebrowsing_constants.h"
 #include "components/safe_browsing/core/features.h"
-#include "components/signin/public/identity_manager/account_info.h"
-#include "components/signin/public/identity_manager/consent_level.h"
-#include "components/signin/public/identity_manager/identity_manager.h"
-#include "components/sync/base/user_selectable_type.h"
-#include "components/sync/driver/sync_service.h"
-#include "components/sync/driver/sync_service_utils.h"
-#include "components/sync/driver/sync_user_settings.h"
 #include "components/unified_consent/pref_names.h"
 #include "components/user_prefs/user_prefs.h"
 #include "components/variations/service/variations_service.h"
@@ -53,15 +46,6 @@
 }
 
 // static
-bool RealTimePolicyEngine::IsPrimaryAccountSignedIn(
-    signin::IdentityManager* identity_manager) {
-  CoreAccountInfo primary_account_info =
-      identity_manager->GetPrimaryAccountInfo(
-          signin::ConsentLevel::kNotRequired);
-  return !primary_account_info.account_id.empty();
-}
-
-// static
 bool RealTimePolicyEngine::CanPerformFullURLLookup(
     PrefService* pref_service,
     bool is_off_the_record,
@@ -84,8 +68,7 @@
 bool RealTimePolicyEngine::CanPerformFullURLLookupWithToken(
     PrefService* pref_service,
     bool is_off_the_record,
-    syncer::SyncService* sync_service,
-    signin::IdentityManager* identity_manager,
+    ClientConfiguredForTokenFetchesCallback client_callback,
     variations::VariationsService* variations_service) {
   if (!CanPerformFullURLLookup(pref_service, is_off_the_record,
                                variations_service)) {
@@ -100,23 +83,7 @@
     return false;
   }
 
-  // If the user has explicitly enabled enhanced protection and the primary
-  // account is available, no further conditions are needed.
-  if (IsUserEpOptedIn(pref_service) &&
-      IsPrimaryAccountSignedIn(identity_manager)) {
-    return true;
-  }
-
-  // Otherwise, check the status of sync: Safe browsing token fetches are
-  // enabled when the user is syncing their browsing history without a custom
-  // passphrase.
-  // NOTE: |sync_service| can be null in Incognito, and can also be set to null
-  // by a cmdline param.
-  return sync_service &&
-         (syncer::GetUploadToGoogleState(
-              sync_service, syncer::ModelType::HISTORY_DELETE_DIRECTIVES) ==
-          syncer::UploadState::ACTIVE) &&
-         !sync_service->GetUserSettings()->IsUsingSecondaryPassphrase();
+  return std::move(client_callback).Run(IsUserEpOptedIn(pref_service));
 }
 
 // static
diff --git a/components/safe_browsing/core/realtime/policy_engine.h b/components/safe_browsing/core/realtime/policy_engine.h
index a94027bd..4252d04 100644
--- a/components/safe_browsing/core/realtime/policy_engine.h
+++ b/components/safe_browsing/core/realtime/policy_engine.h
@@ -7,18 +7,11 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "build/build_config.h"
 
 class PrefService;
 
-namespace syncer {
-class SyncService;
-}
-
-namespace signin {
-class IdentityManager;
-}
-
 namespace variations {
 class VariationsService;
 }
@@ -37,6 +30,12 @@
   RealTimePolicyEngine() = delete;
   ~RealTimePolicyEngine() = delete;
 
+  // A callback via which the client of this component indicates whether they
+  // are configured to support token fetches. Used as part of
+  // CanPerformFullURLLookupWithToken().
+  using ClientConfiguredForTokenFetchesCallback =
+      base::OnceCallback<bool(bool user_has_enabled_enhanced_protection)>;
+
   // Return true if full URL lookups are enabled for |resource_type|. If
   // |can_rt_check_subresource_url| is set to false, return true only if
   // |resource_type| is |kMainFrame|.
@@ -57,8 +56,7 @@
   static bool CanPerformFullURLLookupWithToken(
       PrefService* pref_service,
       bool is_off_the_record,
-      syncer::SyncService* sync_service,
-      signin::IdentityManager* identity_manager,
+      ClientConfiguredForTokenFetchesCallback client_callback,
       variations::VariationsService* variations_service);
 
   static bool CanPerformEnterpriseFullURLLookup(const PrefService* pref_service,
@@ -79,10 +77,6 @@
   // Whether the user has opted-in to Enhanced Protection.
   static bool IsUserEpOptedIn(PrefService* pref_service);
 
-  // Whether the primary account is signed in. Sync is not required.
-  static bool IsPrimaryAccountSignedIn(
-      signin::IdentityManager* identity_manager);
-
   friend class RealTimePolicyEngineTest;
 };  // class RealTimePolicyEngine
 
diff --git a/components/safe_browsing/core/realtime/policy_engine_unittest.cc b/components/safe_browsing/core/realtime/policy_engine_unittest.cc
index 1dfe226..4227847 100644
--- a/components/safe_browsing/core/realtime/policy_engine_unittest.cc
+++ b/components/safe_browsing/core/realtime/policy_engine_unittest.cc
@@ -10,8 +10,6 @@
 #include "components/safe_browsing/core/common/safebrowsing_constants.h"
 #include "components/safe_browsing/core/common/test_task_environment.h"
 #include "components/safe_browsing/core/features.h"
-#include "components/signin/public/identity_manager/identity_test_environment.h"
-#include "components/sync/driver/test_sync_service.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "components/unified_consent/pref_names.h"
 #include "components/unified_consent/unified_consent_service.h"
@@ -20,6 +18,15 @@
 
 namespace safe_browsing {
 
+// Used in tests of CanPerformFullURLLookupWithToken().
+bool AreTokenFetchesEnabledInClient(bool expected_ep_enabled_value,
+                                    bool return_value,
+                                    bool user_has_enabled_enhanced_protection) {
+  EXPECT_EQ(expected_ep_enabled_value, user_has_enabled_enhanced_protection);
+
+  return return_value;
+}
+
 class RealTimePolicyEngineTest : public PlatformTest {
  public:
   RealTimePolicyEngineTest() : task_environment_(CreateTestTaskEnvironment()) {}
@@ -41,10 +48,10 @@
 
   bool CanPerformFullURLLookupWithToken(
       bool is_off_the_record,
-      syncer::SyncService* sync_service,
-      signin::IdentityManager* identity_manager) {
+      RealTimePolicyEngine::ClientConfiguredForTokenFetchesCallback
+          client_callback) {
     return RealTimePolicyEngine::CanPerformFullURLLookupWithToken(
-        &pref_service_, is_off_the_record, sync_service, identity_manager,
+        &pref_service_, is_off_the_record, std::move(client_callback),
         /*variations_service=*/nullptr);
   }
 
@@ -106,26 +113,22 @@
 
 TEST_F(RealTimePolicyEngineTest,
        TestCanPerformFullURLLookup_RTLookupForEpEnabled_WithTokenDisabled) {
-  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env =
-      std::make_unique<signin::IdentityTestEnvironment>();
-  signin::IdentityManager* identity_manager =
-      identity_test_env->identity_manager();
-  syncer::TestSyncService sync_service;
-  // User is signed in.
-  identity_test_env->MakeUnconsentedPrimaryAccountAvailable("test@example.com");
-
   pref_service_.SetBoolean(prefs::kSafeBrowsingEnhanced, true);
   {
     base::test::ScopedFeatureList feature_list;
     feature_list.InitAndEnableFeature(kEnhancedProtection);
     EXPECT_TRUE(CanPerformFullURLLookup(/* is_off_the_record */ false));
     EXPECT_TRUE(CanPerformFullURLLookupWithToken(
-        /* is_off_the_record */ false, &sync_service, identity_manager));
+        /* is_off_the_record */ false,
+        base::BindOnce(&AreTokenFetchesEnabledInClient,
+                       /*expected_ep_enabled_value=*/true,
+                       /*return_value=*/true)));
   }
 }
 
-TEST_F(RealTimePolicyEngineTest,
-       TestCanPerformFullURLLookupWithToken_SyncControlled) {
+TEST_F(
+    RealTimePolicyEngineTest,
+    TestCanPerformFullURLLookupWithToken_ClientControlledWithoutEnhancedProtection) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
       /* enabled_features */ {kRealTimeUrlLookupEnabled,
@@ -134,71 +137,57 @@
   pref_service_.SetUserPref(
       unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
       std::make_unique<base::Value>(true));
-  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env =
-      std::make_unique<signin::IdentityTestEnvironment>();
-  signin::IdentityManager* identity_manager =
-      identity_test_env->identity_manager();
-  syncer::TestSyncService sync_service;
 
-  // Sync is disabled.
-  sync_service.SetDisableReasons(
-      {syncer::SyncService::DISABLE_REASON_USER_CHOICE});
-  sync_service.SetTransportState(syncer::SyncService::TransportState::DISABLED);
+  // Token fetches are not configured in the client.
   EXPECT_FALSE(CanPerformFullURLLookupWithToken(
-      /* is_off_the_record */ false, &sync_service, identity_manager));
+      /* is_off_the_record */ false,
+      base::BindOnce(&AreTokenFetchesEnabledInClient,
+                     /*expected_ep_enabled_value=*/false,
+                     /*return_value=*/false)));
 
-  // Sync is enabled.
-  sync_service.SetDisableReasons({});
-  sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE);
+  // Token fetches are configured in the client.
   EXPECT_TRUE(CanPerformFullURLLookupWithToken(
-      /* is_off_the_record */ false, &sync_service, identity_manager));
-
-  // History sync is disabled.
-  sync_service.GetUserSettings()->SetSelectedTypes(
-      /* sync_everything */ false, {});
-  EXPECT_FALSE(CanPerformFullURLLookupWithToken(
-      /* is_off_the_record */ false, &sync_service, identity_manager));
-
-  // Custom passphrase is enabled.
-  sync_service.GetUserSettings()->SetSelectedTypes(
-      false, {syncer::UserSelectableType::kHistory});
-  sync_service.SetIsUsingSecondaryPassphrase(true);
-  EXPECT_FALSE(CanPerformFullURLLookupWithToken(
-      /* is_off_the_record */ false, &sync_service, identity_manager));
+      /* is_off_the_record */ false,
+      base::BindOnce(&AreTokenFetchesEnabledInClient,
+                     /*expected_ep_enabled_value=*/false,
+                     /*return_value=*/true)));
 }
 
-TEST_F(RealTimePolicyEngineTest,
-       TestCanPerformFullURLLookupWithToken_EnhancedProtection) {
+TEST_F(
+    RealTimePolicyEngineTest,
+    TestCanPerformFullURLLookupWithToken_ClientControlledWithEnhancedProtection) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
       /* enabled_features */ {kEnhancedProtection},
       /* disabled_features */ {kRealTimeUrlLookupEnabledWithToken});
-  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env =
-      std::make_unique<signin::IdentityTestEnvironment>();
-  signin::IdentityManager* identity_manager =
-      identity_test_env->identity_manager();
-  syncer::TestSyncService sync_service;
 
-  // For the purposes of this test, disable sync.
-  sync_service.SetDisableReasons(
-      {syncer::SyncService::DISABLE_REASON_USER_CHOICE});
-  sync_service.SetTransportState(syncer::SyncService::TransportState::DISABLED);
-  sync_service.GetUserSettings()->SetSelectedTypes(
-      /* sync_everything */ false, {});
+  // Enhanced protection is not enabled and the Finch feature is disabled: token
+  // fetches should be disallowed whether or not they are configured in the
+  // client.
+  EXPECT_FALSE(CanPerformFullURLLookupWithToken(
+      /* is_off_the_record */ false,
+      base::BindOnce(&AreTokenFetchesEnabledInClient,
+                     /*expected_ep_enabled_value=*/false,
+                     /*return_value=*/false)));
+  EXPECT_FALSE(CanPerformFullURLLookupWithToken(
+      /* is_off_the_record */ false,
+      base::BindOnce(&AreTokenFetchesEnabledInClient,
+                     /*expected_ep_enabled_value=*/false,
+                     /*return_value=*/true)));
 
-  // Enhanced protection is on but the user is not signed in: it should not be
-  // possible to perform URL lookups with tokens.
+  // With enhanced protection enabled, whether token fetches are allowed should
+  // be dependent on the configuration of the client
   pref_service_.SetBoolean(prefs::kSafeBrowsingEnhanced, true);
   EXPECT_FALSE(CanPerformFullURLLookupWithToken(
-      /* is_off_the_record */ false, &sync_service, identity_manager));
-
-  // Enhanced protection is on and the user is signed in: it should be possible
-  // to perform URL lookups with tokens (even though the
-  // kRealTimeLookupEnabledWithToken feature and sync/history sync are
-  // disabled).
-  identity_test_env->MakeUnconsentedPrimaryAccountAvailable("test@example.com");
+      /* is_off_the_record */ false,
+      base::BindOnce(&AreTokenFetchesEnabledInClient,
+                     /*expected_ep_enabled_value=*/true,
+                     /*return_value=*/false)));
   EXPECT_TRUE(CanPerformFullURLLookupWithToken(
-      /* is_off_the_record */ false, &sync_service, identity_manager));
+      /* is_off_the_record */ false,
+      base::BindOnce(&AreTokenFetchesEnabledInClient,
+                     /*expected_ep_enabled_value=*/true,
+                     /*return_value=*/true)));
 }
 
 TEST_F(RealTimePolicyEngineTest, TestCanPerformEnterpriseFullURLLookup) {
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.cc b/components/safe_browsing/core/realtime/url_lookup_service.cc
index 1c796a7..543a38db 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service.cc
@@ -14,6 +14,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/core/browser/sync/safe_browsing_primary_account_token_fetcher.h"
+#include "components/safe_browsing/core/browser/sync/sync_utils.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/core/common/thread_utils.h"
 #include "components/safe_browsing/core/db/v4_protocol_manager_util.h"
@@ -92,7 +93,10 @@
 
 bool RealTimeUrlLookupService::CanPerformFullURLLookupWithToken() const {
   return RealTimePolicyEngine::CanPerformFullURLLookupWithToken(
-      pref_service_, is_off_the_record_, sync_service_, identity_manager_,
+      pref_service_, is_off_the_record_,
+      base::BindOnce(
+          &SyncUtils::AreSigninAndSyncSetUpForSafeBrowsingTokenFetches,
+          sync_service_, identity_manager_),
       variations_);
 }
 
diff --git a/components/signin/internal/identity_manager/gaia_cookie_manager_service_unittest.cc b/components/signin/internal/identity_manager/gaia_cookie_manager_service_unittest.cc
index 2c298d65..bbe4939 100644
--- a/components/signin/internal/identity_manager/gaia_cookie_manager_service_unittest.cc
+++ b/components/signin/internal/identity_manager/gaia_cookie_manager_service_unittest.cc
@@ -299,8 +299,8 @@
   MockObserver observer(&helper);
 
   auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  base::ScopedClosureRunner task_runner_ =
-      base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner);
+  base::ThreadTaskRunnerHandleOverrideForTesting ttrh_override(
+      test_task_runner);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
   EXPECT_CALL(helper, StartFetchingMergeSession());
@@ -323,8 +323,8 @@
   base::HistogramTester histograms;
 
   auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  base::ScopedClosureRunner task_runner_ =
-      base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner);
+  base::ThreadTaskRunnerHandleOverrideForTesting ttrh_override(
+      test_task_runner);
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
   EXPECT_CALL(helper, StartFetchingMergeSession()).Times(2);
@@ -917,8 +917,8 @@
     MockObserver observer(&helper);
     auto test_task_runner =
         base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-    base::ScopedClosureRunner task_runner_ =
-        base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner);
+    base::ThreadTaskRunnerHandleOverrideForTesting ttrh_override(
+        test_task_runner);
 
     EXPECT_CALL(helper, StartFetchingListAccounts()).Times(3);
 
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
index 211d685..f13f837 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
@@ -18,6 +18,7 @@
 #include "components/subresource_filter/content/browser/activation_state_computing_navigation_throttle.h"
 #include "components/subresource_filter/content/browser/async_document_subresource_filter.h"
 #include "components/subresource_filter/content/browser/page_load_statistics.h"
+#include "components/subresource_filter/content/browser/profile_interaction_manager.h"
 #include "components/subresource_filter/content/browser/subresource_filter_client.h"
 #include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h"
 #include "components/subresource_filter/content/common/subresource_filter_messages.h"
@@ -438,7 +439,7 @@
       client_->GetSafeBrowsingDatabaseManager()) {
     throttles->push_back(
         std::make_unique<SubresourceFilterSafeBrowsingActivationThrottle>(
-            navigation_handle, client_.get(),
+            navigation_handle, client_->GetProfileInteractionManager(),
             content::GetIOThreadTaskRunner({}),
             client_->GetSafeBrowsingDatabaseManager()));
   }
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
index 2c725f2..a3e338e 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
@@ -183,12 +183,6 @@
 
   // SubresourceFilterClient:
   void ShowNotification() override { ++disallowed_notification_count_; }
-  mojom::ActivationLevel OnPageActivationComputed(
-      content::NavigationHandle* navigation_handle,
-      mojom::ActivationLevel effective_activation_level,
-      ActivationDecision* decision) override {
-    return effective_activation_level;
-  }
   void OnAdsViolationTriggered(
       content::RenderFrameHost* rfh,
       mojom::AdsViolation triggered_violation) override {}
@@ -196,6 +190,10 @@
   GetSafeBrowsingDatabaseManager() override {
     return database_manager_;
   }
+  subresource_filter::ProfileInteractionManager* GetProfileInteractionManager()
+      override {
+    return nullptr;
+  }
   void OnReloadRequested() override {}
 
   void CreateSafeBrowsingDatabaseManager() {
diff --git a/components/subresource_filter/content/browser/profile_interaction_manager.h b/components/subresource_filter/content/browser/profile_interaction_manager.h
index 015ecf4..8081870c 100644
--- a/components/subresource_filter/content/browser/profile_interaction_manager.h
+++ b/components/subresource_filter/content/browser/profile_interaction_manager.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_SUBRESOURCE_FILTER_CONTENT_BROWSER_PROFILE_INTERACTION_MANAGER_H_
 #define COMPONENTS_SUBRESOURCE_FILTER_CONTENT_BROWSER_PROFILE_INTERACTION_MANAGER_H_
 
+#include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h"
 #include "components/subresource_filter/core/common/activation_decision.h"
 #include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -21,7 +22,9 @@
 // Class that manages interaction between interaction between the
 // per-navigation/per-tab subresource filter objects (i.e., the throttles and
 // throttle manager) and the per-profile objects (e.g., content settings).
-class ProfileInteractionManager : public content::WebContentsObserver {
+class ProfileInteractionManager
+    : public content::WebContentsObserver,
+      public SubresourceFilterSafeBrowsingActivationThrottle::Delegate {
  public:
   ProfileInteractionManager(content::WebContents* web_contents,
                             SubresourceFilterProfileContext* profile_context);
@@ -43,19 +46,11 @@
   void OnAdsViolationTriggered(content::RenderFrameHost* rfh,
                                mojom::AdsViolation triggered_violation);
 
-  // Called when the initial activation decision has been computed by the
-  // safe browsing activation throttle. This object then applies any adjustments
-  // based on relevant state of the Profile (e.g., content settings). Returns
-  // the effective activation for this navigation.
-  //
-  // Note: |decision| is guaranteed to be non-nullptr, and can be modified by
-  // this method if any decision changes.
-  //
-  // Precondition: The navigation must be a main frame navigation.
+  // SubresourceFilterSafeBrowsingActivationThrottle::Delegate:
   mojom::ActivationLevel OnPageActivationComputed(
       content::NavigationHandle* navigation_handle,
       mojom::ActivationLevel initial_activation_level,
-      ActivationDecision* decision);
+      ActivationDecision* decision) override;
 
  private:
   // Unowned and must outlive this object.
diff --git a/components/subresource_filter/content/browser/subresource_filter_client.h b/components/subresource_filter/content/browser/subresource_filter_client.h
index 2b4e80d..cdd3b9e 100644
--- a/components/subresource_filter/content/browser/subresource_filter_client.h
+++ b/components/subresource_filter/content/browser/subresource_filter_client.h
@@ -6,15 +6,8 @@
 #define COMPONENTS_SUBRESOURCE_FILTER_CONTENT_BROWSER_SUBRESOURCE_FILTER_CLIENT_H_
 
 #include "base/memory/scoped_refptr.h"
-#include "components/subresource_filter/content/browser/verified_ruleset_dealer.h"
-#include "components/subresource_filter/core/common/activation_decision.h"
 #include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
 #include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
-
-namespace content {
-class NavigationHandle;
-}  // namespace content
 
 namespace safe_browsing {
 class SafeBrowsingDatabaseManager;
@@ -22,6 +15,8 @@
 
 namespace subresource_filter {
 
+class ProfileInteractionManager;
+
 class SubresourceFilterClient {
  public:
   virtual ~SubresourceFilterClient() = default;
@@ -30,20 +25,6 @@
   // blocked. This method will be called at most once per main-frame navigation.
   virtual void ShowNotification() = 0;
 
-  // Called when the activation decision is otherwise completely computed by the
-  // subresource filter. At this point, the embedder still has a chance to
-  // alter the effective activation. Returns the effective activation for this
-  // navigation.
-  //
-  // Note: |decision| is guaranteed to be non-nullptr, and can be modified by
-  // the embedder if any decision changes.
-  //
-  // Precondition: The navigation must be a main frame navigation.
-  virtual mojom::ActivationLevel OnPageActivationComputed(
-      content::NavigationHandle* navigation_handle,
-      mojom::ActivationLevel initial_activation_level,
-      subresource_filter::ActivationDecision* decision) = 0;
-
   // Called on the subresource filter client when an ads violation is detected.
   virtual void OnAdsViolationTriggered(
       content::RenderFrameHost* rfh,
@@ -54,6 +35,14 @@
   virtual const scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
   GetSafeBrowsingDatabaseManager() = 0;
 
+  // Returns the ProfileInteractionManager instance associated with this
+  // client, or null if there is no such instance.
+  // TODO(crbug.com/1116095): Have ContentSubresourceFilterThrottleManager
+  // create and own this object internally once ChromeSubresourceFilterClient no
+  // longer calls into it, replacing this method with a getter for
+  // SubresourceFilterProfileContext.
+  virtual ProfileInteractionManager* GetProfileInteractionManager() = 0;
+
   // Invoked when the user has requested a reload of a page with blocked ads
   // (e.g., via an infobar).
   virtual void OnReloadRequested() = 0;
diff --git a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.cc b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.cc
index 3f444aa..6a6d09b 100644
--- a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.cc
+++ b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.cc
@@ -17,7 +17,6 @@
 #include "components/subresource_filter/content/browser/content_activation_list_utils.h"
 #include "components/subresource_filter/content/browser/devtools_interaction_tracker.h"
 #include "components/subresource_filter/content/browser/navigation_console_logger.h"
-#include "components/subresource_filter/content/browser/subresource_filter_client.h"
 #include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
 #include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_client.h"
 #include "components/subresource_filter/core/browser/subresource_filter_constants.h"
@@ -64,7 +63,7 @@
 SubresourceFilterSafeBrowsingActivationThrottle::
     SubresourceFilterSafeBrowsingActivationThrottle(
         content::NavigationHandle* handle,
-        SubresourceFilterClient* client,
+        Delegate* delegate,
         scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
         scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
             database_manager)
@@ -76,7 +75,7 @@
                            io_task_runner_,
                            base::ThreadTaskRunnerHandle::Get()),
                        base::OnTaskRunnerDeleter(io_task_runner_)),
-      client_(client) {
+      delegate_(delegate) {
   DCHECK(handle->IsInMainFrame());
 
   CheckCurrentUrl();
@@ -205,9 +204,11 @@
     activation_decision = ActivationDecision::FORCED_ACTIVATION;
   }
 
-  // Let the embedder get the last word when it comes to activation level.
-  activation_level = client_->OnPageActivationComputed(
-      navigation_handle(), activation_level, &activation_decision);
+  // Let the delegate adjust the activation decision if present.
+  if (delegate_) {
+    activation_level = delegate_->OnPageActivationComputed(
+        navigation_handle(), activation_level, &activation_decision);
+  }
 
   LogMetricsOnChecksComplete(selection.matched_list, activation_decision,
                              activation_level);
diff --git a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h
index b3fc830..43e42b8 100644
--- a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h
+++ b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h
@@ -26,8 +26,6 @@
 
 namespace subresource_filter {
 
-class SubresourceFilterClient;
-
 // Enum representing a position in the redirect chain. These values are
 // persisted to logs. Entries should not be renumbered and numeric values should
 // never be reused.
@@ -46,9 +44,32 @@
       public base::SupportsWeakPtr<
           SubresourceFilterSafeBrowsingActivationThrottle> {
  public:
+  // Interface that allows the client of this class to adjust activation
+  // decisions if/as desired.
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // Called when the initial activation decision has been computed by the
+    // safe browsing activation throttle. Returns
+    // the effective activation for this navigation.
+    //
+    // Note: |decision| is guaranteed to be non-nullptr, and can be modified by
+    // this method if any decision changes.
+    //
+    // Precondition: The navigation must be a main frame navigation.
+    virtual mojom::ActivationLevel OnPageActivationComputed(
+        content::NavigationHandle* navigation_handle,
+        mojom::ActivationLevel initial_activation_level,
+        ActivationDecision* decision) = 0;
+  };
+
+  // |delegate| is allowed to be null, in which case the client creating this
+  // throttle will not be able to adjust activation decisions made by the
+  // throttle.
   SubresourceFilterSafeBrowsingActivationThrottle(
       content::NavigationHandle* handle,
-      SubresourceFilterClient* client,
+      Delegate* delegate,
       scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
       scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
           database_manager);
@@ -110,8 +131,8 @@
                   base::OnTaskRunnerDeleter>
       database_client_;
 
-  // Must outlive this class.
-  SubresourceFilterClient* client_;
+  // May be null. If non-null, must outlive this class.
+  Delegate* delegate_;
 
   // Set to TimeTicks::Now() when the navigation is deferred in
   // WillProcessResponse. If deferral was not necessary, will remain null.
diff --git a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle_unittest.cc b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle_unittest.cc
index 08f1cc5..a86533d 100644
--- a/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle_unittest.cc
+++ b/components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle_unittest.cc
@@ -24,6 +24,7 @@
 #include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
 #include "components/subresource_filter/content/browser/devtools_interaction_tracker.h"
 #include "components/subresource_filter/content/browser/fake_safe_browsing_database_manager.h"
+#include "components/subresource_filter/content/browser/profile_interaction_manager.h"
 #include "components/subresource_filter/content/browser/subresource_filter_client.h"
 #include "components/subresource_filter/content/browser/subresource_filter_observer_test_utils.h"
 #include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_client.h"
@@ -69,11 +70,17 @@
     "SubresourceFilter.PageLoad.ActivationList";
 const char kSubresourceFilterActionsHistogram[] = "SubresourceFilter.Actions2";
 
-class MockSubresourceFilterClient : public SubresourceFilterClient {
+class TestSafeBrowsingActivationThrottleDelegate
+    : public SubresourceFilterSafeBrowsingActivationThrottle::Delegate {
  public:
-  MockSubresourceFilterClient() = default;
-  ~MockSubresourceFilterClient() override = default;
+  TestSafeBrowsingActivationThrottleDelegate() = default;
+  ~TestSafeBrowsingActivationThrottleDelegate() override = default;
+  TestSafeBrowsingActivationThrottleDelegate(
+      const TestSafeBrowsingActivationThrottleDelegate&) = delete;
+  TestSafeBrowsingActivationThrottleDelegate& operator=(
+      const TestSafeBrowsingActivationThrottleDelegate&) = delete;
 
+  // SubresourceFilterSafeBrowsingActivationThrottle::Delegate:
   mojom::ActivationLevel OnPageActivationComputed(
       content::NavigationHandle* handle,
       mojom::ActivationLevel effective_level,
@@ -88,15 +95,6 @@
     return effective_level;
   }
 
-  MOCK_METHOD0(ShowNotification, void());
-  MOCK_METHOD2(OnAdsViolationTriggered,
-               void(content::RenderFrameHost*,
-                    subresource_filter::mojom::AdsViolation));
-  MOCK_METHOD0(
-      GetSafeBrowsingDatabaseManager,
-      const scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>());
-  MOCK_METHOD0(OnReloadRequested, void());
-
   void AllowlistInCurrentWebContents(const GURL& url) {
     ASSERT_TRUE(url.SchemeIsHTTPOrHTTPS());
     allowlisted_hosts_.insert(url.host());
@@ -106,7 +104,25 @@
 
  private:
   std::set<std::string> allowlisted_hosts_;
+};
 
+class MockSubresourceFilterClient : public SubresourceFilterClient {
+ public:
+  MockSubresourceFilterClient() = default;
+  ~MockSubresourceFilterClient() override = default;
+
+  MOCK_METHOD0(ShowNotification, void());
+  MOCK_METHOD2(OnAdsViolationTriggered,
+               void(content::RenderFrameHost*,
+                    subresource_filter::mojom::AdsViolation));
+  MOCK_METHOD0(
+      GetSafeBrowsingDatabaseManager,
+      const scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>());
+  MOCK_METHOD0(GetProfileInteractionManager,
+               subresource_filter::ProfileInteractionManager*());
+  MOCK_METHOD0(OnReloadRequested, void());
+
+ private:
   DISALLOW_COPY_AND_ASSIGN(MockSubresourceFilterClient);
 };
 
@@ -213,7 +229,7 @@
     if (navigation_handle->IsInMainFrame()) {
       navigation_handle->RegisterThrottleForTesting(
           std::make_unique<SubresourceFilterSafeBrowsingActivationThrottle>(
-              navigation_handle, client(), test_io_task_runner_,
+              navigation_handle, delegate(), test_io_task_runner_,
               fake_safe_browsing_database_));
     }
     std::vector<std::unique_ptr<content::NavigationThrottle>> throttles;
@@ -337,6 +353,7 @@
 
   MockSubresourceFilterClient* client() { return client_; }
 
+  TestSafeBrowsingActivationThrottleDelegate* delegate() { return &delegate_; }
   base::TestMockTimeTaskRunner* test_io_task_runner() const {
     return test_io_task_runner_.get();
   }
@@ -352,6 +369,7 @@
   testing::TestRulesetCreator test_ruleset_creator_;
   testing::TestRulesetPair test_ruleset_pair_;
 
+  TestSafeBrowsingActivationThrottleDelegate delegate_;
   std::unique_ptr<VerifiedRulesetDealer::Handle> ruleset_dealer_;
 
   std::unique_ptr<ContentSubresourceFilterThrottleManager> throttle_manager_;
@@ -519,7 +537,7 @@
             *observer()->GetPageActivationForLastCommittedLoad());
 
   // Allowlisting occurs last, so the decision should still be DISABLED.
-  client()->AllowlistInCurrentWebContents(url);
+  delegate()->AllowlistInCurrentWebContents(url);
   SimulateNavigateAndCommit({url}, main_rfh());
   EXPECT_EQ(mojom::ActivationLevel::kDisabled,
             *observer()->GetPageActivationForLastCommittedLoad());
@@ -792,7 +810,7 @@
   EXPECT_EQ(test_data.expected_activation_level,
             *observer()->GetPageActivationForLastCommittedLoad());
   if (test_data.url_matches_activation_list) {
-    client()->AllowlistInCurrentWebContents(test_url);
+    delegate()->AllowlistInCurrentWebContents(test_url);
     SimulateNavigateAndCommit({test_url}, main_rfh());
     EXPECT_EQ(mojom::ActivationLevel::kDisabled,
               *observer()->GetPageActivationForLastCommittedLoad());
diff --git a/components/webapps/installable/installable_manager.h b/components/webapps/installable/installable_manager.h
index 8b0b515..97d56750 100644
--- a/components/webapps/installable/installable_manager.h
+++ b/components/webapps/installable/installable_manager.h
@@ -90,9 +90,6 @@
   FRIEND_TEST_ALL_PREFIXES(InstallableManagerBrowserTest,
                            ManagerBeginsInEmptyState);
   FRIEND_TEST_ALL_PREFIXES(InstallableManagerBrowserTest, ManagerInIncognito);
-  FRIEND_TEST_ALL_PREFIXES(InstallableManagerBrowserTest, CheckWebapp);
-  FRIEND_TEST_ALL_PREFIXES(InstallableManagerBrowserTest,
-                           CheckLazyServiceWorkerPassesWhenWaiting);
   FRIEND_TEST_ALL_PREFIXES(InstallableManagerBrowserTest,
                            CheckLazyServiceWorkerNoFetchHandlerFails);
   FRIEND_TEST_ALL_PREFIXES(InstallableManagerBrowserTest,
@@ -101,6 +98,8 @@
                            CheckLazyServiceWorkerPassesWhenWaiting);
   FRIEND_TEST_ALL_PREFIXES(InstallableManagerOfflineCapabilityBrowserTest,
                            CheckWebapp);
+  FRIEND_TEST_ALL_PREFIXES(InstallableManagerOfflineCapabilityBrowserTest,
+                           CheckNotOfflineCapableStartUrl);
 
   using IconPurpose = blink::mojom::ManifestImageResource_Purpose;
 
diff --git a/content/browser/cookie_store/cookie_change_subscription.cc b/content/browser/cookie_store/cookie_change_subscription.cc
index 6c73ba4..07b9b55 100644
--- a/content/browser/cookie_store/cookie_change_subscription.cc
+++ b/content/browser/cookie_store/cookie_change_subscription.cc
@@ -167,11 +167,15 @@
       break;
   }
 
+  // We assume that this is a same-site, same-party context.
   net::CookieOptions net_options;
   net_options.set_same_site_cookie_context(
       net::CookieOptions::SameSiteCookieContext::MakeInclusive());
   net_options.set_same_party_cookie_context_type(
       net::CookieOptions::SamePartyCookieContextType::kSameParty);
+  // It doesn't matter which we choose here, since both SameParty and SameSite
+  // semantics should allow this access. But we make a choice to be explicit.
+  net_options.set_is_in_nontrivial_first_party_set(true);
 
   return cookie
       .IncludeForRequestURL(
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index b1bab00..3d3a24b 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -2557,7 +2557,7 @@
 }
 
 void RenderFrameHostImpl::SwapIn() {
-  GetNavigationControl()->SwapIn();
+  GetAssociatedLocalFrame()->SwapInImmediately();
 }
 
 void RenderFrameHostImpl::Init() {
diff --git a/content/browser/service_worker/embedded_worker_test_helper.cc b/content/browser/service_worker/embedded_worker_test_helper.cc
index 7873a1f..21cfacc 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.cc
+++ b/content/browser/service_worker/embedded_worker_test_helper.cc
@@ -43,12 +43,11 @@
           base::MakeRefCounted<URLLoaderFactoryGetter>()) {
   scoped_refptr<base::SequencedTaskRunner> database_task_runner =
       base::ThreadTaskRunnerHandle::Get();
-  wrapper_->InitOnCoreThread(
-      user_data_directory, std::move(database_task_runner),
+  wrapper_->InitInternal(
+      user_data_directory,
       /*quota_manager_proxy=*/nullptr, special_storage_policy, nullptr,
-      url_loader_factory_getter_.get(),
-      wrapper_->CreateNonNetworkPendingURLLoaderFactoryBundleForUpdateCheck(
-          browser_context_.get()));
+      url_loader_factory_getter_.get(), std::move(database_task_runner),
+      browser_context_.get());
   wrapper_->process_manager()->SetProcessIdForTest(mock_render_process_id());
   wrapper_->process_manager()->SetNewProcessIdForTest(new_render_process_id());
 
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index 2d16df1..56bff13 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -351,16 +351,15 @@
         shell()->web_contents()->GetBrowserContext());
     wrapper_ = static_cast<ServiceWorkerContextWrapper*>(
         partition->GetServiceWorkerContext());
-
-    RunOnCoreThread(
-        base::BindOnce(&self::SetUpOnCoreThread, base::Unretained(this)));
   }
 
   void TearDownOnMainThread() override {
-    base::RunLoop loop;
-    RunOnCoreThread(base::BindOnce(&self::TearDownOnCoreThread,
-                                   base::Unretained(this), loop.QuitClosure()));
-    loop.Run();
+    // Flush remote storage control so that all pending callbacks are executed.
+    wrapper()
+        ->context()
+        ->registry()
+        ->GetRemoteStorageControl()
+        .FlushForTesting();
     content::RunAllTasksUntilIdle();
     wrapper_ = nullptr;
   }
@@ -379,18 +378,6 @@
         1);
   }
 
-  virtual void SetUpOnCoreThread() {}
-
-  virtual void TearDownOnCoreThread(base::OnceClosure callback) {
-    // Flush remote storage control so that all pending callbacks are executed.
-    wrapper()
-        ->context()
-        ->registry()
-        ->GetRemoteStorageControl()
-        .FlushForTesting();
-    std::move(callback).Run();
-  }
-
   ServiceWorkerContextWrapper* wrapper() { return wrapper_.get(); }
   ServiceWorkerContext* public_context() { return wrapper(); }
 
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index a9a4ebc6..eed3193 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -28,7 +28,6 @@
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/service_worker_container_host.h"
 #include "content/browser/service_worker/service_worker_host.h"
-#include "content/browser/service_worker/service_worker_metrics.h"
 #include "content/browser/service_worker/service_worker_object_host.h"
 #include "content/browser/service_worker/service_worker_process_manager.h"
 #include "content/browser/service_worker/service_worker_quota_client.h"
@@ -60,16 +59,16 @@
 
 void WorkerStarted(ServiceWorkerContextWrapper::StatusCallback callback,
                    blink::ServiceWorkerStatusCode status) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   GetUIThreadTaskRunner({})->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), status));
 }
 
-void StartActiveWorkerOnCoreThread(
+void DidFindRegistrationForStartActiveWorker(
     ServiceWorkerContextWrapper::StatusCallback callback,
     blink::ServiceWorkerStatusCode status,
     scoped_refptr<ServiceWorkerRegistration> registration) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (status != blink::ServiceWorkerStatusCode::kOk ||
       !registration->active_version()) {
@@ -88,24 +87,12 @@
       base::BindOnce(WorkerStarted, std::move(callback)));
 }
 
-void SkipWaitingWorkerOnCoreThread(
-    blink::ServiceWorkerStatusCode status,
-    scoped_refptr<ServiceWorkerRegistration> registration) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  if (status != blink::ServiceWorkerStatusCode::kOk ||
-      !registration->waiting_version())
-    return;
-
-  registration->waiting_version()->set_skip_waiting(true);
-  registration->ActivateWaitingVersionWhenReady();
-}
-
 void DidStartWorker(
     scoped_refptr<ServiceWorkerVersion> version,
     ServiceWorkerContext::StartWorkerCallback info_callback,
     ServiceWorkerContext::StartWorkerFailureCallback error_callback,
     blink::ServiceWorkerStatusCode start_worker_status) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (start_worker_status != blink::ServiceWorkerStatusCode::kOk) {
     std::move(error_callback).Run(start_worker_status);
     return;
@@ -121,7 +108,7 @@
     ServiceWorkerContext::StartWorkerFailureCallback failure_callback,
     blink::ServiceWorkerStatusCode service_worker_status,
     scoped_refptr<ServiceWorkerRegistration> registration) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
     std::move(failure_callback).Run(service_worker_status);
     return;
@@ -154,48 +141,42 @@
                      std::move(failure_callback)));
 }
 
-void FinishRegistrationOnCoreThread(
-    ServiceWorkerContext::ResultCallback callback,
-    blink::ServiceWorkerStatusCode status,
-    const std::string& status_message,
-    int64_t registration_id) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback),
-                                status == blink::ServiceWorkerStatusCode::kOk));
-}
-
-void FinishUnregistrationOnCoreThread(
-    ServiceWorkerContext::ResultCallback callback,
-    blink::ServiceWorkerStatusCode status) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback),
-                                status == blink::ServiceWorkerStatusCode::kOk));
-}
-
-void MessageFinishedSending(ServiceWorkerContext::ResultCallback callback,
-                            scoped_refptr<base::TaskRunner> callback_runner,
-                            blink::ServiceWorkerStatusCode status) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  callback_runner->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback),
-                                status == blink::ServiceWorkerStatusCode::kOk));
-}
-
 void RunOnceClosure(scoped_refptr<ServiceWorkerContextWrapper> ref_holder,
                     base::OnceClosure task) {
   std::move(task).Run();
 }
 
+// Helper class to create a callback that takes blink::ServiceWorkerStatusCode
+// as the first parameter and calls the original callback with a boolean of
+// whether the status is blink::ServiceWorkerStatusCode::kOk or not.
+class WrapResultCallbackToTakeStatusCode {
+ public:
+  explicit WrapResultCallbackToTakeStatusCode(
+      ServiceWorkerContext::ResultCallback callback)
+      : callback_(std::move(callback)) {}
+
+  template <typename... Args>
+  operator base::OnceCallback<void(blink::ServiceWorkerStatusCode, Args...)>() {
+    return Take<Args...>();
+  }
+
+ private:
+  template <typename... Args>
+  base::OnceCallback<void(blink::ServiceWorkerStatusCode, Args...)> Take() {
+    return base::BindOnce(
+        [](ServiceWorkerContext::ResultCallback callback,
+           blink::ServiceWorkerStatusCode status, Args...) {
+          std::move(callback).Run(status ==
+                                  blink::ServiceWorkerStatusCode::kOk);
+        },
+        std::move(callback_));
+  }
+
+  ServiceWorkerContext::ResultCallback callback_;
+};
+
 }  // namespace
 
-// static
-void ServiceWorkerContextWrapper::RunOrPostTaskOnCoreThread(
-    const base::Location& location,
-    base::OnceClosure task) {
-  RunOrPostTaskOnThread(location, GetCoreThreadId(), std::move(task));
-}
 
 // static
 bool ServiceWorkerContext::ScopeMatches(const GURL& scope, const GURL& url) {
@@ -254,23 +235,43 @@
   scoped_refptr<base::SequencedTaskRunner> database_task_runner =
       base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
+  InitInternal(user_data_directory, quota_manager_proxy, special_storage_policy,
+               blob_context, loader_factory_getter,
+               std::move(database_task_runner),
+               storage_partition_->browser_context());
+}
+
+void ServiceWorkerContextWrapper::InitInternal(
+    const base::FilePath& user_data_directory,
+    storage::QuotaManagerProxy* quota_manager_proxy,
+    storage::SpecialStoragePolicy* special_storage_policy,
+    ChromeBlobStorageContext* blob_context,
+    URLLoaderFactoryGetter* loader_factory_getter,
+    scoped_refptr<base::SequencedTaskRunner> database_task_runner,
+    BrowserContext* browser_context) {
   std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
       non_network_pending_loader_factory_bundle_for_update_check;
   non_network_pending_loader_factory_bundle_for_update_check =
       CreateNonNetworkPendingURLLoaderFactoryBundleForUpdateCheck(
-          storage_partition_->browser_context());
+          browser_context);
 
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::InitOnCoreThread, this,
-          user_data_directory, std::move(database_task_runner),
-          base::RetainedRef(quota_manager_proxy),
-          base::RetainedRef(special_storage_policy),
-          base::RetainedRef(blob_context),
-          base::RetainedRef(loader_factory_getter),
-          std::move(
-              non_network_pending_loader_factory_bundle_for_update_check)));
+  if (quota_manager_proxy) {
+    quota_manager_proxy->RegisterClient(
+        base::MakeRefCounted<ServiceWorkerQuotaClient>(this),
+        storage::QuotaClientType::kServiceWorker,
+        {blink::mojom::StorageType::kTemporary});
+  }
+
+  context_core_ = std::make_unique<ServiceWorkerContextCore>(
+      user_data_directory, std::move(database_task_runner), quota_manager_proxy,
+      special_storage_policy, loader_factory_getter,
+      std::move(non_network_pending_loader_factory_bundle_for_update_check),
+      core_observer_list_.get(), this);
+
+  if (storage_partition_) {
+    context()->registry()->GetRegisteredOrigins(base::BindOnce(
+        &ServiceWorkerContextWrapper::DidGetRegisteredOrigins, this));
+  }
 }
 
 void ServiceWorkerContextWrapper::Shutdown() {
@@ -278,12 +279,11 @@
 
   storage_partition_ = nullptr;
   process_manager_->Shutdown();
-
-  ShutdownOnCoreThread();
+  context_core_.reset();
 }
 
 void ServiceWorkerContextWrapper::DeleteAndStartOver() {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     // The context could be null due to system shutdown or restart failure. In
     // either case, we should not have to recover the system, so just return
@@ -472,13 +472,7 @@
     const GURL& script_url,
     const blink::mojom::ServiceWorkerRegistrationOptions& options,
     ResultCallback callback) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(&ServiceWorkerContextWrapper::RegisterServiceWorker,
-                       this, script_url, options, std::move(callback)));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     GetUIThreadTaskRunner({})->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), false));
@@ -495,19 +489,14 @@
           network::mojom::ReferrerPolicy::kDefault,
           /*outgoing_referrer=*/script_url,
           blink::mojom::InsecureRequestsPolicy::kDoNotUpgrade),
-      base::BindOnce(&FinishRegistrationOnCoreThread, std::move(callback)));
+      WrapResultCallbackToTakeStatusCode(std::move(callback)));
 }
 
 void ServiceWorkerContextWrapper::UnregisterServiceWorker(
     const GURL& scope,
     ResultCallback callback) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(&ServiceWorkerContextWrapper::UnregisterServiceWorker,
-                       this, scope, std::move(callback)));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
   if (!context_core_) {
     GetUIThreadTaskRunner({})->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback), false));
@@ -516,14 +505,14 @@
 
   context()->UnregisterServiceWorker(
       net::SimplifyUrlForRequest(scope), /*is_immediate=*/false,
-      base::BindOnce(&FinishUnregistrationOnCoreThread, std::move(callback)));
+      WrapResultCallbackToTakeStatusCode(std::move(callback)));
 }
 
 ServiceWorkerExternalRequestResult
 ServiceWorkerContextWrapper::StartingExternalRequest(
     int64_t service_worker_version_id,
     const std::string& request_uuid) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context())
     return ServiceWorkerExternalRequestResult::kNullContext;
   scoped_refptr<ServiceWorkerVersion> version =
@@ -537,7 +526,7 @@
 ServiceWorkerContextWrapper::FinishedExternalRequest(
     int64_t service_worker_version_id,
     const std::string& request_uuid) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context())
     return ServiceWorkerExternalRequestResult::kNullContext;
   scoped_refptr<ServiceWorkerVersion> version =
@@ -547,14 +536,20 @@
   return version->FinishExternalRequest(request_uuid);
 }
 
-void ServiceWorkerContextWrapper::CountExternalRequestsForTest(
-    const url::Origin& origin,
-    CountExternalRequestsCallback callback) {
+size_t ServiceWorkerContextWrapper::CountExternalRequestsForTest(
+    const url::Origin& origin) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  base::PostTask(
-      FROM_HERE, {GetCoreThreadId()},
-      base::BindOnce(&ServiceWorkerContextWrapper::CountExternalRequests, this,
-                     origin, std::move(callback)));
+
+  std::vector<ServiceWorkerVersionInfo> live_version_info =
+      GetAllLiveVersionInfo();
+  for (const ServiceWorkerVersionInfo& info : live_version_info) {
+    ServiceWorkerVersion* version = GetLiveVersion(info.version_id);
+    if (version && version->origin() == origin) {
+      return version->GetExternalRequestCountForTest();  // IN-TEST
+    }
+  }
+
+  return 0u;
 }
 
 bool ServiceWorkerContextWrapper::MaybeHasRegistrationForOrigin(
@@ -572,18 +567,19 @@
 void ServiceWorkerContextWrapper::GetInstalledRegistrationOrigins(
     base::Optional<std::string> host_filter,
     GetInstalledRegistrationOriginsCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE, base::BindOnce(&ServiceWorkerContextWrapper::
-                                    GetInstalledRegistrationOriginsOnCoreThread,
-                                this, host_filter, std::move(callback),
-                                base::ThreadTaskRunnerHandle::Get()));
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
+      base::BindOnce(&ServiceWorkerContextWrapper::
+                         GetInstalledRegistrationOriginsOnUIThread,
+                     this, host_filter, std::move(callback),
+                     base::ThreadTaskRunnerHandle::Get()));
 }
 
-void ServiceWorkerContextWrapper::GetInstalledRegistrationOriginsOnCoreThread(
+void ServiceWorkerContextWrapper::GetInstalledRegistrationOriginsOnUIThread(
     base::Optional<std::string> host_filter,
     GetInstalledRegistrationOriginsCallback callback,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (!context_core_) {
     task_runner_for_callback->PostTask(
@@ -595,47 +591,37 @@
   context()->registry()->GetRegisteredOrigins(base::BindOnce(
       &ServiceWorkerContextWrapper::
           DidGetRegisteredOriginsForGetInstalledRegistrationOrigins,
-      host_filter, std::move(callback), task_runner_for_callback));
+      host_filter, std::move(callback), std::move(task_runner_for_callback)));
 }
 
 void ServiceWorkerContextWrapper::GetAllOriginsInfo(
     GetUsageInfoCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::GetAllOriginsInfoOnCoreThread, this,
-          std::move(callback), base::ThreadTaskRunnerHandle::Get()));
-}
-
-void ServiceWorkerContextWrapper::GetAllOriginsInfoOnCoreThread(
-    GetUsageInfoCallback callback,
-    scoped_refptr<base::TaskRunner> callback_runner) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
-    callback_runner->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), std::vector<StorageUsageInfo>()));
     return;
   }
   context()->registry()->GetAllRegistrationsInfos(base::BindOnce(
       &ServiceWorkerContextWrapper::DidGetAllRegistrationsForGetAllOrigins,
-      this, base::TimeTicks::Now(), std::move(callback),
-      std::move(callback_runner)));
+      this, std::move(callback)));
 }
 
 void ServiceWorkerContextWrapper::DeleteForOrigin(const url::Origin& origin,
                                                   ResultCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
-      base::BindOnce(&ServiceWorkerContextWrapper::DeleteForOriginOnCoreThread,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
+      base::BindOnce(&ServiceWorkerContextWrapper::DeleteForOriginOnUIThread,
                      this, origin, std::move(callback),
                      base::ThreadTaskRunnerHandle::Get()));
 }
 
-void ServiceWorkerContextWrapper::DeleteForOriginOnCoreThread(
+void ServiceWorkerContextWrapper::DeleteForOriginOnUIThread(
     const url::Origin& origin,
     ResultCallback callback,
     scoped_refptr<base::TaskRunner> callback_runner) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     callback_runner->PostTask(FROM_HERE,
                               base::BindOnce(std::move(callback), false));
@@ -657,16 +643,17 @@
 
 void ServiceWorkerContextWrapper::PerformStorageCleanup(
     base::OnceClosure callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
-          &ServiceWorkerContextWrapper::PerformStorageCleanupOnCoreThread, this,
+          &ServiceWorkerContextWrapper::PerformStorageCleanupOnUIThread, this,
           std::move(callback), base::ThreadTaskRunnerHandle::Get()));
 }
 
-void ServiceWorkerContextWrapper::PerformStorageCleanupOnCoreThread(
+void ServiceWorkerContextWrapper::PerformStorageCleanupOnUIThread(
     base::OnceClosure callback,
     scoped_refptr<base::TaskRunner> callback_runner) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     callback_runner->PostTask(FROM_HERE, std::move(callback));
     return;
@@ -682,61 +669,41 @@
 void ServiceWorkerContextWrapper::CheckHasServiceWorker(
     const GURL& url,
     CheckHasServiceWorkerCallback callback) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(&ServiceWorkerContextWrapper::CheckHasServiceWorker,
-                       this, url, std::move(callback)));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
   if (!context_core_) {
-    GetUIThreadTaskRunner({})->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback),
                                   ServiceWorkerCapability::NO_SERVICE_WORKER));
     return;
   }
-  context()->CheckHasServiceWorker(
-      net::SimplifyUrlForRequest(url),
-      base::BindOnce(&ServiceWorkerContextWrapper::DidCheckHasServiceWorker,
-                     this, std::move(callback)));
+  context()->CheckHasServiceWorker(net::SimplifyUrlForRequest(url),
+                                   std::move(callback));
 }
 
 void ServiceWorkerContextWrapper::CheckOfflineCapability(
     const GURL& url,
     CheckOfflineCapabilityCallback callback) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(&ServiceWorkerContextWrapper::CheckOfflineCapability,
-                       this, url, std::move(callback)));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
   if (!context_core_) {
-    GetUIThreadTaskRunner({})->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), OfflineCapability::kUnsupported,
                        blink::mojom::kInvalidServiceWorkerRegistrationId));
     return;
   }
-  context()->CheckOfflineCapability(
-      net::SimplifyUrlForRequest(url),
-      base::BindOnce(&ServiceWorkerContextWrapper::DidCheckOfflineCapability,
-                     this, std::move(callback)));
+  context()->CheckOfflineCapability(net::SimplifyUrlForRequest(url),
+                                    std::move(callback));
 }
 
 void ServiceWorkerContextWrapper::ClearAllServiceWorkersForTest(
     base::OnceClosure callback) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(
-            &ServiceWorkerContextWrapper::ClearAllServiceWorkersForTest, this,
-            std::move(callback)));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (!context_core_) {
-    GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback));
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  std::move(callback));
     return;
   }
   context_core_->ClearAllServiceWorkersForTest(std::move(callback));
@@ -746,7 +713,7 @@
     const GURL& scope,
     StartWorkerCallback info_callback,
     StartWorkerFailureCallback failure_callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   FindRegistrationForScopeImpl(
       scope, /*include_installing_version=*/true,
       base::BindOnce(&FoundRegistrationForStartWorker, std::move(info_callback),
@@ -757,26 +724,32 @@
     const GURL& scope,
     blink::TransferableMessage message,
     ResultCallback result_callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(&ServiceWorkerContextWrapper::
-                         StartServiceWorkerAndDispatchMessageOnCoreThread,
+                         StartServiceWorkerAndDispatchMessageOnUIThread,
                      this, scope, std::move(message),
-                     std::move(result_callback),
-                     base::ThreadTaskRunnerHandle::Get()));
+                     base::BindOnce(
+                         [](ResultCallback callback,
+                            scoped_refptr<base::TaskRunner> callback_runner,
+                            bool success) {
+                           callback_runner->PostTask(
+                               FROM_HERE,
+                               base::BindOnce(std::move(callback), success));
+                         },
+                         std::move(result_callback),
+                         base::ThreadTaskRunnerHandle::Get())));
 }
 
 void ServiceWorkerContextWrapper::
-    StartServiceWorkerAndDispatchMessageOnCoreThread(
+    StartServiceWorkerAndDispatchMessageOnUIThread(
         const GURL& scope,
         blink::TransferableMessage message,
-        ResultCallback result_callback,
-        scoped_refptr<base::TaskRunner> callback_runner) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+        ResultCallback result_callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (!context_core_) {
-    callback_runner->PostTask(
-        FROM_HERE, base::BindOnce(std::move(result_callback), false));
+    std::move(result_callback).Run(/*success=*/false);
     return;
   }
 
@@ -784,23 +757,20 @@
       net::SimplifyUrlForRequest(scope), false /* include_installing_version */,
       base::BindOnce(
           &ServiceWorkerContextWrapper::DidFindRegistrationForMessageDispatch,
-          this, std::move(message), scope, std::move(result_callback),
-          std::move(callback_runner)));
+          this, std::move(message), scope, std::move(result_callback)));
 }
 
 void ServiceWorkerContextWrapper::DidFindRegistrationForMessageDispatch(
     blink::TransferableMessage message,
     const GURL& source_origin,
     ResultCallback result_callback,
-    scoped_refptr<base::TaskRunner> callback_runner,
     blink::ServiceWorkerStatusCode service_worker_status,
     scoped_refptr<ServiceWorkerRegistration> registration) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
     LOG(WARNING) << "No registration available, status: "
                  << static_cast<int>(service_worker_status);
-    callback_runner->PostTask(
-        FROM_HERE, base::BindOnce(std::move(result_callback), false));
+    std::move(result_callback).Run(/*success=*/false);
     return;
   }
   registration->active_version()->StartWorker(
@@ -808,7 +778,7 @@
       base::BindOnce(
           &ServiceWorkerContextWrapper::DidStartServiceWorkerForMessageDispatch,
           this, std::move(message), source_origin, registration,
-          std::move(result_callback), std::move(callback_runner)));
+          std::move(result_callback)));
 }
 
 void ServiceWorkerContextWrapper::DidStartServiceWorkerForMessageDispatch(
@@ -816,12 +786,10 @@
     const GURL& source_origin,
     scoped_refptr<ServiceWorkerRegistration> registration,
     ResultCallback result_callback,
-    scoped_refptr<base::TaskRunner> callback_runner,
     blink::ServiceWorkerStatusCode status) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (status != blink::ServiceWorkerStatusCode::kOk) {
-    callback_runner->PostTask(
-        FROM_HERE, base::BindOnce(std::move(result_callback), false));
+    std::move(result_callback).Run(/*success=*/false);
     return;
   }
 
@@ -839,8 +807,7 @@
 
   int request_id = version->StartRequest(
       ServiceWorkerMetrics::EventType::MESSAGE,
-      base::BindOnce(&MessageFinishedSending, std::move(result_callback),
-                     std::move(callback_runner)));
+      WrapResultCallbackToTakeStatusCode(std::move(result_callback)));
   version->endpoint()->DispatchExtendableMessageEvent(
       std::move(event), version->CreateSimpleEventCallback(request_id));
 }
@@ -852,27 +819,30 @@
                "document_url", document_url.spec());
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  auto callback_with_recording_metrics = base::BindOnce(
+      [](StartServiceWorkerForNavigationHintCallback callback,
+         StartServiceWorkerForNavigationHintResult result) {
+        ServiceWorkerMetrics::RecordStartServiceWorkerForNavigationHintResult(
+            result);
+        std::move(callback).Run(result);
+      },
+      std::move(callback));
+
+  if (!context_core_) {
+    std::move(callback_with_recording_metrics)
+        .Run(StartServiceWorkerForNavigationHintResult::FAILED);
+    return;
+  }
+  context_core_->registry()->FindRegistrationForClientUrl(
+      net::SimplifyUrlForRequest(document_url),
       base::BindOnce(
-          &ServiceWorkerContextWrapper::
-              StartServiceWorkerForNavigationHintOnCoreThread,
-          this, document_url,
-          base::BindOnce(&ServiceWorkerContextWrapper::
-                             RecordStartServiceWorkerForNavigationHintResult,
-                         this, std::move(callback))));
+          &ServiceWorkerContextWrapper::DidFindRegistrationForNavigationHint,
+          this, std::move(callback_with_recording_metrics)));
 }
 
 void ServiceWorkerContextWrapper::StopAllServiceWorkersForOrigin(
     const url::Origin& origin) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(
-            &ServiceWorkerContextWrapper::StopAllServiceWorkersForOrigin, this,
-            origin));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_.get()) {
     return;
   }
@@ -886,11 +856,21 @@
 
 void ServiceWorkerContextWrapper::StopAllServiceWorkers(
     base::OnceClosure callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::StopAllServiceWorkersOnCoreThread, this,
-          std::move(callback), base::ThreadTaskRunnerHandle::Get()));
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  if (!context_core_.get()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  std::move(callback));
+    return;
+  }
+
+  std::vector<ServiceWorkerVersionInfo> live_versions = GetAllLiveVersionInfo();
+  base::RepeatingClosure barrier =
+      base::BarrierClosure(live_versions.size(), std::move(callback));
+  for (const ServiceWorkerVersionInfo& info : live_versions) {
+    ServiceWorkerVersion* version = GetLiveVersion(info.version_id);
+    DCHECK(version);
+    version->StopWorker(barrier);
+  }
 }
 
 const base::flat_map<int64_t, ServiceWorkerRunningInfo>&
@@ -900,7 +880,7 @@
 
 ServiceWorkerRegistration* ServiceWorkerContextWrapper::GetLiveRegistration(
     int64_t registration_id) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_)
     return nullptr;
   return context_core_->GetLiveRegistration(registration_id);
@@ -908,7 +888,7 @@
 
 ServiceWorkerVersion* ServiceWorkerContextWrapper::GetLiveVersion(
     int64_t version_id) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_)
     return nullptr;
   return context_core_->GetLiveVersion(version_id);
@@ -916,7 +896,7 @@
 
 std::vector<ServiceWorkerRegistrationInfo>
 ServiceWorkerContextWrapper::GetAllLiveRegistrationInfo() {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_)
     return std::vector<ServiceWorkerRegistrationInfo>();
   return context_core_->GetAllLiveRegistrationInfo();
@@ -924,7 +904,7 @@
 
 std::vector<ServiceWorkerVersionInfo>
 ServiceWorkerContextWrapper::GetAllLiveVersionInfo() {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_)
     return std::vector<ServiceWorkerVersionInfo>();
   return context_core_->GetAllLiveVersionInfo();
@@ -933,39 +913,20 @@
 void ServiceWorkerContextWrapper::HasMainFrameWindowClient(
     const GURL& origin,
     BoolCallback callback) const {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::HasMainFrameWindowClientOnCoreThread,
-          this, origin, std::move(callback),
-          base::ThreadTaskRunnerHandle::Get()));
-}
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-void ServiceWorkerContextWrapper::HasMainFrameWindowClientOnCoreThread(
-    const GURL& origin,
-    BoolCallback callback,
-    scoped_refptr<base::TaskRunner> callback_runner) const {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
   if (!context_core_) {
-    callback_runner->PostTask(FROM_HERE,
-                              base::BindOnce(std::move(callback), false));
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false));
     return;
   }
-  context_core_->HasMainFrameWindowClient(
-      origin,
-      base::BindOnce(
-          [](BoolCallback callback,
-             scoped_refptr<base::TaskRunner> callback_runner, bool result) {
-            callback_runner->PostTask(
-                FROM_HERE, base::BindOnce(std::move(callback), result));
-          },
-          std::move(callback), std::move(callback_runner)));
+  context_core_->HasMainFrameWindowClient(origin, std::move(callback));
 }
 
 std::unique_ptr<std::vector<GlobalFrameRoutingId>>
 ServiceWorkerContextWrapper::GetWindowClientFrameRoutingIds(
     const GURL& origin) const {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   std::unique_ptr<std::vector<GlobalFrameRoutingId>> frame_routing_ids(
       new std::vector<GlobalFrameRoutingId>());
@@ -988,7 +949,7 @@
 void ServiceWorkerContextWrapper::FindReadyRegistrationForClientUrl(
     const GURL& client_url,
     FindRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
                             nullptr);
@@ -1004,7 +965,7 @@
 void ServiceWorkerContextWrapper::FindReadyRegistrationForScope(
     const GURL& scope,
     FindRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
                             nullptr);
@@ -1021,7 +982,7 @@
 void ServiceWorkerContextWrapper::FindRegistrationForScope(
     const GURL& scope,
     FindRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   const bool include_installing_version = true;
   FindRegistrationForScopeImpl(scope, include_installing_version,
                                std::move(callback));
@@ -1031,7 +992,7 @@
     int64_t registration_id,
     const url::Origin& origin,
     FindRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
                             nullptr);
@@ -1047,7 +1008,7 @@
 void ServiceWorkerContextWrapper::FindReadyRegistrationForIdOnly(
     int64_t registration_id,
     FindRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
                             nullptr);
@@ -1062,29 +1023,13 @@
 
 void ServiceWorkerContextWrapper::GetAllRegistrations(
     GetRegistrationsInfosCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::GetAllRegistrationsOnCoreThread, this,
-          base::BindOnce(
-              [](GetRegistrationsInfosCallback callback,
-                 scoped_refptr<base::TaskRunner> callback_runner,
-                 blink::ServiceWorkerStatusCode status,
-                 const std::vector<ServiceWorkerRegistrationInfo>&
-                     registrations) {
-                callback_runner->PostTask(
-                    FROM_HERE,
-                    base::BindOnce(std::move(callback), status, registrations));
-              },
-              std::move(callback), base::ThreadTaskRunnerHandle::Get())));
-}
-
-void ServiceWorkerContextWrapper::GetAllRegistrationsOnCoreThread(
-    GetRegistrationsInfosCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
-    std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
-                            std::vector<ServiceWorkerRegistrationInfo>());
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback),
+                       blink::ServiceWorkerStatusCode::kErrorAbort,
+                       std::vector<ServiceWorkerRegistrationInfo>()));
     return;
   }
   context_core_->registry()->GetAllRegistrationsInfos(std::move(callback));
@@ -1093,10 +1038,10 @@
 void ServiceWorkerContextWrapper::GetStorageUsageForOrigin(
     const url::Origin& origin,
     GetStorageUsageForOriginCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
-          &ServiceWorkerContextWrapper::GetStorageUsageForOriginOnCoreThread,
+          &ServiceWorkerContextWrapper::GetStorageUsageForOriginOnUIThread,
           this, origin,
           base::BindOnce(
               [](GetStorageUsageForOriginCallback callback,
@@ -1109,10 +1054,10 @@
               std::move(callback), base::ThreadTaskRunnerHandle::Get())));
 }
 
-void ServiceWorkerContextWrapper::GetStorageUsageForOriginOnCoreThread(
+void ServiceWorkerContextWrapper::GetStorageUsageForOriginOnUIThread(
     const url::Origin& origin,
     GetStorageUsageForOriginCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort, 0);
     return;
@@ -1124,7 +1069,7 @@
 void ServiceWorkerContextWrapper::GetRegistrationsForOrigin(
     const url::Origin& origin,
     GetRegistrationsCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
@@ -1141,11 +1086,11 @@
     int64_t registration_id,
     const std::vector<std::string>& keys,
     GetUserDataCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
-          &ServiceWorkerContextWrapper::GetRegistrationUserDataOnCoreThread,
-          this, registration_id, keys,
+          &ServiceWorkerContextWrapper::GetRegistrationUserDataOnUIThread, this,
+          registration_id, keys,
           base::BindOnce(
               [](GetUserDataCallback callback,
                  scoped_refptr<base::TaskRunner> callback_runner,
@@ -1158,11 +1103,11 @@
               std::move(callback), base::ThreadTaskRunnerHandle::Get())));
 }
 
-void ServiceWorkerContextWrapper::GetRegistrationUserDataOnCoreThread(
+void ServiceWorkerContextWrapper::GetRegistrationUserDataOnUIThread(
     int64_t registration_id,
     const std::vector<std::string>& keys,
     GetUserDataCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(std::vector<std::string>(),
                             blink::ServiceWorkerStatusCode::kErrorAbort);
@@ -1176,11 +1121,11 @@
     int64_t registration_id,
     const std::string& key_prefix,
     GetUserDataCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
           &ServiceWorkerContextWrapper::
-              GetRegistrationUserDataByKeyPrefixOnCoreThread,
+              GetRegistrationUserDataByKeyPrefixOnUIThread,
           this, registration_id, key_prefix,
           base::BindOnce(
               [](GetUserDataCallback callback,
@@ -1194,12 +1139,11 @@
               std::move(callback), base::ThreadTaskRunnerHandle::Get())));
 }
 
-void ServiceWorkerContextWrapper::
-    GetRegistrationUserDataByKeyPrefixOnCoreThread(
-        int64_t registration_id,
-        const std::string& key_prefix,
-        GetUserDataCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+void ServiceWorkerContextWrapper::GetRegistrationUserDataByKeyPrefixOnUIThread(
+    int64_t registration_id,
+    const std::string& key_prefix,
+    GetUserDataCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(std::vector<std::string>(),
                             blink::ServiceWorkerStatusCode::kErrorAbort);
@@ -1213,11 +1157,11 @@
     int64_t registration_id,
     const std::string& key_prefix,
     GetUserKeysAndDataCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
           &ServiceWorkerContextWrapper::
-              GetRegistrationUserKeysAndDataByKeyPrefixOnCoreThread,
+              GetRegistrationUserKeysAndDataByKeyPrefixOnUIThread,
           this, registration_id, key_prefix,
           base::BindOnce(
               [](GetUserKeysAndDataCallback callback,
@@ -1232,11 +1176,11 @@
 }
 
 void ServiceWorkerContextWrapper::
-    GetRegistrationUserKeysAndDataByKeyPrefixOnCoreThread(
+    GetRegistrationUserKeysAndDataByKeyPrefixOnUIThread(
         int64_t registration_id,
         const std::string& key_prefix,
         GetUserKeysAndDataCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
                             base::flat_map<std::string, std::string>());
@@ -1251,10 +1195,10 @@
     const url::Origin& origin,
     const std::vector<std::pair<std::string, std::string>>& key_value_pairs,
     StatusCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
-          &ServiceWorkerContextWrapper::StoreRegistrationUserDataOnCoreThread,
+          &ServiceWorkerContextWrapper::StoreRegistrationUserDataOnUIThread,
           this, registration_id, origin, key_value_pairs,
           base::BindOnce(
               [](StatusCallback callback,
@@ -1266,12 +1210,12 @@
               std::move(callback), base::ThreadTaskRunnerHandle::Get())));
 }
 
-void ServiceWorkerContextWrapper::StoreRegistrationUserDataOnCoreThread(
+void ServiceWorkerContextWrapper::StoreRegistrationUserDataOnUIThread(
     int64_t registration_id,
     const url::Origin& origin,
     const std::vector<std::pair<std::string, std::string>>& key_value_pairs,
     StatusCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort);
     return;
@@ -1284,10 +1228,10 @@
     int64_t registration_id,
     const std::vector<std::string>& keys,
     StatusCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
-          &ServiceWorkerContextWrapper::ClearRegistrationUserDataOnCoreThread,
+          &ServiceWorkerContextWrapper::ClearRegistrationUserDataOnUIThread,
           this, registration_id, keys,
           base::BindOnce(
               [](StatusCallback callback,
@@ -1299,11 +1243,11 @@
               std::move(callback), base::ThreadTaskRunnerHandle::Get())));
 }
 
-void ServiceWorkerContextWrapper::ClearRegistrationUserDataOnCoreThread(
+void ServiceWorkerContextWrapper::ClearRegistrationUserDataOnUIThread(
     int64_t registration_id,
     const std::vector<std::string>& keys,
     StatusCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort);
     return;
@@ -1316,11 +1260,11 @@
     int64_t registration_id,
     const std::vector<std::string>& key_prefixes,
     StatusCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
           &ServiceWorkerContextWrapper::
-              ClearRegistrationUserDataByKeyPrefixesOnCoreThread,
+              ClearRegistrationUserDataByKeyPrefixesOnUIThread,
           this, registration_id, key_prefixes,
           base::BindOnce(
               [](StatusCallback callback,
@@ -1333,11 +1277,11 @@
 }
 
 void ServiceWorkerContextWrapper::
-    ClearRegistrationUserDataByKeyPrefixesOnCoreThread(
+    ClearRegistrationUserDataByKeyPrefixesOnUIThread(
         int64_t registration_id,
         const std::vector<std::string>& key_prefixes,
         StatusCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort);
     return;
@@ -1349,11 +1293,11 @@
 void ServiceWorkerContextWrapper::GetUserDataForAllRegistrations(
     const std::string& key,
     GetUserDataForAllRegistrationsCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
           &ServiceWorkerContextWrapper::
-              GetUserDataForAllRegistrationsOnCoreThread,
+              GetUserDataForAllRegistrationsOnUIThread,
           this, key,
           base::BindOnce(
               [](GetUserDataForAllRegistrationsCallback callback,
@@ -1367,10 +1311,10 @@
               std::move(callback), base::ThreadTaskRunnerHandle::Get())));
 }
 
-void ServiceWorkerContextWrapper::GetUserDataForAllRegistrationsOnCoreThread(
+void ServiceWorkerContextWrapper::GetUserDataForAllRegistrationsOnUIThread(
     const std::string& key,
     GetUserDataForAllRegistrationsCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(std::vector<std::pair<int64_t, std::string>>(),
                             blink::ServiceWorkerStatusCode::kErrorAbort);
@@ -1383,11 +1327,11 @@
 void ServiceWorkerContextWrapper::GetUserDataForAllRegistrationsByKeyPrefix(
     const std::string& key_prefix,
     GetUserDataForAllRegistrationsCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
           &ServiceWorkerContextWrapper::
-              GetUserDataForAllRegistrationsByKeyPrefixOnCoreThread,
+              GetUserDataForAllRegistrationsByKeyPrefixOnUIThread,
           this, key_prefix,
           base::BindOnce(
               [](GetUserDataForAllRegistrationsCallback callback,
@@ -1402,10 +1346,10 @@
 }
 
 void ServiceWorkerContextWrapper::
-    GetUserDataForAllRegistrationsByKeyPrefixOnCoreThread(
+    GetUserDataForAllRegistrationsByKeyPrefixOnUIThread(
         const std::string& key_prefix,
         GetUserDataForAllRegistrationsCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(std::vector<std::pair<int64_t, std::string>>(),
                             blink::ServiceWorkerStatusCode::kErrorAbort);
@@ -1418,11 +1362,11 @@
 void ServiceWorkerContextWrapper::ClearUserDataForAllRegistrationsByKeyPrefix(
     const std::string& key_prefix,
     StatusCallback callback) {
-  RunOrPostTaskOnCoreThread(
-      FROM_HERE,
+  RunOrPostTaskOnThread(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
           &ServiceWorkerContextWrapper::
-              ClearUserDataForAllRegistrationsByKeyPrefixOnCoreThread,
+              ClearUserDataForAllRegistrationsByKeyPrefixOnUIThread,
           this, key_prefix,
           base::BindOnce(
               [](StatusCallback callback,
@@ -1435,10 +1379,10 @@
 }
 
 void ServiceWorkerContextWrapper::
-    ClearUserDataForAllRegistrationsByKeyPrefixOnCoreThread(
+    ClearUserDataForAllRegistrationsByKeyPrefixOnUIThread(
         const std::string& key_prefix,
         StatusCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort);
     return;
@@ -1450,47 +1394,38 @@
 void ServiceWorkerContextWrapper::StartActiveServiceWorker(
     const GURL& scope,
     StatusCallback callback) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(&ServiceWorkerContextWrapper::StartActiveServiceWorker,
-                       this, scope, std::move(callback)));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
-    GetUIThreadTaskRunner({})->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback),
                                   blink::ServiceWorkerStatusCode::kErrorAbort));
     return;
   }
   context_core_->registry()->FindRegistrationForScope(
       net::SimplifyUrlForRequest(scope),
-      base::BindOnce(&StartActiveWorkerOnCoreThread, std::move(callback)));
+      base::BindOnce(&DidFindRegistrationForStartActiveWorker,
+                     std::move(callback)));
 }
 
 void ServiceWorkerContextWrapper::SkipWaitingWorker(const GURL& scope) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(&ServiceWorkerContextWrapper::SkipWaitingWorker, this,
-                       scope));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_)
     return;
   context_core_->registry()->FindRegistrationForScope(
       net::SimplifyUrlForRequest(scope),
-      base::BindOnce(&SkipWaitingWorkerOnCoreThread));
+      base::BindOnce([](blink::ServiceWorkerStatusCode status,
+                        scoped_refptr<ServiceWorkerRegistration> registration) {
+        if (status != blink::ServiceWorkerStatusCode::kOk ||
+            !registration->waiting_version())
+          return;
+
+        registration->waiting_version()->set_skip_waiting(true);
+        registration->ActivateWaitingVersionWhenReady();
+      }));
 }
 
 void ServiceWorkerContextWrapper::UpdateRegistration(const GURL& scope) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(&ServiceWorkerContextWrapper::UpdateRegistration, this,
-                       scope));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_)
     return;
   context_core_->registry()->FindRegistrationForScope(
@@ -1501,13 +1436,7 @@
 
 void ServiceWorkerContextWrapper::SetForceUpdateOnPageLoad(
     bool force_update_on_page_load) {
-  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
-    base::PostTask(
-        FROM_HERE, {GetCoreThreadId()},
-        base::BindOnce(&ServiceWorkerContextWrapper::SetForceUpdateOnPageLoad,
-                       this, force_update_on_page_load));
-    return;
-  }
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_)
     return;
   context_core_->set_force_update_on_page_load(force_update_on_page_load);
@@ -1537,42 +1466,11 @@
     core_observer_list_->RemoveObserver(identifiability_metrics_.get());
 }
 
-void ServiceWorkerContextWrapper::InitOnCoreThread(
-    const base::FilePath& user_data_directory,
-    scoped_refptr<base::SequencedTaskRunner> database_task_runner,
-    storage::QuotaManagerProxy* quota_manager_proxy,
-    storage::SpecialStoragePolicy* special_storage_policy,
-    ChromeBlobStorageContext* blob_context,
-    URLLoaderFactoryGetter* loader_factory_getter,
-    std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
-        non_network_pending_loader_factory_bundle_for_update_check) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-  DCHECK(!context_core_);
-
-  if (quota_manager_proxy) {
-    quota_manager_proxy->RegisterClient(
-        base::MakeRefCounted<ServiceWorkerQuotaClient>(this),
-        storage::QuotaClientType::kServiceWorker,
-        {blink::mojom::StorageType::kTemporary});
-  }
-
-  context_core_ = std::make_unique<ServiceWorkerContextCore>(
-      user_data_directory, std::move(database_task_runner), quota_manager_proxy,
-      special_storage_policy, loader_factory_getter,
-      std::move(non_network_pending_loader_factory_bundle_for_update_check),
-      core_observer_list_.get(), this);
-
-  if (storage_partition_) {
-    context()->registry()->GetRegisteredOrigins(base::BindOnce(
-        &ServiceWorkerContextWrapper::DidGetRegisteredOrigins, this));
-  }
-}
-
 void ServiceWorkerContextWrapper::FindRegistrationForScopeImpl(
     const GURL& scope,
     bool include_installing_version,
     FindRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!context_core_) {
     std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
                             nullptr);
@@ -1585,17 +1483,12 @@
           include_installing_version, std::move(callback)));
 }
 
-void ServiceWorkerContextWrapper::ShutdownOnCoreThread() {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-  context_core_.reset();
-}
-
 void ServiceWorkerContextWrapper::DidFindRegistrationForFindImpl(
     bool include_installing_version,
     FindRegistrationCallback callback,
     blink::ServiceWorkerStatusCode status,
     scoped_refptr<ServiceWorkerRegistration> registration) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (status != blink::ServiceWorkerStatusCode::kOk) {
     std::move(callback).Run(status, nullptr);
     return;
@@ -1635,7 +1528,7 @@
 void ServiceWorkerContextWrapper::OnStatusChangedForFindReadyRegistration(
     FindRegistrationCallback callback,
     scoped_refptr<ServiceWorkerRegistration> registration) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   scoped_refptr<ServiceWorkerVersion> active_version =
       registration->active_version();
   if (!active_version ||
@@ -1649,7 +1542,7 @@
 
 void ServiceWorkerContextWrapper::DidDeleteAndStartOver(
     blink::ServiceWorkerStatusCode status) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (status != blink::ServiceWorkerStatusCode::kOk) {
     context_core_.reset();
     return;
@@ -1660,12 +1553,10 @@
 }
 
 void ServiceWorkerContextWrapper::DidGetAllRegistrationsForGetAllOrigins(
-    base::TimeTicks start_time,
     GetUsageInfoCallback callback,
-    scoped_refptr<base::TaskRunner> callback_runner,
     blink::ServiceWorkerStatusCode status,
     const std::vector<ServiceWorkerRegistrationInfo>& registrations) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   std::vector<StorageUsageInfo> usage_infos;
 
   std::map<GURL, StorageUsageInfo> origins;
@@ -1687,36 +1578,14 @@
     usage_infos.push_back(origin_info_pair.second);
   }
 
-  ServiceWorkerMetrics::RecordGetAllOriginsInfoTime(base::TimeTicks::Now() -
-                                                    start_time);
-
-  callback_runner->PostTask(FROM_HERE,
-                            base::BindOnce(std::move(callback), usage_infos));
+  std::move(callback).Run(usage_infos);
 }
 
-void ServiceWorkerContextWrapper::DidCheckHasServiceWorker(
-    CheckHasServiceWorkerCallback callback,
-    ServiceWorkerCapability capability) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), capability));
-}
-
-void ServiceWorkerContextWrapper::DidCheckOfflineCapability(
-    CheckOfflineCapabilityCallback callback,
-    OfflineCapability capability,
-    int64_t registration_id) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(std::move(callback), capability, registration_id));
-}
 
 void ServiceWorkerContextWrapper::DidFindRegistrationForUpdate(
     blink::ServiceWorkerStatusCode status,
     scoped_refptr<ServiceWorkerRegistration> registration) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (status != blink::ServiceWorkerStatusCode::kOk)
     return;
@@ -1732,54 +1601,13 @@
                                      true /* force_bypass_cache */);
 }
 
-void ServiceWorkerContextWrapper::CountExternalRequests(
-    const url::Origin& origin,
-    CountExternalRequestsCallback callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-
-  std::vector<ServiceWorkerVersionInfo> live_version_info =
-      GetAllLiveVersionInfo();
-  size_t pending_external_request_count = 0;
-  for (const ServiceWorkerVersionInfo& info : live_version_info) {
-    ServiceWorkerVersion* version = GetLiveVersion(info.version_id);
-    if (version && version->origin() == origin) {
-      pending_external_request_count =
-          version->GetExternalRequestCountForTest();
-      break;
-    }
-  }
-
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(std::move(callback), pending_external_request_count));
-}
-
-void ServiceWorkerContextWrapper::
-    StartServiceWorkerForNavigationHintOnCoreThread(
-        const GURL& document_url,
-        StartServiceWorkerForNavigationHintCallback callback) {
-  TRACE_EVENT1("ServiceWorker",
-               "StartServiceWorkerForNavigationHintOnCoreThread",
-               "document_url", document_url.spec());
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-  if (!context_core_) {
-    std::move(callback).Run(StartServiceWorkerForNavigationHintResult::FAILED);
-    return;
-  }
-  context_core_->registry()->FindRegistrationForClientUrl(
-      net::SimplifyUrlForRequest(document_url),
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::DidFindRegistrationForNavigationHint,
-          this, std::move(callback)));
-}
-
 void ServiceWorkerContextWrapper::DidFindRegistrationForNavigationHint(
     StartServiceWorkerForNavigationHintCallback callback,
     blink::ServiceWorkerStatusCode status,
     scoped_refptr<ServiceWorkerRegistration> registration) {
   TRACE_EVENT1("ServiceWorker", "DidFindRegistrationForNavigationHint",
                "status", blink::ServiceWorkerStatusToString(status));
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!registration) {
     DCHECK_NE(status, blink::ServiceWorkerStatusCode::kOk);
     std::move(callback).Run(StartServiceWorkerForNavigationHintResult::
@@ -1817,46 +1645,16 @@
     blink::ServiceWorkerStatusCode code) {
   TRACE_EVENT2("ServiceWorker", "DidStartServiceWorkerForNavigationHint", "url",
                scope.spec(), "code", blink::ServiceWorkerStatusToString(code));
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
   std::move(callback).Run(
       code == blink::ServiceWorkerStatusCode::kOk
           ? StartServiceWorkerForNavigationHintResult::STARTED
           : StartServiceWorkerForNavigationHintResult::FAILED);
 }
 
-void ServiceWorkerContextWrapper::
-    RecordStartServiceWorkerForNavigationHintResult(
-        StartServiceWorkerForNavigationHintCallback callback,
-        StartServiceWorkerForNavigationHintResult result) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-  ServiceWorkerMetrics::RecordStartServiceWorkerForNavigationHintResult(result);
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), result));
-}
-
-void ServiceWorkerContextWrapper::StopAllServiceWorkersOnCoreThread(
-    base::OnceClosure callback,
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-  if (!context_core_.get()) {
-    task_runner_for_callback->PostTask(FROM_HERE, std::move(callback));
-    return;
-  }
-  std::vector<ServiceWorkerVersionInfo> live_versions = GetAllLiveVersionInfo();
-  base::RepeatingClosure barrier = base::BarrierClosure(
-      live_versions.size(),
-      base::BindOnce(
-          base::IgnoreResult(&base::SingleThreadTaskRunner::PostTask),
-          std::move(task_runner_for_callback), FROM_HERE, std::move(callback)));
-  for (const ServiceWorkerVersionInfo& info : live_versions) {
-    ServiceWorkerVersion* version = GetLiveVersion(info.version_id);
-    DCHECK(version);
-    version->StopWorker(base::BindOnce(barrier));
-  }
-}
-
 ServiceWorkerContextCore* ServiceWorkerContextWrapper::context() {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   return context_core_.get();
 }
 
@@ -1884,33 +1682,12 @@
   return factory_bundle;
 }
 
-void ServiceWorkerContextWrapper::GetLoaderFactoryForUpdateCheck(
-    const GURL& scope,
-    base::OnceCallback<void(scoped_refptr<network::SharedURLLoaderFactory>)>
-        callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-
-  RunOrPostTaskOnThread(
-      FROM_HERE, BrowserThread::UI,
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::SetUpLoaderFactoryForUpdateCheckOnUI,
-          this, scope, std::move(callback)));
-}
-
-void ServiceWorkerContextWrapper::SetUpLoaderFactoryForUpdateCheckOnUI(
-    const GURL& scope,
-    base::OnceCallback<void(scoped_refptr<network::SharedURLLoaderFactory>)>
-        callback) {
+scoped_refptr<network::SharedURLLoaderFactory>
+ServiceWorkerContextWrapper::GetLoaderFactoryForUpdateCheck(const GURL& scope) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   if (!storage_partition()) {
-    RunOrPostTaskOnThread(
-        FROM_HERE, GetCoreThreadId(),
-        base::BindOnce(
-            &ServiceWorkerContextWrapper::DidSetUpLoaderFactoryForUpdateCheck,
-            this, mojo::NullRemote(), mojo::NullReceiver(),
-            /* bypass_redirect_checks=*/false, std::move(callback)));
-    return;
+    return nullptr;
   }
 
   mojo::PendingRemote<network::mojom::URLLoaderFactory> remote;
@@ -1938,28 +1715,6 @@
         storage_partition());
   }
 
-  RunOrPostTaskOnThread(
-      FROM_HERE, GetCoreThreadId(),
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::DidSetUpLoaderFactoryForUpdateCheck,
-          this, std::move(remote), std::move(pending_receiver),
-          bypass_redirect_checks, std::move(callback)));
-}
-
-void ServiceWorkerContextWrapper::DidSetUpLoaderFactoryForUpdateCheck(
-    mojo::PendingRemote<network::mojom::URLLoaderFactory> remote,
-    mojo::PendingReceiver<network::mojom::URLLoaderFactory> pending_receiver,
-    bool bypass_redirect_checks,
-    base::OnceCallback<void(scoped_refptr<network::SharedURLLoaderFactory>)>
-        callback) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-
-  // Return nullptr if preparation on the UI thread failed.
-  if (!remote) {
-    std::move(callback).Run(nullptr);
-    return;
-  }
-
   // Set up a Mojo connection to the network loader factory if it's not been
   // created yet.
   if (pending_receiver) {
@@ -1980,10 +1735,8 @@
   static_cast<blink::PendingURLLoaderFactoryBundle*>(
       loader_factory_bundle_info.get())
       ->set_bypass_redirect_checks(bypass_redirect_checks);
-  scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
-      network::SharedURLLoaderFactory::Create(
-          std::move(loader_factory_bundle_info));
-  std::move(callback).Run(std::move(loader_factory));
+  return network::SharedURLLoaderFactory::Create(
+      std::move(loader_factory_bundle_info));
 }
 
 void ServiceWorkerContextWrapper::WaitForRegistrationsInitializedForTest() {
@@ -1997,17 +1750,6 @@
 
 void ServiceWorkerContextWrapper::DidGetRegisteredOrigins(
     const std::vector<url::Origin>& origins) {
-  DCHECK_CURRENTLY_ON(GetCoreThreadId());
-  GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &ServiceWorkerContextWrapper::InitializeRegisteredOriginsOnUI, this,
-          origins));
-}
-
-void ServiceWorkerContextWrapper::InitializeRegisteredOriginsOnUI(
-    const std::vector<url::Origin>& origins) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   registered_origins_.insert(origins.begin(), origins.end());
   registrations_initialized_ = true;
   if (on_registrations_initialized_)
diff --git a/content/browser/service_worker/service_worker_context_wrapper.h b/content/browser/service_worker/service_worker_context_wrapper.h
index 38f56fa..26f1fa3 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.h
+++ b/content/browser/service_worker/service_worker_context_wrapper.h
@@ -52,9 +52,15 @@
 
 // A refcounted wrapper class for ServiceWorkerContextCore. Higher level content
 // lib classes keep references to this class on multiple threads. The inner core
-// instance is strictly single threaded (this is called the "core thread") and
-// is not refcounted. The core object is what is used internally by service
-// worker classes.
+// instance is strictly single threaded (on the UI thread) and is not
+// refcounted. The core object is what is used internally by service worker
+// classes.
+//
+// All the methods are expected to be called on the UI thread.
+// Some of the methods are exceptionally allowed to be called on any threads,
+// but it's now discouraged.
+// TODO(https://crbug.com/1161153): Disallow methods to be called on any
+// threads.
 class CONTENT_EXPORT ServiceWorkerContextWrapper
     : public ServiceWorkerContext,
       public ServiceWorkerContextCoreObserver,
@@ -82,9 +88,6 @@
 
   explicit ServiceWorkerContextWrapper(BrowserContext* browser_context);
 
-  static void RunOrPostTaskOnCoreThread(const base::Location& location,
-                                        base::OnceClosure task);
-
   // Init and Shutdown are for use on the UI thread when the profile,
   // storagepartition is being setup and torn down.
   void Init(const base::FilePath& user_data_directory,
@@ -95,8 +98,7 @@
   void Shutdown();
 
   // Deletes all files on disk and restarts the system asynchronously. This
-  // leaves the system in a disabled state until it's done. This should be
-  // called on the core thread.
+  // leaves the system in a disabled state until it's done.
   void DeleteAndStartOver();
 
   // The StoragePartition should only be used on the UI thread.
@@ -105,10 +107,8 @@
 
   void set_storage_partition(StoragePartitionImpl* storage_partition);
 
-  // UI thread.
   BrowserContext* browser_context();
 
-  // The process manager can be used on either UI or IO.
   ServiceWorkerProcessManager* process_manager() {
     return process_manager_.get();
   }
@@ -162,9 +162,7 @@
   ServiceWorkerExternalRequestResult FinishedExternalRequest(
       int64_t service_worker_version_id,
       const std::string& request_uuid) override;
-  void CountExternalRequestsForTest(
-      const url::Origin& origin,
-      CountExternalRequestsCallback callback) override;
+  size_t CountExternalRequestsForTest(const url::Origin& origin) override;
   bool MaybeHasRegistrationForOrigin(const url::Origin& origin) override;
   void GetInstalledRegistrationOrigins(
       base::Optional<std::string> host_filter,
@@ -195,18 +193,15 @@
   const base::flat_map<int64_t, ServiceWorkerRunningInfo>&
   GetRunningServiceWorkerInfos() override;
 
-  // These methods must only be called from the core thread.
   ServiceWorkerRegistration* GetLiveRegistration(int64_t registration_id);
   ServiceWorkerVersion* GetLiveVersion(int64_t version_id);
   std::vector<ServiceWorkerRegistrationInfo> GetAllLiveRegistrationInfo();
   std::vector<ServiceWorkerVersionInfo> GetAllLiveVersionInfo();
 
-  // May be called from any thread, and the callback is called on that thread.
   void HasMainFrameWindowClient(const GURL& origin,
                                 BoolCallback callback) const;
 
-  // Returns all frame routing ids for the given |origin|. Must be called on the
-  // core thread.
+  // Returns all frame routing ids for the given |origin|.
   std::unique_ptr<std::vector<GlobalFrameRoutingId>>
   GetWindowClientFrameRoutingIds(const GURL& origin) const;
 
@@ -220,7 +215,6 @@
   //    version, activates the waiting version and runs |callback| when it is
   //    activated.
   //
-  // Must be called on the core thread, and |callback| is called on that thread.
   // There is no guarantee for whether the callback is called synchronously or
   // asynchronously.
   void FindReadyRegistrationForClientUrl(const GURL& client_url,
@@ -236,7 +230,6 @@
   //    version, activates the waiting version and runs |callback| when it is
   //    activated.
   //
-  // Must be called on the core thread, and |callback| is called on that thread.
   // There is no guarantee for whether the callback is called synchronously or
   // asynchronously.
   void FindReadyRegistrationForScope(const GURL& scope,
@@ -258,9 +251,8 @@
   //    version, activates the waiting version and runs |callback| when it is
   //    activated.
   //
-  // Must be called on the core thread, and the callback is called on that
-  // thread. There is no guarantee about whether the callback is called
-  // asynchronously or synchronously.
+  // There is no guarantee about whether the callback is called asynchronously
+  // or synchronously.
   void FindReadyRegistrationForId(int64_t registration_id,
                                   const url::Origin& origin,
                                   FindRegistrationCallback callback);
@@ -279,15 +271,17 @@
   //    version, activates the waiting version and runs |callback| when it is
   //    activated.
   //
-  // Must be called on the core thread, and the callback is called on that
-  // thread. There is no guarantee about whether the callback is called
-  // synchronously or asynchronously.
+  // There is no guarantee about whether the callback is called synchronously or
+  // asynchronously.
   void FindReadyRegistrationForIdOnly(int64_t registration_id,
                                       FindRegistrationCallback callback);
 
+  void GetAllRegistrations(GetRegistrationsInfosCallback callback);
+
   // These can be called from any thread, and the callback is called on that
   // thread.
-  void GetAllRegistrations(GetRegistrationsInfosCallback callback);
+  // TODO(https://crbug.com/1161153): Make these methods called only on the UI
+  // thread.
   void GetRegistrationUserData(int64_t registration_id,
                                const std::vector<std::string>& keys,
                                GetUserDataCallback callback);
@@ -328,24 +322,19 @@
 
   // Returns a list of ServiceWorkerRegistration for |origin|. The list includes
   // stored registrations and installing (not stored yet) registrations.
-  // Must be called on the core thread, and the callback is called on that
-  // thread. This restriction is because the callback gets pointers to live
-  // registrations, which live on the core thread.
   void GetRegistrationsForOrigin(const url::Origin& origin,
                                  GetRegistrationsCallback callback);
 
-  // This function can be called from any thread, but the callback will always
-  // be called on the UI thread.
   // Fails with kErrorNotFound if there is no active registration for the given
   // scope. It means that there is no registration at all or that the
   // registration doesn't have an active version yet (which is the case for
   // installing service workers).
   void StartActiveServiceWorker(const GURL& scope, StatusCallback callback);
 
-  // These methods can be called from any thread.
   void SkipWaitingWorker(const GURL& scope);
   void UpdateRegistration(const GURL& scope);
   void SetForceUpdateOnPageLoad(bool force_update_on_page_load);
+
   // Different from AddObserver/RemoveObserver(ServiceWorkerContextObserver*).
   // But we must keep the same name, or else base::ScopedObserver breaks.
   void AddObserver(ServiceWorkerContextCoreObserver* observer);
@@ -353,23 +342,18 @@
 
   bool is_incognito() const { return is_incognito_; }
 
-  // The core context is only for use on the IO thread.
   // Can be null before/during init, during/after shutdown, and after
   // DeleteAndStartOver fails.
   ServiceWorkerContextCore* context();
 
-  // This method waits for service worker registrations to be initialized on the
-  // UI thread, and depends on |on_registrations_initialized_| and
-  // |registrations_initialized_| which are called in
-  // InitializeRegisteredOriginsOnUI().
+  // This method waits for service worker registrations to be initialized, and
+  // depends on |on_registrations_initialized_| and |registrations_initialized_|
+  // which are called in InitializeRegisteredOrigins().
   void WaitForRegistrationsInitializedForTest();
 
-  // This must be called on the core thread, and the |callback| also runs on
-  // the core thread which can be called with nullptr on failure.
-  void GetLoaderFactoryForUpdateCheck(
-      const GURL& scope,
-      base::OnceCallback<void(scoped_refptr<network::SharedURLLoaderFactory>)>
-          callback);
+  // Returns nullptr on failure.
+  scoped_refptr<network::SharedURLLoaderFactory> GetLoaderFactoryForUpdateCheck(
+      const GURL& scope);
 
  private:
   friend class BackgroundSyncManagerTest;
@@ -386,16 +370,16 @@
 
   ~ServiceWorkerContextWrapper() override;
 
-  void InitOnCoreThread(
+  // Init() with a custom database task runner and BrowserContext. Explicitly
+  // called from EmbeddedWorkerTestHelper.
+  void InitInternal(
       const base::FilePath& user_data_directory,
-      scoped_refptr<base::SequencedTaskRunner> database_task_runner,
       storage::QuotaManagerProxy* quota_manager_proxy,
       storage::SpecialStoragePolicy* special_storage_policy,
       ChromeBlobStorageContext* blob_context,
-      URLLoaderFactoryGetter* url_loader_factory_getter,
-      std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
-          non_network_pending_loader_factory_bundle_for_update_check);
-  void ShutdownOnCoreThread();
+      URLLoaderFactoryGetter* loader_factory_getter,
+      scoped_refptr<base::SequencedTaskRunner> database_task_runner,
+      BrowserContext* browser_context);
 
   // If |include_installing_version| is true, |callback| is called if there is
   // an installing version with no waiting or active version.
@@ -415,25 +399,14 @@
   void DidDeleteAndStartOver(blink::ServiceWorkerStatusCode status);
 
   void DidGetAllRegistrationsForGetAllOrigins(
-      base::TimeTicks start_time,
       GetUsageInfoCallback callback,
-      scoped_refptr<base::TaskRunner> callback_runner,
       blink::ServiceWorkerStatusCode status,
       const std::vector<ServiceWorkerRegistrationInfo>& registrations);
 
-  void DidCheckHasServiceWorker(CheckHasServiceWorkerCallback callback,
-                                content::ServiceWorkerCapability status);
-  void DidCheckOfflineCapability(CheckOfflineCapabilityCallback callback,
-                                 content::OfflineCapability status,
-                                 int64_t registration_id);
-
   void DidFindRegistrationForUpdate(
       blink::ServiceWorkerStatusCode status,
       scoped_refptr<content::ServiceWorkerRegistration> registration);
 
-  void CountExternalRequests(const url::Origin& origin,
-                             CountExternalRequestsCallback callback);
-
   void DidFindRegistrationForNavigationHint(
       StartServiceWorkerForNavigationHintCallback callback,
       blink::ServiceWorkerStatusCode status,
@@ -452,7 +425,6 @@
       blink::TransferableMessage message,
       const GURL& source_origin,
       ResultCallback result_callback,
-      scoped_refptr<base::TaskRunner> callback_runner,
       blink::ServiceWorkerStatusCode service_worker_status,
       scoped_refptr<ServiceWorkerRegistration> registration);
 
@@ -461,7 +433,6 @@
       const GURL& source_origin,
       scoped_refptr<ServiceWorkerRegistration> registration,
       ServiceWorkerContext::ResultCallback result_callback,
-      scoped_refptr<base::TaskRunner> callback_runner,
       blink::ServiceWorkerStatusCode service_worker_status);
 
   // Called when ServiceWorkerImportedScriptUpdateCheck is enabled.
@@ -485,11 +456,9 @@
       base::OnceCallback<void(scoped_refptr<network::SharedURLLoaderFactory>)>
           callback);
 
-  // These methods are used as a callback for GetRegisteredOrigins when
-  // initialising on the core thread, so registered origins can be tracked
-  // on the UI thread as well.
+  // This is used as a callback of GetRegisteredOrigins when initialising to
+  // store a list of origins that have registered service workers.
   void DidGetRegisteredOrigins(const std::vector<url::Origin>& origins);
-  void InitializeRegisteredOriginsOnUI(const std::vector<url::Origin>& origins);
 
   static void DidGetRegisteredOriginsForGetInstalledRegistrationOrigins(
       base::Optional<std::string> host_filter,
@@ -497,71 +466,55 @@
       scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_callback,
       const std::vector<url::Origin>& origins);
 
-  // Temporary for crbug.com/824858.
-  void GetAllOriginsInfoOnCoreThread(
-      GetUsageInfoCallback callback,
-      scoped_refptr<base::TaskRunner> callback_runner);
-  void PerformStorageCleanupOnCoreThread(
+  // Temporary for https://crbug.com/1161153.
+  void PerformStorageCleanupOnUIThread(
       base::OnceClosure callback,
       scoped_refptr<base::TaskRunner> callback_runner);
-  void StartServiceWorkerAndDispatchMessageOnCoreThread(
+  void StartServiceWorkerAndDispatchMessageOnUIThread(
       const GURL& scope,
       blink::TransferableMessage message,
-      ResultCallback result_callback,
-      scoped_refptr<base::TaskRunner> callback_runner);
-  void DeleteForOriginOnCoreThread(
+      ResultCallback result_callback);
+  void DeleteForOriginOnUIThread(
       const url::Origin& origin,
       ResultCallback callback,
       scoped_refptr<base::TaskRunner> callback_runner);
-  void HasMainFrameWindowClientOnCoreThread(
-      const GURL& origin,
-      BoolCallback callback,
-      scoped_refptr<base::TaskRunner> callback_runner) const;
-  void GetAllRegistrationsOnCoreThread(GetRegistrationsInfosCallback callback);
-  void GetRegistrationUserDataOnCoreThread(int64_t registration_id,
-                                           const std::vector<std::string>& keys,
-                                           GetUserDataCallback callback);
-  void GetRegistrationUserDataByKeyPrefixOnCoreThread(
+  void GetRegistrationUserDataOnUIThread(int64_t registration_id,
+                                         const std::vector<std::string>& keys,
+                                         GetUserDataCallback callback);
+  void GetRegistrationUserDataByKeyPrefixOnUIThread(
       int64_t registration_id,
       const std::string& key_prefix,
       GetUserDataCallback callback);
-  void GetRegistrationUserKeysAndDataByKeyPrefixOnCoreThread(
+  void GetRegistrationUserKeysAndDataByKeyPrefixOnUIThread(
       int64_t registration_id,
       const std::string& key_prefix,
       GetUserKeysAndDataCallback callback);
-  void StoreRegistrationUserDataOnCoreThread(
+  void StoreRegistrationUserDataOnUIThread(
       int64_t registration_id,
       const url::Origin& origin,
       const std::vector<std::pair<std::string, std::string>>& key_value_pairs,
       StatusCallback callback);
-  void ClearRegistrationUserDataOnCoreThread(
-      int64_t registration_id,
-      const std::vector<std::string>& keys,
-      StatusCallback callback);
-  void ClearRegistrationUserDataByKeyPrefixesOnCoreThread(
+  void ClearRegistrationUserDataOnUIThread(int64_t registration_id,
+                                           const std::vector<std::string>& keys,
+                                           StatusCallback callback);
+  void ClearRegistrationUserDataByKeyPrefixesOnUIThread(
       int64_t registration_id,
       const std::vector<std::string>& key_prefixes,
       StatusCallback callback);
-  void GetUserDataForAllRegistrationsOnCoreThread(
+  void GetUserDataForAllRegistrationsOnUIThread(
       const std::string& key,
       GetUserDataForAllRegistrationsCallback callback);
-  void GetUserDataForAllRegistrationsByKeyPrefixOnCoreThread(
+  void GetUserDataForAllRegistrationsByKeyPrefixOnUIThread(
       const std::string& key_prefix,
       GetUserDataForAllRegistrationsCallback callback);
-  void ClearUserDataForAllRegistrationsByKeyPrefixOnCoreThread(
+  void ClearUserDataForAllRegistrationsByKeyPrefixOnUIThread(
       const std::string& key_prefix,
       StatusCallback callback);
-  void StartServiceWorkerForNavigationHintOnCoreThread(
-      const GURL& document_url,
-      StartServiceWorkerForNavigationHintCallback callback);
-  void StopAllServiceWorkersOnCoreThread(
-      base::OnceClosure callback,
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_callback);
-  void GetInstalledRegistrationOriginsOnCoreThread(
+  void GetInstalledRegistrationOriginsOnUIThread(
       base::Optional<std::string> host_filter,
       GetInstalledRegistrationOriginsCallback callback,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_callback);
-  void GetStorageUsageForOriginOnCoreThread(
+  void GetStorageUsageForOriginOnUIThread(
       const url::Origin& origin,
       GetStorageUsageForOriginCallback callback);
 
@@ -577,7 +530,6 @@
       observer_list_;
 
   const std::unique_ptr<ServiceWorkerProcessManager> process_manager_;
-  // Cleared in ShutdownOnCoreThread():
   std::unique_ptr<ServiceWorkerContextCore> context_core_;
 
   // Initialized in Init(); true if the user data directory is empty.
diff --git a/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc b/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
index fe85f03..b6763487 100644
--- a/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
+++ b/content/browser/service_worker/service_worker_main_resource_loader_interceptor.cc
@@ -206,6 +206,9 @@
 base::Optional<SubresourceLoaderParams>
 ServiceWorkerMainResourceLoaderInterceptor::
     MaybeCreateSubresourceLoaderParams() {
+  if (!handle_) {
+    return base::nullopt;
+  }
   base::WeakPtr<ServiceWorkerContainerHost> container_host =
       handle_->container_host();
 
diff --git a/content/browser/service_worker/service_worker_metrics.cc b/content/browser/service_worker/service_worker_metrics.cc
index bc69de06..9f77049 100644
--- a/content/browser/service_worker/service_worker_metrics.cc
+++ b/content/browser/service_worker/service_worker_metrics.cc
@@ -569,8 +569,4 @@
   }
 }
 
-void ServiceWorkerMetrics::RecordGetAllOriginsInfoTime(base::TimeDelta time) {
-  base::UmaHistogramMediumTimes("ServiceWorker.GetAllOriginsInfoTime", time);
-}
-
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_metrics.h b/content/browser/service_worker/service_worker_metrics.h
index 5df034e..a81d49a 100644
--- a/content/browser/service_worker/service_worker_metrics.h
+++ b/content/browser/service_worker/service_worker_metrics.h
@@ -242,8 +242,6 @@
       blink::ServiceWorkerStatusCode status,
       base::TimeDelta duration);
 
-  static void RecordGetAllOriginsInfoTime(base::TimeDelta time);
-
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(ServiceWorkerMetrics);
 };
diff --git a/content/browser/service_worker/service_worker_register_job.cc b/content/browser/service_worker/service_worker_register_job.cc
index b25c66d8..00048ba6 100644
--- a/content/browser/service_worker/service_worker_register_job.cc
+++ b/content/browser/service_worker/service_worker_register_job.cc
@@ -336,39 +336,6 @@
   return !skip_script_comparison_;
 }
 
-void ServiceWorkerRegisterJob::TriggerUpdateCheck(
-    scoped_refptr<network::SharedURLLoaderFactory> loader_factory) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-
-  if (!loader_factory) {
-    // We can't continue with update checking appropriately without
-    // |loader_factory|. Null |loader_factory| means that the storage partition
-    // was not available probably because it's shutting down.
-    // This terminates the current job (|this|).
-    Complete(blink::ServiceWorkerStatusCode::kErrorAbort,
-             ServiceWorkerConsts::kShutdownErrorMessage);
-    return;
-  }
-
-  ServiceWorkerVersion* version_to_update = registration()->GetNewestVersion();
-  base::TimeDelta time_since_last_check =
-      base::Time::Now() - registration()->last_update_check();
-  std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> resources;
-  version_to_update->script_cache_map()->GetResources(&resources);
-  int64_t script_resource_id =
-      version_to_update->script_cache_map()->LookupResourceId(script_url_);
-  DCHECK_NE(script_resource_id, blink::mojom::kInvalidServiceWorkerResourceId);
-
-  update_checker_ = std::make_unique<ServiceWorkerUpdateChecker>(
-      std::move(resources), script_url_, script_resource_id, version_to_update,
-      std::move(loader_factory), force_bypass_cache_,
-      registration()->update_via_cache(), time_since_last_check, context_,
-      outside_fetch_client_settings_object_.Clone());
-  update_checker_->Start(
-      base::BindOnce(&ServiceWorkerRegisterJob::OnUpdateCheckFinished,
-                     weak_factory_.GetWeakPtr()));
-}
-
 void ServiceWorkerRegisterJob::OnUpdateCheckFinished(
     ServiceWorkerSingleScriptUpdateChecker::Result result,
     std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::FailureInfo>
@@ -518,10 +485,35 @@
     return;
   }
 
-  // This will start the update check after loader factory is retrieved.
-  context_->wrapper()->GetLoaderFactoryForUpdateCheck(
-      scope_, base::BindOnce(&ServiceWorkerRegisterJob::TriggerUpdateCheck,
-                             weak_factory_.GetWeakPtr()));
+  scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
+      context_->wrapper()->GetLoaderFactoryForUpdateCheck(scope_);
+  if (!loader_factory) {
+    // We can't continue with update checking appropriately without
+    // |loader_factory|. Null |loader_factory| means that the storage partition
+    // was not available probably because it's shutting down.
+    // This terminates the current job (|this|).
+    Complete(blink::ServiceWorkerStatusCode::kErrorAbort,
+             ServiceWorkerConsts::kShutdownErrorMessage);
+    return;
+  }
+
+  ServiceWorkerVersion* version_to_update = registration()->GetNewestVersion();
+  base::TimeDelta time_since_last_check =
+      base::Time::Now() - registration()->last_update_check();
+  std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> resources;
+  version_to_update->script_cache_map()->GetResources(&resources);
+  int64_t script_resource_id =
+      version_to_update->script_cache_map()->LookupResourceId(script_url_);
+  DCHECK_NE(script_resource_id, blink::mojom::kInvalidServiceWorkerResourceId);
+
+  update_checker_ = std::make_unique<ServiceWorkerUpdateChecker>(
+      std::move(resources), script_url_, script_resource_id, version_to_update,
+      std::move(loader_factory), force_bypass_cache_,
+      registration()->update_via_cache(), time_since_last_check, context_,
+      outside_fetch_client_settings_object_.Clone());
+  update_checker_->Start(
+      base::BindOnce(&ServiceWorkerRegisterJob::OnUpdateCheckFinished,
+                     weak_factory_.GetWeakPtr()));
 }
 
 void ServiceWorkerRegisterJob::OnStartWorkerFinished(
diff --git a/content/browser/service_worker/service_worker_register_job.h b/content/browser/service_worker/service_worker_register_job.h
index d01452ab..f8f6aae8 100644
--- a/content/browser/service_worker/service_worker_register_job.h
+++ b/content/browser/service_worker/service_worker_register_job.h
@@ -115,8 +115,6 @@
       scoped_refptr<ServiceWorkerRegistration> registration);
 
   bool IsUpdateCheckNeeded() const;
-  void TriggerUpdateCheck(
-      scoped_refptr<network::SharedURLLoaderFactory> loader_factory);
 
   // Refer ServiceWorkerUpdateChecker::UpdateStatusCallback for the meaning of
   // the parameters.
diff --git a/content/browser/worker_host/worker_script_loader.cc b/content/browser/worker_host/worker_script_loader.cc
index 488671b0..720c6d9 100644
--- a/content/browser/worker_host/worker_script_loader.cc
+++ b/content/browser/worker_host/worker_script_loader.cc
@@ -118,6 +118,12 @@
   DCHECK(!completed_);
   DCHECK(interceptor);
 
+  if (!service_worker_handle_) {
+    // The DedicatedWorkerHost or SharedWorkerHost is already destroyed.
+    Abort();
+    return;
+  }
+
   // Create SubresourceLoaderParams for intercepting subresource requests and
   // populating the "controller" field in ServiceWorkerContainer. This can be
   // null if the interceptor is not interested in this request.
diff --git a/content/child/child_process_sandbox_support_impl_mac.cc b/content/child/child_process_sandbox_support_impl_mac.cc
index 55ece81f..20fa9f5 100644
--- a/content/child/child_process_sandbox_support_impl_mac.cc
+++ b/content/child/child_process_sandbox_support_impl_mac.cc
@@ -59,8 +59,9 @@
       out_descriptor);
 }
 
-SkColor WebSandboxSupportMac::GetSystemColor(blink::MacSystemColorID color_id,
-                                             blink::ColorScheme color_scheme) {
+SkColor WebSandboxSupportMac::GetSystemColor(
+    blink::MacSystemColorID color_id,
+    blink::mojom::ColorScheme color_scheme) {
   if (!color_map_.IsValid()) {
     DLOG(ERROR) << "GetSystemColor does not have a valid color_map_";
     return SK_ColorMAGENTA;
@@ -69,10 +70,11 @@
                 "Light and dark color scheme system colors loaded.");
   base::span<const SkColor> color_map = color_map_.GetMemoryAsSpan<SkColor>(
       blink::kMacSystemColorIDCount * blink::kMacSystemColorSchemeCount);
-  base::span<const SkColor> color_map_for_scheme = color_map.subspan(
-      color_scheme == blink::ColorScheme::kDark ? blink::kMacSystemColorIDCount
-                                                : 0,
-      blink::kMacSystemColorIDCount);
+  base::span<const SkColor> color_map_for_scheme =
+      color_map.subspan(color_scheme == blink::mojom::ColorScheme::kDark
+                            ? blink::kMacSystemColorIDCount
+                            : 0,
+                        blink::kMacSystemColorIDCount);
   return color_map_for_scheme[static_cast<size_t>(color_id)];
 }
 
diff --git a/content/child/child_process_sandbox_support_impl_mac.h b/content/child/child_process_sandbox_support_impl_mac.h
index 1456f46..96c9cbc 100644
--- a/content/child/child_process_sandbox_support_impl_mac.h
+++ b/content/child/child_process_sandbox_support_impl_mac.h
@@ -29,7 +29,7 @@
                 base::ScopedCFTypeRef<CTFontDescriptorRef>* out_descriptor,
                 uint32_t* font_id) override;
   SkColor GetSystemColor(blink::MacSystemColorID color_id,
-                         blink::ColorScheme color_scheme) override;
+                         blink::mojom::ColorScheme color_scheme) override;
 
  private:
   void OnGotSystemColors(base::ReadOnlySharedMemoryRegion region);
diff --git a/content/child/webthemeengine_impl_android.cc b/content/child/webthemeengine_impl_android.cc
index 0696ab7..a6be4c16 100644
--- a/content/child/webthemeengine_impl_android.cc
+++ b/content/child/webthemeengine_impl_android.cc
@@ -10,7 +10,6 @@
 #include "skia/ext/platform_canvas.h"
 #include "ui/native_theme/native_theme.h"
 
-using blink::ColorScheme;
 using blink::WebThemeEngine;
 
 namespace content {
@@ -156,7 +155,7 @@
     WebThemeEngine::State state,
     const gfx::Rect& rect,
     const WebThemeEngine::ExtraParams* extra_params,
-    blink::ColorScheme color_scheme) {
+    blink::mojom::ColorScheme color_scheme) {
   ui::NativeTheme::ExtraParams native_theme_extra_params;
   GetNativeThemeExtraParams(
       part, state, extra_params, &native_theme_extra_params);
diff --git a/content/child/webthemeengine_impl_android.h b/content/child/webthemeengine_impl_android.h
index 06cc7b8..aff6b7c 100644
--- a/content/child/webthemeengine_impl_android.h
+++ b/content/child/webthemeengine_impl_android.h
@@ -21,7 +21,7 @@
              blink::WebThemeEngine::State state,
              const gfx::Rect& rect,
              const blink::WebThemeEngine::ExtraParams* extra_params,
-             blink::ColorScheme color_scheme) override;
+             blink::mojom::ColorScheme color_scheme) override;
   blink::ForcedColors GetForcedColors() const override;
   void SetForcedColors(const blink::ForcedColors forced_colors) override;
 };
diff --git a/content/child/webthemeengine_impl_conversions.cc b/content/child/webthemeengine_impl_conversions.cc
index 84509d4..fa3747b 100644
--- a/content/child/webthemeengine_impl_conversions.cc
+++ b/content/child/webthemeengine_impl_conversions.cc
@@ -84,11 +84,11 @@
 }
 
 ui::NativeTheme::ColorScheme NativeColorScheme(
-    blink::ColorScheme color_scheme) {
+    blink::mojom::ColorScheme color_scheme) {
   switch (color_scheme) {
-    case blink::ColorScheme::kLight:
+    case blink::mojom::ColorScheme::kLight:
       return ui::NativeTheme::ColorScheme::kLight;
-    case blink::ColorScheme::kDark:
+    case blink::mojom::ColorScheme::kDark:
       return ui::NativeTheme::ColorScheme::kDark;
   }
 }
diff --git a/content/child/webthemeengine_impl_conversions.h b/content/child/webthemeengine_impl_conversions.h
index 88858bc..92f7826f 100644
--- a/content/child/webthemeengine_impl_conversions.h
+++ b/content/child/webthemeengine_impl_conversions.h
@@ -22,7 +22,7 @@
     blink::WebThemeEngine::State state);
 
 CONTENT_EXPORT ui::NativeTheme::ColorScheme NativeColorScheme(
-    blink::ColorScheme color_scheme);
+    blink::mojom::ColorScheme color_scheme);
 
 CONTENT_EXPORT ui::NativeTheme::SystemThemeColor NativeSystemThemeColor(
     blink::WebThemeEngine::SystemThemeColor theme_color);
diff --git a/content/child/webthemeengine_impl_default.cc b/content/child/webthemeengine_impl_default.cc
index 836fa111..312bc84 100644
--- a/content/child/webthemeengine_impl_default.cc
+++ b/content/child/webthemeengine_impl_default.cc
@@ -10,9 +10,9 @@
 #include "ui/native_theme/native_theme.h"
 #include "ui/native_theme/overlay_scrollbar_constants_aura.h"
 
-using blink::ColorScheme;
 using blink::WebScrollbarOverlayColorTheme;
 using blink::WebThemeEngine;
+using blink::mojom::ColorScheme;
 
 namespace content {
 namespace {
@@ -185,7 +185,7 @@
     WebThemeEngine::State state,
     const gfx::Rect& rect,
     const WebThemeEngine::ExtraParams* extra_params,
-    blink::ColorScheme color_scheme) {
+    blink::mojom::ColorScheme color_scheme) {
   ui::NativeTheme::ExtraParams native_theme_extra_params;
   GetNativeThemeExtraParams(
       part, state, extra_params, &native_theme_extra_params);
diff --git a/content/child/webthemeengine_impl_default.h b/content/child/webthemeengine_impl_default.h
index 91715b8c..9da412ed 100644
--- a/content/child/webthemeengine_impl_default.h
+++ b/content/child/webthemeengine_impl_default.h
@@ -22,7 +22,7 @@
              blink::WebThemeEngine::State state,
              const gfx::Rect& rect,
              const blink::WebThemeEngine::ExtraParams* extra_params,
-             blink::ColorScheme color_scheme) override;
+             blink::mojom::ColorScheme color_scheme) override;
   void GetOverlayScrollbarStyle(
       blink::WebThemeEngine::ScrollbarStyle*) override;
   bool SupportsNinePatch(Part part) const override;
diff --git a/content/child/webthemeengine_impl_mac.cc b/content/child/webthemeengine_impl_mac.cc
index d442351..009f761 100644
--- a/content/child/webthemeengine_impl_mac.cc
+++ b/content/child/webthemeengine_impl_mac.cc
@@ -14,7 +14,7 @@
                               WebThemeEngine::State state,
                               const gfx::Rect& rect,
                               const WebThemeEngine::ExtraParams* extra_params,
-                              blink::ColorScheme color_scheme) {
+                              blink::mojom::ColorScheme color_scheme) {
   if (IsScrollbarPart(part)) {
     PaintMacScrollBarParts(canvas, part, state, rect, extra_params,
                            color_scheme);
@@ -44,7 +44,7 @@
     WebThemeEngine::State state,
     const gfx::Rect& rect,
     const WebThemeEngine::ExtraParams* extra_params,
-    blink::ColorScheme color_scheme) {
+    blink::mojom::ColorScheme color_scheme) {
   ui::NativeTheme::ExtraParams native_theme_extra_params;
   native_theme_extra_params.scrollbar_extra.is_hovering =
       extra_params->scrollbar_extra.is_hovering;
diff --git a/content/child/webthemeengine_impl_mac.h b/content/child/webthemeengine_impl_mac.h
index 6a3aac0..64b17c7 100644
--- a/content/child/webthemeengine_impl_mac.h
+++ b/content/child/webthemeengine_impl_mac.h
@@ -18,7 +18,7 @@
              blink::WebThemeEngine::State state,
              const gfx::Rect& rect,
              const blink::WebThemeEngine::ExtraParams* extra_params,
-             blink::ColorScheme color_scheme) override;
+             blink::mojom::ColorScheme color_scheme) override;
 
   static bool IsScrollbarPart(WebThemeEngine::Part part);
   static void PaintMacScrollBarParts(
@@ -27,7 +27,7 @@
       WebThemeEngine::State state,
       const gfx::Rect& rect,
       const WebThemeEngine::ExtraParams* extra_params,
-      blink::ColorScheme color_scheme);
+      blink::mojom::ColorScheme color_scheme);
 };
 
 }  // namespace content
diff --git a/content/child/webthemeengine_impl_unittest.cc b/content/child/webthemeengine_impl_unittest.cc
index 45c2598d..45bf16a6 100644
--- a/content/child/webthemeengine_impl_unittest.cc
+++ b/content/child/webthemeengine_impl_unittest.cc
@@ -97,8 +97,8 @@
 }
 
 TEST(WebThemeEngineTest, NativeColorScheme) {
-  std::vector<blink::ColorScheme> blink_inputs = {blink::ColorScheme::kLight,
-                                                  blink::ColorScheme::kDark};
+  std::vector<blink::mojom::ColorScheme> blink_inputs = {
+      blink::mojom::ColorScheme::kLight, blink::mojom::ColorScheme::kDark};
 
   std::vector<ui::NativeTheme::ColorScheme> native_theme_outputs = {
       ui::NativeTheme::ColorScheme::kLight,
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index 5a31b87..d14891f 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -208,15 +208,6 @@
   // all possible.
   SetWantErrorMessageStackTrace();
 
-  // Requests that a provisional RenderFrame swap itself into the frame tree,
-  // replacing the RenderFrameProxy that it is associated with. This is used
-  // with remote-to-local frame navigations when the RenderFrameProxy
-  // corresponds to a non-live (crashed) frame. In that case, the browser
-  // process will send this message as part of an early commit to stop showing
-  // the sad iframe without waiting for the provisional RenderFrame's navigation
-  // to commit.
-  SwapIn();
-
   // Unload this RenderFrame and replace it by a RenderFrameProxy, so the frame
   // can navigate to a document rendered by a different process. The unload can
   // fail if the RenderFrame is currently detached (it was removed from the
diff --git a/content/public/browser/service_worker_context.h b/content/public/browser/service_worker_context.h
index 8db242d..47abbd1 100644
--- a/content/public/browser/service_worker_context.h
+++ b/content/public/browser/service_worker_context.h
@@ -64,6 +64,12 @@
 // See service_worker_context_wrapper.cc for the implementation
 // of ServiceWorkerContext and ServiceWorkerContextWrapper (the
 // primary implementation of this abstract class).
+//
+// All methods are basically expected to be called on the UI thread.
+// Currently, only two methods are allowed to be called on any threads but it's
+// discouraged.
+// TODO(https://crbug.com/1161153): Disallow methods to be called on any
+// threads.
 class CONTENT_EXPORT ServiceWorkerContext {
  public:
   using ResultCallback = base::OnceCallback<void(bool success)>;
@@ -81,9 +87,6 @@
       base::OnceCallback<void(OfflineCapability capability,
                               int64_t registration_id)>;
 
-  using CountExternalRequestsCallback =
-      base::OnceCallback<void(size_t external_request_count)>;
-
   using StartServiceWorkerForNavigationHintCallback = base::OnceCallback<void(
       StartServiceWorkerForNavigationHintResult result)>;
 
@@ -93,6 +96,8 @@
   using StartWorkerFailureCallback =
       base::OnceCallback<void(blink::ServiceWorkerStatusCode status_code)>;
 
+  // Returns BrowserThread::UI always.
+  // TODO(https://crbug.com/1138155): Remove this.
   static content::BrowserThread::ID GetCoreThreadId();
 
   // Returns true if |url| is within the service worker |scope|.
@@ -105,7 +110,6 @@
                       ServiceWorkerContext* service_worker_context,
                       base::OnceClosure task);
 
-  // Observer methods are always dispatched on the UI thread.
   virtual void AddObserver(ServiceWorkerContextObserver* observer) = 0;
   virtual void RemoveObserver(ServiceWorkerContextObserver* observer) = 0;
 
@@ -118,9 +122,6 @@
   //  * Fetching |script_url| fails.
   //  * |script_url| fails to parse or its top-level execution fails.
   //  * Something unexpected goes wrong, like a renderer crash or a full disk.
-  //
-  // This function can be called from any thread, but the callback will always
-  // be called on the UI thread.
   virtual void RegisterServiceWorker(
       const GURL& script_url,
       const blink::mojom::ServiceWorkerRegistrationOptions& options,
@@ -133,9 +134,6 @@
   // Unregistration can fail if:
   //  * No registration exists for |scope|.
   //  * Something unexpected goes wrong, like a renderer crash.
-  //
-  // This function can be called from any thread, but the callback will always
-  // be called on the UI thread.
   virtual void UnregisterServiceWorker(const GURL& scope,
                                        ResultCallback callback) = 0;
 
@@ -147,8 +145,6 @@
   // calls FinishedExternalRequest(). This ensures that content/ does not
   // shut the worker down while embedder is expecting the worker to be kept
   // alive.
-  //
-  // Must be called from the core thread.
   virtual ServiceWorkerExternalRequestResult StartingExternalRequest(
       int64_t service_worker_version_id,
       const std::string& request_uuid) = 0;
@@ -157,18 +153,14 @@
       const std::string& request_uuid) = 0;
 
   // Returns the pending external request count for the worker with the
-  // specified |origin| via |callback|. Must be called from the UI thread. The
-  // callback is called on the UI thread.
-  virtual void CountExternalRequestsForTest(
-      const url::Origin& origin,
-      CountExternalRequestsCallback callback) = 0;
+  // specified |origin|.
+  virtual size_t CountExternalRequestsForTest(const url::Origin& origin) = 0;
 
   // Whether |origin| has any registrations. Uninstalling and uninstalled
   // registrations do not cause this to return true, that is, only registrations
   // with status ServiceWorkerRegistration::Status::kIntact are considered, such
   // as even if the corresponding live registrations may still exist. Also,
   // returns true if it doesn't know (registrations are not yet initialized).
-  // Must be called on the UI thread.
   virtual bool MaybeHasRegistrationForOrigin(const url::Origin& origin) = 0;
 
   // Returns a set of origins which have at least one stored registration.
@@ -181,14 +173,13 @@
       base::Optional<std::string> host_filter,
       GetInstalledRegistrationOriginsCallback callback) = 0;
 
-  // May be called from any thread, and the callback is called on that thread.
   virtual void GetAllOriginsInfo(GetUsageInfoCallback callback) = 0;
 
-  // This function can be called from any thread, and the callback is called
-  // on that thread.  Deletes all registrations in the origin and clears all
-  // service workers belonging to the registrations. All clients controlled by
-  // those service workers will lose their controllers immediately after this
-  // operation.
+  // Deletes all registrations in the origin and clears all service workers
+  // belonging to the registrations. All clients controlled by those service
+  // workers will lose their controllers immediately after this operation.
+  // This function can be called from any thread, and the callback is called on
+  // that thread.
   virtual void DeleteForOrigin(const url::Origin& origin_url,
                                ResultCallback callback) = 0;
 
@@ -203,9 +194,6 @@
   // Service Worker registration matching |url|. In case the service
   // worker is being installed as of calling this method, it will wait for the
   // installation to finish before coming back with the result.
-  //
-  // This function can be called from any thread, but the callback will always
-  // be called on the UI thread.
   virtual void CheckHasServiceWorker(
       const GURL& url,
       CheckHasServiceWorkerCallback callback) = 0;
@@ -214,9 +202,6 @@
   // event. Returns OfflineCapability::kSupported and the registration id if
   // the response's status code is 200.
   //
-  // This function can be called from any thread, but the callback will always
-  // be called on the UI thread.
-  //
   // TODO(hayato): Re-visit to integrate this function with
   // |ServiceWorkerContext::CheckHasServiceWorker|.
   virtual void CheckOfflineCapability(
@@ -226,9 +211,6 @@
   // Stops all running service workers and unregisters all service worker
   // registrations. This method is used in web tests to make sure that the
   // existing service worker will not affect the succeeding tests.
-  //
-  // This function can be called from any thread, but the callback will always
-  // be called on the UI thread.
   virtual void ClearAllServiceWorkersForTest(base::OnceClosure callback) = 0;
 
   // Starts the active worker of the registration for the given |scope|. If
@@ -237,9 +219,8 @@
   // successful, otherwise |failure_callback| is passed information about the
   // error.
   //
-  // Must be called on the core thread, and the callback is called on that
-  // thread. There is no guarantee about whether the callback is called
-  // synchronously or asynchronously.
+  // There is no guarantee about whether the callback is called synchronously or
+  // asynchronously.
   virtual void StartWorkerForScope(
       const GURL& scope,
       StartWorkerCallback info_callback,
@@ -257,26 +238,18 @@
       ResultCallback result_callback) = 0;
 
   // Starts the service worker for |document_url|. Called when a navigation to
-  // that URL is predicted to occur soon. Must be called from the UI thread. The
-  // |callback| will always be called on the UI thread.
+  // that URL is predicted to occur soon.
   virtual void StartServiceWorkerForNavigationHint(
       const GURL& document_url,
       StartServiceWorkerForNavigationHintCallback callback) = 0;
 
   // Stops all running workers on the given |origin|.
-  //
-  // This function can be called from any thread.
   virtual void StopAllServiceWorkersForOrigin(const url::Origin& origin) = 0;
 
   // Stops all running service workers.
-  //
-  // This function can be called from any thread.
-  // |callback| is called on the caller's thread.
   virtual void StopAllServiceWorkers(base::OnceClosure callback) = 0;
 
   // Gets info about all running workers.
-  //
-  // Must be called on the UI thread. The callback is called on the UI thread.
   virtual const base::flat_map<int64_t /* version_id */,
                                ServiceWorkerRunningInfo>&
   GetRunningServiceWorkerInfos() = 0;
diff --git a/content/public/test/fake_local_frame.cc b/content/public/test/fake_local_frame.cc
index 7169507e..c6784136 100644
--- a/content/public/test/fake_local_frame.cc
+++ b/content/public/test/fake_local_frame.cc
@@ -45,6 +45,8 @@
 void FakeLocalFrame::AddInspectorIssue(
     blink::mojom::InspectorIssueInfoPtr info) {}
 
+void FakeLocalFrame::SwapInImmediately() {}
+
 void FakeLocalFrame::CheckCompleted() {}
 
 void FakeLocalFrame::StopLoading() {}
diff --git a/content/public/test/fake_local_frame.h b/content/public/test/fake_local_frame.h
index bd58a30..d9c48a7 100644
--- a/content/public/test/fake_local_frame.h
+++ b/content/public/test/fake_local_frame.h
@@ -49,6 +49,7 @@
                            const std::string& message,
                            bool discard_duplicates) override;
   void AddInspectorIssue(blink::mojom::InspectorIssueInfoPtr info) override;
+  void SwapInImmediately() override;
   void CheckCompleted() override;
   void StopLoading() override;
   void Collapse(bool collapsed) override;
diff --git a/content/public/test/fake_service_worker_context.cc b/content/public/test/fake_service_worker_context.cc
index 45257b5..1945153 100644
--- a/content/public/test/fake_service_worker_context.cc
+++ b/content/public/test/fake_service_worker_context.cc
@@ -50,10 +50,10 @@
   NOTREACHED();
   return ServiceWorkerExternalRequestResult::kWorkerNotFound;
 }
-void FakeServiceWorkerContext::CountExternalRequestsForTest(
-    const url::Origin& origin,
-    CountExternalRequestsCallback callback) {
+size_t FakeServiceWorkerContext::CountExternalRequestsForTest(
+    const url::Origin& origin) {
   NOTREACHED();
+  return 0u;
 }
 bool FakeServiceWorkerContext::MaybeHasRegistrationForOrigin(
     const url::Origin& origin) {
diff --git a/content/public/test/fake_service_worker_context.h b/content/public/test/fake_service_worker_context.h
index 560ff20f..02b3995 100644
--- a/content/public/test/fake_service_worker_context.h
+++ b/content/public/test/fake_service_worker_context.h
@@ -45,9 +45,7 @@
   ServiceWorkerExternalRequestResult FinishedExternalRequest(
       int64_t service_worker_version_id,
       const std::string& request_uuid) override;
-  void CountExternalRequestsForTest(
-      const url::Origin& origin,
-      CountExternalRequestsCallback callback) override;
+  size_t CountExternalRequestsForTest(const url::Origin& origin) override;
   bool MaybeHasRegistrationForOrigin(const url::Origin& origin) override;
   void GetInstalledRegistrationOrigins(
       base::Optional<std::string> host_filter,
diff --git a/content/renderer/media/android/stream_texture_proxy_unittest.cc b/content/renderer/media/android/stream_texture_proxy_unittest.cc
index 74e03d6f..6d74e789 100644
--- a/content/renderer/media/android/stream_texture_proxy_unittest.cc
+++ b/content/renderer/media/android/stream_texture_proxy_unittest.cc
@@ -39,8 +39,7 @@
  public:
   StreamTextureProxyTest()
       : task_runner_(base::MakeRefCounted<base::TestSimpleTaskRunner>()),
-        thread_task_runner_handle_override_(
-            base::ThreadTaskRunnerHandle::OverrideForTesting(task_runner_)),
+        thread_task_runner_handle_override_(task_runner_),
         channel_(base::MakeRefCounted<TestGpuChannelHost>()) {}
 
   ~StreamTextureProxyTest() override { channel_ = nullptr; }
@@ -59,7 +58,8 @@
 
  protected:
   scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
-  base::ScopedClosureRunner thread_task_runner_handle_override_;
+  base::ThreadTaskRunnerHandleOverrideForTesting
+      thread_task_runner_handle_override_;
   scoped_refptr<TestGpuChannelHost> channel_;
 };
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index b42f671..8d9b4417 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1154,6 +1154,13 @@
       std::move(in->remote));
 }
 
+std::string GetUniqueNameOfWebFrame(WebFrame* web_frame) {
+  if (web_frame->IsWebLocalFrame())
+    return RenderFrameImpl::FromWebFrame(web_frame)->unique_name();
+  return RenderFrameProxy::FromWebFrame(web_frame->ToWebRemoteFrame())
+      ->unique_name();
+}
+
 }  // namespace
 
 RenderFrameImpl::AssertNavigationCommits::AssertNavigationCommits(
@@ -1714,13 +1721,17 @@
         agent_scheduling_group, render_view, routing_id,
         std::move(browser_interface_broker), devtools_frame_token);
     render_frame->InitializeBlameContext(nullptr);
-    render_frame->previous_routing_id_ = previous_routing_id;
     web_frame = blink::WebLocalFrame::CreateProvisional(
         render_frame, render_frame->blink_interface_registry_.get(),
         frame_token, previous_web_frame, replicated_state.frame_policy,
         WebString::FromUTF8(replicated_state.name));
-    // The new |web_frame| is a main frame iff the proxy's frame was.
+    // The new |web_frame| is a main frame iff the previous frame was.
     DCHECK_EQ(!previous_web_frame->Parent(), !web_frame->Parent());
+    // Clone the current unique name so web tests that log frame unique names
+    // output something meaningful. At `SwapIn()` time, the unique name will be
+    // updated to the latest value.
+    render_frame->unique_name_helper_.set_propagated_name(
+        GetUniqueNameOfWebFrame(previous_web_frame));
   }
 
   CHECK(render_view);
@@ -1965,7 +1976,6 @@
       in_frame_tree_(false),
       render_view_(params.render_view),
       routing_id_(params.routing_id),
-      previous_routing_id_(MSG_ROUTING_NONE),
       selection_text_offset_(0),
       selection_range_(gfx::Range::InvalidRange()),
       render_accessibility_manager_(
@@ -2588,10 +2598,6 @@
   return base::Value();
 }
 
-void RenderFrameImpl::SwapIn() {
-  SwapInInternal();
-}
-
 void RenderFrameImpl::SnapshotAccessibilityTree(
     uint32_t ax_mode,
     SnapshotAccessibilityTreeCallback callback) {
@@ -4102,15 +4108,6 @@
                "id", routing_id_, "url",
                GetLoadingUrl().possibly_invalid_spec());
 
-  bool is_provisional_frame = previous_routing_id_ != MSG_ROUTING_NONE;
-  if (is_provisional_frame) {
-    // If this is a provisional frame associated with a proxy (i.e., a frame
-    // created for a remote-to-local navigation), swap it into the frame tree
-    // now.
-    if (!SwapInInternal())
-      return;
-  }
-
   // Generate a new embedding token on each document change.
   GetWebFrame()->SetEmbeddingToken(base::UnguessableToken::Create());
 
@@ -5237,38 +5234,17 @@
   return blink::mojom::CommitResult::Ok;
 }
 
-std::string RenderFrameImpl::GetPreviousFrameUniqueName() {
-  DCHECK(!in_frame_tree_);
-
-  RenderFrameProxy* previous_proxy =
-      RenderFrameProxy::FromRoutingID(previous_routing_id_);
-  RenderFrameImpl* previous_frame =
-      RenderFrameImpl::FromRoutingID(previous_routing_id_);
-
-  // The |previous_proxy| or the |previous_frame| should always exist.
-  // If it was detached while the provisional LocalFrame was being navigated,
-  // the provisional frame would've been cleaned up by
-  // RenderFrameProxy::FrameDetached.
-  // See https://crbug.com/526304 and https://crbug.com/568676 for context.
-  CHECK_NE(!!previous_proxy, !!previous_frame);
-
-  if (previous_proxy)
-    return previous_proxy->unique_name();
-  return previous_frame->unique_name();
-}
-
-bool RenderFrameImpl::SwapInInternal() {
-  CHECK_NE(previous_routing_id_, MSG_ROUTING_NONE);
+bool RenderFrameImpl::SwapIn(WebFrame* previous_web_frame) {
   CHECK(!in_frame_tree_);
 
-  unique_name_helper_.set_propagated_name(GetPreviousFrameUniqueName());
+  // The unique name can still change in `WebFrame::Swap()` below (due to JS
+  // changing the browsing context name), but in practice, this seems to be good
+  // enough.
+  unique_name_helper_.set_propagated_name(
+      GetUniqueNameOfWebFrame(previous_web_frame));
 
-  // Note: Calling swap() will detach and delete |previous_frame|, so do not
-  // reference it after this.
-  WebFrame* previous_web_frame = ResolveWebFrame(previous_routing_id_);
-
-  // With RenderDocument it's possible for the frame to be deleted as a side
-  // effect of JS event handlers called during swap.
+  // Swapping out a frame can dispatch JS event handlers, causing `this` to be
+  // deleted.
   bool is_main_frame = is_main_frame_;
   if (!previous_web_frame->Swap(frame_)) {
     // Main frames should always swap successfully because there is no parent
@@ -5277,7 +5253,8 @@
     return false;
   }
 
-  previous_routing_id_ = MSG_ROUTING_NONE;
+  // `previous_web_frame` is now detached, and should no longer be referenced.
+
   in_frame_tree_ = true;
 
   // If this is the main frame going from a remote frame to a local frame,
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index f55598f..306e88f 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -522,7 +522,6 @@
       int32_t world_id,
       JavaScriptExecuteRequestInIsolatedWorldCallback callback) override;
   void SetWantErrorMessageStackTrace() override;
-  void SwapIn() override;
   void Unload(int proxy_routing_id,
               bool is_loading,
               const FrameReplicationState& replicated_frame_state,
@@ -602,6 +601,7 @@
   void WillSendSubmitEvent(const blink::WebFormElement& form) override;
   void DidCreateDocumentLoader(
       blink::WebDocumentLoader* document_loader) override;
+  bool SwapIn(blink::WebFrame* previous_web_frame) override;
   void DidCommitNavigation(
       blink::WebHistoryCommitType commit_type,
       bool should_reset_browser_interface_broker,
@@ -800,11 +800,6 @@
   bool IsLocalRoot() const;
   const RenderFrameImpl* GetLocalRoot() const;
 
-  // Gets the unique_name() of the frame being replaced by this frame, when
-  // it is a provisional frame. Invalid to call on frames that are already
-  // attached to the frame tree.
-  std::string GetPreviousFrameUniqueName();
-
  private:
   friend class RenderFrameImplTest;
   friend class RenderFrameObserver;
@@ -884,14 +879,6 @@
   void AddObserver(RenderFrameObserver* observer);
   void RemoveObserver(RenderFrameObserver* observer);
 
-  // Swaps the current frame into the frame tree, replacing the
-  // RenderFrameProxy it is associated with. Return value indicates whether
-  // the swap operation succeeded. This should only be used for provisional
-  // frames associated with a proxy, while the proxy is still in the frame tree.
-  // If the associated proxy has been detached before this is called, this
-  // returns false and aborts the swap.
-  bool SwapInInternal();
-
   // Checks whether accessibility support for this frame is currently enabled.
   bool IsAccessibilityEnabled() const;
 
@@ -1211,13 +1198,6 @@
   RenderViewImpl* render_view_;
   const int routing_id_;
 
-  // If this RenderFrame was created to replace a previous object, this will
-  // store its routing id. The previous object can be:
-  // - A RenderFrame. This requires RenderDocument to be enabled.
-  // - A RenderFrameProxy.
-  // At commit time, the two objects will be swapped and the old one cleared.
-  int previous_routing_id_;
-
   // Keeps track of which future subframes the browser process has history items
   // for during a history navigation, as well as whether those items are for
   // about:blank.  The renderer process should ask the browser for history items
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.cc b/content/renderer/service_worker/service_worker_subresource_loader.cc
index 2b919092..6d9fafd 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader.cc
@@ -8,6 +8,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/command_line.h"
+#include "base/debug/crash_logging.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
@@ -479,6 +480,7 @@
       return;
     }
     response_head_->encoded_data_length = 0;
+    received_redirect_for_bug1162035_ = true;
     url_loader_client_->OnReceiveRedirect(*redirect_info_,
                                           response_head_.Clone());
     TransitionToStatus(Status::kSentRedirect);
@@ -684,7 +686,18 @@
          "https://crbug.com/845683";
   DCHECK(!new_url.has_value()) << "Redirect with modified url was not "
                                   "supported yet. crbug.com/845683";
-  DCHECK(redirect_info_);
+
+  // TODO(crbug.com/1162035): Replace with a DCHECK or early return when
+  // the bug is understood.
+  if (!redirect_info_) {
+    SCOPED_CRASH_KEY_NUMBER("bug1162035", "follow_status",
+                            static_cast<int>(status_));
+    SCOPED_CRASH_KEY_BOOL("bug1162035", "received_redirect",
+                          received_redirect_for_bug1162035_);
+    SCOPED_CRASH_KEY_BOOL("bug1162035", "followed_redirect",
+                          followed_redirect_for_bug1162035_);
+    CHECK(false);
+  }
 
   bool should_clear_upload = false;
   net::RedirectUtil::UpdateHttpRequest(
@@ -707,6 +720,7 @@
   // Restart the request.
   TransitionToStatus(Status::kNotStarted);
   redirect_info_.reset();
+  followed_redirect_for_bug1162035_ = true;
   response_callback_receiver_.reset();
   StartRequest(resource_request_);
 }
@@ -858,25 +872,31 @@
 }
 
 void ServiceWorkerSubresourceLoader::TransitionToStatus(Status new_status) {
-#if DCHECK_IS_ON()
+  // TODO(crbug.com/1162035): Remove once the bug is understood and replace
+  // the CHECKs below to DCHECKs.
+  SCOPED_CRASH_KEY_NUMBER("bug1162035", "transition_old",
+                          static_cast<int>(status_));
+  SCOPED_CRASH_KEY_NUMBER("bug1162035", "transition_new",
+                          static_cast<int>(new_status));
+
   switch (new_status) {
     case Status::kNotStarted:
-      DCHECK_EQ(status_, Status::kSentRedirect);
+      CHECK_EQ(status_, Status::kSentRedirect);
       break;
     case Status::kStarted:
-      DCHECK_EQ(status_, Status::kNotStarted);
+      CHECK_EQ(status_, Status::kNotStarted);
       break;
     case Status::kSentRedirect:
-      DCHECK_EQ(status_, Status::kStarted);
+      CHECK_EQ(status_, Status::kStarted);
       break;
     case Status::kSentHeader:
-      DCHECK_EQ(status_, Status::kStarted);
+      CHECK_EQ(status_, Status::kStarted);
       break;
     case Status::kSentBody:
-      DCHECK_EQ(status_, Status::kSentHeader);
+      CHECK_EQ(status_, Status::kSentHeader);
       break;
     case Status::kCompleted:
-      DCHECK(
+      CHECK(
           // Network fallback before interception.
           status_ == Status::kNotStarted ||
           // Network fallback after interception.
@@ -887,7 +907,6 @@
           status_ == Status::kSentBody);
       break;
   }
-#endif  // DCHECK_IS_ON()
 
   status_ = new_status;
 }
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.h b/content/renderer/service_worker/service_worker_subresource_loader.h
index fc5a133..636b37e 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.h
+++ b/content/renderer/service_worker/service_worker_subresource_loader.h
@@ -206,6 +206,11 @@
   blink::mojom::ServiceWorkerFetchEventTimingPtr fetch_event_timing_;
   network::mojom::FetchResponseSource response_source_;
 
+  // For debugging crbug.com/1162035. Set to true after a redirect is
+  // received/followed.
+  bool received_redirect_for_bug1162035_ = false;
+  bool followed_redirect_for_bug1162035_ = false;
+
   base::WeakPtrFactory<ServiceWorkerSubresourceLoader> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerSubresourceLoader);
diff --git a/content/web_test/renderer/web_frame_test_proxy.cc b/content/web_test/renderer/web_frame_test_proxy.cc
index 0c52cd9..6ad2e410 100644
--- a/content/web_test/renderer/web_frame_test_proxy.cc
+++ b/content/web_test/renderer/web_frame_test_proxy.cc
@@ -280,13 +280,7 @@
 }
 
 std::string WebFrameTestProxy::GetFrameNameForWebTests() {
-  // If the frame is provisional, use the name of the frame it will replace in
-  // the tree, as the provisional frame has no name until swap. The name isn't
-  // moved onto the provisional frame until swap because it may change in the
-  // meantime, but this grabs the value it currently is, which is good enough
-  // for tests.
-  return blink::UniqueNameHelper::ExtractStableNameForTesting(
-      in_frame_tree() ? unique_name() : GetPreviousFrameUniqueName());
+  return blink::UniqueNameHelper::ExtractStableNameForTesting(unique_name());
 }
 
 std::string WebFrameTestProxy::GetFrameDescriptionForWebTests() {
diff --git a/extensions/browser/event_router_unittest.cc b/extensions/browser/event_router_unittest.cc
index 31a67f7..89f49381 100644
--- a/extensions/browser/event_router_unittest.cc
+++ b/extensions/browser/event_router_unittest.cc
@@ -20,7 +20,6 @@
 #include "extensions/browser/extensions_test.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_messages.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::DictionaryValue;
@@ -383,7 +382,6 @@
                      true /** did_enqueue */);
   ExpectHistogramCounts(7, 3, 2, 2, 2, 0);
 
-  ScopedWorkerBasedExtensionsChannel current_channel_override;
   scoped_refptr<const Extension> service_worker_extension =
       CreateServiceWorkerExtension();
   router.ReportEvent(events::HistogramValue::FOR_TEST,
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 0c5e7e9..b7e5b49 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -422,8 +422,6 @@
       "api/extension_action/action_info_test_util.h",
       "file_test_util.cc",
       "file_test_util.h",
-      "scoped_worker_based_extensions_channel.cc",
-      "scoped_worker_based_extensions_channel.h",
     ]
 
     deps = [
diff --git a/extensions/common/extension_builder_unittest.cc b/extensions/common/extension_builder_unittest.cc
index 29b18c47..6319fe7 100644
--- a/extensions/common/extension_builder_unittest.cc
+++ b/extensions/common/extension_builder_unittest.cc
@@ -13,7 +13,6 @@
 #include "extensions/common/manifest_handlers/content_scripts_handler.h"
 #include "extensions/common/manifest_handlers/externally_connectable.h"
 #include "extensions/common/permissions/permissions_data.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/common/user_script.h"
 #include "extensions/common/value_builder.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -122,9 +121,6 @@
     EXPECT_FALSE(BackgroundInfo::IsServiceWorkerBased(extension.get()));
   }
   {
-    // TODO(crbug.com/853378): Remove this when we open up support for
-    // service workers.
-    ScopedWorkerBasedExtensionsChannel current_channel_override;
     scoped_refptr<const Extension> extension =
         ExtensionBuilder("service worker")
             .SetBackgroundContext(
diff --git a/extensions/common/features/simple_feature_unittest.cc b/extensions/common/features/simple_feature_unittest.cc
index f2d467b..e771b54 100644
--- a/extensions/common/features/simple_feature_unittest.cc
+++ b/extensions/common/features/simple_feature_unittest.cc
@@ -24,7 +24,6 @@
 #include "extensions/common/features/feature_session_type.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/manifest_handlers/background_info.h"
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
 #include "extensions/common/switches.h"
 #include "extensions/common/value_builder.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -1035,8 +1034,6 @@
 }
 
 TEST(SimpleFeatureUnitTest, DisallowForServiceWorkers) {
-  ScopedWorkerBasedExtensionsChannel worker_channel_override;
-
   SimpleFeature feature;
   feature.set_name("somefeature");
   feature.set_contexts({Feature::BLESSED_EXTENSION_CONTEXT});
diff --git a/extensions/common/scoped_worker_based_extensions_channel.cc b/extensions/common/scoped_worker_based_extensions_channel.cc
deleted file mode 100644
index ba74d40..0000000
--- a/extensions/common/scoped_worker_based_extensions_channel.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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.
-
-#include "extensions/common/scoped_worker_based_extensions_channel.h"
-
-#include "extensions/common/constants.h"
-
-namespace extensions {
-
-ScopedWorkerBasedExtensionsChannel::ScopedWorkerBasedExtensionsChannel()
-    : worker_based_extensions_channel_(
-          extension_misc::kMinChannelForServiceWorkerBasedExtension) {}
-ScopedWorkerBasedExtensionsChannel::~ScopedWorkerBasedExtensionsChannel() =
-    default;
-
-}  // namespace extensions
diff --git a/extensions/common/scoped_worker_based_extensions_channel.h b/extensions/common/scoped_worker_based_extensions_channel.h
deleted file mode 100644
index ceee4ba9..0000000
--- a/extensions/common/scoped_worker_based_extensions_channel.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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.
-
-#ifndef EXTENSIONS_COMMON_SCOPED_WORKER_BASED_EXTENSIONS_CHANNEL_H_
-#define EXTENSIONS_COMMON_SCOPED_WORKER_BASED_EXTENSIONS_CHANNEL_H_
-
-#include "extensions/common/features/feature_channel.h"
-
-namespace extensions {
-
-// Sets the current channel so that Service Worker based extensions are enabled
-// and also and extension APIs from those Service Workers are enabled.
-//
-// Note: It is important to set this override early in tests so that the
-// channel change is visible to renderers running with service workers.
-class ScopedWorkerBasedExtensionsChannel {
- public:
-  ScopedWorkerBasedExtensionsChannel();
-  ScopedWorkerBasedExtensionsChannel(
-      const ScopedWorkerBasedExtensionsChannel&) = delete;
-  ScopedWorkerBasedExtensionsChannel& operator=(
-      const ScopedWorkerBasedExtensionsChannel&) = delete;
-  ~ScopedWorkerBasedExtensionsChannel();
-
- private:
-  ScopedCurrentChannel worker_based_extensions_channel_;
-};
-
-}  // namespace extensions
-
-#endif  // EXTENSIONS_COMMON_SCOPED_WORKER_BASED_EXTENSIONS_CHANNEL_H_
diff --git a/gin/BUILD.gn b/gin/BUILD.gn
index 4fa8514..dfc59c1 100644
--- a/gin/BUILD.gn
+++ b/gin/BUILD.gn
@@ -75,10 +75,6 @@
 
   defines = [ "GIN_IMPLEMENTATION" ]
 
-  if (use_perfetto_client_library) {
-    defines += [ "V8_USE_PERFETTO" ]
-  }
-
   public_deps = [
     "//base",
     "//v8",
diff --git a/gpu/ipc/client/command_buffer_proxy_impl_unittest.cc b/gpu/ipc/client/command_buffer_proxy_impl_unittest.cc
index 229d81b..c9a3db0 100644
--- a/gpu/ipc/client/command_buffer_proxy_impl_unittest.cc
+++ b/gpu/ipc/client/command_buffer_proxy_impl_unittest.cc
@@ -61,8 +61,7 @@
  public:
   CommandBufferProxyImplTest()
       : task_runner_(base::MakeRefCounted<base::TestSimpleTaskRunner>()),
-        thread_task_runner_handle_override_(
-            base::ThreadTaskRunnerHandle::OverrideForTesting(task_runner_)),
+        thread_task_runner_handle_override_(task_runner_),
         channel_(base::MakeRefCounted<TestGpuChannelHost>(&sink_)) {}
 
   ~CommandBufferProxyImplTest() override {
@@ -97,7 +96,8 @@
  protected:
   IPC::TestSink sink_;
   scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
-  base::ScopedClosureRunner thread_task_runner_handle_override_;
+  base::ThreadTaskRunnerHandleOverrideForTesting
+      thread_task_runner_handle_override_;
   scoped_refptr<TestGpuChannelHost> channel_;
 };
 
diff --git a/ios/chrome/browser/ui/alert_view/alert_view_controller.mm b/ios/chrome/browser/ui/alert_view/alert_view_controller.mm
index 1538693..fd34467 100644
--- a/ios/chrome/browser/ui/alert_view/alert_view_controller.mm
+++ b/ios/chrome/browser/ui/alert_view/alert_view_controller.mm
@@ -144,7 +144,8 @@
   self.contentView = [[UIView alloc] init];
   self.contentView.accessibilityIdentifier = self.alertAccessibilityIdentifier;
   self.contentView.clipsToBounds = YES;
-  self.contentView.backgroundColor = UIColor.cr_systemBackgroundColor;
+  self.contentView.backgroundColor =
+      [UIColor colorNamed:kPrimaryBackgroundColor];
   self.contentView.layer.cornerRadius = kCornerRadius;
   self.contentView.layer.shadowOffset =
       CGSizeMake(kShadowOffsetX, kShadowOffsetY);
@@ -294,7 +295,8 @@
     }
     stackHolder.layer.borderWidth = 1.0 / [UIScreen mainScreen].scale;
     stackHolder.clipsToBounds = YES;
-    stackHolder.backgroundColor = UIColor.cr_secondarySystemBackgroundColor;
+    stackHolder.backgroundColor =
+        [UIColor colorNamed:kSecondaryBackgroundColor];
     stackHolder.translatesAutoresizingMaskIntoConstraints = NO;
     self.textFieldStackHolder = stackHolder;
 
diff --git a/ios/chrome/browser/ui/authentication/signin/user_signin/gradient_view.mm b/ios/chrome/browser/ui/authentication/signin/user_signin/gradient_view.mm
index 44cb6fd..220c77a 100644
--- a/ios/chrome/browser/ui/authentication/signin/user_signin/gradient_view.mm
+++ b/ios/chrome/browser/ui/authentication/signin/user_signin/gradient_view.mm
@@ -5,7 +5,7 @@
 #import "ios/chrome/browser/ui/authentication/signin/user_signin/gradient_view.h"
 
 #import "base/mac/foundation_util.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -53,8 +53,9 @@
   [CATransaction setDisableActions:YES];
 
   self.gradientLayer.colors = @[
-    (id)[UIColor.cr_systemBackgroundColor colorWithAlphaComponent:0].CGColor,
-    (id)UIColor.cr_systemBackgroundColor.CGColor,
+    (id)[[UIColor colorNamed:kPrimaryBackgroundColor] colorWithAlphaComponent:0]
+        .CGColor,
+    (id)[UIColor colorNamed:kPrimaryBackgroundColor].CGColor,
   ];
   [CATransaction commit];
 }
diff --git a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
index c93c77f..90ad96b 100644
--- a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_view_controller.mm
@@ -12,7 +12,6 @@
 #import "ios/chrome/browser/ui/authentication/signin/user_signin/gradient_view.h"
 #import "ios/chrome/browser/ui/util/rtl_geometry.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
 #import "ios/chrome/common/ui/util/pointer_interaction_util.h"
@@ -307,7 +306,7 @@
 #pragma mark - Properties
 
 - (UIColor*)systemBackgroundColor {
-  return UIColor.cr_systemBackgroundColor;
+  return [UIColor colorNamed:kPrimaryBackgroundColor];
 }
 
 - (NSString*)confirmationButtonTitle {
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_picker_view.mm b/ios/chrome/browser/ui/authentication/unified_consent/identity_picker_view.mm
index 538e5e1..6d8ccf65 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_picker_view.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_picker_view.mm
@@ -12,6 +12,7 @@
 #import "ios/chrome/browser/ui/authentication/unified_consent/unified_consent_constants.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
 #import "ios/chrome/common/ui/util/pointer_interaction_util.h"
 
@@ -47,7 +48,7 @@
   if (self) {
     self.accessibilityIdentifier = kIdentityPickerViewIdentifier;
     self.layer.cornerRadius = kIdentityPickerViewRadius;
-    self.backgroundColor = UIColor.cr_secondarySystemBackgroundColor;
+    self.backgroundColor = [UIColor colorNamed:kSecondaryBackgroundColor];
     // Adding view elements inside.
     // Ink view.
     _rippleView = [[MDCRippleView alloc] initWithFrame:CGRectZero];
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm b/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
index 1f2f4900..10c7f11 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
@@ -193,7 +193,7 @@
   // Separator.
   UIView* separator = [[UIView alloc] initWithFrame:CGRectZero];
   separator.translatesAutoresizingMaskIntoConstraints = NO;
-  separator.backgroundColor = UIColor.cr_secondarySystemBackgroundColor;
+  separator.backgroundColor = [UIColor colorNamed:kSecondaryBackgroundColor];
   [container addSubview:separator];
 
   // Customize label.
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_navigation_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_navigation_controller.mm
index f09595a..7cd5405d 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_navigation_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_navigation_controller.mm
@@ -6,7 +6,7 @@
 
 #import "ios/chrome/browser/ui/bookmarks/bookmark_ui_constants.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -16,7 +16,7 @@
 
 - (void)viewDidLoad {
   [super viewDidLoad];
-  self.view.backgroundColor = UIColor.cr_systemBackgroundColor;
+  self.view.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
   self.navigationBar.accessibilityIdentifier = kBookmarkNavigationBarIdentifier;
 }
 
diff --git a/ios/chrome/browser/ui/collection_view/collection_view_controller.mm b/ios/chrome/browser/ui/collection_view/collection_view_controller.mm
index efe8c6ee..a62dbb3 100644
--- a/ios/chrome/browser/ui/collection_view/collection_view_controller.mm
+++ b/ios/chrome/browser/ui/collection_view/collection_view_controller.mm
@@ -13,7 +13,7 @@
 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
 #import "ios/chrome/browser/ui/material_components/chrome_app_bar_view_controller.h"
 #import "ios/chrome/browser/ui/material_components/utils.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -65,9 +65,10 @@
   }
 
   // Suport dark mode.
-  self.collectionView.backgroundColor = UIColor.cr_systemGroupedBackgroundColor;
+  self.collectionView.backgroundColor =
+      [UIColor colorNamed:kGroupedPrimaryBackgroundColor];
   self.styler.cellBackgroundColor =
-      UIColor.cr_secondarySystemGroupedBackgroundColor;
+      [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
 }
 
 - (void)contentSizeCategoryDidChange:(id)sender {
diff --git a/ios/chrome/browser/ui/fancy_ui/primary_action_button.mm b/ios/chrome/browser/ui/fancy_ui/primary_action_button.mm
index 29a07fc..a87ae86 100644
--- a/ios/chrome/browser/ui/fancy_ui/primary_action_button.mm
+++ b/ios/chrome/browser/ui/fancy_ui/primary_action_button.mm
@@ -5,7 +5,6 @@
 #import "ios/chrome/browser/ui/fancy_ui/primary_action_button.h"
 
 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/util/pointer_interaction_util.h"
 
@@ -43,7 +42,7 @@
   }
 #endif  // defined(__IPHONE_13_4)
 
-  UIColor* hintColor = UIColor.cr_systemBackgroundColor;
+  UIColor* hintColor = [UIColor colorNamed:kPrimaryBackgroundColor];
   UIColor* inkColor = [UIColor colorWithWhite:1 alpha:0.2f];
   UIColor* backgroundColor = [UIColor colorNamed:kBlueColor];
   UIColor* disabledColor = [UIColor colorNamed:kDisabledTintColor];
diff --git a/ios/chrome/browser/ui/first_run/static_file_view_controller.mm b/ios/chrome/browser/ui/first_run/static_file_view_controller.mm
index 68958ff..b61597a 100644
--- a/ios/chrome/browser/ui/first_run/static_file_view_controller.mm
+++ b/ios/chrome/browser/ui/first_run/static_file_view_controller.mm
@@ -13,7 +13,7 @@
 #import "ios/chrome/browser/ui/icons/chrome_icon.h"
 #import "ios/chrome/browser/ui/material_components/utils.h"
 #include "ios/chrome/browser/ui/util/rtl_geometry.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/web/common/web_view_creation_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -70,7 +70,7 @@
                             cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                         timeoutInterval:60.0];
   [_webView loadRequest:request];
-  [_webView setBackgroundColor:UIColor.cr_systemBackgroundColor];
+  [_webView setBackgroundColor:[UIColor colorNamed:kPrimaryBackgroundColor]];
   [self.view addSubview:_webView];
 
   ConfigureAppBarViewControllerWithCardStyle(_appBarViewController);
diff --git a/ios/chrome/browser/ui/material_components/utils.mm b/ios/chrome/browser/ui/material_components/utils.mm
index bee7e27..b9c6a6d 100644
--- a/ios/chrome/browser/ui/material_components/utils.mm
+++ b/ios/chrome/browser/ui/material_components/utils.mm
@@ -15,6 +15,7 @@
 
 #include "base/mac/foundation_util.h"
 #import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -44,7 +45,7 @@
   viewController.headerView.shiftBehavior =
       MDCFlexibleHeaderShiftBehaviorDisabled;
   viewController.headerView.backgroundColor =
-      UIColor.cr_secondarySystemBackgroundColor;
+      [UIColor colorNamed:kSecondaryBackgroundColor];
   viewController.navigationBar.tintColor = UIColor.cr_labelColor;
   viewController.navigationBar.titleAlignment =
       MDCNavigationBarTitleAlignmentLeading;
diff --git a/ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_table_view_controller.mm b/ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_table_view_controller.mm
index 31fdb75c..46a28fc 100644
--- a/ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_table_view_controller.mm
+++ b/ios/chrome/browser/ui/send_tab_to_self/send_tab_to_self_table_view_controller.mm
@@ -19,7 +19,6 @@
 #include "ios/chrome/browser/ui/table_view/cells/table_view_url_item.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -82,8 +81,9 @@
 
 - (void)viewDidLoad {
   [super viewDidLoad];
-  self.view.backgroundColor = UIColor.cr_systemBackgroundColor;
-  self.styler.cellBackgroundColor = UIColor.cr_systemBackgroundColor;
+  self.view.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
+  self.styler.cellBackgroundColor =
+      [UIColor colorNamed:kPrimaryBackgroundColor];
   self.tableView.sectionHeaderHeight = 0;
   self.tableView.sectionFooterHeight = 0;
   [self.tableView
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_view_controller.mm b/ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_view_controller.mm
index 0868e19..11c78c05 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_view_controller.mm
@@ -15,7 +15,6 @@
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_controller.h"
 #import "ios/chrome/browser/ui/table_view/table_view_utils.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ui/base/l10n/l10n_util_mac.h"
@@ -87,7 +86,8 @@
 - (void)viewDidLoad {
   [super viewDidLoad];
 
-  self.view.backgroundColor = UIColor.cr_systemGroupedBackgroundColor;
+  self.view.backgroundColor =
+      [UIColor colorNamed:kGroupedPrimaryBackgroundColor];
   self.tableView.accessibilityIdentifier = kAddCreditCardViewID;
 
   self.navigationItem.title = l10n_util::GetNSString(
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.mm b/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.mm
index 2e822f9..e45bdec 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.mm
@@ -139,10 +139,10 @@
 
   if (!base::FeatureList::IsEnabled(kSettingsRefresh)) {
     self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
-    self.styler.cellBackgroundColor = UIColor.cr_systemBackgroundColor;
-    self.styler.tableViewBackgroundColor = UIColor.cr_systemBackgroundColor;
     self.tableView.accessibilityIdentifier =
         kClearBrowsingDataViewAccessibilityIdentifier;
+    self.styler.tableViewBackgroundColor =
+        [UIColor colorNamed:kPrimaryBackgroundColor];
     self.tableView.backgroundColor = self.styler.tableViewBackgroundColor;
 
     // TableView configuration
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data/time_range_selector_table_view_controller.mm b/ios/chrome/browser/ui/settings/clear_browsing_data/time_range_selector_table_view_controller.mm
index 7002d7d..09d8f10 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data/time_range_selector_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data/time_range_selector_table_view_controller.mm
@@ -15,6 +15,7 @@
 #import "ios/chrome/browser/ui/table_view/table_view_utils.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 
@@ -78,7 +79,8 @@
 - (void)viewDidLoad {
   [super viewDidLoad];
   if (!base::FeatureList::IsEnabled(kSettingsRefresh)) {
-    self.styler.tableViewBackgroundColor = UIColor.cr_systemBackgroundColor;
+    self.styler.tableViewBackgroundColor =
+        [UIColor colorNamed:kPrimaryBackgroundColor];
     self.tableView.backgroundColor = self.styler.tableViewBackgroundColor;
   }
   [self loadModel];
diff --git a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
index befb055..7fa7884 100644
--- a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
@@ -25,7 +25,6 @@
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/util/constraints_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -289,7 +288,7 @@
 - (void)viewDidLoad {
   [super viewDidLoad];
 
-  self.view.backgroundColor = UIColor.cr_systemBackgroundColor;
+  self.view.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
 
   if (base::FeatureList::IsEnabled(kSettingsRefresh)) {
     self.navigationBar.translucent = NO;
diff --git a/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
index 29cbff5..f20c692 100644
--- a/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
@@ -117,7 +117,7 @@
 - (void)viewDidLoad {
   if (!base::FeatureList::IsEnabled(kSettingsRefresh)) {
     self.styler.tableViewBackgroundColor =
-        UIColor.cr_systemGroupedBackgroundColor;
+        [UIColor colorNamed:kGroupedPrimaryBackgroundColor];
   }
   UIBarButtonItem* flexibleSpace = [[UIBarButtonItem alloc]
       initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
@@ -128,7 +128,7 @@
 
   [super viewDidLoad];
   self.styler.cellBackgroundColor =
-      UIColor.cr_secondarySystemGroupedBackgroundColor;
+      [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
   self.styler.cellTitleColor = UIColor.cr_labelColor;
   self.tableView.estimatedSectionHeaderHeight = kEstimatedHeaderFooterHeight;
   self.tableView.estimatedRowHeight = kSettingsCellDefaultHeight;
diff --git a/ios/chrome/browser/ui/table_view/chrome_table_view_styler.mm b/ios/chrome/browser/ui/table_view/chrome_table_view_styler.mm
index fb2725c..6ee04a2 100644
--- a/ios/chrome/browser/ui/table_view/chrome_table_view_styler.mm
+++ b/ios/chrome/browser/ui/table_view/chrome_table_view_styler.mm
@@ -6,7 +6,6 @@
 
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -20,9 +19,10 @@
     if (base::FeatureList::IsEnabled(kSettingsRefresh)) {
       _tableViewBackgroundColor =
           [UIColor colorNamed:kSecondaryBackgroundColor];
-      _cellBackgroundColor = UIColor.cr_secondarySystemGroupedBackgroundColor;
+      _cellBackgroundColor =
+          [UIColor colorNamed:kGroupedSecondaryBackgroundColor];
     } else {
-      _tableViewBackgroundColor = UIColor.cr_systemBackgroundColor;
+      _tableViewBackgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
     }
   }
   return self;
diff --git a/ios/chrome/browser/ui/table_view/table_view_navigation_controller.mm b/ios/chrome/browser/ui/table_view/table_view_navigation_controller.mm
index 416f3ef..08c4369 100644
--- a/ios/chrome/browser/ui/table_view/table_view_navigation_controller.mm
+++ b/ios/chrome/browser/ui/table_view/table_view_navigation_controller.mm
@@ -8,7 +8,6 @@
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_controller.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
-#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -43,9 +42,10 @@
     self.toolbar.barTintColor = [UIColor colorNamed:kSecondaryBackgroundColor];
     self.view.backgroundColor = [UIColor colorNamed:kSecondaryBackgroundColor];
   } else {
-    self.navigationBar.barTintColor = UIColor.cr_systemBackgroundColor;
-    self.toolbar.barTintColor = UIColor.cr_systemBackgroundColor;
-    self.view.backgroundColor = UIColor.cr_systemBackgroundColor;
+    self.navigationBar.barTintColor =
+        [UIColor colorNamed:kPrimaryBackgroundColor];
+    self.toolbar.barTintColor = [UIColor colorNamed:kPrimaryBackgroundColor];
+    self.view.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
   }
 }
 
diff --git a/ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h b/ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h
index c4026725..e95226e 100644
--- a/ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h
+++ b/ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h
@@ -13,16 +13,6 @@
 // TODO (crbug.com/981889): Remove along with iOS 12.
 @interface UIColor (CRSemanticColors)
 
-// System Background Color
-@property(class, nonatomic, readonly) UIColor* cr_systemBackgroundColor;
-@property(class, nonatomic, readonly)
-    UIColor* cr_secondarySystemBackgroundColor;
-
-// System Grouped Background Colors
-@property(class, nonatomic, readonly) UIColor* cr_systemGroupedBackgroundColor;
-@property(class, nonatomic, readonly)
-    UIColor* cr_secondarySystemGroupedBackgroundColor;
-
 // Label Colors
 @property(class, nonatomic, readonly) UIColor* cr_labelColor;
 @property(class, nonatomic, readonly) UIColor* cr_secondaryLabelColor;
diff --git a/ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.mm b/ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.mm
index 39ced8a..70f20f4 100644
--- a/ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.mm
+++ b/ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.mm
@@ -10,42 +10,6 @@
 
 @implementation UIColor (CRSemanticColors)
 
-#pragma mark - System Background Colors
-
-+ (UIColor*)cr_systemBackgroundColor {
-  if (@available(iOS 13, *)) {
-    return UIColor.systemBackgroundColor;
-  }
-  return UIColor.whiteColor;
-}
-
-+ (UIColor*)cr_secondarySystemBackgroundColor {
-  if (@available(iOS 13, *)) {
-    return UIColor.secondarySystemBackgroundColor;
-  }
-  // This is the value for secondarySystemBackgroundColor in light mode.
-  return [UIColor colorWithRed:244 / (CGFloat)0xFF
-                         green:244 / (CGFloat)0xFF
-                          blue:248 / (CGFloat)0xFF
-                         alpha:1];
-}
-
-#pragma mark - System Grouped Background Colors
-
-+ (UIColor*)cr_systemGroupedBackgroundColor {
-  if (@available(iOS 13, *)) {
-    return UIColor.systemGroupedBackgroundColor;
-  }
-  return UIColor.groupTableViewBackgroundColor;
-}
-
-+ (UIColor*)cr_secondarySystemGroupedBackgroundColor {
-  if (@available(iOS 13, *)) {
-    return UIColor.secondarySystemGroupedBackgroundColor;
-  }
-  return UIColor.whiteColor;
-}
-
 #pragma mark - Label Colors
 
 + (UIColor*)cr_labelColor {
diff --git a/ios/chrome/common/ui/colors/resources/BUILD.gn b/ios/chrome/common/ui/colors/resources/BUILD.gn
index c6935c0..59ca80c 100644
--- a/ios/chrome/common/ui/colors/resources/BUILD.gn
+++ b/ios/chrome/common/ui/colors/resources/BUILD.gn
@@ -27,9 +27,12 @@
     ":grey_700_color",
     ":grey_800_color",
     ":grey_900_color",
+    ":grouped_primary_background_color",
+    ":grouped_secondary_background_color",
     ":mdc_ink_color",
     ":mdc_secondary_ink_color",
     ":placeholder_image_tint_color",
+    ":primary_background_color",
     ":red_color",
     ":red_dark_color",
     ":scrim_background_color",
@@ -223,3 +226,15 @@
 colorset("secondary_background_color") {
   sources = [ "secondary_background_color.colorset/Contents.json" ]
 }
+
+colorset("primary_background_color") {
+  sources = [ "primary_background_color.colorset/Contents.json" ]
+}
+
+colorset("grouped_secondary_background_color") {
+  sources = [ "grouped_secondary_background_color.colorset/Contents.json" ]
+}
+
+colorset("grouped_primary_background_color") {
+  sources = [ "grouped_primary_background_color.colorset/Contents.json" ]
+}
diff --git a/ios/chrome/common/ui/colors/resources/grouped_primary_background_color.colorset/Contents.json b/ios/chrome/common/ui/colors/resources/grouped_primary_background_color.colorset/Contents.json
new file mode 100644
index 0000000..f1f5bfa
--- /dev/null
+++ b/ios/chrome/common/ui/colors/resources/grouped_primary_background_color.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "info": {
+    "version": 1,
+    "author": "xcode"
+  },
+  "colors": [
+    {
+      "idiom": "universal",
+      "color": {
+        "color-space": "display-p3",
+        "components": {
+          "red": "0xF1",
+          "alpha": "1.000",
+          "blue": "0xF4",
+          "green": "0xF3"
+        }
+      }
+    },
+    {
+      "idiom": "universal",
+      "appearances": [
+        {
+          "appearance": "luminosity",
+          "value": "dark"
+        }
+      ],
+      "color": {
+        "color-space": "display-p3",
+        "components": {
+          "red": "0x20",
+          "alpha": "1.000",
+          "blue": "0x24",
+          "green": "0x21"
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/ios/chrome/common/ui/colors/resources/grouped_secondary_background_color.colorset/Contents.json b/ios/chrome/common/ui/colors/resources/grouped_secondary_background_color.colorset/Contents.json
new file mode 100644
index 0000000..0d5ff736
--- /dev/null
+++ b/ios/chrome/common/ui/colors/resources/grouped_secondary_background_color.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "info": {
+    "version": 1,
+    "author": "xcode"
+  },
+  "colors": [
+    {
+      "idiom": "universal",
+      "color": {
+        "color-space": "display-p3",
+        "components": {
+          "red": "0xFF",
+          "alpha": "1.000",
+          "blue": "0xFF",
+          "green": "0xFF"
+        }
+      }
+    },
+    {
+      "idiom": "universal",
+      "appearances": [
+        {
+          "appearance": "luminosity",
+          "value": "dark"
+        }
+      ],
+      "color": {
+        "color-space": "display-p3",
+        "components": {
+          "red": "0x35",
+          "alpha": "1.000",
+          "blue": "0x39",
+          "green": "0x37"
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/ios/chrome/common/ui/colors/resources/primary_background_color.colorset/Contents.json b/ios/chrome/common/ui/colors/resources/primary_background_color.colorset/Contents.json
new file mode 100644
index 0000000..a66621c8
--- /dev/null
+++ b/ios/chrome/common/ui/colors/resources/primary_background_color.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "info": {
+    "version": 1,
+    "author": "xcode"
+  },
+  "colors": [
+    {
+      "idiom": "universal",
+      "color": {
+        "color-space": "display-p3",
+        "components": {
+          "red": "0xFF",
+          "alpha": "1.000",
+          "blue": "0xFF",
+          "green": "0xFF"
+        }
+      }
+    },
+    {
+      "idiom": "universal",
+      "appearances": [
+        {
+          "appearance": "luminosity",
+          "value": "dark"
+        }
+      ],
+      "color": {
+        "color-space": "display-p3",
+        "components": {
+          "red": "0x20",
+          "alpha": "1.000",
+          "blue": "0x24",
+          "green": "0x21"
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/ios/chrome/common/ui/colors/semantic_color_names.h b/ios/chrome/common/ui/colors/semantic_color_names.h
index ce13cb6..d66f212 100644
--- a/ios/chrome/common/ui/colors/semantic_color_names.h
+++ b/ios/chrome/common/ui/colors/semantic_color_names.h
@@ -14,6 +14,10 @@
 extern NSString* const kDisabledTintColor;
 // Background color used in the rounded squares behind favicons.
 extern NSString* const kFaviconBackgroundColor;
+// Primary grouped background color.
+extern NSString* const kGroupedPrimaryBackgroundColor;
+// Secondary grouped background color.
+extern NSString* const kGroupedSecondaryBackgroundColor;
 // Ink color for an MDC button.
 extern NSString* const kMDCInkColor;
 // Ink color for a secondary style MDC button (button with transparent
@@ -21,6 +25,8 @@
 extern NSString* const kMDCSecondaryInkColor;
 // Color used to tint placeholder images and icons.
 extern NSString* const kPlaceholderImageTintColor;
+// Primary background color.
+extern NSString* const kPrimaryBackgroundColor;
 extern NSString* const kScrimBackgroundColor;
 // Secondary background color.
 extern NSString* const kSecondaryBackgroundColor;
diff --git a/ios/chrome/common/ui/colors/semantic_color_names.mm b/ios/chrome/common/ui/colors/semantic_color_names.mm
index 74d8960..22d8e42 100644
--- a/ios/chrome/common/ui/colors/semantic_color_names.mm
+++ b/ios/chrome/common/ui/colors/semantic_color_names.mm
@@ -13,9 +13,14 @@
 NSString* const kCloseButtonColor = @"close_button_color";
 NSString* const kDisabledTintColor = @"disabled_tint_color";
 NSString* const kFaviconBackgroundColor = @"favicon_background_color";
+NSString* const kGroupedPrimaryBackgroundColor =
+    @"grouped_primary_background_color";
+NSString* const kGroupedSecondaryBackgroundColor =
+    @"grouped_secondary_background_color";
 NSString* const kMDCInkColor = @"mdc_ink_color";
 NSString* const kMDCSecondaryInkColor = @"mdc_secondary_ink_color";
 NSString* const kPlaceholderImageTintColor = @"placeholder_image_tint_color";
+NSString* const kPrimaryBackgroundColor = @"primary_background_color";
 NSString* const kScrimBackgroundColor = @"scrim_background_color";
 NSString* const kSecondaryBackgroundColor = @"secondary_background_color";
 NSString* const kSeparatorColor = @"separator_color";
diff --git a/ios/web/js_messaging/BUILD.gn b/ios/web/js_messaging/BUILD.gn
index 7466714..bae4756 100644
--- a/ios/web/js_messaging/BUILD.gn
+++ b/ios/web/js_messaging/BUILD.gn
@@ -37,10 +37,30 @@
   ]
 }
 
+source_set("java_script_feature") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  deps = [
+    ":js_messaging",
+    "//base",
+    "//ios/web/public",
+    "//ios/web/public/js_messaging",
+    "//ios/web/web_state/ui:wk_web_view_configuration_provider_header",
+  ]
+
+  sources = [
+    "java_script_content_world.h",
+    "java_script_content_world.mm",
+    "java_script_feature.mm",
+    "java_script_feature_manager.h",
+    "java_script_feature_manager.mm",
+  ]
+}
+
 source_set("unittests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
   deps = [
+    ":java_script_feature",
     ":js_messaging",
     "//base",
     "//base/test:test_support",
@@ -56,6 +76,8 @@
   sources = [
     "crw_js_window_id_manager_unittest.mm",
     "crw_wk_script_message_router_unittest.mm",
+    "java_script_content_world_unittest.mm",
+    "java_script_feature_unittest.mm",
     "page_script_util_unittest.mm",
     "web_frame_impl_unittest.mm",
     "web_frame_util_unittest.mm",
diff --git a/ios/web/js_messaging/java_script_content_world.h b/ios/web/js_messaging/java_script_content_world.h
new file mode 100644
index 0000000..ea092c8f
--- /dev/null
+++ b/ios/web/js_messaging/java_script_content_world.h
@@ -0,0 +1,63 @@
+// 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 IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_CONTENT_WORLD_H_
+#define IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_CONTENT_WORLD_H_
+
+#include <set>
+
+#import <WebKit/WebKit.h>
+
+namespace web {
+
+class JavaScriptFeature;
+
+// Represents a content world which can be configured with a given set of
+// JavaScriptFeatures. An isolated world prevents the loaded web page’s
+// JavaScript from interacting with the browser's feature JavaScript. This can
+// improve the security and robustness of the feature JavaScript.
+class JavaScriptContentWorld {
+ public:
+  ~JavaScriptContentWorld();
+  JavaScriptContentWorld(const JavaScriptContentWorld&) = delete;
+
+  // Creates a content world for features which will interact with the page
+  // content world shared by the webpage's JavaScript.
+  explicit JavaScriptContentWorld(
+      WKUserContentController* user_content_controller);
+
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+  // Creates a content world for features which will interact with the given
+  // |content_world|.
+  JavaScriptContentWorld(WKUserContentController* user_content_controller,
+                         WKContentWorld* content_world)
+      API_AVAILABLE(ios(14.0));
+#endif  // defined(__IPHONE14_0)
+
+  // Adds |feature| by configuring the feature scripts and communication
+  // callbacks.
+  void AddFeature(const JavaScriptFeature* feature);
+
+  // Returns true if and only if |feature| has been added to this content world.
+  bool HasFeature(const JavaScriptFeature* feature);
+
+ private:
+  // The features which have already been configured for |content_world_|.
+  std::set<const JavaScriptFeature*> features_;
+
+  // The associated user content controller for configuring injected scripts and
+  // communication.
+  WKUserContentController* user_content_controller_ = nil;
+
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+  // The associated WKContentWorld. May be null which represents the main world
+  // which the page content itself uses. (The same content world can also be
+  // represented by [WKContentWorld pageWorld] on iOS 14 and later.)
+  WKContentWorld* content_world_ API_AVAILABLE(ios(14.0)) = nullptr;
+#endif  // defined(__IPHONE14_0)
+};
+
+}  // namespace web
+
+#endif  // IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_CONTENT_WORLD_H_
diff --git a/ios/web/js_messaging/java_script_content_world.mm b/ios/web/js_messaging/java_script_content_world.mm
new file mode 100644
index 0000000..60047e2
--- /dev/null
+++ b/ios/web/js_messaging/java_script_content_world.mm
@@ -0,0 +1,109 @@
+// 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.
+
+#import "ios/web/js_messaging/java_script_content_world.h"
+
+#include "base/check_op.h"
+#include "base/notreached.h"
+#include "ios/web/public/js_messaging/java_script_feature.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace web {
+
+namespace {
+
+WKUserScriptInjectionTime InjectionTimeToWKUserScriptInjectionTime(
+    JavaScriptFeature::FeatureScript::InjectionTime injection_time) {
+  switch (injection_time) {
+    case JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart:
+      return WKUserScriptInjectionTimeAtDocumentStart;
+    case JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd:
+      return WKUserScriptInjectionTimeAtDocumentEnd;
+  }
+  NOTREACHED();
+  return WKUserScriptInjectionTimeAtDocumentStart;
+}
+
+}  // namespace
+
+JavaScriptContentWorld::JavaScriptContentWorld(
+    WKUserContentController* user_content_controller)
+    : user_content_controller_(user_content_controller) {}
+
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+JavaScriptContentWorld::JavaScriptContentWorld(
+    WKUserContentController* user_content_controller,
+    WKContentWorld* content_world)
+    : user_content_controller_(user_content_controller),
+      content_world_(content_world) {}
+#endif  // defined(__IPHONE14_0)
+
+JavaScriptContentWorld::~JavaScriptContentWorld() {}
+
+bool JavaScriptContentWorld::HasFeature(const JavaScriptFeature* feature) {
+  return features_.find(feature) != features_.end();
+}
+
+void JavaScriptContentWorld::AddFeature(const JavaScriptFeature* feature) {
+  if (HasFeature(feature)) {
+    // |feature| has already been added to this content world.
+    return;
+  }
+
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+  if (@available(iOS 14, *)) {
+    // Ensure |feature| supports this content world.
+    if (content_world_ && content_world_ != WKContentWorld.pageWorld) {
+      DCHECK_EQ(feature->GetSupportedContentWorld(),
+                JavaScriptFeature::ContentWorld::kAnyContentWorld);
+    }
+  }
+#endif  // defined(__IPHONE14_0)
+
+  features_.insert(feature);
+
+  // Add dependent features first.
+  for (const JavaScriptFeature* dep_feature : feature->GetDependentFeatures()) {
+    AddFeature(dep_feature);
+  }
+
+  // Setup user scripts.
+  for (const JavaScriptFeature::FeatureScript& feature_script :
+       feature->GetScripts()) {
+    WKUserScriptInjectionTime injection_time =
+        InjectionTimeToWKUserScriptInjectionTime(
+            feature_script.GetInjectionTime());
+
+    bool main_frame_only =
+        feature_script.GetTargetFrames() !=
+        JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
+
+    WKUserScript* user_script = nil;
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+    if (@available(iOS 14, *)) {
+      if (content_world_) {
+        user_script = [[WKUserScript alloc]
+              initWithSource:feature_script.GetScriptString()
+               injectionTime:injection_time
+            forMainFrameOnly:main_frame_only
+              inContentWorld:content_world_];
+      }
+    }
+#endif  // defined(__IPHONE14_0)
+
+    if (!user_script) {
+      user_script =
+          [[WKUserScript alloc] initWithSource:feature_script.GetScriptString()
+                                 injectionTime:injection_time
+                              forMainFrameOnly:main_frame_only];
+    }
+
+    [user_content_controller_ addUserScript:user_script];
+  }
+}
+
+}  // namespace web
diff --git a/ios/web/js_messaging/java_script_content_world_unittest.mm b/ios/web/js_messaging/java_script_content_world_unittest.mm
new file mode 100644
index 0000000..2cb638e6
--- /dev/null
+++ b/ios/web/js_messaging/java_script_content_world_unittest.mm
@@ -0,0 +1,44 @@
+// 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.
+
+#import "ios/web/js_messaging/java_script_content_world.h"
+
+#import "ios/web/public/js_messaging/java_script_feature.h"
+#import "testing/gtest_mac.h"
+#import "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+typedef PlatformTest JavaScriptContentWorldTest;
+
+// Tests adding a JavaScriptFeature to a JavaScriptContentWorld.
+TEST_F(JavaScriptContentWorldTest, AddFeature) {
+  WKUserContentController* user_content_controller =
+      [[WKUserContentController alloc] init];
+  web::JavaScriptContentWorld world(user_content_controller);
+
+  const web::JavaScriptFeature& feature = web::JavaScriptFeature(
+      web::JavaScriptFeature::ContentWorld::kAnyContentWorld, {});
+  world.AddFeature(&feature);
+  EXPECT_TRUE(world.HasFeature(&feature));
+}
+
+// Tests adding a JavaScriptFeature to a specific JavaScriptContentWorld.
+TEST_F(JavaScriptContentWorldTest, AddFeatureToSpecificWKContentWorld) {
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+  if (@available(iOS 14, *)) {
+    WKUserContentController* user_content_controller =
+        [[WKUserContentController alloc] init];
+    web::JavaScriptContentWorld world(user_content_controller,
+                                      [WKContentWorld defaultClientWorld]);
+
+    const web::JavaScriptFeature& feature = web::JavaScriptFeature(
+        web::JavaScriptFeature::ContentWorld::kAnyContentWorld, {});
+    world.AddFeature(&feature);
+    EXPECT_TRUE(world.HasFeature(&feature));
+  }
+#endif  // defined(__IPHONE14_0)
+}
diff --git a/ios/web/js_messaging/java_script_feature.mm b/ios/web/js_messaging/java_script_feature.mm
new file mode 100644
index 0000000..ec40110
--- /dev/null
+++ b/ios/web/js_messaging/java_script_feature.mm
@@ -0,0 +1,103 @@
+// 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 "ios/web/public/js_messaging/java_script_feature.h"
+
+#import <Foundation/Foundation.h>
+
+#include "base/bind.h"
+#import "base/strings/sys_string_conversions.h"
+#import "ios/web/js_messaging/java_script_content_world.h"
+#import "ios/web/js_messaging/java_script_feature_manager.h"
+#include "ios/web/js_messaging/page_script_util.h"
+#include "ios/web/js_messaging/web_frame_impl.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// Returns a JavaScript safe string based on |script_filename|. This is used as
+// a unique identifier for a given script and passed to
+// |MakeScriptInjectableOnce| which ensures JS isn't executed multiple times due
+// to duplicate injection.
+NSString* InjectionTokenForScript(NSString* script_filename) {
+  NSMutableCharacterSet* validCharacters =
+      [NSMutableCharacterSet alphanumericCharacterSet];
+  [validCharacters addCharactersInString:@"$_"];
+  NSCharacterSet* invalidCharacters = validCharacters.invertedSet;
+  NSString* token =
+      [script_filename stringByTrimmingCharactersInSet:invalidCharacters];
+  DCHECK_GT(token.length, 0ul);
+  return token;
+}
+
+}  // namespace
+
+namespace web {
+
+#pragma mark - JavaScriptFeature::FeatureScript
+
+JavaScriptFeature::FeatureScript
+JavaScriptFeature::FeatureScript::CreateWithFilename(
+    const std::string& filename,
+    InjectionTime injection_time,
+    TargetFrames target_frames) {
+  return JavaScriptFeature::FeatureScript(filename, injection_time,
+                                          target_frames);
+}
+
+JavaScriptFeature::FeatureScript::FeatureScript(const std::string& filename,
+                                                InjectionTime injection_time,
+                                                TargetFrames target_frames)
+    : script_filename_(filename),
+      injection_time_(injection_time),
+      target_frames_(target_frames) {}
+
+JavaScriptFeature::FeatureScript::~FeatureScript() = default;
+
+NSString* JavaScriptFeature::FeatureScript::GetScriptString() const {
+  NSString* script_filename = base::SysUTF8ToNSString(script_filename_);
+  NSString* injection_token = InjectionTokenForScript(script_filename);
+  return MakeScriptInjectableOnce(injection_token,
+                                  GetPageScript(script_filename));
+}
+
+#pragma mark - JavaScriptFeature
+
+JavaScriptFeature::JavaScriptFeature(ContentWorld supported_world)
+    : supported_world_(supported_world) {}
+
+JavaScriptFeature::JavaScriptFeature(
+    ContentWorld supported_world,
+    std::vector<const FeatureScript> feature_scripts)
+    : supported_world_(supported_world), scripts_(feature_scripts) {}
+
+JavaScriptFeature::JavaScriptFeature(
+    ContentWorld supported_world,
+    std::vector<const FeatureScript> feature_scripts,
+    std::vector<const JavaScriptFeature*> dependent_features)
+    : supported_world_(supported_world),
+      scripts_(feature_scripts),
+      dependent_features_(dependent_features) {}
+
+JavaScriptFeature::~JavaScriptFeature() = default;
+
+JavaScriptFeature::ContentWorld JavaScriptFeature::GetSupportedContentWorld()
+    const {
+  return supported_world_;
+}
+
+const std::vector<const JavaScriptFeature::FeatureScript>
+JavaScriptFeature::GetScripts() const {
+  return scripts_;
+}
+
+const std::vector<const JavaScriptFeature*>
+JavaScriptFeature::GetDependentFeatures() const {
+  return dependent_features_;
+}
+
+}  // namespace web
diff --git a/ios/web/js_messaging/java_script_feature_manager.h b/ios/web/js_messaging/java_script_feature_manager.h
new file mode 100644
index 0000000..a957065
--- /dev/null
+++ b/ios/web/js_messaging/java_script_feature_manager.h
@@ -0,0 +1,57 @@
+// 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 IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_FEATURE_MANAGER_H_
+#define IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_FEATURE_MANAGER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/supports_user_data.h"
+#import "ios/web/js_messaging/java_script_content_world.h"
+
+@class WKUserContentController;
+
+namespace web {
+
+class BrowserState;
+class JavaScriptFeature;
+
+// Configures JavaScriptFeatures for |browser_state|. The features will be
+// added to either |page_content_world_| or |isolated_world_|  based on
+// JavaScriptFeature::GetSupportedContentWorld() and the operating system of the
+// user's device (which determines if isolated worlds are supported).
+class JavaScriptFeatureManager : public base::SupportsUserData::Data {
+ public:
+  ~JavaScriptFeatureManager() override;
+
+  // Returns the JavaScriptFeatureManager associated with |browser_state.|
+  // If a JavaScriptFeatureManager does not already exist, one will be created
+  // and associated with |browser_state|. |browser_state| must not be null.
+  static JavaScriptFeatureManager* FromBrowserState(
+      BrowserState* browser_state);
+
+  // Configures |features| on |user_content_controller_| by adding user scripts
+  // and script message handlers.
+  // NOTE: |page_content_world_| and |isolated_world_| will be recreated.
+  void ConfigureFeatures(std::vector<JavaScriptFeature*> features);
+
+  JavaScriptFeatureManager(const JavaScriptFeatureManager&) = delete;
+  JavaScriptFeatureManager& operator=(const JavaScriptFeatureManager&) = delete;
+
+ private:
+  JavaScriptFeatureManager(WKUserContentController* user_content_controller);
+
+  WKUserContentController* user_content_controller_ = nullptr;
+
+  // The content world shared with the page content JavaScript.
+  std::unique_ptr<JavaScriptContentWorld> page_content_world_;
+  // A content world isolated from the page content JavaScript for application
+  // JavaScript execution.
+  std::unique_ptr<JavaScriptContentWorld> isolated_world_;
+};
+
+}  // namespace web
+
+#endif  // IOS_WEB_JS_MESSAGING_JAVA_SCRIPT_FEATURE_MANAGER_H_
diff --git a/ios/web/js_messaging/java_script_feature_manager.mm b/ios/web/js_messaging/java_script_feature_manager.mm
new file mode 100644
index 0000000..8ee5888a
--- /dev/null
+++ b/ios/web/js_messaging/java_script_feature_manager.mm
@@ -0,0 +1,76 @@
+// 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.
+
+#import "ios/web/js_messaging/java_script_feature_manager.h"
+
+#import <WebKit/WebKit.h>
+
+#include "base/ios/ios_util.h"
+#import "ios/web/public/browser_state.h"
+#import "ios/web/public/js_messaging/java_script_feature.h"
+#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// Key used to associate a JavaScriptFeatureManager instances with a
+// BrowserState.
+const char kWebJavaScriptFeatureManagerKeyName[] =
+    "web_java_script_feature_manager";
+
+}  // namespace
+
+namespace web {
+
+JavaScriptFeatureManager::JavaScriptFeatureManager(
+    WKUserContentController* user_content_controller)
+    : user_content_controller_(user_content_controller) {}
+JavaScriptFeatureManager::~JavaScriptFeatureManager() {}
+
+JavaScriptFeatureManager* JavaScriptFeatureManager::FromBrowserState(
+    BrowserState* browser_state) {
+  DCHECK(browser_state);
+
+  JavaScriptFeatureManager* feature_manager =
+      static_cast<JavaScriptFeatureManager*>(
+          browser_state->GetUserData(kWebJavaScriptFeatureManagerKeyName));
+  if (!feature_manager) {
+    WKWebViewConfigurationProvider& configuration_provider =
+        WKWebViewConfigurationProvider::FromBrowserState(browser_state);
+    WKUserContentController* user_content_controller =
+        configuration_provider.GetWebViewConfiguration().userContentController;
+    feature_manager = new JavaScriptFeatureManager(user_content_controller);
+    browser_state->SetUserData(kWebJavaScriptFeatureManagerKeyName,
+                               base::WrapUnique(feature_manager));
+  }
+  return feature_manager;
+}
+
+void JavaScriptFeatureManager::ConfigureFeatures(
+    std::vector<JavaScriptFeature*> features) {
+  page_content_world_ =
+      std::make_unique<JavaScriptContentWorld>(user_content_controller_);
+
+#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
+  if (@available(iOS 14, *)) {
+    isolated_world_ = std::make_unique<JavaScriptContentWorld>(
+        user_content_controller_, WKContentWorld.defaultClientWorld);
+  }
+#endif  // defined(__IPHONE14_0)
+
+  for (JavaScriptFeature* feature : features) {
+    if (isolated_world_ &&
+        feature->GetSupportedContentWorld() ==
+            JavaScriptFeature::ContentWorld::kAnyContentWorld) {
+      isolated_world_->AddFeature(feature);
+    } else {
+      page_content_world_->AddFeature(feature);
+    }
+  }
+}
+
+}  // namespace web
diff --git a/ios/web/js_messaging/java_script_feature_unittest.mm b/ios/web/js_messaging/java_script_feature_unittest.mm
new file mode 100644
index 0000000..97f2be643
--- /dev/null
+++ b/ios/web/js_messaging/java_script_feature_unittest.mm
@@ -0,0 +1,103 @@
+// 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.
+
+#import "ios/web/public/js_messaging/java_script_feature.h"
+
+#import "testing/gtest_mac.h"
+#import "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+typedef PlatformTest JavaScriptFeatureTest;
+
+// Tests the creation of FeatureScripts.
+TEST_F(JavaScriptFeatureTest, CreateFeatureScript) {
+  auto document_start_injection_time =
+      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
+  auto target_frames_all =
+      web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
+  auto feature_script =
+      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
+          "base_js", document_start_injection_time, target_frames_all);
+
+  EXPECT_EQ(document_start_injection_time, feature_script.GetInjectionTime());
+  EXPECT_EQ(target_frames_all, feature_script.GetTargetFrames());
+  EXPECT_TRUE([feature_script.GetScriptString() containsString:@"__gCrWeb"]);
+
+  auto document_end_injection_time =
+      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd;
+  auto target_frames_main =
+      web::JavaScriptFeature::FeatureScript::TargetFrames::kMainFrame;
+  auto feature_script2 =
+      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
+          "common_js", document_end_injection_time, target_frames_main);
+
+  EXPECT_EQ(document_end_injection_time, feature_script2.GetInjectionTime());
+  EXPECT_EQ(target_frames_main, feature_script2.GetTargetFrames());
+  EXPECT_TRUE(
+      [feature_script2.GetScriptString() containsString:@"__gCrWeb.common"]);
+}
+
+// Tests creating a JavaScriptFeature.
+TEST_F(JavaScriptFeatureTest, CreateFeature) {
+  auto document_start_injection_time =
+      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
+  auto target_frames_all =
+      web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
+  const web::JavaScriptFeature::FeatureScript feature_script =
+      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
+          "base_js", document_start_injection_time, target_frames_all);
+
+  auto any_content_world =
+      web::JavaScriptFeature::ContentWorld::kAnyContentWorld;
+  web::JavaScriptFeature feature(any_content_world, {feature_script});
+
+  EXPECT_EQ(any_content_world, feature.GetSupportedContentWorld());
+  EXPECT_EQ(0ul, feature.GetDependentFeatures().size());
+
+  auto feature_scripts = feature.GetScripts();
+  ASSERT_EQ(1ul, feature_scripts.size());
+  EXPECT_NSEQ(feature_script.GetScriptString(),
+              feature_scripts[0].GetScriptString());
+}
+
+// Tests creating a JavaScriptFeature which relies on a dependent feature.
+TEST_F(JavaScriptFeatureTest, CreateFeatureWithDependentFeature) {
+  auto document_start_injection_time =
+      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentStart;
+  auto target_frames_all =
+      web::JavaScriptFeature::FeatureScript::TargetFrames::kAllFrames;
+  const web::JavaScriptFeature::FeatureScript dependent_feature_script =
+      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
+          "base_js", document_start_injection_time, target_frames_all);
+
+  auto document_end_injection_time =
+      web::JavaScriptFeature::FeatureScript::InjectionTime::kDocumentEnd;
+  auto target_frames_main =
+      web::JavaScriptFeature::FeatureScript::TargetFrames::kMainFrame;
+  const web::JavaScriptFeature::FeatureScript feature_script =
+      web::JavaScriptFeature::FeatureScript::CreateWithFilename(
+          "common_js", document_end_injection_time, target_frames_main);
+
+  auto page_content_world =
+      web::JavaScriptFeature::ContentWorld::kPageContentWorld;
+  web::JavaScriptFeature dependent_feature(page_content_world,
+                                           {dependent_feature_script});
+  web::JavaScriptFeature feature(page_content_world, {feature_script},
+                                 {&dependent_feature});
+  EXPECT_EQ(page_content_world, feature.GetSupportedContentWorld());
+
+  auto feature_scripts = feature.GetScripts();
+  ASSERT_EQ(1ul, feature_scripts.size());
+  auto dependent_features = feature.GetDependentFeatures();
+  ASSERT_EQ(1ul, dependent_features.size());
+  auto dependent_feature_scripts = dependent_features[0]->GetScripts();
+  ASSERT_EQ(1ul, dependent_feature_scripts.size());
+  EXPECT_NSEQ(feature_script.GetScriptString(),
+              feature_scripts[0].GetScriptString());
+  EXPECT_NSEQ(dependent_feature_script.GetScriptString(),
+              dependent_feature_scripts[0].GetScriptString());
+}
diff --git a/ios/web/js_messaging/page_script_util.h b/ios/web/js_messaging/page_script_util.h
index 280e81f..ba17052 100644
--- a/ios/web/js_messaging/page_script_util.h
+++ b/ios/web/js_messaging/page_script_util.h
@@ -15,6 +15,20 @@
 // bundled resource file with the given name (excluding extension).
 NSString* GetPageScript(NSString* script_file_name);
 
+// Make sure that script is injected only once. For example, content of
+// WKUserScript can be injected into the same page multiple times
+// without notifying WKNavigationDelegate (e.g. after window.document.write
+// JavaScript call). Injecting the script multiple times invalidates the
+// __gCrWeb.windowId variable and will break the ability to send messages from
+// JS to the native code. Wrapping injected script into "if (!injected)" check
+// prevents multiple injections into the same page. |script_identifier| should
+// identify the script being injected in order to enforce the injection of
+// |script| to only once.
+// NOTE: |script_identifier| will be used as the suffix for a JavaScript var, so
+// it must adhere to JavaScript var naming rules.
+NSString* MakeScriptInjectableOnce(NSString* script_identifier,
+                                   NSString* script);
+
 // Returns an autoreleased string containing the JavaScript to be injected into
 // the main frame of the web view as early as possible.
 NSString* GetDocumentStartScriptForMainFrame(BrowserState* browser_state);
diff --git a/ios/web/js_messaging/page_script_util.mm b/ios/web/js_messaging/page_script_util.mm
index 1176af3..68777aa 100644
--- a/ios/web/js_messaging/page_script_util.mm
+++ b/ios/web/js_messaging/page_script_util.mm
@@ -18,29 +18,6 @@
 
 namespace {
 
-// Make sure that script is injected only once. For example, content of
-// WKUserScript can be injected into the same page multiple times
-// without notifying WKNavigationDelegate (e.g. after window.document.write
-// JavaScript call). Injecting the script multiple times invalidates the
-// __gCrWeb.windowId variable and will break the ability to send messages from
-// JS to the native code. Wrapping injected script into "if (!injected)" check
-// prevents multiple injections into the same page. |script_identifier| should
-// identify the script being injected in order to enforce the injection of
-// |script| to only once.
-// NOTE: |script_identifier| will be used as the prefix for a JavaScript var, so
-// it must adhere to JavaScript var naming rules.
-NSString* MakeScriptInjectableOnce(NSString* script_identifier,
-                                   NSString* script) {
-  NSString* kOnceWrapperTemplate =
-      @"if (typeof %@ === 'undefined') { var %@ = true; %%@ }";
-  NSString* injected_var_name =
-      [NSString stringWithFormat:@"%@_injected", script_identifier];
-  NSString* once_wrapper =
-      [NSString stringWithFormat:kOnceWrapperTemplate, injected_var_name,
-                                 injected_var_name];
-  return [NSString stringWithFormat:once_wrapper, script];
-}
-
 // Returns a string with \ and ' escaped.
 // This is used instead of GetQuotedJSONString because that will convert
 // UTF-16 to UTF-8, which can cause problems when injecting scripts depending
@@ -72,6 +49,18 @@
   return content;
 }
 
+NSString* MakeScriptInjectableOnce(NSString* script_identifier,
+                                   NSString* script) {
+  NSString* kOnceWrapperTemplate =
+      @"if (typeof %@ === 'undefined') { var %@ = true; %%@ }";
+  NSString* injected_var_name =
+      [NSString stringWithFormat:@"_injected_%@", script_identifier];
+  NSString* once_wrapper =
+      [NSString stringWithFormat:kOnceWrapperTemplate, injected_var_name,
+                                 injected_var_name];
+  return [NSString stringWithFormat:once_wrapper, script];
+}
+
 NSString* GetDocumentStartScriptForMainFrame(BrowserState* browser_state) {
   DCHECK(GetWebClient());
   NSString* embedder_page_script =
diff --git a/ios/web/js_messaging/page_script_util_unittest.mm b/ios/web/js_messaging/page_script_util_unittest.mm
index dcf6579..f92c1d5 100644
--- a/ios/web/js_messaging/page_script_util_unittest.mm
+++ b/ios/web/js_messaging/page_script_util_unittest.mm
@@ -24,6 +24,20 @@
 using base::test::ios::WaitUntilConditionOrTimeout;
 using base::test::ios::kWaitForPageLoadTimeout;
 
+namespace {
+
+void AddSharedScriptsToWebView(WKWebView* web_view) {
+  // Scripts must be all injected at once because as soon as __gCrWeb exists,
+  // injection is assumed to be done and __gCrWeb.message is used.
+  NSString* scripts = [NSString
+      stringWithFormat:@"%@; %@; %@", web::test::GetPageScript(@"base_js"),
+                       web::test::GetPageScript(@"common_js"),
+                       web::test::GetPageScript(@"message_js")];
+  web::test::ExecuteJavaScript(web_view, scripts);
+}
+
+}  // namespace
+
 namespace web {
 namespace {
 
@@ -37,10 +51,26 @@
   }
 };
 
+// Tests that |MakeScriptInjectableOnce| prevents a script from being injected
+// twice.
+TEST_F(PageScriptUtilTest, MakeScriptInjectableOnce) {
+  WKWebView* web_view = BuildWKWebView(CGRectZero, GetBrowserState());
+  NSString* identifier = @"script_id";
+
+  test::ExecuteJavaScript(
+      web_view, MakeScriptInjectableOnce(identifier, @"var value = 1;"));
+  EXPECT_NSEQ(@(1), test::ExecuteJavaScript(web_view, @"value"));
+
+  test::ExecuteJavaScript(web_view,
+                          MakeScriptInjectableOnce(identifier, @"value = 2;"));
+  EXPECT_NSEQ(@(1), test::ExecuteJavaScript(web_view, @"value"));
+}
+
 // Tests that WKWebView early page script is a valid script that injects global
 // __gCrWeb object.
 TEST_F(PageScriptUtilTest, WKWebViewEarlyPageScript) {
   WKWebView* web_view = BuildWKWebView(CGRectZero, GetBrowserState());
+  AddSharedScriptsToWebView(web_view);
   test::ExecuteJavaScript(
       web_view, GetDocumentStartScriptForAllFrames(GetBrowserState()));
   EXPECT_NSEQ(@"object", test::ExecuteJavaScript(web_view, @"typeof __gCrWeb"));
@@ -50,6 +80,7 @@
 TEST_F(PageScriptUtilTest, WKEmbedderScript) {
   GetWebClient()->SetEarlyPageScript(@"__gCrEmbedder = {};");
   WKWebView* web_view = BuildWKWebView(CGRectZero, GetBrowserState());
+  AddSharedScriptsToWebView(web_view);
   test::ExecuteJavaScript(
       web_view, GetDocumentStartScriptForAllFrames(GetBrowserState()));
   test::ExecuteJavaScript(
diff --git a/ios/web/public/js_messaging/BUILD.gn b/ios/web/public/js_messaging/BUILD.gn
index 4ce249f..f884da7 100644
--- a/ios/web/public/js_messaging/BUILD.gn
+++ b/ios/web/public/js_messaging/BUILD.gn
@@ -10,6 +10,7 @@
   ]
 
   sources = [
+    "java_script_feature.h",
     "web_frame.h",
     "web_frame_user_data.h",
     "web_frame_util.h",
diff --git a/ios/web/public/js_messaging/java_script_feature.h b/ios/web/public/js_messaging/java_script_feature.h
new file mode 100644
index 0000000..b1049ef1
--- /dev/null
+++ b/ios/web/public/js_messaging/java_script_feature.h
@@ -0,0 +1,105 @@
+// 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 IOS_WEB_PUBLIC_JS_MESSAGING_JAVA_SCRIPT_FEATURE_H_
+#define IOS_WEB_PUBLIC_JS_MESSAGING_JAVA_SCRIPT_FEATURE_H_
+
+#include <string>
+#include <vector>
+
+@class NSString;
+
+namespace web {
+
+// Describes a feature implemented in Javascript and native<->JS communication
+// (if any). It is intended to be instantiated directly for simple features
+// requiring injection only, but should subclassed into feature specific classes
+// to handle JS<->native communication.
+// NOTE: As implemented within //ios/web, JavaScriptFeature instances holds no
+// state itself and can be used application-wide across browser states. However,
+// this is not guaranteed of JavaScriptFeature subclasses.
+class JavaScriptFeature {
+ public:
+  // The content world which this feature supports.
+  // NOTE: Features should use kAnyContentWorld whenever possible to allow for
+  // isolation between the feature and the loaded webpage JavaScript.
+  enum class ContentWorld {
+    // Represents any content world.
+    kAnyContentWorld = 0,
+    // Represents the page content world which is shared by the JavaScript of
+    // the webpage. This value should only be used if the feature provides
+    // JavaScript which needs to be accessible to the client JavaScript. For
+    // example, JavaScript polyfills.
+    kPageContentWorld,
+  };
+
+  // A script to be injected into webpage frames which support this feature.
+  class FeatureScript {
+   public:
+    // The time at which this script will be injected into the page.
+    enum class InjectionTime {
+      kDocumentStart = 0,
+      kDocumentEnd,
+    };
+
+    // The frames which this script will be injected into.
+    enum class TargetFrames {
+      kAllFrames = 0,
+      kMainFrame,
+    };
+
+    // Creates a FeatureScript with the script file from the application bundle
+    // with |filename| to be injected at |injection_time| into |target_frames|.
+    static FeatureScript CreateWithFilename(const std::string& filename,
+                                            InjectionTime injection_time,
+                                            TargetFrames target_frames);
+
+    // Returns the JavaScript string of the script with |script_filename_|.
+    NSString* GetScriptString() const;
+
+    InjectionTime GetInjectionTime() const { return injection_time_; }
+    TargetFrames GetTargetFrames() const { return target_frames_; }
+
+    ~FeatureScript();
+
+   private:
+    FeatureScript(const std::string& filename,
+                  InjectionTime injection_time,
+                  TargetFrames target_frames);
+
+    std::string script_filename_;
+    InjectionTime injection_time_;
+    TargetFrames target_frames_;
+  };
+
+  JavaScriptFeature(ContentWorld supported_world,
+                    std::vector<const FeatureScript> feature_scripts);
+  JavaScriptFeature(ContentWorld supported_world,
+                    std::vector<const FeatureScript> feature_scripts,
+                    std::vector<const JavaScriptFeature*> dependent_features);
+  virtual ~JavaScriptFeature();
+
+  // Returns the supported content world for this feature.
+  ContentWorld GetSupportedContentWorld() const;
+
+  // Returns a vector of scripts used by this feature.
+  virtual const std::vector<const FeatureScript> GetScripts() const;
+  // Returns a vector of features which this one depends upon being available.
+  virtual const std::vector<const JavaScriptFeature*> GetDependentFeatures()
+      const;
+
+  JavaScriptFeature(const JavaScriptFeature&) = delete;
+
+ protected:
+  explicit JavaScriptFeature(ContentWorld supported_world);
+
+ private:
+  ContentWorld supported_world_;
+  std::vector<const FeatureScript> scripts_;
+  std::vector<const JavaScriptFeature*> dependent_features_;
+};
+
+}  // namespace web
+
+#endif  // IOS_WEB_PUBLIC_JS_MESSAGING_JAVA_SCRIPT_FEATURE_H_
diff --git a/ios/web/web_state/ui/BUILD.gn b/ios/web/web_state/ui/BUILD.gn
index 6c03c00..7fbb38d 100644
--- a/ios/web/web_state/ui/BUILD.gn
+++ b/ios/web/web_state/ui/BUILD.gn
@@ -117,7 +117,14 @@
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
+source_set("wk_web_view_configuration_provider_header") {
+  deps = [ "//base" ]
+  sources = [ "wk_web_view_configuration_provider.h" ]
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
 source_set("wk_web_view_configuration_provider") {
+  public_deps = [ ":wk_web_view_configuration_provider_header" ]
   deps = [
     "//base",
     "//components/safe_browsing/core:features",
@@ -134,7 +141,6 @@
     "wk_content_rule_list_provider.mm",
     "wk_content_rule_list_util.h",
     "wk_content_rule_list_util.mm",
-    "wk_web_view_configuration_provider.h",
     "wk_web_view_configuration_provider.mm",
     "wk_web_view_configuration_provider_observer.h",
   ]
diff --git a/media/capture/video/chromeos/camera_app_device_bridge_impl.cc b/media/capture/video/chromeos/camera_app_device_bridge_impl.cc
index e71efb4..77b0675 100644
--- a/media/capture/video/chromeos/camera_app_device_bridge_impl.cc
+++ b/media/capture/video/chromeos/camera_app_device_bridge_impl.cc
@@ -39,6 +39,11 @@
   camera_info_getter_ = std::move(camera_info_getter);
 }
 
+void CameraAppDeviceBridgeImpl::SetVirtualDeviceController(
+    VirtualDeviceController virtual_device_controller) {
+  virtual_device_controller_ = std::move(virtual_device_controller);
+}
+
 void CameraAppDeviceBridgeImpl::UnsetCameraInfoGetter() {
   camera_info_getter_ = {};
 }
@@ -81,4 +86,12 @@
   std::move(callback).Run(is_supported_);
 }
 
+void CameraAppDeviceBridgeImpl::SetMultipleStreamsEnabled(
+    const std::string& device_id,
+    bool enabled,
+    SetMultipleStreamsEnabledCallback callback) {
+  virtual_device_controller_.Run(device_id, enabled);
+  std::move(callback).Run(true);
+}
+
 }  // namespace media
diff --git a/media/capture/video/chromeos/camera_app_device_bridge_impl.h b/media/capture/video/chromeos/camera_app_device_bridge_impl.h
index 42a1972d..f388166 100644
--- a/media/capture/video/chromeos/camera_app_device_bridge_impl.h
+++ b/media/capture/video/chromeos/camera_app_device_bridge_impl.h
@@ -21,6 +21,8 @@
  public:
   using CameraInfoGetter =
       base::RepeatingCallback<cros::mojom::CameraInfoPtr(const std::string&)>;
+  using VirtualDeviceController =
+      base::RepeatingCallback<void(const std::string&, bool)>;
 
   CameraAppDeviceBridgeImpl();
 
@@ -37,6 +39,9 @@
 
   void UnsetCameraInfoGetter();
 
+  void SetVirtualDeviceController(
+      VirtualDeviceController virtual_device_controller);
+
   CameraAppDeviceImpl* GetCameraAppDevice(const std::string& device_id);
 
   // cros::mojom::CameraAppDeviceBridge implementations.
@@ -45,6 +50,11 @@
 
   void IsSupported(IsSupportedCallback callback) override;
 
+  void SetMultipleStreamsEnabled(
+      const std::string& device_id,
+      bool enabled,
+      SetMultipleStreamsEnabledCallback callback) override;
+
  private:
   CameraAppDeviceImpl* CreateCameraAppDevice(const std::string& device_id);
 
@@ -52,6 +62,8 @@
 
   CameraInfoGetter camera_info_getter_;
 
+  VirtualDeviceController virtual_device_controller_;
+
   mojo::ReceiverSet<cros::mojom::CameraAppDeviceBridge> receivers_;
 
   base::flat_map<std::string, std::unique_ptr<media::CameraAppDeviceImpl>>
@@ -62,4 +74,4 @@
 
 }  // namespace media
 
-#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_CAMERA_APP_DEVICE_BRIDGE_IMPL_H_
\ No newline at end of file
+#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_CAMERA_APP_DEVICE_BRIDGE_IMPL_H_
diff --git a/media/capture/video/chromeos/camera_app_device_provider_impl.cc b/media/capture/video/chromeos/camera_app_device_provider_impl.cc
index 0cca221..53defd2 100644
--- a/media/capture/video/chromeos/camera_app_device_provider_impl.cc
+++ b/media/capture/video/chromeos/camera_app_device_provider_impl.cc
@@ -54,4 +54,27 @@
   bridge_->IsSupported(std::move(callback));
 }
 
-}  // namespace media
\ No newline at end of file
+void CameraAppDeviceProviderImpl::SetMultipleStreamsEnabled(
+    const std::string& source_id,
+    bool enabled,
+    SetMultipleStreamsEnabledCallback callback) {
+  mapping_callback_.Run(
+      source_id,
+      media::BindToCurrentLoop(base::BindOnce(
+          &CameraAppDeviceProviderImpl::SetMultipleStreamsEnabledWithDeviceId,
+          weak_ptr_factory_.GetWeakPtr(), enabled, std::move(callback))));
+}
+
+void CameraAppDeviceProviderImpl::SetMultipleStreamsEnabledWithDeviceId(
+    bool enabled,
+    SetMultipleStreamsEnabledCallback callback,
+    const base::Optional<std::string>& device_id) {
+  if (!device_id.has_value()) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  bridge_->SetMultipleStreamsEnabled(*device_id, enabled, std::move(callback));
+}
+
+}  // namespace media
diff --git a/media/capture/video/chromeos/camera_app_device_provider_impl.h b/media/capture/video/chromeos/camera_app_device_provider_impl.h
index 615d6bdba..6bf036e 100644
--- a/media/capture/video/chromeos/camera_app_device_provider_impl.h
+++ b/media/capture/video/chromeos/camera_app_device_provider_impl.h
@@ -32,12 +32,21 @@
   void GetCameraAppDevice(const std::string& source_id,
                           GetCameraAppDeviceCallback callback) override;
   void IsSupported(IsSupportedCallback callback) override;
+  void SetMultipleStreamsEnabled(
+      const std::string& device_id,
+      bool enabled,
+      SetMultipleStreamsEnabledCallback callback) override;
 
  private:
   void GetCameraAppDeviceWithDeviceId(
       GetCameraAppDeviceCallback callback,
       const base::Optional<std::string>& device_id);
 
+  void SetMultipleStreamsEnabledWithDeviceId(
+      bool enable,
+      SetMultipleStreamsEnabledCallback callback,
+      const base::Optional<std::string>& device_id);
+
   mojo::Remote<cros::mojom::CameraAppDeviceBridge> bridge_;
 
   DeviceIdMappingCallback mapping_callback_;
@@ -51,4 +60,4 @@
 
 }  // namespace media
 
-#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_CAMERA_APP_DEVICE_PROVIDER_IMPL_H_
\ No newline at end of file
+#endif  // MEDIA_CAPTURE_VIDEO_CHROMEOS_CAMERA_APP_DEVICE_PROVIDER_IMPL_H_
diff --git a/media/capture/video/chromeos/camera_hal_delegate.cc b/media/capture/video/chromeos/camera_hal_delegate.cc
index 903c535..b4b8cd48 100644
--- a/media/capture/video/chromeos/camera_hal_delegate.cc
+++ b/media/capture/video/chromeos/camera_hal_delegate.cc
@@ -19,6 +19,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
 #include "base/system/system_monitor.h"
 #include "base/unguessable_token.h"
 #include "media/capture/video/chromeos/camera_app_device_bridge_impl.h"
@@ -33,6 +34,7 @@
 namespace {
 
 constexpr int32_t kDefaultFps = 30;
+constexpr char kVirtualPrefix[] = "VIRTUAL_";
 
 constexpr base::TimeDelta kEventWaitTimeoutSecs =
     base::TimeDelta::FromSeconds(1);
@@ -196,7 +198,8 @@
 
   auto* delegate = GetVCDDelegate(task_runner_for_screen_observer,
                                   device_descriptor, camera_app_device_bridge);
-  return std::make_unique<VideoCaptureDeviceChromeOSHalv3>(delegate);
+  return std::make_unique<VideoCaptureDeviceChromeOSHalv3>(delegate,
+                                                           device_descriptor);
 }
 
 void CameraHalDelegate::GetSupportedFormats(
@@ -292,6 +295,7 @@
   {
     base::AutoLock info_lock(camera_info_lock_);
     base::AutoLock id_map_lock(device_id_to_camera_id_lock_);
+    base::AutoLock virtual_lock(enable_virtual_device_lock_);
     for (const auto& it : camera_info_) {
       int camera_id = it.first;
       const cros::mojom::CameraInfoPtr& camera_info = it.second;
@@ -339,6 +343,12 @@
           // Mojo validates the input parameters for us so we don't need to
           // worry about malformed values.
         }
+        case cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_BACK:
+        case cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_FRONT:
+        case cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_EXTERNAL:
+          // |camera_info_| should not have these facing types.
+          LOG(ERROR) << "Invalid facing type: " << camera_info->facing;
+          break;
       }
       auto* vid = get_vendor_string("com.google.usb.vendorId");
       auto* pid = get_vendor_string("com.google.usb.productId");
@@ -350,9 +360,20 @@
       devices_info.emplace_back(desc);
       GetSupportedFormats(camera_info_[camera_id],
                           &devices_info.back().supported_formats);
+
+      // Create a virtual device when multiple streams are enabled.
+      if (enable_virtual_device_[camera_id]) {
+        desc.facing = VideoFacingMode::MEDIA_VIDEO_FACING_NONE;
+        desc.device_id =
+            std::string(kVirtualPrefix) + base::NumberToString(camera_id);
+        desc.set_display_name("Virtual Camera");
+        device_id_to_camera_id_[desc.device_id] = camera_id;
+        devices_info.emplace_back(desc);
+        GetSupportedFormats(camera_info_[camera_id],
+                            &devices_info.back().supported_formats);
+      }
     }
   }
-
   // TODO(shik): Report external camera first when lid is closed.
   // TODO(jcliang): Remove this after JS API supports query camera facing
   // (http://crbug.com/543997).
@@ -410,7 +431,36 @@
   if (it == camera_info_.end()) {
     return {};
   }
-  return it->second.Clone();
+  auto info = it->second.Clone();
+  if (base::StartsWith(device_id, std::string(kVirtualPrefix))) {
+    switch (it->second->facing) {
+      case cros::mojom::CameraFacing::CAMERA_FACING_BACK:
+        info->facing = cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_BACK;
+        break;
+      case cros::mojom::CameraFacing::CAMERA_FACING_FRONT:
+        info->facing = cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_FRONT;
+        break;
+      case cros::mojom::CameraFacing::CAMERA_FACING_EXTERNAL:
+        info->facing =
+            cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_EXTERNAL;
+        break;
+      default:
+        break;
+    }
+  }
+  return info;
+}
+
+void CameraHalDelegate::EnableVirtualDevice(const std::string& device_id,
+                                            bool enable) {
+  if (base::StartsWith(device_id, std::string(kVirtualPrefix))) {
+    return;
+  }
+  auto camera_id = GetCameraIdFromDeviceId(device_id);
+  if (camera_id != -1) {
+    base::AutoLock lock(enable_virtual_device_lock_);
+    enable_virtual_device_[camera_id] = enable;
+  }
 }
 
 const VendorTagInfo* CameraHalDelegate::GetVendorTagInfoByName(
diff --git a/media/capture/video/chromeos/camera_hal_delegate.h b/media/capture/video/chromeos/camera_hal_delegate.h
index a5f7a29..267b45a 100644
--- a/media/capture/video/chromeos/camera_hal_delegate.h
+++ b/media/capture/video/chromeos/camera_hal_delegate.h
@@ -93,6 +93,8 @@
 
   const VendorTagInfo* GetVendorTagInfoByName(const std::string& full_name);
 
+  void EnableVirtualDevice(const std::string& device_id, bool enable);
+
  private:
   friend class base::RefCountedThreadSafe<CameraHalDelegate>;
 
@@ -193,6 +195,10 @@
   base::Lock device_id_to_camera_id_lock_;
   std::map<std::string, int> device_id_to_camera_id_
       GUARDED_BY(device_id_to_camera_id_lock_);
+  // A virtual device is enabled/disabled for camera id.
+  base::Lock enable_virtual_device_lock_;
+  base::flat_map<int, bool> enable_virtual_device_
+      GUARDED_BY(enable_virtual_device_lock_);
 
   SEQUENCE_CHECKER(sequence_checker_);
 
@@ -214,6 +220,7 @@
   // information of vendor tags.  Bound to |ipc_task_runner_|.
   VendorTagOpsDelegate vendor_tag_ops_delegate_;
 
+  // A map from camera id to corresponding delegate instance.
   base::flat_map<int, std::unique_ptr<VideoCaptureDeviceChromeOSDelegate>>
       vcd_delegate_map_;
 
diff --git a/media/capture/video/chromeos/mojom/camera_app.mojom b/media/capture/video/chromeos/mojom/camera_app.mojom
index 7bc0da4..59df9f82 100644
--- a/media/capture/video/chromeos/mojom/camera_app.mojom
+++ b/media/capture/video/chromeos/mojom/camera_app.mojom
@@ -53,6 +53,11 @@
   // and camera app. Currently only devices running camera HAL v3 support this
   // feature.
   IsSupported() => (bool is_supported);
+
+  // Add/Remove a virtual device for recording stream according to |enabled|.
+  // The virtual device has the same config as |device_id| except facing
+  // attribute.
+  SetMultipleStreamsEnabled(string device_id, bool enabled) => (bool success);
 };
 
 // Inner interface that used to communicate between browser process (Remote) and
@@ -68,6 +73,11 @@
   // and camera app. Currently only devices running camera HAL v3 support this
   // feature.
   IsSupported() => (bool is_supported);
+
+  // Add/Remove a virtual device for recording stream according to |enabled|.
+  // The virtual device has the same config as |device_id| except facing
+  // attribute.
+  SetMultipleStreamsEnabled(string device_id, bool enabled) => (bool success);
 };
 
 // Interface for communication between Chrome Camera App (Remote) and camera
diff --git a/media/capture/video/chromeos/mojom/camera_common.mojom b/media/capture/video/chromeos/mojom/camera_common.mojom
index 0547429..f26bcd9 100644
--- a/media/capture/video/chromeos/mojom/camera_common.mojom
+++ b/media/capture/video/chromeos/mojom/camera_common.mojom
@@ -13,6 +13,9 @@
   CAMERA_FACING_BACK = 0,
   CAMERA_FACING_FRONT = 1,
   CAMERA_FACING_EXTERNAL = 2,
+  CAMERA_FACING_VIRTUAL_BACK = 3,
+  CAMERA_FACING_VIRTUAL_FRONT = 4,
+  CAMERA_FACING_VIRTUAL_EXTERNAL = 5,
 };
 
 struct CameraResourceCost {
diff --git a/media/capture/video/chromeos/video_capture_device_chromeos_halv3.cc b/media/capture/video/chromeos/video_capture_device_chromeos_halv3.cc
index 7e24d63a4..d7dc818 100644
--- a/media/capture/video/chromeos/video_capture_device_chromeos_halv3.cc
+++ b/media/capture/video/chromeos/video_capture_device_chromeos_halv3.cc
@@ -4,13 +4,21 @@
 
 #include "media/capture/video/chromeos/video_capture_device_chromeos_halv3.h"
 
+#include "base/strings/string_util.h"
 #include "media/capture/video/chromeos/video_capture_device_chromeos_delegate.h"
 
 namespace media {
 
+constexpr char kVirtualPrefix[] = "VIRTUAL_";
+
 VideoCaptureDeviceChromeOSHalv3::VideoCaptureDeviceChromeOSHalv3(
-    VideoCaptureDeviceChromeOSDelegate* delegate)
-    : vcd_delegate_(delegate), client_type_(ClientType::kPreviewClient) {}
+    VideoCaptureDeviceChromeOSDelegate* delegate,
+    const VideoCaptureDeviceDescriptor& vcd_descriptor)
+    : vcd_delegate_(delegate) {
+  client_type_ = base::StartsWith(vcd_descriptor.device_id, kVirtualPrefix)
+                     ? ClientType::kVideoClient
+                     : ClientType::kPreviewClient;
+}
 
 VideoCaptureDeviceChromeOSHalv3::~VideoCaptureDeviceChromeOSHalv3() {
   vcd_delegate_->Shutdown();
diff --git a/media/capture/video/chromeos/video_capture_device_chromeos_halv3.h b/media/capture/video/chromeos/video_capture_device_chromeos_halv3.h
index fde6525..5c5188a82 100644
--- a/media/capture/video/chromeos/video_capture_device_chromeos_halv3.h
+++ b/media/capture/video/chromeos/video_capture_device_chromeos_halv3.h
@@ -19,8 +19,9 @@
 class CAPTURE_EXPORT VideoCaptureDeviceChromeOSHalv3 final
     : public VideoCaptureDevice {
  public:
-  explicit VideoCaptureDeviceChromeOSHalv3(
-      VideoCaptureDeviceChromeOSDelegate* delegate);
+  VideoCaptureDeviceChromeOSHalv3(
+      VideoCaptureDeviceChromeOSDelegate* delegate,
+      const VideoCaptureDeviceDescriptor& vcd_descriptor);
 
   ~VideoCaptureDeviceChromeOSHalv3() final;
 
diff --git a/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc b/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
index be6c8138..97b6d909 100644
--- a/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
+++ b/media/capture/video/chromeos/video_capture_device_factory_chromeos.cc
@@ -95,6 +95,9 @@
     camera_app_device_bridge_->SetCameraInfoGetter(
         base::BindRepeating(&CameraHalDelegate::GetCameraInfoFromDeviceId,
                             base::Unretained(camera_hal_delegate_.get())));
+    camera_app_device_bridge_->SetVirtualDeviceController(
+        base::BindRepeating(&CameraHalDelegate::EnableVirtualDevice,
+                            base::Unretained(camera_hal_delegate_.get())));
   }
   return true;
 }
diff --git a/media/cast/sender/video_encoder_unittest.cc b/media/cast/sender/video_encoder_unittest.cc
index e566564..ad0827619 100644
--- a/media/cast/sender/video_encoder_unittest.cc
+++ b/media/cast/sender/video_encoder_unittest.cc
@@ -40,8 +40,7 @@
  protected:
   VideoEncoderTest()
       : task_runner_(new FakeSingleThreadTaskRunner(&testing_clock_)),
-        thread_task_runner_override_reverter_(
-            base::ThreadTaskRunnerHandle::OverrideForTesting(task_runner_)),
+        task_runner_handle_override_(task_runner_),
         cast_environment_(new CastEnvironment(&testing_clock_,
                                               task_runner_,
                                               task_runner_,
@@ -194,7 +193,7 @@
 
   base::SimpleTestTickClock testing_clock_;
   const scoped_refptr<FakeSingleThreadTaskRunner> task_runner_;
-  base::ScopedClosureRunner thread_task_runner_override_reverter_;
+  base::ThreadTaskRunnerHandleOverrideForTesting task_runner_handle_override_;
   const scoped_refptr<CastEnvironment> cast_environment_;
   FrameSenderConfig video_config_;
   std::unique_ptr<FakeVideoEncodeAcceleratorFactory> vea_factory_;
diff --git a/media/gpu/video_encode_accelerator_tests.cc b/media/gpu/video_encode_accelerator_tests.cc
index ef671bb..ae92230 100644
--- a/media/gpu/video_encode_accelerator_tests.cc
+++ b/media/gpu/video_encode_accelerator_tests.cc
@@ -333,8 +333,14 @@
   // Check if there is no keyframe except the first frame.
   EXPECT_EQ(encoder->GetEventCount(VideoEncoder::kKeyFrame), 1u);
   encoder->ForceKeyFrame();
-  // Check if the |middle_frame|+1-th frame is keyframe.
-  encoder->EncodeUntil(VideoEncoder::kBitstreamReady, middle_frame + 1u);
+  // Since kFrameReleased and kBitstreamReady events are asynchronous, the
+  // number of bitstreams being processed is unknown. We check keyframe request
+  // is applied by seeing if there is a keyframe in a few frames after
+  // requested. 10 is arbitrarily chosen.
+  constexpr size_t kKeyFrameRequestWindow = 10u;
+  encoder->EncodeUntil(VideoEncoder::kBitstreamReady,
+                       std::min(middle_frame + kKeyFrameRequestWindow,
+                                config.num_frames_to_encode));
   EXPECT_TRUE(encoder->WaitUntilIdle());
   EXPECT_EQ(encoder->GetEventCount(VideoEncoder::kKeyFrame), 2u);
 
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index f4d4428..6565908e 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -254,10 +254,10 @@
   texture_info.fFormat = GL_RGBA8_OES;
   GrBackendTexture backend_texture(size.width(), size.height(),
                                    GrMipMapped::kNo, texture_info);
-  return SkImage::MakeFromAdoptedTexture(
+  return SkImage::MakeFromTexture(
       raster_context_provider->GrContext(), backend_texture,
       kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
-      color_space.ToSkColorSpace());
+      color_space.ToSkColorSpace(), nullptr, nullptr);
 }
 
 void VideoFrameCopyTextureOrSubTexture(gpu::gles2::GLES2Interface* gl,
@@ -912,7 +912,7 @@
 
   const bool need_rotation = video_transformation.rotation != VIDEO_ROTATION_0;
   const bool need_scaling =
-      dest_rect.size() != gfx::SizeF(video_frame->visible_rect().size());
+      dest_rect.size() != gfx::SizeF(image.width(), image.height());
   const bool need_translation = !dest_rect.origin().IsOrigin();
   // TODO(tmathmeyer): apply horizontal / vertical mirroring if needed.
   bool need_transform = need_rotation || need_scaling || need_translation;
@@ -943,13 +943,10 @@
       rotated_dest_size =
           gfx::SizeF(rotated_dest_size.height(), rotated_dest_size.width());
     }
-    canvas->scale(SkFloatToScalar(rotated_dest_size.width() /
-                                  video_frame->visible_rect().width()),
-                  SkFloatToScalar(rotated_dest_size.height() /
-                                  video_frame->visible_rect().height()));
-    canvas->translate(
-        -SkFloatToScalar(video_frame->visible_rect().width() * 0.5f),
-        -SkFloatToScalar(video_frame->visible_rect().height() * 0.5f));
+    canvas->scale(SkFloatToScalar(rotated_dest_size.width() / image.width()),
+                  SkFloatToScalar(rotated_dest_size.height() / image.height()));
+    canvas->translate(-SkFloatToScalar(image.width() * 0.5f),
+                      -SkFloatToScalar(image.height() * 0.5f));
   }
 
   SkImageInfo info;
@@ -968,15 +965,7 @@
     const size_t offset = info.computeOffset(origin.x(), origin.y(), row_bytes);
     void* const pixels_offset = reinterpret_cast<char*>(pixels) + offset;
     ConvertVideoFrameToRGBPixels(video_frame.get(), pixels_offset, row_bytes);
-  } else if (video_frame->HasTextures()) {
-    DCHECK_EQ(video_frame->coded_size(),
-              gfx::Size(image.width(), image.height()));
-    canvas->drawImageRect(image, gfx::RectToSkRect(video_frame->visible_rect()),
-                          dest, &video_flags,
-                          SkCanvas::kStrict_SrcRectConstraint);
   } else {
-    DCHECK_EQ(video_frame->visible_rect().size(),
-              gfx::Size(image.width(), image.height()));
     canvas->drawImage(image, 0, 0, &video_flags);
   }
 
@@ -1709,6 +1698,8 @@
   DCHECK(!source_mailbox.IsZero());
   DCHECK(source_texture);
   auto* ri = raster_context_provider->RasterInterface();
+  if (!texture_ownership_in_skia)
+    ri->DeleteGpuRasterTexture(source_texture);
   if (!wraps_video_frame_texture) {
     gpu::SyncToken sync_token;
     ri->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
@@ -1718,7 +1709,9 @@
 }
 
 bool PaintCanvasVideoRenderer::Cache::Recycle() {
-  DCHECK(!wraps_video_frame_texture);
+  if (!texture_ownership_in_skia)
+    return true;
+
   if (!paint_image.HasExclusiveTextureAccess())
     return false;
 
@@ -1728,6 +1721,7 @@
   paint_image = cc::PaintImage();
   // We need a new texture ID because skia will destroy the previous one with
   // the SkImage.
+  texture_ownership_in_skia = false;
   source_texture = 0;
   return true;
 }
@@ -1773,6 +1767,7 @@
         } else {
           cache_.emplace(video_frame->unique_id());
           auto* sii = raster_context_provider->SharedImageInterface();
+
           // TODO(nazabris): Sort out what to do when GLES2 is needed but the
           // cached shared image is created without it.
           uint32_t flags =
@@ -1788,6 +1783,8 @@
           ri->WaitSyncTokenCHROMIUM(
               sii->GenUnverifiedSyncToken().GetConstData());
         }
+
+        DCHECK(!cache_->texture_ownership_in_skia);
         if (video_frame->NumTextures() == 1) {
           auto frame_mailbox =
               SynchronizeVideoFrameSingleMailbox(ri, video_frame.get());
@@ -1828,10 +1825,35 @@
       cache_->raster_context_provider = raster_context_provider;
       cache_->coded_size = video_frame->coded_size();
       cache_->visible_rect = video_frame->visible_rect();
-
+      GrDirectContext* direct =
+          GrAsDirectContext(raster_context_provider->GrContext());
+      sk_sp<SkImage> source_subset = source_image->makeSubset(
+          gfx::RectToSkIRect(cache_->visible_rect), direct);
+      if (source_subset) {
+        // We use the flushPendingGrContextIO = true so we can flush any pending
+        // GPU work on the GrContext to ensure that skia exectues the work for
+        // generating the subset and it can be safely destroyed.
+        GrBackendTexture image_backend =
+            source_image->getBackendTexture(/*flushPendingGrContextIO*/ true);
+        GrBackendTexture subset_backend =
+            source_subset->getBackendTexture(/*flushPendingGrContextIO*/ true);
+#if DCHECK_IS_ON()
+        GrGLTextureInfo backend_info;
+        if (image_backend.getGLTextureInfo(&backend_info))
+          DCHECK_EQ(backend_info.fID, cache_->source_texture);
+#endif
+        if (subset_backend.isValid() &&
+            subset_backend.isSameTexture(image_backend)) {
+          cache_->texture_ownership_in_skia = true;
+          source_subset = SkImage::MakeFromAdoptedTexture(
+              cache_->raster_context_provider->GrContext(), image_backend,
+              kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,
+              kPremul_SkAlphaType, source_image->imageInfo().refColorSpace());
+        }
+      }
       paint_image_builder.set_texture_backing(
           sk_sp<VideoTextureBacking>(new VideoTextureBacking(
-              std::move(source_image), raster_context_provider)),
+              std::move(source_subset), raster_context_provider)),
           cc::PaintImage::GetNextContentId());
     } else {
       cache_.emplace(video_frame->unique_id());
diff --git a/media/renderers/paint_canvas_video_renderer.h b/media/renderers/paint_canvas_video_renderer.h
index a422e23..943cc37 100644
--- a/media/renderers/paint_canvas_video_renderer.h
+++ b/media/renderers/paint_canvas_video_renderer.h
@@ -239,6 +239,9 @@
     // (if true), or to an allocated shared image (if false).
     bool wraps_video_frame_texture = false;
 
+    // Whether the texture pointed by |paint_image| is owned by skia or not.
+    bool texture_ownership_in_skia = false;
+
     // Used to allow recycling of the previous shared image. This requires that
     // no external users have access to this resource via SkImage. Returns true
     // if the existing resource can be recycled.
diff --git a/net/cookies/cookie_options.cc b/net/cookies/cookie_options.cc
index 2553b07..874a506 100644
--- a/net/cookies/cookie_options.cc
+++ b/net/cookies/cookie_options.cc
@@ -66,6 +66,7 @@
   options.set_do_not_update_access_time();
   options.set_same_party_cookie_context_type(
       SamePartyCookieContextType::kSameParty);
+  options.set_is_in_nontrivial_first_party_set(true);
   return options;
 }
 
diff --git a/net/cookies/cookie_options.h b/net/cookies/cookie_options.h
index b36614e0..992dde9 100644
--- a/net/cookies/cookie_options.h
+++ b/net/cookies/cookie_options.h
@@ -155,6 +155,13 @@
   }
   uint32_t full_party_context_size() const { return full_party_context_size_; }
 
+  void set_is_in_nontrivial_first_party_set(bool is_member) {
+    is_in_nontrivial_first_party_set_ = is_member;
+  }
+  bool is_in_nontrivial_first_party_set() const {
+    return is_in_nontrivial_first_party_set_;
+  }
+
   // Convenience method for where you need a CookieOptions that will
   // work for getting/setting all types of cookies, including HttpOnly and
   // SameSite cookies. Also specifies not to update the access time, because
@@ -174,6 +181,13 @@
   // The size of the isolation_info.party_context plus the top-frame site.
   // Stored for logging purposes.
   uint32_t full_party_context_size_ = 0;
+  // Whether the site requesting cookie access (as opposed to e.g. the
+  // `site_for_cookies`) is a member (or owner) of a nontrivial First-Party
+  // Set.
+  // This is included here temporarily, for the purpose of ignoring SameParty
+  // for sites that are not participating in the Origin Trial.
+  // TODO(https://crbug.com/1163990): remove this field.
+  bool is_in_nontrivial_first_party_set_ = false;
 };
 
 }  // namespace net
diff --git a/net/cookies/cookie_util.cc b/net/cookies/cookie_util.cc
index b051c2b..04ae883b 100644
--- a/net/cookies/cookie_util.cc
+++ b/net/cookies/cookie_util.cc
@@ -612,8 +612,10 @@
 
 CookieSamePartyStatus GetSamePartyStatus(const CanonicalCookie& cookie,
                                          const CookieOptions& options) {
-  if (!IsFirstPartySetsEnabled() || !cookie.IsSameParty())
+  if (!IsFirstPartySetsEnabled() || !cookie.IsSameParty() ||
+      !options.is_in_nontrivial_first_party_set()) {
     return CookieSamePartyStatus::kNoSamePartyEnforcement;
+  }
 
   switch (options.same_party_cookie_context_type()) {
     case CookieOptions::SamePartyCookieContextType::kCrossParty:
diff --git a/net/cookies/cookie_util_unittest.cc b/net/cookies/cookie_util_unittest.cc
index 4271b0c..34df85f0 100644
--- a/net/cookies/cookie_util_unittest.cc
+++ b/net/cookies/cookie_util_unittest.cc
@@ -8,6 +8,10 @@
 #include "base/callback.h"
 #include "base/strings/string_split.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/time/time.h"
+#include "net/base/features.h"
+#include "net/cookies/cookie_constants.h"
 #include "net/cookies/cookie_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -1267,6 +1271,175 @@
   EXPECT_TRUE(result_out);
 }
 
+TEST(CookieUtilTest, GetSamePartyStatus_NotInSet) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kFirstPartySets);
+  CookieOptions options;
+  options.set_is_in_nontrivial_first_party_set(false);
+
+  for (bool same_party : {false, true}) {
+    for (bool secure : {false, true}) {
+      for (bool httponly : {false, true}) {
+        for (CookieSameSite same_site : {
+                 CookieSameSite::NO_RESTRICTION,
+                 CookieSameSite::LAX_MODE,
+                 CookieSameSite::STRICT_MODE,
+                 CookieSameSite::UNSPECIFIED,
+             }) {
+          for (CookieOptions::SamePartyCookieContextType party_context_type : {
+                   CookieOptions::SamePartyCookieContextType::kCrossParty,
+                   CookieOptions::SamePartyCookieContextType::kSameParty,
+               }) {
+            base::Time now = base::Time::Now();
+            std::unique_ptr<CanonicalCookie> cookie =
+                CanonicalCookie::CreateUnsafeCookieForTesting(
+                    "cookie", "tasty", "example.test", "/", now, now, now,
+                    secure, httponly, same_site,
+                    CookiePriority::COOKIE_PRIORITY_DEFAULT, same_party);
+
+            options.set_same_party_cookie_context_type(party_context_type);
+            EXPECT_EQ(CookieSamePartyStatus::kNoSamePartyEnforcement,
+                      cookie_util::GetSamePartyStatus(*cookie, options));
+          }
+        }
+      }
+    }
+  }
+}
+
+TEST(CookieUtilTest, GetSamePartyStatus_FeatureDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(features::kFirstPartySets);
+  CookieOptions options;
+  options.set_is_in_nontrivial_first_party_set(true);
+
+  for (bool same_party : {false, true}) {
+    for (bool secure : {false, true}) {
+      for (bool httponly : {false, true}) {
+        for (CookieSameSite same_site : {
+                 CookieSameSite::NO_RESTRICTION,
+                 CookieSameSite::LAX_MODE,
+                 CookieSameSite::STRICT_MODE,
+                 CookieSameSite::UNSPECIFIED,
+             }) {
+          for (CookieOptions::SamePartyCookieContextType party_context_type : {
+                   CookieOptions::SamePartyCookieContextType::kCrossParty,
+                   CookieOptions::SamePartyCookieContextType::kSameParty,
+               }) {
+            base::Time now = base::Time::Now();
+            std::unique_ptr<CanonicalCookie> cookie =
+                CanonicalCookie::CreateUnsafeCookieForTesting(
+                    "cookie", "tasty", "example.test", "/", now, now, now,
+                    secure, httponly, same_site,
+                    CookiePriority::COOKIE_PRIORITY_DEFAULT, same_party);
+
+            options.set_same_party_cookie_context_type(party_context_type);
+            EXPECT_EQ(CookieSamePartyStatus::kNoSamePartyEnforcement,
+                      cookie_util::GetSamePartyStatus(*cookie, options));
+          }
+        }
+      }
+    }
+  }
+}
+
+TEST(CookieUtilTest, GetSamePartyStatus_NotSameParty) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kFirstPartySets);
+  CookieOptions options;
+  options.set_is_in_nontrivial_first_party_set(true);
+
+  for (bool secure : {false, true}) {
+    for (bool httponly : {false, true}) {
+      for (CookieSameSite same_site : {
+               CookieSameSite::NO_RESTRICTION,
+               CookieSameSite::LAX_MODE,
+               CookieSameSite::STRICT_MODE,
+               CookieSameSite::UNSPECIFIED,
+           }) {
+        for (CookieOptions::SamePartyCookieContextType party_context_type : {
+                 CookieOptions::SamePartyCookieContextType::kCrossParty,
+                 CookieOptions::SamePartyCookieContextType::kSameParty,
+             }) {
+          base::Time now = base::Time::Now();
+          std::unique_ptr<CanonicalCookie> cookie =
+              CanonicalCookie::CreateUnsafeCookieForTesting(
+                  "cookie", "tasty", "example.test", "/", now, now, now, secure,
+                  httponly, same_site, CookiePriority::COOKIE_PRIORITY_DEFAULT,
+                  false /* same_party */);
+
+          options.set_same_party_cookie_context_type(party_context_type);
+          EXPECT_EQ(CookieSamePartyStatus::kNoSamePartyEnforcement,
+                    cookie_util::GetSamePartyStatus(*cookie, options));
+        }
+      }
+    }
+  }
+}
+
+TEST(CookieUtilTest, GetSamePartyStatus_SamePartySemantics) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kFirstPartySets);
+  CookieOptions options;
+  options.set_is_in_nontrivial_first_party_set(true);
+
+  // Note: some SameParty cookie configurations (e.g. non-Secure cookies) are
+  // skipped, because they are invalid.
+  for (bool httponly : {false, true}) {
+    for (CookieSameSite same_site : {
+             CookieSameSite::NO_RESTRICTION,
+             CookieSameSite::LAX_MODE,
+             CookieSameSite::UNSPECIFIED,
+         }) {
+      for (CookieOptions::SameSiteCookieContext::ContextType same_site_context :
+           {
+               CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE,
+               CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX,
+               CookieOptions::SameSiteCookieContext::ContextType::
+                   SAME_SITE_LAX_METHOD_UNSAFE,
+               CookieOptions::SameSiteCookieContext::ContextType::
+                   SAME_SITE_STRICT,
+           }) {
+        for (CookieOptions::SameSiteCookieContext::ContextType
+                 schemeful_same_site_context :
+             {
+                 CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE,
+                 CookieOptions::SameSiteCookieContext::ContextType::
+                     SAME_SITE_LAX,
+                 CookieOptions::SameSiteCookieContext::ContextType::
+                     SAME_SITE_LAX_METHOD_UNSAFE,
+                 CookieOptions::SameSiteCookieContext::ContextType::
+                     SAME_SITE_STRICT,
+             }) {
+          if (same_site_context < schemeful_same_site_context)
+            continue;
+          options.set_same_site_cookie_context(
+              CookieOptions::SameSiteCookieContext(
+                  same_site_context, schemeful_same_site_context));
+
+          base::Time now = base::Time::Now();
+          std::unique_ptr<CanonicalCookie> cookie =
+              CanonicalCookie::CreateUnsafeCookieForTesting(
+                  "cookie", "tasty", "example.test", "/", now, now, now,
+                  true /* secure */, httponly, same_site,
+                  CookiePriority::COOKIE_PRIORITY_DEFAULT,
+                  true /* same_party */);
+
+          options.set_same_party_cookie_context_type(
+              CookieOptions::SamePartyCookieContextType::kCrossParty);
+          EXPECT_EQ(CookieSamePartyStatus::kEnforceSamePartyExclude,
+                    cookie_util::GetSamePartyStatus(*cookie, options));
+
+          options.set_same_party_cookie_context_type(
+              CookieOptions::SamePartyCookieContextType::kSameParty);
+          EXPECT_EQ(CookieSamePartyStatus::kEnforceSamePartyInclude,
+                    cookie_util::GetSamePartyStatus(*cookie, options));
+        }
+      }
+    }
+  }
+}
+
 }  // namespace
 
 }  // namespace net
diff --git a/net/dns/host_resolver_manager_unittest.cc b/net/dns/host_resolver_manager_unittest.cc
index 6d0aa18f..563035d2 100644
--- a/net/dns/host_resolver_manager_unittest.cc
+++ b/net/dns/host_resolver_manager_unittest.cc
@@ -2224,8 +2224,8 @@
   // Override the current thread task runner, so we can simulate the passage of
   // time and avoid any actual sleeps.
   auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  base::ScopedClosureRunner task_runner_override_scoped_cleanup =
-      base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner);
+  base::ThreadTaskRunnerHandleOverrideForTesting task_runner_handle_override(
+      test_task_runner);
 
   // Resolve "host1".
   ResolveHostResponseHelper response(resolver_->CreateRequest(
@@ -2265,8 +2265,8 @@
 // number of retries used is 4 rather than something higher.
 TEST_F(HostResolverManagerTest, DefaultMaxRetryAttempts) {
   auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  base::ScopedClosureRunner task_runner_override_scoped_cleanup =
-      base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner);
+  base::ThreadTaskRunnerHandleOverrideForTesting task_runner_handle_override(
+      test_task_runner);
 
   // Instantiate a ResolverProc that will block all incoming requests.
   auto resolver_proc = base::MakeRefCounted<LookupAttemptHostResolverProc>(
@@ -2982,8 +2982,8 @@
   // Override the current thread task runner, so we can simulate the passage of
   // time to trigger the timeout.
   auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  base::ScopedClosureRunner task_runner_override_scoped_cleanup =
-      base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner);
+  base::ThreadTaskRunnerHandleOverrideForTesting task_runner_handle_override(
+      test_task_runner);
 
   HostResolver::ResolveHostParameters parameters;
   parameters.source = HostResolverSource::MULTICAST_DNS;
@@ -3021,8 +3021,8 @@
   // Override the current thread task runner, so we can simulate the passage of
   // time to trigger the timeout.
   auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  base::ScopedClosureRunner task_runner_override_scoped_cleanup =
-      base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner);
+  base::ThreadTaskRunnerHandleOverrideForTesting task_runner_handle_override(
+      test_task_runner);
 
   HostResolver::ResolveHostParameters parameters;
   parameters.dns_query_type = DnsQueryType::A;
@@ -3067,8 +3067,8 @@
   // Override the current thread task runner, so we can simulate the passage of
   // time to trigger the timeout.
   auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
-  base::ScopedClosureRunner task_runner_override_scoped_cleanup =
-      base::ThreadTaskRunnerHandle::OverrideForTesting(test_task_runner);
+  base::ThreadTaskRunnerHandleOverrideForTesting task_runner_handle_override(
+      test_task_runner);
 
   HostResolver::ResolveHostParameters parameters;
   parameters.source = HostResolverSource::MULTICAST_DNS;
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index b327383..dec89f6 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -156,7 +156,8 @@
 net::CookieOptions CreateCookieOptions(
     net::CookieOptions::SameSiteCookieContext same_site_context,
     net::CookieOptions::SamePartyCookieContextType same_party_context,
-    const net::IsolationInfo& isolation_info) {
+    const net::IsolationInfo& isolation_info,
+    bool is_in_nontrivial_first_party_set) {
   net::CookieOptions options;
   options.set_return_excluded_cookies();
   options.set_include_httponly();
@@ -167,6 +168,8 @@
     options.set_full_party_context_size(isolation_info.party_context()->size() +
                                         1);
   }
+  options.set_is_in_nontrivial_first_party_set(
+      is_in_nontrivial_first_party_set);
   return options;
 }
 
@@ -179,13 +182,13 @@
 // IsolationInfo.  But that might not be true for other embedders yet
 // (including cast, WebView, etc).  Also not sure about iOS.
 net::CookieOptions::SamePartyCookieContextType ComputeSamePartyContext(
-    const GURL& request_url,
+    const net::SchemefulSite& request_site,
     const net::IsolationInfo& isolation_info,
     const net::CookieAccessDelegate* cookie_access_delegate) {
   if (!isolation_info.IsEmpty() && isolation_info.party_context().has_value() &&
       cookie_access_delegate &&
       cookie_access_delegate->IsContextSamePartyWithSite(
-          net::SchemefulSite(request_url),
+          request_site,
           isolation_info.network_isolation_key().GetTopFrameSite().value(),
           isolation_info.party_context().value())) {
     return net::CookieOptions::SamePartyCookieContextType::kSameParty;
@@ -573,11 +576,19 @@
             request_->method(), request_->url(), request_->site_for_cookies(),
             request_->initiator(), force_ignore_site_for_cookies);
 
+    net::SchemefulSite request_site(request_->url());
+
+    const CookieAccessDelegate* delegate =
+        cookie_store->cookie_access_delegate();
+
     CookieOptions::SamePartyCookieContextType same_party_context =
-        ComputeSamePartyContext(request_->url(), request_->isolation_info(),
-                                cookie_store->cookie_access_delegate());
+        ComputeSamePartyContext(request_site, request_->isolation_info(),
+                                delegate);
+    bool is_in_nontrivial_first_party_set =
+        delegate && delegate->IsInNontrivialFirstPartySet(request_site);
     CookieOptions options = CreateCookieOptions(
-        same_site_context, same_party_context, request_->isolation_info());
+        same_site_context, same_party_context, request_->isolation_info(),
+        is_in_nontrivial_first_party_set);
 
     cookie_store->GetCookieListWithOptionsAsync(
         request_->url(), options,
@@ -716,12 +727,17 @@
           request_->url(), request_->site_for_cookies(), request_->initiator(),
           force_ignore_site_for_cookies);
 
+  const CookieAccessDelegate* delegate = cookie_store->cookie_access_delegate();
+  net::SchemefulSite request_site(request_->url());
   CookieOptions::SamePartyCookieContextType same_party_context =
-      ComputeSamePartyContext(request_->url(), request_->isolation_info(),
-                              cookie_store->cookie_access_delegate());
+      ComputeSamePartyContext(request_site, request_->isolation_info(),
+                              delegate);
+  bool is_in_nontrivial_first_party_set =
+      delegate && delegate->IsInNontrivialFirstPartySet(request_site);
 
   CookieOptions options = CreateCookieOptions(
-      same_site_context, same_party_context, request_->isolation_info());
+      same_site_context, same_party_context, request_->isolation_info(),
+      is_in_nontrivial_first_party_set);
 
   // Set all cookies, without waiting for them to be set. Any subsequent
   // read will see the combined result of all cookie operation.
diff --git a/pdf/document_metadata.h b/pdf/document_metadata.h
index 82d00ac..e1ad46d 100644
--- a/pdf/document_metadata.h
+++ b/pdf/document_metadata.h
@@ -30,8 +30,8 @@
 // dictionary (see section 14.3.3 "Document Information Dictionary" of the ISO
 // 32000-1 standard), as well as other properties about the file.
 // TODO(crbug.com/93619): Finish adding information dictionary fields like
-// |keywords|, |creation_date|, and |mod_date|. Also add fields like
-// |size_bytes|, |is_encrypted|, and |is_linearized|.
+// `keywords`, `creation_date`, and `mod_date`. Also add fields like
+// `size_bytes` and `is_encrypted`.
 struct DocumentMetadata {
   DocumentMetadata();
   DocumentMetadata(const DocumentMetadata&) = delete;
@@ -41,6 +41,9 @@
   // Version of the document
   PdfVersion version = PdfVersion::kUnknown;
 
+  // Whether the document is optimized by linearization.
+  bool linearized = false;
+
   // The document's title.
   std::string title;
 
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index 3bb90f3..465d70da 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -674,13 +674,13 @@
   if (!fpdf_availability()) {
     document_->file_access().m_FileLen = doc_loader_->GetDocumentSize();
     document_->CreateFPDFAvailability();
-    DCHECK(fpdf_availability());
+
     // Currently engine does not deal efficiently with some non-linearized
     // files.
     // See http://code.google.com/p/chromium/issues/detail?id=59400
     // To improve user experience we download entire file for non-linearized
     // PDF.
-    if (FPDFAvail_IsLinearized(fpdf_availability()) != PDF_LINEARIZED) {
+    if (!IsLinearized()) {
       // Wait complete document.
       process_when_pending_request_complete_ = false;
       document_->ResetFPDFAvailability();
@@ -2752,9 +2752,8 @@
   pending_pages_.clear();
   size_t new_page_count = FPDF_GetPageCount(doc());
 
-  bool doc_complete = doc_loader_->IsDocumentComplete();
-  bool is_linear =
-      FPDFAvail_IsLinearized(fpdf_availability()) == PDF_LINEARIZED;
+  const bool doc_complete = doc_loader_->IsDocumentComplete();
+  const bool is_linear = IsLinearized();
   for (size_t i = 0; i < new_page_count; ++i) {
     // Get page availability. If |document_loaded_| == true and the page is not
     // new, then the page has been constructed already. Get page availability
@@ -2805,12 +2804,10 @@
 
 void PDFiumEngine::LoadBody() {
   DCHECK(doc());
-  DCHECK(fpdf_availability());
   if (doc_loader_->IsDocumentComplete()) {
     LoadForm();
-  } else if (FPDFAvail_IsLinearized(fpdf_availability()) == PDF_LINEARIZED &&
-             FPDF_GetPageCount(doc()) == 1) {
-    // If we have only one page we should load form first, bacause it is may be
+  } else if (IsLinearized() && FPDF_GetPageCount(doc()) == 1) {
+    // If we have only one page we should load form first, because it may be an
     // XFA document. And after loading form the page count and its contents may
     // be changed.
     LoadForm();
@@ -2865,6 +2862,11 @@
   }
 }
 
+bool PDFiumEngine::IsLinearized() {
+  DCHECK(fpdf_availability());
+  return FPDFAvail_IsLinearized(fpdf_availability()) == PDF_LINEARIZED;
+}
+
 void PDFiumEngine::CalculateVisiblePages() {
   // Early return if the PDF isn't being loaded or if we don't have the document
   // info yet. The latter is important because otherwise as the PDF is being
@@ -3948,8 +3950,10 @@
 void PDFiumEngine::LoadDocumentMetadata() {
   DCHECK(document_loaded_);
 
-  // Document information dictionary entries
   doc_metadata_.version = GetDocumentVersion();
+  doc_metadata_.linearized = IsLinearized();
+
+  // Document information dictionary entries
   doc_metadata_.title = GetTrimmedMetadataByField("Title");
   doc_metadata_.author = GetTrimmedMetadataByField("Author");
   doc_metadata_.subject = GetTrimmedMetadataByField("Subject");
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index 2bbdba1..448b012f 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -302,6 +302,9 @@
 
   void LoadForm();
 
+  // Checks whether the document is optimized by linearization.
+  bool IsLinearized();
+
   // Calculates which pages should be displayed right now.
   void CalculateVisiblePages();
 
diff --git a/pdf/pdfium/pdfium_engine_unittest.cc b/pdf/pdfium/pdfium_engine_unittest.cc
index f6c27b1..f6a47b3 100644
--- a/pdf/pdfium/pdfium_engine_unittest.cc
+++ b/pdf/pdfium/pdfium_engine_unittest.cc
@@ -349,6 +349,7 @@
   const DocumentMetadata& doc_metadata = engine->GetDocumentMetadata();
 
   EXPECT_EQ(PdfVersion::k1_7, doc_metadata.version);
+  EXPECT_FALSE(doc_metadata.linearized);
   EXPECT_EQ("Sample PDF Document Info", doc_metadata.title);
   EXPECT_EQ("Chromium Authors", doc_metadata.author);
   EXPECT_EQ("Testing", doc_metadata.subject);
@@ -365,6 +366,7 @@
   const DocumentMetadata& doc_metadata = engine->GetDocumentMetadata();
 
   EXPECT_EQ(PdfVersion::k1_7, doc_metadata.version);
+  EXPECT_FALSE(doc_metadata.linearized);
   EXPECT_THAT(doc_metadata.title, IsEmpty());
   EXPECT_THAT(doc_metadata.author, IsEmpty());
   EXPECT_THAT(doc_metadata.subject, IsEmpty());
@@ -372,6 +374,14 @@
   EXPECT_THAT(doc_metadata.producer, IsEmpty());
 }
 
+TEST_F(PDFiumEngineTest, GetLinearizedDocumentMetadata) {
+  NiceMock<MockTestClient> client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("linearized.pdf"));
+  ASSERT_TRUE(engine);
+  EXPECT_TRUE(engine->GetDocumentMetadata().linearized);
+}
+
 TEST_F(PDFiumEngineTest, GetBadPdfVersion) {
   NiceMock<MockTestClient> client;
   std::unique_ptr<PDFiumEngine> engine =
diff --git a/services/device/usb/usb_device_handle_win.cc b/services/device/usb/usb_device_handle_win.cc
index 208f2e0..b2c6316 100644
--- a/services/device/usb/usb_device_handle_win.cc
+++ b/services/device/usb/usb_device_handle_win.cc
@@ -222,8 +222,8 @@
   // Aborting requests may run or destroy callbacks holding the last reference
   // to this object so hold a reference for the rest of this method.
   scoped_refptr<UsbDeviceHandleWin> self(this);
-  while (!requests_.empty())
-    requests_.begin()->second->Abort();
+  for (const auto& request : requests_)
+    request->Abort();
 
   device_ = nullptr;
 }
@@ -995,15 +995,19 @@
   auto request = std::make_unique<Request>(
       handle, interface ? interface->info->interface_number : -1);
   Request* request_ptr = request.get();
-  requests_[request_ptr] = std::move(request);
+  requests_.push_back(std::move(request));
   return request_ptr;
 }
 
 std::unique_ptr<UsbDeviceHandleWin::Request> UsbDeviceHandleWin::UnlinkRequest(
     UsbDeviceHandleWin::Request* request_ptr) {
-  auto it = requests_.find(request_ptr);
+  auto it = std::find_if(
+      requests_.begin(), requests_.end(),
+      [request_ptr](const std::unique_ptr<Request>& request) -> bool {
+        return request.get() == request_ptr;
+      });
   DCHECK(it != requests_.end());
-  std::unique_ptr<Request> request = std::move(it->second);
+  std::unique_ptr<Request> request = std::move(*it);
   requests_.erase(it);
   return request;
 }
@@ -1085,7 +1089,13 @@
 
     buffer = nullptr;
     bytes_transferred = 0;
-    status = UsbTransferStatus::TRANSFER_ERROR;
+    switch (win32_result) {
+      case ERROR_REQUEST_ABORTED:
+        status = UsbTransferStatus::CANCELLED;
+        break;
+      default:
+        status = UsbTransferStatus::TRANSFER_ERROR;
+    }
   }
 
   DCHECK_NE(request->interface_number(), -1);
diff --git a/services/device/usb/usb_device_handle_win.h b/services/device/usb/usb_device_handle_win.h
index a07bc7b..8fc499e 100644
--- a/services/device/usb/usb_device_handle_win.h
+++ b/services/device/usb/usb_device_handle_win.h
@@ -5,6 +5,7 @@
 #ifndef SERVICES_DEVICE_USB_USB_DEVICE_HANDLE_WIN_H_
 #define SERVICES_DEVICE_USB_USB_DEVICE_HANDLE_WIN_H_
 
+#include <list>
 #include <map>
 #include <memory>
 #include <vector>
@@ -206,7 +207,7 @@
 
   std::map<uint8_t, Interface> interfaces_;
   std::map<uint8_t, Endpoint> endpoints_;
-  std::map<Request*, std::unique_ptr<Request>> requests_;
+  std::list<std::unique_ptr<Request>> requests_;
 
   // Control transfers which are waiting for a function handle to be ready.
   std::vector<OpenInterfaceCallback> ep0_ready_callbacks_;
diff --git a/services/network/cookie_manager_unittest.cc b/services/network/cookie_manager_unittest.cc
index 9578723..fa66ec7 100644
--- a/services/network/cookie_manager_unittest.cc
+++ b/services/network/cookie_manager_unittest.cc
@@ -316,6 +316,7 @@
         net::CookieOptions::SameSiteCookieContext::MakeInclusive());
     options.set_same_party_cookie_context_type(
         net::CookieOptions::SamePartyCookieContextType::kSameParty);
+    options.set_is_in_nontrivial_first_party_set(true);
     if (can_modify_httponly)
       options.set_include_httponly();
 
@@ -873,55 +874,114 @@
           net::COOKIE_PRIORITY_MEDIUM, /*same_party=*/true),
       "https", true));
 
-  // Retrieve only unrestricted cookies. SameParty cookies are excluded, and
-  // non-SameSite=None cookies are excluded.
-  net::CookieOptions options;
-  options.set_return_excluded_cookies();
-  ASSERT_EQ(net::CookieOptions::SamePartyCookieContextType::kCrossParty,
-            options.same_party_cookie_context_type());
-  ASSERT_EQ(
-      net::CookieOptions::SameSiteCookieContext(
-          net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE),
-      options.same_site_cookie_context());
-
   const GURL cookie_url("https://foo_host.com/with/path");
-  EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
-              UnorderedElementsAre(CookieWithName("nonSameParty-None")));
 
-  EXPECT_THAT(
-      service_wrapper()->GetExcludedCookieList(cookie_url, options),
-      UnorderedElementsAre(CookieAccessWithName("SameParty-Unspecified"),
-                           CookieAccessWithName("SameParty-None"),
-                           CookieAccessWithName("SameParty-Lax"),
-                           CookieAccessWithName("nonSameParty-Unspecified"),
-                           CookieAccessWithName("nonSameParty-Lax")));
+  // Verify that sites in a First-Party Set get SameParty semantics.
+  {
+    net::CookieOptions options;
+    options.set_return_excluded_cookies();
+    options.set_is_in_nontrivial_first_party_set(true);
+    ASSERT_EQ(net::CookieOptions::SamePartyCookieContextType::kCrossParty,
+              options.same_party_cookie_context_type());
+    ASSERT_EQ(
+        net::CookieOptions::SameSiteCookieContext(
+            net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE),
+        options.same_site_cookie_context());
 
-  // In a same-party, cross-site context, SameParty cookies should be included,
-  // and non-SameParty cookies should be excluded based on SameSite value.
-  options.set_same_party_cookie_context_type(
-      net::CookieOptions::SamePartyCookieContextType::kSameParty);
-  EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
-              UnorderedElementsAre(CookieWithName("SameParty-Unspecified"),
-                                   CookieWithName("SameParty-None"),
-                                   CookieWithName("SameParty-Lax"),
-                                   CookieWithName("nonSameParty-None")));
-  EXPECT_THAT(
-      service_wrapper()->GetExcludedCookieList(cookie_url, options),
-      UnorderedElementsAre(CookieAccessWithName("nonSameParty-Unspecified"),
-                           CookieAccessWithName("nonSameParty-Lax")));
+    // Retrieve only unrestricted cookies. SameParty cookies are excluded, and
+    // non-SameSite=None cookies are excluded.
+    EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
+                UnorderedElementsAre(CookieWithName("nonSameParty-None")));
 
-  // In a same-party, same-site context, all cookies should be included.
-  options.set_same_site_cookie_context(
-      net::CookieOptions::SameSiteCookieContext::MakeInclusive());
-  EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
-              UnorderedElementsAre(CookieWithName("SameParty-Unspecified"),
-                                   CookieWithName("SameParty-None"),
-                                   CookieWithName("SameParty-Lax"),
-                                   CookieWithName("nonSameParty-Unspecified"),
-                                   CookieWithName("nonSameParty-None"),
-                                   CookieWithName("nonSameParty-Lax")));
-  EXPECT_THAT(service_wrapper()->GetExcludedCookieList(cookie_url, options),
-              IsEmpty());
+    EXPECT_THAT(
+        service_wrapper()->GetExcludedCookieList(cookie_url, options),
+        UnorderedElementsAre(CookieAccessWithName("SameParty-Unspecified"),
+                             CookieAccessWithName("SameParty-None"),
+                             CookieAccessWithName("SameParty-Lax"),
+                             CookieAccessWithName("nonSameParty-Unspecified"),
+                             CookieAccessWithName("nonSameParty-Lax")));
+
+    // In a same-party, cross-site context, SameParty cookies should be
+    // included, and non-SameParty cookies should be excluded based on SameSite
+    // value.
+    options.set_same_party_cookie_context_type(
+        net::CookieOptions::SamePartyCookieContextType::kSameParty);
+    EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
+                UnorderedElementsAre(CookieWithName("SameParty-Unspecified"),
+                                     CookieWithName("SameParty-None"),
+                                     CookieWithName("SameParty-Lax"),
+                                     CookieWithName("nonSameParty-None")));
+    EXPECT_THAT(
+        service_wrapper()->GetExcludedCookieList(cookie_url, options),
+        UnorderedElementsAre(CookieAccessWithName("nonSameParty-Unspecified"),
+                             CookieAccessWithName("nonSameParty-Lax")));
+
+    // In a same-party, same-site context, all cookies should be included.
+    options.set_same_site_cookie_context(
+        net::CookieOptions::SameSiteCookieContext::MakeInclusive());
+    EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
+                UnorderedElementsAre(CookieWithName("SameParty-Unspecified"),
+                                     CookieWithName("SameParty-None"),
+                                     CookieWithName("SameParty-Lax"),
+                                     CookieWithName("nonSameParty-Unspecified"),
+                                     CookieWithName("nonSameParty-None"),
+                                     CookieWithName("nonSameParty-Lax")));
+    EXPECT_THAT(service_wrapper()->GetExcludedCookieList(cookie_url, options),
+                IsEmpty());
+  }
+
+  // Now verify that sites not in a First-Party Set ignore SameParty and fall
+  // back to SameSite semantics instead.
+  {
+    net::CookieOptions options;
+    options.set_return_excluded_cookies();
+    // Default, but set for explicitness.
+    options.set_is_in_nontrivial_first_party_set(false);
+    ASSERT_EQ(net::CookieOptions::SamePartyCookieContextType::kCrossParty,
+              options.same_party_cookie_context_type());
+    ASSERT_EQ(
+        net::CookieOptions::SameSiteCookieContext(
+            net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE),
+        options.same_site_cookie_context());
+
+    // Cross-party, cross-site.
+    EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
+                UnorderedElementsAre(CookieWithName("SameParty-None"),
+                                     CookieWithName("nonSameParty-None")));
+
+    EXPECT_THAT(
+        service_wrapper()->GetExcludedCookieList(cookie_url, options),
+        UnorderedElementsAre(CookieAccessWithName("SameParty-Unspecified"),
+                             CookieAccessWithName("SameParty-Lax"),
+                             CookieAccessWithName("nonSameParty-Unspecified"),
+                             CookieAccessWithName("nonSameParty-Lax")));
+
+    // Same-party, cross-site.
+    options.set_same_party_cookie_context_type(
+        net::CookieOptions::SamePartyCookieContextType::kSameParty);
+    EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
+                UnorderedElementsAre(CookieWithName("SameParty-None"),
+                                     CookieWithName("nonSameParty-None")));
+    EXPECT_THAT(
+        service_wrapper()->GetExcludedCookieList(cookie_url, options),
+        UnorderedElementsAre(CookieAccessWithName("SameParty-Unspecified"),
+                             CookieAccessWithName("SameParty-Lax"),
+                             CookieAccessWithName("nonSameParty-Unspecified"),
+                             CookieAccessWithName("nonSameParty-Lax")));
+
+    // Same-party, same-site.
+    options.set_same_site_cookie_context(
+        net::CookieOptions::SameSiteCookieContext::MakeInclusive());
+    EXPECT_THAT(service_wrapper()->GetCookieList(cookie_url, options),
+                UnorderedElementsAre(CookieWithName("SameParty-Unspecified"),
+                                     CookieWithName("SameParty-None"),
+                                     CookieWithName("SameParty-Lax"),
+                                     CookieWithName("nonSameParty-Unspecified"),
+                                     CookieWithName("nonSameParty-None"),
+                                     CookieWithName("nonSameParty-Lax")));
+    EXPECT_THAT(service_wrapper()->GetExcludedCookieList(cookie_url, options),
+                IsEmpty());
+  }
 }
 
 TEST_F(CookieManagerTest, GetCookieListAccessTime) {
diff --git a/services/network/public/cpp/cookie_manager_mojom_traits.cc b/services/network/public/cpp/cookie_manager_mojom_traits.cc
index 9be0ce2..da81027 100644
--- a/services/network/public/cpp/cookie_manager_mojom_traits.cc
+++ b/services/network/public/cpp/cookie_manager_mojom_traits.cc
@@ -382,6 +382,9 @@
   cookie_options->set_full_party_context_size(
       mojo_options.full_party_context_size());
 
+  cookie_options->set_is_in_nontrivial_first_party_set(
+      mojo_options.is_in_nontrivial_first_party_set());
+
   return true;
 }
 
diff --git a/services/network/public/cpp/cookie_manager_mojom_traits.h b/services/network/public/cpp/cookie_manager_mojom_traits.h
index 0fbf44dd..d8cd73f 100644
--- a/services/network/public/cpp/cookie_manager_mojom_traits.h
+++ b/services/network/public/cpp/cookie_manager_mojom_traits.h
@@ -129,6 +129,10 @@
     return o.full_party_context_size();
   }
 
+  static bool is_in_nontrivial_first_party_set(const net::CookieOptions& o) {
+    return o.is_in_nontrivial_first_party_set();
+  }
+
   static bool Read(network::mojom::CookieOptionsDataView mojo_options,
                    net::CookieOptions* cookie_options);
 };
diff --git a/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc b/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
index fb0620106..6164206 100644
--- a/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc
@@ -326,6 +326,8 @@
     EXPECT_FALSE(least_trusted.return_excluded_cookies());
     least_trusted.set_return_excluded_cookies();  // differ from default.
     least_trusted.set_full_party_context_size(10u);
+    least_trusted.set_is_in_nontrivial_first_party_set(
+        true);  // differ from default.
 
     EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieOptions>(
         least_trusted, copy));
@@ -338,6 +340,7 @@
     EXPECT_EQ(net::CookieOptions::SamePartyCookieContextType::kCrossParty,
               copy.same_party_cookie_context_type());
     EXPECT_EQ(10u, copy.full_party_context_size());
+    EXPECT_TRUE(copy.is_in_nontrivial_first_party_set());
   }
 
   {
@@ -348,6 +351,7 @@
     very_trusted.set_same_party_cookie_context_type(
         net::CookieOptions::SamePartyCookieContextType::kSameParty);
     very_trusted.set_full_party_context_size(1u);
+    very_trusted.set_is_in_nontrivial_first_party_set(true);
 
     EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieOptions>(
         very_trusted, copy));
@@ -358,6 +362,7 @@
     EXPECT_EQ(net::CookieOptions::SamePartyCookieContextType::kSameParty,
               copy.same_party_cookie_context_type());
     EXPECT_EQ(1u, copy.full_party_context_size());
+    EXPECT_TRUE(copy.is_in_nontrivial_first_party_set());
   }
 }
 
diff --git a/services/network/public/mojom/cookie_manager.mojom b/services/network/public/mojom/cookie_manager.mojom
index ceec3f6..c736353 100644
--- a/services/network/public/mojom/cookie_manager.mojom
+++ b/services/network/public/mojom/cookie_manager.mojom
@@ -130,6 +130,8 @@
   // The size of the isolation_info.party_context plus the top-frame site for
   // logging purposes.
   uint32 full_party_context_size = 0;
+  // Whether the site is a member of a nontrivial First-Party Set.
+  bool is_in_nontrivial_first_party_set = false;
 };
 
 // See net/cookies/canonical_cookie.{h,cc} for documentation.
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index 9edade2..1b409bc 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -69,7 +69,6 @@
     "client_hints/client_hints.h",
     "context_menu_data/edit_flags.h",
     "context_menu_data/input_field_type.h",
-    "css/color_scheme.h",
     "css/forced_colors.h",
     "css/navigation_controls.h",
     "css/page_orientation.h",
diff --git a/third_party/blink/public/common/css/color_scheme.h b/third_party/blink/public/common/css/color_scheme.h
deleted file mode 100644
index 3ea04a9f..0000000
--- a/third_party/blink/public/common/css/color_scheme.h
+++ /dev/null
@@ -1,21 +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 THIRD_PARTY_BLINK_PUBLIC_COMMON_CSS_COLOR_SCHEME_H_
-#define THIRD_PARTY_BLINK_PUBLIC_COMMON_CSS_COLOR_SCHEME_H_
-
-namespace blink {
-
-// This is the color scheme for rendering web content. The color scheme affects
-// the UA style sheet (setting the text color to white instead of black on the
-// root element for kDark), the frame backdrop color (black instead of white for
-// kDark), theming form controls and scrollbars, etc.
-enum ColorScheme {
-  kLight = 1,
-  kDark = 2,
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_CSS_COLOR_SCHEME_H_
diff --git a/third_party/blink/public/common/frame/frame_owner_properties_mojom_traits.h b/third_party/blink/public/common/frame/frame_owner_properties_mojom_traits.h
deleted file mode 100644
index 76907d7..0000000
--- a/third_party/blink/public/common/frame/frame_owner_properties_mojom_traits.h
+++ /dev/null
@@ -1,44 +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 THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_FRAME_OWNER_PROPERTIES_MOJOM_TRAITS_H_
-#define THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_FRAME_OWNER_PROPERTIES_MOJOM_TRAITS_H_
-
-#include "mojo/public/cpp/bindings/enum_traits.h"
-#include "third_party/blink/public/common/common_export.h"
-#include "third_party/blink/public/common/css/color_scheme.h"
-#include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom-shared.h"
-
-namespace mojo {
-
-template <>
-struct BLINK_COMMON_EXPORT
-    EnumTraits<blink::mojom::ColorScheme, ::blink::ColorScheme> {
-  static blink::mojom::ColorScheme ToMojom(::blink::ColorScheme color_scheme) {
-    switch (color_scheme) {
-      case ::blink::ColorScheme::kLight:
-        return blink::mojom::ColorScheme::kLight;
-      case ::blink::ColorScheme::kDark:
-        return blink::mojom::ColorScheme::kDark;
-    }
-    NOTREACHED();
-    return blink::mojom::ColorScheme::kLight;
-  }
-  static bool FromMojom(blink::mojom::ColorScheme input,
-                        ::blink::ColorScheme* out) {
-    switch (input) {
-      case blink::mojom::ColorScheme::kLight:
-        *out = ::blink::ColorScheme::kLight;
-        return true;
-      case blink::mojom::ColorScheme::kDark:
-        *out = ::blink::ColorScheme::kDark;
-        return true;
-    }
-    return false;
-  }
-};
-
-}  // namespace mojo
-
-#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_FRAME_OWNER_PROPERTIES_MOJOM_TRAITS_H_
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index ea7cd9f..135ade3b 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -212,6 +212,7 @@
   public_deps = [
     ":android_mojo_bindings",
     ":authenticator_test_mojo_bindings",
+    ":color_scheme_mojo_bindings",
     ":mojom_mhtml_load_result",
     ":script_type_mojo_bindings",
     ":web_feature_mojo_bindings",
@@ -415,17 +416,6 @@
     {
       types = [
         {
-          mojom = "blink.mojom.ColorScheme"
-          cpp = "::blink::ColorScheme"
-          copyable_pass_by_value = true
-        },
-      ]
-      traits_headers = [ "//third_party/blink/public/common/frame/frame_owner_properties_mojom_traits.h" ]
-      traits_public_deps = [ "//third_party/blink/public/common:headers" ]
-    },
-    {
-      types = [
-        {
           mojom = "blink.mojom.WebPreferences"
           cpp = "::blink::web_pref::WebPreferences"
         },
@@ -838,6 +828,7 @@
 
   public_deps = [
     ":android_mojo_bindings",
+    ":color_scheme_mojo_bindings",
     ":mojom_platform",
     ":script_type_mojo_bindings",
     ":web_feature_mojo_bindings",
@@ -1309,6 +1300,23 @@
   export_header_blink = "third_party/blink/renderer/platform/platform_export.h"
 }
 
+mojom("color_scheme_mojo_bindings") {
+  generate_java = true
+  visibility_blink = [
+    "//third_party/blink/public/mojom:mojom_core_blink",
+    "//third_party/blink/public/mojom:mojom_platform_blink",
+    "//third_party/blink/renderer/core:prerequisites",
+    "//third_party/blink/renderer/modules",
+    "//third_party/blink/renderer/platform",
+  ]
+
+  sources = [ "frame/color_scheme.mojom" ]
+
+  export_class_attribute = "BLINK_COMMON_EXPORT"
+  export_define = "BLINK_COMMON_IMPLEMENTATION=1"
+  export_header = "third_party/blink/public/common/common_export.h"
+}
+
 mojom("mobile_metrics") {
   sources = [ "mobile_metrics/mobile_friendliness.mojom" ]
   cpp_typemaps = [
diff --git a/third_party/blink/public/mojom/frame/color_scheme.mojom b/third_party/blink/public/mojom/frame/color_scheme.mojom
new file mode 100644
index 0000000..d4f3f3c
--- /dev/null
+++ b/third_party/blink/public/mojom/frame/color_scheme.mojom
@@ -0,0 +1,10 @@
+// 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 blink.mojom;
+
+enum ColorScheme {
+  kLight,
+  kDark,
+};
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index 4d307db..dfc7ea9 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -513,6 +513,14 @@
   // doing so via console messages.
   AddInspectorIssue(InspectorIssueInfo info);
 
+  // Requests that a provisional frame swap itself into the frame tree,
+  // immediately replacing the RemoteFrame that it is associated with. Normally,
+  // this swap happens when the navigation commits in the provisional frame.
+  // However, if the RemoteFrame corresponds to a crashed (and non-live) frame,
+  // the browser will immediately call this method to stop showing the sad
+  // iframe without having to wait for the navigation to commit.
+  SwapInImmediately();
+
   // Sent to a frame when one of its remote children finishes loading, so that
   // the frame can update its loading state.
   CheckCompleted();
diff --git a/third_party/blink/public/mojom/frame/frame_owner_properties.mojom b/third_party/blink/public/mojom/frame/frame_owner_properties.mojom
index aeb9f5ce..c6f9ef1d 100644
--- a/third_party/blink/public/mojom/frame/frame_owner_properties.mojom
+++ b/third_party/blink/public/mojom/frame/frame_owner_properties.mojom
@@ -5,11 +5,7 @@
 module blink.mojom;
 
 import "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom";
-
-enum ColorScheme {
-  kLight,
-  kDark,
-};
+import "third_party/blink/public/mojom/frame/color_scheme.mojom";
 
 struct FrameOwnerProperties {
   // Browsing context container's name
diff --git a/third_party/blink/public/platform/mac/web_sandbox_support.h b/third_party/blink/public/platform/mac/web_sandbox_support.h
index a91384b7..0dbe483 100644
--- a/third_party/blink/public/platform/mac/web_sandbox_support.h
+++ b/third_party/blink/public/platform/mac/web_sandbox_support.h
@@ -32,8 +32,8 @@
 #define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MAC_WEB_SANDBOX_SUPPORT_H_
 
 #include "base/mac/scoped_cftyperef.h"
-#include "third_party/blink/public/common/css/color_scheme.h"
 #include "third_party/blink/public/common/sandbox_support/sandbox_support_mac.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-shared.h"
 #include "third_party/skia/include/core/SkColor.h"
 
 typedef struct CGFont* CGFontRef;
@@ -57,7 +57,7 @@
       uint32_t* font_id) = 0;
 
   // Returns the system's preferred value for a named color.
-  virtual SkColor GetSystemColor(MacSystemColorID, ColorScheme) = 0;
+  virtual SkColor GetSystemColor(MacSystemColorID, mojom::ColorScheme) = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/platform/web_theme_engine.h b/third_party/blink/public/platform/web_theme_engine.h
index 97a07fd..8873bdf 100644
--- a/third_party/blink/public/platform/web_theme_engine.h
+++ b/third_party/blink/public/platform/web_theme_engine.h
@@ -34,8 +34,8 @@
 #include "base/optional.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "third_party/blink/public/common/css/color_scheme.h"
 #include "third_party/blink/public/common/css/forced_colors.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-shared.h"
 #include "third_party/blink/public/platform/web_scrollbar_overlay_color_theme.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect.h"
@@ -189,7 +189,7 @@
   struct ScrollbarExtraParams {
     bool is_hovering;
     bool is_overlay;
-    ColorScheme scrollbar_theme;
+    mojom::ColorScheme scrollbar_theme;
     ScrollbarOrientation orientation;
   };
 #endif
@@ -246,7 +246,7 @@
                      State,
                      const gfx::Rect&,
                      const ExtraParams*,
-                     blink::ColorScheme) {}
+                     blink::mojom::ColorScheme) {}
 
   virtual base::Optional<SkColor> GetSystemColor(
       SystemThemeColor system_theme) const {
diff --git a/third_party/blink/public/web/web_frame_owner_properties.h b/third_party/blink/public/web/web_frame_owner_properties.h
index 150fdd6..bc8a70072 100644
--- a/third_party/blink/public/web/web_frame_owner_properties.h
+++ b/third_party/blink/public/web/web_frame_owner_properties.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_FRAME_OWNER_PROPERTIES_H_
 #define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_FRAME_OWNER_PROPERTIES_H_
 
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-shared.h"
 #include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-shared.h"
 #include "third_party/blink/public/platform/web_string.h"
 
@@ -19,7 +19,7 @@
   bool allow_fullscreen{false};
   bool allow_payment_request{false};
   bool is_display_none{false};
-  ColorScheme color_scheme{ColorScheme::kLight};
+  mojom::ColorScheme color_scheme{mojom::ColorScheme::kLight};
   WebString required_csp;
 
  public:
@@ -33,7 +33,7 @@
                           bool allow_fullscreen,
                           bool allow_payment_request,
                           bool is_display_none,
-                          ColorScheme color_scheme,
+                          mojom::ColorScheme color_scheme,
                           const WebString& required_csp)
       : name(name),
         scrollbar_mode(scrollbar_mode),
diff --git a/third_party/blink/public/web/web_local_frame_client.h b/third_party/blink/public/web/web_local_frame_client.h
index 03bbdf9d..d20c4427 100644
--- a/third_party/blink/public/web/web_local_frame_client.h
+++ b/third_party/blink/public/web/web_local_frame_client.h
@@ -335,6 +335,16 @@
   // datasource will become the provisional datasource for the frame.
   virtual void DidCreateDocumentLoader(WebDocumentLoader*) {}
 
+  // A navigation is about to commit in a new frame that is still provisional
+  // (i.e. not swapped into the frame tree). Implementations should perform any
+  // bookkeeping work to sync the state of the previous frame and the new frame
+  // and use `WebFrame::Swap()` to swap in the new frame.
+  //
+  // The return value should be the return value of `WebFrame::Swap()`, which
+  // returns false if the navigation should not proceed due to the frame being
+  // removed from the frame tree by JS while swapping it in, or true otherwise.
+  virtual bool SwapIn(WebFrame* previous_frame) { return false; }
+
   // The navigation has been committed, as a result of
   // WebNavigationControl::CommitNavigation call. The newly created document
   // is committed to the frame, the encoding of the response body is known,
diff --git a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.h b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.h
index f23c503..27f175f3 100644
--- a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.h
+++ b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.h
@@ -71,6 +71,7 @@
       v8::Context::AbortScriptExecutionCallback callback);
 
  private:
+  // LocalWindowProxy overrides:
   bool IsLocal() const override { return true; }
   void Initialize() override;
   void DisposeContext(Lifecycle next_status, FrameReuseStatus) override;
diff --git a/third_party/blink/renderer/bindings/core/v8/remote_window_proxy.h b/third_party/blink/renderer/bindings/core/v8/remote_window_proxy.h
index 0841c42..a8b9818 100644
--- a/third_party/blink/renderer/bindings/core/v8/remote_window_proxy.h
+++ b/third_party/blink/renderer/bindings/core/v8/remote_window_proxy.h
@@ -48,6 +48,9 @@
   RemoteWindowProxy(v8::Isolate*, RemoteFrame&, scoped_refptr<DOMWrapperWorld>);
 
  private:
+  friend class WindowProxyManager;
+
+  // WindowProxy overrides:
   void Initialize() override;
   void DisposeContext(Lifecycle next_status, FrameReuseStatus) override;
 
@@ -62,6 +65,11 @@
   void SetupWindowPrototypeChain();
 };
 
+template <>
+struct DowncastTraits<RemoteWindowProxy> {
+  static bool AllowFrom(const WindowProxy& proxy) { return !proxy.IsLocal(); }
+};
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_REMOTE_WINDOW_PROXY_H_
diff --git a/third_party/blink/renderer/bindings/core/v8/window_proxy.cc b/third_party/blink/renderer/bindings/core/v8/window_proxy.cc
index 32e5a75..8e590ff 100644
--- a/third_party/blink/renderer/bindings/core/v8/window_proxy.cc
+++ b/third_party/blink/renderer/bindings/core/v8/window_proxy.cc
@@ -95,9 +95,9 @@
          lifecycle_ == Lifecycle::kGlobalObjectIsDetached);
 
   // Make sure the global object was detached from the proxy by calling
-  // clearForNavigation().
+  // ClearForSwap().
   DLOG_IF(FATAL, is_global_object_attached_)
-      << "Context not detached by calling clearForNavigation()";
+      << "Context not detached by calling ClearForSwap()";
 
   v8::Local<v8::Object> global_proxy = global_proxy_.NewLocal(isolate_);
   global_proxy_.Clear();
@@ -109,6 +109,10 @@
 
   CHECK(global_proxy_.IsEmpty());
   global_proxy_.Set(isolate_, global_proxy);
+  // The global proxy was transferred from a previous WindowProxy, so the state
+  // should be detached, not uninitialized. This ensures that it will be
+  // properly reinitialized when needed, e.g. by `UpdateDocument()`.
+  lifecycle_ = Lifecycle::kGlobalObjectIsDetached;
 }
 
 // Create a new environment and setup the global object.
diff --git a/third_party/blink/renderer/bindings/core/v8/window_proxy_manager.cc b/third_party/blink/renderer/bindings/core/v8/window_proxy_manager.cc
index 1032a80..373a0ef6c 100644
--- a/third_party/blink/renderer/bindings/core/v8/window_proxy_manager.cc
+++ b/third_party/blink/renderer/bindings/core/v8/window_proxy_manager.cc
@@ -4,8 +4,12 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h"
 
+#include "third_party/blink/renderer/bindings/core/v8/local_window_proxy.h"
+#include "third_party/blink/renderer/bindings/core/v8/remote_window_proxy.h"
+#include "third_party/blink/renderer/core/frame/remote_frame.h"
 #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
+#include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
 
@@ -58,14 +62,22 @@
   for (const auto& entry : global_proxies)
     WindowProxyMaybeUninitialized(*entry.first)->SetGlobalProxy(entry.second);
 
-  // Initialize the window proxies now, to re-establish the connection between
-  // the global object and the v8::Context. This is really only needed for a
-  // RemoteDOMWindow, since it has no scripting environment of its own.
-  // Without this, existing script references to a swapped in RemoteDOMWindow
-  // would be broken until that RemoteDOMWindow was vended again through an
-  // interface like window.frames.
-  for (const auto& entry : global_proxies)
-    WindowProxyMaybeUninitialized(*entry.first)->InitializeIfNeeded();
+  // Any transferred global proxies must now be reinitialized to ensure any
+  // preexisting JS references to global proxies don't break.
+
+  // For local frames, the global proxies cannot be reinitialized yet. Blink is
+  // in the midst of committing a navigation and swapping in the new frame.
+  // Instead, the global proxies will be reinitialized after this via a call to
+  // `UpdateDocument()` when the new `Document` is installed: this will happen
+  // before committing the navigation completes and yields back to the event
+  // loop.
+  if (frame_type_ == FrameType::kLocal)
+    return;
+
+  for (const auto& entry : global_proxies) {
+    To<RemoteWindowProxy>(WindowProxyMaybeUninitialized(*entry.first))
+        ->Initialize();
+  }
 }
 
 void WindowProxyManager::ResetIsolatedWorldsForTesting() {
@@ -125,20 +137,17 @@
   MainWorldProxyMaybeUninitialized()->UpdateDocument();
 
   for (auto& entry : isolated_worlds_) {
-    auto* isolated_window_proxy =
-        static_cast<LocalWindowProxy*>(entry.value.Get());
-    isolated_window_proxy->UpdateDocument();
+    To<LocalWindowProxy>(entry.value.Get())->UpdateDocument();
   }
 }
 
 void LocalWindowProxyManager::UpdateSecurityOrigin(
     const SecurityOrigin* security_origin) {
-  static_cast<LocalWindowProxy*>(window_proxy_.Get())
+  To<LocalWindowProxy>(window_proxy_.Get())
       ->UpdateSecurityOrigin(security_origin);
 
   for (auto& entry : isolated_worlds_) {
-    auto* isolated_window_proxy =
-        static_cast<LocalWindowProxy*>(entry.value.Get());
+    auto* isolated_window_proxy = To<LocalWindowProxy>(entry.value.Get());
     scoped_refptr<SecurityOrigin> isolated_security_origin =
         isolated_window_proxy->World().IsolatedWorldSecurityOrigin(
             security_origin->AgentClusterId());
@@ -154,9 +163,7 @@
       ->SetAbortScriptExecution(callback);
 
   for (auto& entry : isolated_worlds_) {
-    auto* isolated_window_proxy =
-        static_cast<LocalWindowProxy*>(entry.value.Get());
-    isolated_window_proxy->SetAbortScriptExecution(callback);
+    To<LocalWindowProxy>(entry.value.Get())->SetAbortScriptExecution(callback);
   }
 }
 
diff --git a/third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h b/third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h
index 53e1fd7..eb810cf9b 100644
--- a/third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h
+++ b/third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h
@@ -31,9 +31,10 @@
   void ClearForSwap();
   void ClearForV8MemoryPurge();
 
-  // Global proxies are passed in a vector to maintain their order: global proxy
-  // object for the main world is always first. This is needed to prevent bugs
-  // like https://crbug.com/700077 .
+  // Helpers used to transfer global proxies from the previous frame to the new
+  // frame when swapping frames. Global proxies are passed in a vector to ensure
+  // the main world is always processed first. This is needed to prevent bugs
+  // like https://crbug.com/700077.
   using GlobalProxyVector =
       Vector<std::pair<DOMWrapperWorld*, v8::Local<v8::Object>>>;
   void CORE_EXPORT ReleaseGlobalProxies(GlobalProxyVector&);
diff --git a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl_test.cc b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl_test.cc
index 4d5ec06..92185a64 100644
--- a/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl_test.cc
+++ b/third_party/blink/renderer/controller/performance_manager/renderer_resource_coordinator_impl_test.cc
@@ -209,7 +209,8 @@
                                        &current_v8_context_token),
                                    iframe_attribution_matcher));
   }
-  main_frame->FirstChild()->Swap(local_frame);
+  // Committing a navigation in the provisional frame swaps it in.
+  frame_test_helpers::LoadFrame(local_frame, "data:text/html,");
   mock_process_coordination_unit_->VerifyExpectations();
 
   // Local -> Local
@@ -224,7 +225,8 @@
                                        &current_v8_context_token),
                                    iframe_attribution_matcher));
   }
-  main_frame->FirstChild()->Swap(new_local_frame);
+  // Committing a navigation in the provisional frame swaps it in.
+  frame_test_helpers::LoadFrame(new_local_frame, "data:text/html,");
   mock_process_coordination_unit_->VerifyExpectations();
 
   // Local -> Remote
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 4310ee3..b9e8f4e2d73 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -125,6 +125,7 @@
     "//services/service_manager/public/cpp",
     "//skia",
     "//third_party/angle:translator",
+    "//third_party/blink/public/mojom:color_scheme_mojo_bindings",
     "//third_party/blink/public/mojom:mojom_broadcastchannel_bindings_blink",
     "//third_party/blink/renderer/bindings/core/v8:bindings_core_v8_generated",
     "//third_party/blink/renderer/bindings/core/v8:generated",
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 1e4dca11..f57b9b0 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -1898,7 +1898,7 @@
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal", "InitialValue"],
       style_builder_custom_functions: ["initial", "inherit", "value"],
       inherited: true,
-      include_paths: ["third_party/blink/public/common/css/color_scheme.h"],
+      include_paths: ["third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h"],
       type_name: "Vector<AtomicString>",
       default_value: "Vector<AtomicString, 0>()",
       field_template: "external",
diff --git a/third_party/blink/renderer/core/css/parser/css_parser.cc b/third_party/blink/renderer/core/css/parser/css_parser.cc
index 8ac3af1..24e43759 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser.cc
@@ -280,7 +280,7 @@
 
 bool CSSParser::ParseSystemColor(Color& color,
                                  const String& color_string,
-                                 ColorScheme color_scheme) {
+                                 mojom::blink::ColorScheme color_scheme) {
   CSSValueID id = CssValueKeywordID(color_string);
   if (!StyleColor::IsSystemColor(id))
     return false;
diff --git a/third_party/blink/renderer/core/css/parser/css_parser.h b/third_party/blink/renderer/core/css/parser/css_parser.h
index 99a926f..9277f458 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser.h
+++ b/third_party/blink/renderer/core/css/parser/css_parser.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PARSER_CSS_PARSER_H_
 
 #include <memory>
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_property_names.h"
 #include "third_party/blink/renderer/core/css/css_property_value_set.h"
@@ -103,7 +103,9 @@
   // The color will only be changed when string contains a valid CSS color, so
   // callers can set it to a default color and ignore the boolean result.
   static bool ParseColor(Color&, const String&, bool strict = false);
-  static bool ParseSystemColor(Color&, const String&, ColorScheme color_scheme);
+  static bool ParseSystemColor(Color&,
+                               const String&,
+                               mojom::blink::ColorScheme color_scheme);
 
   static void ParseSheetForInspector(const CSSParserContext*,
                                      StyleSheetContents*,
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
index 8aa2d4db..6e41b3b 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
@@ -1928,8 +1928,9 @@
     if (value_id == CSSValueID::kCurrentcolor)
       return value;
     if (StyleColor::IsColorKeyword(value_id)) {
-      ColorScheme scheme =
-          state ? state->Style()->UsedColorScheme() : ColorScheme::kLight;
+      mojom::blink::ColorScheme scheme =
+          state ? state->Style()->UsedColorScheme()
+                : mojom::blink::ColorScheme::kLight;
       Color color = document.GetTextLinkColors().ColorFromCSSValue(
           value, Color(), scheme, false);
       return *cssvalue::CSSColorValue::Create(color.Rgb());
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_state.cc b/third_party/blink/renderer/core/css/resolver/style_resolver_state.cc
index 4023f26..6be1002 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_state.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_state.cc
@@ -215,7 +215,7 @@
   if (const auto* pair = DynamicTo<CSSLightDarkValuePair>(value)) {
     if (!property.IsInherited())
       Style()->SetHasNonInheritedLightDarkValue();
-    if (Style()->UsedColorScheme() == ColorScheme::kLight)
+    if (Style()->UsedColorScheme() == mojom::blink::ColorScheme::kLight)
       return pair->First();
     return pair->Second();
   }
diff --git a/third_party/blink/renderer/core/css/style_color.cc b/third_party/blink/renderer/core/css/style_color.cc
index b62e67c..0a9dab2 100644
--- a/third_party/blink/renderer/core/css/style_color.cc
+++ b/third_party/blink/renderer/core/css/style_color.cc
@@ -10,7 +10,7 @@
 namespace blink {
 
 Color StyleColor::Resolve(Color current_color,
-                          ColorScheme color_scheme,
+                          mojom::blink::ColorScheme color_scheme,
                           bool is_forced_color) const {
   if (IsCurrentColor())
     return current_color;
@@ -21,7 +21,7 @@
 }
 
 Color StyleColor::ResolveWithAlpha(Color current_color,
-                                   ColorScheme color_scheme,
+                                   mojom::blink::ColorScheme color_scheme,
                                    int alpha,
                                    bool is_forced_color) const {
   Color color = Resolve(current_color, color_scheme, is_forced_color);
@@ -29,7 +29,7 @@
 }
 
 Color StyleColor::ColorFromKeyword(CSSValueID keyword,
-                                   ColorScheme color_scheme) {
+                                   mojom::blink::ColorScheme color_scheme) {
   if (const char* value_name = getValueName(keyword)) {
     if (const NamedColor* named_color =
             FindColor(value_name, static_cast<wtf_size_t>(strlen(value_name))))
diff --git a/third_party/blink/renderer/core/css/style_color.h b/third_party/blink/renderer/core/css/style_color.h
index 8a0b6ca..4cab8f2 100644
--- a/third_party/blink/renderer/core/css/style_color.h
+++ b/third_party/blink/renderer/core/css/style_color.h
@@ -31,7 +31,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_COLOR_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_COLOR_H_
 
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
@@ -74,7 +74,7 @@
   // TODO(1081945):  Once CSSSystemColorComputeToSelf is enabled, we can remove
   // |is_forced_color|.
   Color Resolve(Color current_color,
-                ColorScheme color_scheme,
+                mojom::blink::ColorScheme color_scheme,
                 bool is_forced_color = false) const;
 
   // Resolve and override the resolved color's alpha channel as specified by
@@ -82,7 +82,7 @@
   // TODO(1081945):  Once CSSSystemColorComputeToSelf is enabled, we can remove
   // |is_forced_color|.
   Color ResolveWithAlpha(Color current_color,
-                         ColorScheme color_scheme,
+                         mojom::blink::ColorScheme color_scheme,
                          int alpha,
                          bool is_forced_color = false) const;
 
@@ -90,7 +90,8 @@
     return EffectiveColorKeyword() == CSSValueID::kInvalid;
   }
 
-  static Color ColorFromKeyword(CSSValueID, ColorScheme color_scheme);
+  static Color ColorFromKeyword(CSSValueID,
+                                mojom::blink::ColorScheme color_scheme);
   static bool IsColorKeyword(CSSValueID);
   static bool IsSystemColor(CSSValueID);
 
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 0e6b81b..7ec6c361 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -29,6 +29,7 @@
 
 #include "third_party/blink/renderer/core/css/style_engine.h"
 
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_theme_engine.h"
 #include "third_party/blink/renderer/core/css/counter_style_map.h"
@@ -111,7 +112,8 @@
     : document_(&document),
       is_html_import_(document.IsHTMLImport()),
       document_style_sheet_collection_(
-          MakeGarbageCollected<DocumentStyleSheetCollection>(document)) {
+          MakeGarbageCollected<DocumentStyleSheetCollection>(document)),
+      owner_color_scheme_(mojom::blink::ColorScheme::kLight) {
   if (document.GetFrame()) {
     // We don't need to create CSSFontSelector for imported document or
     // HTMLTemplateElement's document, because those documents have no frame.
@@ -2272,18 +2274,20 @@
     // view's base background color in order to match the root element color-
     // scheme. See spec:
     // https://drafts.csswg.org/css-color-adjust/#color-scheme-effect
-    ColorScheme root_color_scheme = ColorScheme::kLight;
+    mojom::blink::ColorScheme root_color_scheme =
+        mojom::blink::ColorScheme::kLight;
     if (auto* root_element = GetDocument().documentElement()) {
       if (const ComputedStyle* style = root_element->GetComputedStyle())
         root_color_scheme = style->UsedColorSchemeForInitialColors();
       else if (SupportsDarkColorScheme())
-        root_color_scheme = ColorScheme::kDark;
+        root_color_scheme = mojom::blink::ColorScheme::kDark;
     }
-    color_scheme_background_ = root_color_scheme == ColorScheme::kLight
-                                   ? Color::kWhite
-                                   : Color(0x12, 0x12, 0x12);
+    color_scheme_background_ =
+        root_color_scheme == mojom::blink::ColorScheme::kLight
+            ? Color::kWhite
+            : Color(0x12, 0x12, 0x12);
     if (GetDocument().IsInMainFrame()) {
-      if (root_color_scheme == ColorScheme::kDark) {
+      if (root_color_scheme == mojom::blink::ColorScheme::kDark) {
         use_color_adjust_background =
             LocalFrameView::UseColorAdjustBackground::kIfBaseNotTransparent;
       }
@@ -2300,7 +2304,7 @@
                                     color_scheme_changed);
 }
 
-void StyleEngine::SetOwnerColorScheme(ColorScheme color_scheme) {
+void StyleEngine::SetOwnerColorScheme(mojom::blink::ColorScheme color_scheme) {
   DCHECK(!GetDocument().IsInMainFrame());
   if (owner_color_scheme_ == color_scheme)
     return;
@@ -2310,7 +2314,7 @@
 
 void StyleEngine::UpdateForcedBackgroundColor() {
   forced_background_color_ = LayoutTheme::GetTheme().SystemColor(
-      CSSValueID::kCanvas, ColorScheme::kLight);
+      CSSValueID::kCanvas, mojom::blink::ColorScheme::kLight);
 }
 
 Color StyleEngine::ColorAdjustBackgroundColor() const {
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index 647ec315..5cf889c 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -159,8 +159,10 @@
   void WatchedSelectorsChanged();
   void InitialStyleChanged();
   void ColorSchemeChanged();
-  void SetOwnerColorScheme(ColorScheme);
-  ColorScheme GetOwnerColorScheme() const { return owner_color_scheme_; }
+  void SetOwnerColorScheme(mojom::blink::ColorScheme);
+  mojom::blink::ColorScheme GetOwnerColorScheme() const {
+    return owner_color_scheme_;
+  }
   void InitialViewportChanged();
   void ViewportRulesChanged();
   void HtmlImportAddedOrRemoved();
@@ -666,7 +668,7 @@
   // embedding document. If the color-scheme of the owner element and the root
   // element in the embedded document differ, use a solid backdrop color instead
   // of the default transparency of an iframe.
-  ColorScheme owner_color_scheme_{ColorScheme::kLight};
+  mojom::blink::ColorScheme owner_color_scheme_;
 
   // The color of the canvas backdrop for the used color-scheme.
   Color color_scheme_background_;
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 aac42901..896b68a2 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -2402,7 +2402,7 @@
   color_scheme_helper.SetForcedColors(GetDocument(), ForcedColors::kActive);
   UpdateAllLifecyclePhases();
   Color system_background_color = LayoutTheme::GetTheme().SystemColor(
-      CSSValueID::kCanvas, ColorScheme::kLight);
+      CSSValueID::kCanvas, mojom::blink::ColorScheme::kLight);
 
   EXPECT_EQ(system_background_color,
             GetDocument().View()->BaseBackgroundColor());
@@ -2420,7 +2420,7 @@
   UpdateAllLifecyclePhases();
 
   EXPECT_EQ(
-      ColorScheme::kLight,
+      mojom::blink::ColorScheme::kLight,
       GetDocument().documentElement()->GetComputedStyle()->UsedColorScheme());
 
   GetDocument().GetPage()->SetMediaFeatureOverride("prefers-color-scheme",
@@ -2428,7 +2428,7 @@
 
   UpdateAllLifecyclePhases();
   EXPECT_EQ(
-      ColorScheme::kDark,
+      mojom::blink::ColorScheme::kDark,
       GetDocument().documentElement()->GetComputedStyle()->UsedColorScheme());
 }
 
@@ -2984,7 +2984,7 @@
 
   EXPECT_EQ(Color::kWhite, root->GetComputedStyle()->VisitedDependentColor(
                                GetCSSPropertyColor()));
-  EXPECT_EQ(ColorScheme::kDark,
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark,
             root->GetComputedStyle()->UsedColorSchemeForInitialColors());
   EXPECT_EQ(MakeRGB(255, 0, 0), body->GetComputedStyle()->VisitedDependentColor(
                                     GetCSSPropertyColor()));
@@ -2993,7 +2993,7 @@
   GetDocument().GetFrame()->StartPrinting(page_size, page_size, 1);
   EXPECT_EQ(Color::kBlack, root->GetComputedStyle()->VisitedDependentColor(
                                GetCSSPropertyColor()));
-  EXPECT_EQ(ColorScheme::kLight,
+  EXPECT_EQ(mojom::blink::ColorScheme::kLight,
             root->GetComputedStyle()->UsedColorSchemeForInitialColors());
   EXPECT_EQ(MakeRGB(0, 128, 0), body->GetComputedStyle()->VisitedDependentColor(
                                     GetCSSPropertyColor()));
@@ -3001,7 +3001,7 @@
   GetDocument().GetFrame()->EndPrinting();
   EXPECT_EQ(Color::kWhite, root->GetComputedStyle()->VisitedDependentColor(
                                GetCSSPropertyColor()));
-  EXPECT_EQ(ColorScheme::kDark,
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark,
             root->GetComputedStyle()->UsedColorSchemeForInitialColors());
   EXPECT_EQ(MakeRGB(255, 0, 0), body->GetComputedStyle()->VisitedDependentColor(
                                     GetCSSPropertyColor()));
@@ -3492,14 +3492,14 @@
       To<HTMLIFrameElement>(GetDocument().getElementById("frame"));
   auto* frame_document = frame_element->contentDocument();
   ASSERT_TRUE(frame_document);
-  EXPECT_EQ(ColorScheme::kDark,
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark,
             frame_document->GetStyleEngine().GetOwnerColorScheme());
 
   frame_element->SetInlineStyleProperty(CSSPropertyID::kColorScheme, "light");
 
   test::RunPendingTasks();
   Compositor().BeginFrame();
-  EXPECT_EQ(ColorScheme::kLight,
+  EXPECT_EQ(mojom::blink::ColorScheme::kLight,
             frame_document->GetStyleEngine().GetOwnerColorScheme());
 }
 
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index bb5f422..3640d73 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -4034,6 +4034,9 @@
     parser_->StopParsing();
 
   if (load_event_progress_ == kLoadEventNotRun ||
+      // TODO(dcheng): We should consider if we can make this conditional check
+      // stronger with a DCHECK() that this isn't called if the unload event is
+      // already complete.
       load_event_progress_ > kUnloadEventInProgress) {
     return;
   }
@@ -5703,7 +5706,7 @@
     int margin_height,
     mojom::blink::ScrollbarMode scrollbar_mode,
     bool is_display_none,
-    ColorScheme color_scheme) {
+    mojom::blink::ColorScheme color_scheme) {
   DCHECK(GetFrame() && GetFrame()->Owner());
   FrameOwner* owner = GetFrame()->Owner();
 
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index b9afdd1..8e818cd 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -42,6 +42,7 @@
 #include "third_party/blink/public/common/metrics/document_update_reason.h"
 #include "third_party/blink/public/mojom/feature_policy/document_policy_feature.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/federated_learning/floc.mojom-blink-forward.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-blink-forward.h"
@@ -992,7 +993,7 @@
                                       int margin_height,
                                       mojom::blink::ScrollbarMode,
                                       bool is_display_none,
-                                      ColorScheme color_scheme);
+                                      mojom::blink::ColorScheme color_scheme);
 
   String title() const { return title_; }
   void setTitle(const String&);
diff --git a/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc b/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc
index 4a388fa..81ae8f6 100644
--- a/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc
+++ b/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc
@@ -182,12 +182,17 @@
       return nullptr;
     } else if (first_letter_text_layout_object->IsInline() &&
                !first_letter_text_layout_object->SlowFirstChild()) {
-      LayoutObject* next_sibling =
-          first_letter_text_layout_object->NextSibling();
-      first_letter_text_layout_object =
-          next_sibling
-              ? next_sibling
-              : first_letter_text_layout_object->Parent()->NextSibling();
+      if (LayoutObject* next_sibling =
+              first_letter_text_layout_object->NextSibling()) {
+        first_letter_text_layout_object = next_sibling;
+        continue;
+      }
+      LayoutObject* parent = first_letter_text_layout_object->Parent();
+      if (parent && parent != parent_layout_object) {
+        first_letter_text_layout_object = parent->NextSibling();
+        continue;
+      }
+      return nullptr;
     } else {
       first_letter_text_layout_object =
           first_letter_text_layout_object->SlowFirstChild();
diff --git a/third_party/blink/renderer/core/dom/first_letter_pseudo_element_test.cc b/third_party/blink/renderer/core/dom/first_letter_pseudo_element_test.cc
index 3090df5..1d380f4 100644
--- a/third_party/blink/renderer/core/dom/first_letter_pseudo_element_test.cc
+++ b/third_party/blink/renderer/core/dom/first_letter_pseudo_element_test.cc
@@ -4,17 +4,32 @@
 
 #include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h"
 
-#include <gtest/gtest.h>
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
 
 namespace blink {
 
-class FirstLetterPseudoElementTest : public testing::Test {};
+class FirstLetterPseudoElementTest : public PageTestBase {};
 
 TEST_F(FirstLetterPseudoElementTest, DoesNotBreakEmoji) {
   const UChar emoji[] = {0xD83D, 0xDE31, 0};
   EXPECT_EQ(2u, FirstLetterPseudoElement::FirstLetterLength(emoji));
 }
 
+// http://crbug.com/1161370
+TEST_F(FirstLetterPseudoElementTest, EmptySpanOnly) {
+  InsertStyleElement("p::first-letter { color: red; }");
+  SetBodyContent("<div><p id=sample><b></b></p>abc</div>");
+  Element& sample = *GetElementById("sample");
+  // Call Element::RebuildFirstLetterLayoutTree()
+  sample.setAttribute(html_names::kContenteditableAttr, "true");
+  const PseudoElement* const first_letter =
+      sample.GetPseudoElement(kPseudoIdFirstLetter);
+  // We should not have ::first-letter pseudo element because <p> has no text.
+  // See |FirstLetterPseudoElement::FirstLetterTextLayoutObject()| should
+  // return nullptr during rebuilding layout tree.
+  EXPECT_FALSE(first_letter);
+}
+
 TEST_F(FirstLetterPseudoElementTest, UnicodePairBreaking) {
   const UChar test_string[] = {0xD800, 0xDD00, 'A', 0xD800, 0xDD00,
                                0xD800, 0xDD00, 'B', 0};
diff --git a/third_party/blink/renderer/core/dom/text_link_colors.cc b/third_party/blink/renderer/core/dom/text_link_colors.cc
index aa3bf1ac..2cb600b 100644
--- a/third_party/blink/renderer/core/dom/text_link_colors.cc
+++ b/third_party/blink/renderer/core/dom/text_link_colors.cc
@@ -29,6 +29,7 @@
 
 #include "third_party/blink/renderer/core/dom/text_link_colors.h"
 
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h"
 #include "third_party/blink/renderer/core/css/css_color_value.h"
 #include "third_party/blink/renderer/core/css/css_identifier_value.h"
 #include "third_party/blink/renderer/core/css/css_light_dark_value_pair.h"
@@ -60,11 +61,16 @@
   has_custom_text_color_ = true;
 }
 
-Color TextLinkColors::TextColor(ColorScheme color_scheme) const {
+Color TextLinkColors::TextColor() const {
+  return TextColor(mojom::blink::ColorScheme::kLight);
+}
+
+Color TextLinkColors::TextColor(mojom::blink::ColorScheme color_scheme) const {
   return has_custom_text_color_
              ? text_color_
-             : color_scheme == ColorScheme::kLight ? Color::kBlack
-                                                   : Color::kWhite;
+             : color_scheme == mojom::blink::ColorScheme::kLight
+                   ? Color::kBlack
+                   : Color::kWhite;
 }
 
 void TextLinkColors::SetLinkColor(const Color& color) {
@@ -72,11 +78,17 @@
   has_custom_link_color_ = true;
 }
 
-const Color& TextLinkColors::LinkColor(ColorScheme color_scheme) const {
+const Color& TextLinkColors::LinkColor() const {
+  return LinkColor(mojom::blink::ColorScheme::kLight);
+}
+
+const Color& TextLinkColors::LinkColor(
+    mojom::blink::ColorScheme color_scheme) const {
   return has_custom_link_color_
              ? link_color_
-             : color_scheme == ColorScheme::kLight ? kDefaultLinkColorLight
-                                                   : kDefaultLinkColorDark;
+             : color_scheme == mojom::blink::ColorScheme::kLight
+                   ? kDefaultLinkColorLight
+                   : kDefaultLinkColorDark;
 }
 
 void TextLinkColors::SetVisitedLinkColor(const Color& color) {
@@ -84,11 +96,17 @@
   has_custom_visited_link_color_ = true;
 }
 
-const Color& TextLinkColors::VisitedLinkColor(ColorScheme color_scheme) const {
-  return has_custom_visited_link_color_ ? visited_link_color_
-                                        : color_scheme == ColorScheme::kLight
-                                              ? kDefaultVisitedLinkColorLight
-                                              : kDefaultVisitedLinkColorDark;
+const Color& TextLinkColors::VisitedLinkColor() const {
+  return VisitedLinkColor(mojom::blink::ColorScheme::kLight);
+}
+
+const Color& TextLinkColors::VisitedLinkColor(
+    mojom::blink::ColorScheme color_scheme) const {
+  return has_custom_visited_link_color_
+             ? visited_link_color_
+             : color_scheme == mojom::blink::ColorScheme::kLight
+                   ? kDefaultVisitedLinkColorLight
+                   : kDefaultVisitedLinkColorDark;
 }
 
 void TextLinkColors::SetActiveLinkColor(const Color& color) {
@@ -96,23 +114,30 @@
   has_custom_active_link_color_ = true;
 }
 
-const Color& TextLinkColors::ActiveLinkColor(ColorScheme color_scheme) const {
-  return has_custom_active_link_color_ ? active_link_color_
-                                       : color_scheme == ColorScheme::kLight
-                                             ? kDefaultActiveLinkColorLight
-                                             : kDefaultActiveLinkColorDark;
+const Color& TextLinkColors::ActiveLinkColor() const {
+  return ActiveLinkColor(mojom::blink::ColorScheme::kLight);
+}
+
+const Color& TextLinkColors::ActiveLinkColor(
+    mojom::blink::ColorScheme color_scheme) const {
+  return has_custom_active_link_color_
+             ? active_link_color_
+             : color_scheme == mojom::blink::ColorScheme::kLight
+                   ? kDefaultActiveLinkColorLight
+                   : kDefaultActiveLinkColorDark;
 }
 
 Color TextLinkColors::ColorFromCSSValue(const CSSValue& value,
                                         Color current_color,
-                                        ColorScheme color_scheme,
+                                        mojom::blink::ColorScheme color_scheme,
                                         bool for_visited_link) const {
   if (auto* color_value = DynamicTo<cssvalue::CSSColorValue>(value))
     return color_value->Value();
 
   if (auto* pair = DynamicTo<CSSLightDarkValuePair>(value)) {
     const CSSValue& color_value =
-        color_scheme == ColorScheme::kLight ? pair->First() : pair->Second();
+        color_scheme == mojom::blink::ColorScheme::kLight ? pair->First()
+                                                          : pair->Second();
     return ColorFromCSSValue(color_value, current_color, color_scheme,
                              for_visited_link);
   }
diff --git a/third_party/blink/renderer/core/dom/text_link_colors.h b/third_party/blink/renderer/core/dom/text_link_colors.h
index 14efed8a..055ae99 100644
--- a/third_party/blink/renderer/core/dom/text_link_colors.h
+++ b/third_party/blink/renderer/core/dom/text_link_colors.h
@@ -30,7 +30,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TEXT_LINK_COLORS_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TEXT_LINK_COLORS_H_
 
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
@@ -47,13 +47,15 @@
   TextLinkColors& operator=(const TextLinkColors&) = delete;
 
   void SetTextColor(const Color& color);
-  Color TextColor(ColorScheme color_scheme = ColorScheme::kLight) const;
+  Color TextColor() const;
+  Color TextColor(mojom::blink::ColorScheme color_scheme) const;
 
-  const Color& LinkColor(ColorScheme color_scheme = ColorScheme::kLight) const;
-  const Color& VisitedLinkColor(
-      ColorScheme color_scheme = ColorScheme::kLight) const;
-  const Color& ActiveLinkColor(
-      ColorScheme color_scheme = ColorScheme::kLight) const;
+  const Color& LinkColor() const;
+  const Color& LinkColor(mojom::blink::ColorScheme color_scheme) const;
+  const Color& VisitedLinkColor() const;
+  const Color& VisitedLinkColor(mojom::blink::ColorScheme color_scheme) const;
+  const Color& ActiveLinkColor() const;
+  const Color& ActiveLinkColor(mojom::blink::ColorScheme color_scheme) const;
   void SetLinkColor(const Color& color);
   void SetVisitedLinkColor(const Color& color);
   void SetActiveLinkColor(const Color& color);
@@ -62,7 +64,7 @@
   void ResetActiveLinkColor() { has_custom_active_link_color_ = false; }
   Color ColorFromCSSValue(const CSSValue&,
                           Color current_color,
-                          ColorScheme color_scheme,
+                          mojom::blink::ColorScheme color_scheme,
                           bool for_visited_link = false) const;
 
  private:
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index 1110c224..eb962ab2 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -356,7 +356,8 @@
     bool in_forced_colors_mode =
         popup_client_->OwnerElement().GetDocument().InForcedColorsMode();
     page_->GetSettings().SetPreferredColorScheme(
-        style->UsedColorScheme() == ColorScheme::kDark && !in_forced_colors_mode
+        style->UsedColorScheme() == mojom::blink::ColorScheme::kDark &&
+                !in_forced_colors_mode
             ? mojom::blink::PreferredColorScheme::kDark
             : mojom::blink::PreferredColorScheme::kLight);
   }
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 64589ed..a7c7f66 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -593,7 +593,7 @@
   UpdateAllLifecyclePhases();
 
   Color system_background_color = LayoutTheme::GetTheme().SystemColor(
-      CSSValueID::kCanvas, ColorScheme::kLight);
+      CSSValueID::kCanvas, mojom::blink::ColorScheme::kLight);
   EXPECT_EQ(system_background_color, frame_view->BaseBackgroundColor());
 
   color_scheme_helper.SetForcedColors(*(web_view->GetPage()),
diff --git a/third_party/blink/renderer/core/frame/frame.cc b/third_party/blink/renderer/core/frame/frame.cc
index 0d5d1bf..9305b576 100644
--- a/third_party/blink/renderer/core/frame/frame.cc
+++ b/third_party/blink/renderer/core/frame/frame.cc
@@ -542,11 +542,10 @@
 }
 
 bool Frame::Swap(WebFrame* new_web_frame) {
+  DCHECK(IsAttached());
+
   using std::swap;
-  // TODO(dcheng): This should not be reachable. Reaching this implies `Swap()`
-  // is being called on an already-detached frame which should never happen...
-  if (!IsAttached())
-    return false;
+
   // Important: do not cache frame tree pointers (e.g.  `previous_sibling_`,
   // `next_sibling_`, `first_child_`, `last_child_`) here. It is possible for
   // `Detach()` to mutate the frame tree and cause cached values to become
diff --git a/third_party/blink/renderer/core/frame/frame_owner.h b/third_party/blink/renderer/core/frame/frame_owner.h
index 163493e..00c4bcf 100644
--- a/third_party/blink/renderer/core/frame/frame_owner.h
+++ b/third_party/blink/renderer/core/frame/frame_owner.h
@@ -4,6 +4,7 @@
 
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_FRAME_OWNER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_FRAME_OWNER_H_
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-shared.h"
 
 #include "third_party/blink/public/common/frame/frame_policy.h"
 #include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-blink.h"
@@ -72,7 +73,7 @@
   virtual bool AllowFullscreen() const = 0;
   virtual bool AllowPaymentRequest() const = 0;
   virtual bool IsDisplayNone() const = 0;
-  virtual ColorScheme GetColorScheme() const = 0;
+  virtual mojom::blink::ColorScheme GetColorScheme() const = 0;
   virtual AtomicString RequiredCsp() const = 0;
 
   // Returns whether or not children of the owned frame should be lazily loaded.
@@ -151,7 +152,9 @@
   bool AllowFullscreen() const override { return false; }
   bool AllowPaymentRequest() const override { return false; }
   bool IsDisplayNone() const override { return false; }
-  ColorScheme GetColorScheme() const override { return ColorScheme::kLight; }
+  mojom::blink::ColorScheme GetColorScheme() const override {
+    return mojom::blink::ColorScheme::kLight;
+  }
   AtomicString RequiredCsp() const override { return g_null_atom; }
   bool ShouldLazyLoadChildren() const override { return false; }
 
diff --git a/third_party/blink/renderer/core/frame/frame_test_helpers.cc b/third_party/blink/renderer/core/frame/frame_test_helpers.cc
index 171d487..eed12fd 100644
--- a/third_party/blink/renderer/core/frame/frame_test_helpers.cc
+++ b/third_party/blink/renderer/core/frame/frame_test_helpers.cc
@@ -654,6 +654,10 @@
   --loads_in_progress_;
 }
 
+bool TestWebFrameClient::SwapIn(WebFrame* previous_frame) {
+  return previous_frame->Swap(frame_);
+}
+
 void TestWebFrameClient::BeginNavigation(
     std::unique_ptr<WebNavigationInfo> info) {
   navigation_callback_.Cancel();
diff --git a/third_party/blink/renderer/core/frame/frame_test_helpers.h b/third_party/blink/renderer/core/frame/frame_test_helpers.h
index 5f28a822b..509b5f7 100644
--- a/third_party/blink/renderer/core/frame/frame_test_helpers.h
+++ b/third_party/blink/renderer/core/frame/frame_test_helpers.h
@@ -505,6 +505,7 @@
                                       policy_container_host_receiver) override;
   void DidStartLoading() override;
   void DidStopLoading() override;
+  bool SwapIn(WebFrame* previous_frame) override;
   std::unique_ptr<blink::WebURLLoaderFactory> CreateURLLoaderFactory()
       override {
     return std::make_unique<WebURLLoaderFactoryWithMock>(
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index b11ef956..e3af04e 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -576,12 +576,7 @@
   // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
   if (IsProvisional()) {
-    Frame* provisional_owner = nullptr;
-    if (Owner()) {
-      provisional_owner = Owner()->ContentFrame();
-    } else {
-      provisional_owner = GetPage()->MainFrame();
-    }
+    Frame* provisional_owner = GetProvisionalOwnerFrame();
     // Having multiple provisional frames somehow associated with the same frame
     // to potentially replace is a logic error.
     DCHECK_EQ(provisional_owner->ProvisionalFrame(), this);
@@ -2391,6 +2386,11 @@
   return frozen_ || paused_;
 }
 
+bool LocalFrame::SwapIn() {
+  WebLocalFrameClient* client = Client()->GetWebFrame()->Client();
+  return client->SwapIn(WebFrame::FromCoreFrame(GetProvisionalOwnerFrame()));
+}
+
 WebURLLoader::DeferType LocalFrame::GetLoadDeferType() {
   if (GetPage()->GetPageScheduler()->IsInBackForwardCache() &&
       base::FeatureList::IsEnabled(features::kLoadingTasksUnfreezable)) {
@@ -2919,6 +2919,10 @@
   }
 }
 
+void LocalFrame::SwapInImmediately() {
+  SwapIn();
+}
+
 void LocalFrame::StopLoading() {
   Loader().StopAllLoaders(/*abort_client=*/true);
 
@@ -3366,6 +3370,14 @@
 }
 #endif
 
+Frame* LocalFrame::GetProvisionalOwnerFrame() {
+  DCHECK(IsProvisional());
+  if (Owner()) {
+    return Owner()->ContentFrame();
+  }
+  return GetPage()->MainFrame();
+}
+
 void LocalFrame::BindToReceiver(
     blink::LocalFrame* frame,
     mojo::PendingAssociatedReceiver<mojom::blink::LocalFrame> receiver) {
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index 4a77518..e17c360 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -610,6 +610,7 @@
                            const WTF::String& message,
                            bool discard_duplicates) final;
   void AddInspectorIssue(mojom::blink::InspectorIssueInfoPtr) final;
+  void SwapInImmediately() final;
   void StopLoading() final;
   void Collapse(bool collapsed) final;
   void EnableViewSourceMode() final;
@@ -736,6 +737,8 @@
   WebURLLoader::DeferType GetLoadDeferType();
   bool IsLoadDeferred();
 
+  bool SwapIn();
+
  private:
   friend class FrameNavigationDisabler;
   FRIEND_TEST_ALL_PREFIXES(LocalFrameTest, CharacterIndexAtPointWithPinchZoom);
@@ -797,6 +800,10 @@
   mojom::blink::TextInputHost& GetTextInputHost();
 #endif
 
+  // Returns the `Frame` for which `provisional_frame_ == this`. May only be
+  // called on a provisional frame.
+  Frame* GetProvisionalOwnerFrame();
+
   static void BindToReceiver(
       blink::LocalFrame* frame,
       mojo::PendingAssociatedReceiver<mojom::blink::LocalFrame> receiver);
diff --git a/third_party/blink/renderer/core/frame/local_frame_view_test.cc b/third_party/blink/renderer/core/frame/local_frame_view_test.cc
index 612655ae..f4d780e 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view_test.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view_test.cc
@@ -196,7 +196,8 @@
   EXPECT_FALSE(ChildDocument().View()->CanHaveScrollbars());
 
   ChildDocument().WillChangeFrameOwnerProperties(
-      0, 0, mojom::blink::ScrollbarMode::kAlwaysOn, false, ColorScheme::kLight);
+      0, 0, mojom::blink::ScrollbarMode::kAlwaysOn, false,
+      mojom::blink::ColorScheme::kLight);
   EXPECT_TRUE(ChildDocument().View()->CanHaveScrollbars());
 }
 
diff --git a/third_party/blink/renderer/core/frame/remote_frame_owner.h b/third_party/blink/renderer/core/frame/remote_frame_owner.h
index 1094753..201ae55 100644
--- a/third_party/blink/renderer/core/frame/remote_frame_owner.h
+++ b/third_party/blink/renderer/core/frame/remote_frame_owner.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_REMOTE_FRAME_OWNER_H_
 
 #include "third_party/blink/public/common/frame/frame_policy.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_element_type.mojom-blink.h"
 #include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-blink.h"
 #include "third_party/blink/public/web/web_frame_owner_properties.h"
@@ -56,7 +57,9 @@
   bool AllowFullscreen() const override { return allow_fullscreen_; }
   bool AllowPaymentRequest() const override { return allow_payment_request_; }
   bool IsDisplayNone() const override { return is_display_none_; }
-  ColorScheme GetColorScheme() const override { return color_scheme_; }
+  mojom::blink::ColorScheme GetColorScheme() const override {
+    return color_scheme_;
+  }
   AtomicString RequiredCsp() const override { return required_csp_; }
   bool ShouldLazyLoadChildren() const final;
 
@@ -78,7 +81,7 @@
   void SetIsDisplayNone(bool is_display_none) {
     is_display_none_ = is_display_none;
   }
-  void SetColorScheme(ColorScheme color_scheme) {
+  void SetColorScheme(mojom::blink::ColorScheme color_scheme) {
     color_scheme_ = color_scheme;
   }
   void SetRequiredCsp(const WebString& required_csp) {
@@ -102,7 +105,7 @@
   bool allow_fullscreen_;
   bool allow_payment_request_;
   bool is_display_none_;
-  ColorScheme color_scheme_;
+  mojom::blink::ColorScheme color_scheme_;
   bool needs_occlusion_tracking_;
   WebString required_csp_;
   const mojom::blink::FrameOwnerElementType frame_owner_element_type_;
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.cc b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
index eda9c839..d2f47ad 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.cc
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
@@ -310,7 +310,7 @@
   return LayoutViewport().ScrollBehaviorStyle();
 }
 
-ColorScheme RootFrameViewport::UsedColorScheme() const {
+mojom::blink::ColorScheme RootFrameViewport::UsedColorScheme() const {
   return LayoutViewport().UsedColorScheme();
 }
 
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.h b/third_party/blink/renderer/core/frame/root_frame_viewport.h
index c1c339645..58fe030 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.h
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.h
@@ -112,7 +112,7 @@
   void UpdateCompositorScrollAnimations() override;
   void CancelProgrammaticScrollAnimation() override;
   mojom::blink::ScrollBehavior ScrollBehaviorStyle() const override;
-  ColorScheme UsedColorScheme() const override;
+  mojom::blink::ColorScheme UsedColorScheme() const override;
   void ClearScrollableArea() override;
   LayoutBox* GetLayoutBox() const override;
   FloatQuad LocalToVisibleContentQuad(const FloatQuad&,
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport_test.cc b/third_party/blink/renderer/core/frame/root_frame_viewport_test.cc
index 75a58b3a..bac6d698 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport_test.cc
@@ -107,7 +107,7 @@
                                                : user_input_scrollable_y_;
   }
   bool ScheduleAnimation() override { return true; }
-  ColorScheme UsedColorScheme() const override {
+  mojom::blink::ColorScheme UsedColorScheme() const override {
     return ComputedStyle::InitialStyle().UsedColorScheme();
   }
 
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc
index 6951851..c0e9b4c 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport.cc
@@ -836,7 +836,7 @@
   return LocalMainFrame()->GetTaskRunner(TaskType::kInternalDefault);
 }
 
-ColorScheme VisualViewport::UsedColorScheme() const {
+mojom::blink::ColorScheme VisualViewport::UsedColorScheme() const {
   if (LocalFrame* main_frame = LocalMainFrame()) {
     if (Document* main_document = main_frame->GetDocument())
       return main_document->GetLayoutView()->StyleRef().UsedColorScheme();
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.h b/third_party/blink/renderer/core/frame/visual_viewport.h
index 0a6fa4d8..4dc38ebe 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.h
+++ b/third_party/blink/renderer/core/frame/visual_viewport.h
@@ -220,7 +220,7 @@
       IncludeScrollbarsInRect = kExcludeScrollbars) const override;
   scoped_refptr<base::SingleThreadTaskRunner> GetTimerTaskRunner()
       const override;
-  ColorScheme UsedColorScheme() const override;
+  mojom::blink::ColorScheme UsedColorScheme() const override;
 
   // VisualViewport scrolling may involve pinch zoom and gets routed through
   // WebViewImpl explicitly rather than via ScrollingCoordinator::DidScroll
diff --git a/third_party/blink/renderer/core/frame/visual_viewport_test.cc b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
index 18923ee..3855370f9 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
@@ -2729,7 +2729,8 @@
   const VisualViewport& visual_viewport =
       WebView().GetPage()->GetVisualViewport();
 
-  EXPECT_EQ(ColorScheme::kLight, visual_viewport.UsedColorScheme());
+  EXPECT_EQ(mojom::blink::ColorScheme::kLight,
+            visual_viewport.UsedColorScheme());
 
   SimRequest request("https://example.com/test.html", "text/html");
   LoadURL("https://example.com/test.html");
@@ -2741,7 +2742,8 @@
       )HTML");
   Compositor().BeginFrame();
 
-  EXPECT_EQ(ColorScheme::kDark, visual_viewport.UsedColorScheme());
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark,
+            visual_viewport.UsedColorScheme());
 }
 
 TEST_P(VisualViewportTest, SetLocationBeforePrePaint) {
diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc
index 5a579c3..c125c58 100644
--- a/third_party/blink/renderer/core/frame/web_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_test.cc
@@ -8838,10 +8838,8 @@
 
   WebLocalFrame* local_frame =
       web_view_helper_.CreateProvisional(*remote_frame);
-  remote_frame->Swap(local_frame);
 
-  // Finally, make sure an embedder triggered load in the local frame swapped
-  // back in works.
+  // Committing a navigation in `local_frame` should swap it back in.
   frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
 
   std::string content =
@@ -9294,11 +9292,13 @@
 
   WebLocalFrame* local_frame =
       web_view_helper_.CreateProvisional(*remote_frame);
-  remote_frame->Swap(local_frame);
+  // Committing a navigation in a provisional frame will swap it in.
+  frame_test_helpers::LoadFrame(local_frame, "data:text/html,");
   v8::Local<v8::Value> local_window_top =
       MainFrame()->ExecuteScriptAndReturnValue(WebScriptSource("saved.top"));
   EXPECT_TRUE(local_window_top->IsObject());
   EXPECT_TRUE(window_top->StrictEquals(local_window_top));
+  local_frame->ExecuteScriptAndReturnValue(WebScriptSource("42"));
 }
 
 TEST_F(WebFrameSwapTest, RemoteFramesAreIndexable) {
@@ -9410,9 +9410,6 @@
 class RemoteToLocalSwapWebFrameClient
     : public frame_test_helpers::TestWebFrameClient {
  public:
-  explicit RemoteToLocalSwapWebFrameClient(WebRemoteFrame* remote_frame)
-      : history_commit_type_(kWebHistoryInertCommit),
-        remote_frame_(remote_frame) {}
   ~RemoteToLocalSwapWebFrameClient() override = default;
 
   // frame_test_helpers::TestWebFrameClient:
@@ -9423,15 +9420,13 @@
       const ParsedFeaturePolicy& feature_policy_header,
       const DocumentPolicyFeatureState& document_policy_header) override {
     history_commit_type_ = history_commit_type;
-    remote_frame_->Swap(Frame());
   }
 
   WebHistoryCommitType HistoryCommitType() const {
-    return history_commit_type_;
+    return *history_commit_type_;
   }
 
-  WebHistoryCommitType history_commit_type_;
-  WebRemoteFrame* remote_frame_;
+  base::Optional<WebHistoryCommitType> history_commit_type_;
 };
 
 // The commit type should be Initial if we are swapping a RemoteFrame to a
@@ -9446,7 +9441,7 @@
   ASSERT_TRUE(MainFrame()->FirstChild());
   ASSERT_EQ(MainFrame()->FirstChild(), remote_frame);
 
-  RemoteToLocalSwapWebFrameClient client(remote_frame);
+  RemoteToLocalSwapWebFrameClient client;
   WebLocalFrame* local_frame =
       web_view_helper_.CreateProvisional(*remote_frame, &client);
   frame_test_helpers::LoadFrame(local_frame, base_url_ + "subframe-hello.html");
@@ -9468,7 +9463,7 @@
   ASSERT_TRUE(MainFrame()->FirstChild());
   ASSERT_EQ(MainFrame()->FirstChild(), remote_frame);
 
-  RemoteToLocalSwapWebFrameClient client(remote_frame);
+  RemoteToLocalSwapWebFrameClient client;
   WebLocalFrameImpl* local_frame =
       web_view_helper_.CreateProvisional(*remote_frame, &client);
   local_frame->SetCommittedFirstRealLoad();
diff --git a/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc b/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc
index 4e79ecf..6855e10 100644
--- a/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc
+++ b/third_party/blink/renderer/core/html/forms/date_time_chooser_impl.cc
@@ -218,8 +218,8 @@
     AddProperty("otherDateLabel", other_date_label_string, data);
 
     const ComputedStyle* style = OwnerElement().GetComputedStyle();
-    ColorScheme color_scheme =
-        style ? style->UsedColorScheme() : ColorScheme::kLight;
+    mojom::blink::ColorScheme color_scheme =
+        style ? style->UsedColorScheme() : mojom::blink::ColorScheme::kLight;
 
     AddProperty("suggestionHighlightColor",
                 LayoutTheme::GetTheme()
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.cc b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
index 761d1d93..cc54e54d 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.cc
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom-blink.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
@@ -689,13 +690,14 @@
   return content_frame_->IsAdSubframe();
 }
 
-ColorScheme HTMLFrameOwnerElement::GetColorScheme() const {
+mojom::blink::ColorScheme HTMLFrameOwnerElement::GetColorScheme() const {
   if (const auto* style = GetComputedStyle())
     return style->UsedColorSchemeForInitialColors();
-  return ColorScheme::kLight;
+  return mojom::blink::ColorScheme::kLight;
 }
 
-void HTMLFrameOwnerElement::SetColorScheme(ColorScheme color_scheme) {
+void HTMLFrameOwnerElement::SetColorScheme(
+    mojom::blink::ColorScheme color_scheme) {
   Document* doc = contentDocument();
   if (doc && doc->GetFrame()) {
     doc->WillChangeFrameOwnerProperties(MarginWidth(), MarginHeight(),
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.h b/third_party/blink/renderer/core/html/html_frame_owner_element.h
index 268bc70c..eda50e4 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.h
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.h
@@ -74,7 +74,7 @@
     return embedded_content_view_;
   }
 
-  void SetColorScheme(ColorScheme);
+  void SetColorScheme(mojom::blink::ColorScheme);
 
   class PluginDisposeSuspendScope {
     STACK_ALLOCATED();
@@ -118,7 +118,7 @@
   bool AllowFullscreen() const override { return false; }
   bool AllowPaymentRequest() const override { return false; }
   bool IsDisplayNone() const override { return !embedded_content_view_; }
-  ColorScheme GetColorScheme() const override;
+  mojom::blink::ColorScheme GetColorScheme() const override;
   AtomicString RequiredCsp() const override { return g_null_atom; }
   bool ShouldLazyLoadChildren() const final;
 
diff --git a/third_party/blink/renderer/core/html/html_link_element.cc b/third_party/blink/renderer/core/html/html_link_element.cc
index 8ae0f2ff..6e5ed75 100644
--- a/third_party/blink/renderer/core/html/html_link_element.cc
+++ b/third_party/blink/renderer/core/html/html_link_element.cc
@@ -134,9 +134,6 @@
   } else if (name == html_names::kMediaAttr) {
     media_ = value.LowerASCII();
     Process();
-  } else if (name == html_names::kScopeAttr) {
-    scope_ = value;
-    Process();
   } else if (name == html_names::kIntegrityAttr) {
     integrity_ = value;
   } else if (name == html_names::kImportanceAttr &&
diff --git a/third_party/blink/renderer/core/html/html_link_element.h b/third_party/blink/renderer/core/html/html_link_element.h
index 8bb59ce..a7bc4426 100644
--- a/third_party/blink/renderer/core/html/html_link_element.h
+++ b/third_party/blink/renderer/core/html/html_link_element.h
@@ -69,7 +69,6 @@
   DOMTokenList& relList() const {
     return static_cast<DOMTokenList&>(*rel_list_);
   }
-  String Scope() const { return scope_; }
 
   const AtomicString& GetType() const;
 
@@ -177,7 +176,6 @@
   Vector<gfx::Size> icon_sizes_;
   Member<RelList> rel_list_;
   LinkRelAttribute rel_attribute_;
-  String scope_;
   Member<DOMTokenList> resources_;
   HashSet<KURL> valid_resource_urls_;
 
diff --git a/third_party/blink/renderer/core/html/html_meta_element.cc b/third_party/blink/renderer/core/html/html_meta_element.cc
index 389dcc9..d6b0d29 100644
--- a/third_party/blink/renderer/core/html/html_meta_element.cc
+++ b/third_party/blink/renderer/core/html/html_meta_element.cc
@@ -22,8 +22,8 @@
 
 #include "third_party/blink/renderer/core/html/html_meta_element.h"
 
-#include "third_party/blink/public/common/css/color_scheme.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
diff --git a/third_party/blink/renderer/core/layout/layout_theme.cc b/third_party/blink/renderer/core/layout/layout_theme.cc
index 6628945..de02277e 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme.cc
@@ -325,50 +325,50 @@
 }
 
 Color LayoutTheme::ActiveSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   Color color = PlatformActiveSelectionBackgroundColor(color_scheme);
 #if defined(OS_MAC)
   // BlendWithWhite() darkens Mac system colors too much.
   // Apply .8 (204/255) alpha instead, same as Safari.
-  if (color_scheme == ColorScheme::kDark)
+  if (color_scheme == mojom::blink::ColorScheme::kDark)
     return Color(color.Red(), color.Green(), color.Blue(), 204);
 #endif
   return color.BlendWithWhite();
 }
 
 Color LayoutTheme::InactiveSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformInactiveSelectionBackgroundColor(color_scheme)
       .BlendWithWhite();
 }
 
 Color LayoutTheme::ActiveSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformActiveSelectionForegroundColor(color_scheme);
 }
 
 Color LayoutTheme::InactiveSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformInactiveSelectionForegroundColor(color_scheme);
 }
 
 Color LayoutTheme::ActiveListBoxSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformActiveListBoxSelectionBackgroundColor(color_scheme);
 }
 
 Color LayoutTheme::InactiveListBoxSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformInactiveListBoxSelectionBackgroundColor(color_scheme);
 }
 
 Color LayoutTheme::ActiveListBoxSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformActiveListBoxSelectionForegroundColor(color_scheme);
 }
 
 Color LayoutTheme::InactiveListBoxSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformInactiveListBoxSelectionForegroundColor(color_scheme);
 }
 
@@ -385,47 +385,47 @@
 }
 
 Color LayoutTheme::PlatformActiveSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   // Use a blue color by default if the platform theme doesn't define anything.
   return Color(0, 0, 255);
 }
 
 Color LayoutTheme::PlatformActiveSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   // Use a white color by default if the platform theme doesn't define anything.
   return Color::kWhite;
 }
 
 Color LayoutTheme::PlatformInactiveSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   // Use a grey color by default if the platform theme doesn't define anything.
   // This color matches Firefox's inactive color.
   return Color(176, 176, 176);
 }
 
 Color LayoutTheme::PlatformInactiveSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   // Use a black color by default.
   return Color::kBlack;
 }
 
 Color LayoutTheme::PlatformActiveListBoxSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformActiveSelectionBackgroundColor(color_scheme);
 }
 
 Color LayoutTheme::PlatformActiveListBoxSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformActiveSelectionForegroundColor(color_scheme);
 }
 
 Color LayoutTheme::PlatformInactiveListBoxSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformInactiveSelectionBackgroundColor(color_scheme);
 }
 
 Color LayoutTheme::PlatformInactiveListBoxSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return PlatformInactiveSelectionForegroundColor(color_scheme);
 }
 
@@ -618,7 +618,7 @@
 }
 
 Color LayoutTheme::SystemColor(CSSValueID css_value_id,
-                               ColorScheme color_scheme) const {
+                               mojom::blink::ColorScheme color_scheme) const {
   switch (css_value_id) {
     case CSSValueID::kActiveborder:
       return 0xFFFFFFFF;
@@ -627,29 +627,36 @@
     case CSSValueID::kActivetext:
       return 0xFFFF0000;
     case CSSValueID::kAppworkspace:
-      return color_scheme == ColorScheme::kDark ? 0xFF000000 : 0xFFFFFFFF;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFF000000
+                                                              : 0xFFFFFFFF;
     case CSSValueID::kBackground:
       return 0xFF6363CE;
     case CSSValueID::kButtonface:
-      return color_scheme == ColorScheme::kDark ? 0xFF444444 : 0xFFDDDDDD;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFF444444
+                                                              : 0xFFDDDDDD;
     case CSSValueID::kButtonhighlight:
       return 0xFFDDDDDD;
     case CSSValueID::kButtonshadow:
       return 0xFF888888;
     case CSSValueID::kButtontext:
-      return color_scheme == ColorScheme::kDark ? 0xFFAAAAAA : 0xFF000000;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFAAAAAA
+                                                              : 0xFF000000;
     case CSSValueID::kCaptiontext:
-      return color_scheme == ColorScheme::kDark ? 0xFFFFFFFF : 0xFF000000;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFFFFFFF
+                                                              : 0xFF000000;
     case CSSValueID::kField:
-      return color_scheme == ColorScheme::kDark ? 0xFF000000 : 0xFFFFFFFF;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFF000000
+                                                              : 0xFFFFFFFF;
     case CSSValueID::kFieldtext:
-      return color_scheme == ColorScheme::kDark ? 0xFFFFFFFF : 0xFF000000;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFFFFFFF
+                                                              : 0xFF000000;
     case CSSValueID::kGraytext:
       return 0xFF808080;
     case CSSValueID::kHighlight:
       return 0xFFB5D5FF;
     case CSSValueID::kHighlighttext:
-      return color_scheme == ColorScheme::kDark ? 0xFFFFFFFF : 0xFF000000;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFFFFFFF
+                                                              : 0xFF000000;
     case CSSValueID::kInactiveborder:
       return 0xFFFFFFFF;
     case CSSValueID::kInactivecaption:
@@ -657,19 +664,24 @@
     case CSSValueID::kInactivecaptiontext:
       return 0xFF7F7F7F;
     case CSSValueID::kInfobackground:
-      return color_scheme == ColorScheme::kDark ? 0xFFB46E32 : 0xFFFBFCC5;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFB46E32
+                                                              : 0xFFFBFCC5;
     case CSSValueID::kInfotext:
-      return color_scheme == ColorScheme::kDark ? 0xFFFFFFFF : 0xFF000000;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFFFFFFF
+                                                              : 0xFF000000;
     case CSSValueID::kLinktext:
       return 0xFF0000EE;
     case CSSValueID::kMenu:
-      return color_scheme == ColorScheme::kDark ? 0xFF404040 : 0xFFF7F7F7;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFF404040
+                                                              : 0xFFF7F7F7;
     case CSSValueID::kMenutext:
-      return color_scheme == ColorScheme::kDark ? 0xFFFFFFFF : 0xFF000000;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFFFFFFF
+                                                              : 0xFF000000;
     case CSSValueID::kScrollbar:
       return 0xFFFFFFFF;
     case CSSValueID::kText:
-      return color_scheme == ColorScheme::kDark ? 0xFFFFFFFF : 0xFF000000;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFFFFFFF
+                                                              : 0xFF000000;
     case CSSValueID::kThreeddarkshadow:
       return 0xFF666666;
     case CSSValueID::kThreedface:
@@ -684,12 +696,14 @@
       return 0xFF551A8B;
     case CSSValueID::kWindow:
     case CSSValueID::kCanvas:
-      return color_scheme == ColorScheme::kDark ? 0xFF000000 : 0xFFFFFFFF;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFF000000
+                                                              : 0xFFFFFFFF;
     case CSSValueID::kWindowframe:
       return 0xFFCCCCCC;
     case CSSValueID::kWindowtext:
     case CSSValueID::kCanvastext:
-      return color_scheme == ColorScheme::kDark ? 0xFFFFFFFF : 0xFF000000;
+      return color_scheme == mojom::blink::ColorScheme::kDark ? 0xFFFFFFFF
+                                                              : 0xFF000000;
     case CSSValueID::kInternalActiveListBoxSelection:
       return ActiveListBoxSelectionBackgroundColor(color_scheme);
     case CSSValueID::kInternalActiveListBoxSelectionText:
@@ -708,7 +722,7 @@
 Color LayoutTheme::PlatformTextSearchHighlightColor(
     bool active_match,
     bool in_forced_colors_mode,
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   if (active_match) {
     if (in_forced_colors_mode)
       return GetTheme().SystemColor(CSSValueID::kHighlight, color_scheme);
@@ -717,9 +731,10 @@
   return Color(255, 255, 0);     // Yellow.
 }
 
-Color LayoutTheme::PlatformTextSearchColor(bool active_match,
-                                           bool in_forced_colors_mode,
-                                           ColorScheme color_scheme) const {
+Color LayoutTheme::PlatformTextSearchColor(
+    bool active_match,
+    bool in_forced_colors_mode,
+    mojom::blink::ColorScheme color_scheme) const {
   if (in_forced_colors_mode && active_match)
     return GetTheme().SystemColor(CSSValueID::kHighlighttext, color_scheme);
   return Color::kBlack;
diff --git a/third_party/blink/renderer/core/layout/layout_theme.h b/third_party/blink/renderer/core/layout/layout_theme.h
index e6d9e20..927f9f0 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.h
+++ b/third_party/blink/renderer/core/layout/layout_theme.h
@@ -23,7 +23,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_THEME_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_THEME_H_
 
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
@@ -91,20 +91,28 @@
   virtual bool SupportsCalendarPicker(const AtomicString&) const;
 
   // Text selection colors.
-  Color ActiveSelectionBackgroundColor(ColorScheme color_scheme) const;
-  Color InactiveSelectionBackgroundColor(ColorScheme color_scheme) const;
-  Color ActiveSelectionForegroundColor(ColorScheme color_scheme) const;
-  Color InactiveSelectionForegroundColor(ColorScheme color_scheme) const;
+  Color ActiveSelectionBackgroundColor(
+      mojom::blink::ColorScheme color_scheme) const;
+  Color InactiveSelectionBackgroundColor(
+      mojom::blink::ColorScheme color_scheme) const;
+  Color ActiveSelectionForegroundColor(
+      mojom::blink::ColorScheme color_scheme) const;
+  Color InactiveSelectionForegroundColor(
+      mojom::blink::ColorScheme color_scheme) const;
   virtual void SetSelectionColors(Color active_background_color,
                                   Color active_foreground_color,
                                   Color inactive_background_color,
                                   Color inactive_foreground_color) {}
 
   // List box selection colors
-  Color ActiveListBoxSelectionBackgroundColor(ColorScheme color_scheme) const;
-  Color ActiveListBoxSelectionForegroundColor(ColorScheme color_scheme) const;
-  Color InactiveListBoxSelectionBackgroundColor(ColorScheme color_scheme) const;
-  Color InactiveListBoxSelectionForegroundColor(ColorScheme color_scheme) const;
+  Color ActiveListBoxSelectionBackgroundColor(
+      mojom::blink::ColorScheme color_scheme) const;
+  Color ActiveListBoxSelectionForegroundColor(
+      mojom::blink::ColorScheme color_scheme) const;
+  Color InactiveListBoxSelectionBackgroundColor(
+      mojom::blink::ColorScheme color_scheme) const;
+  Color InactiveListBoxSelectionForegroundColor(
+      mojom::blink::ColorScheme color_scheme) const;
 
   virtual Color PlatformSpellingMarkerUnderlineColor() const;
   virtual Color PlatformGrammarMarkerUnderlineColor() const;
@@ -112,12 +120,13 @@
   Color PlatformActiveSpellingMarkerHighlightColor() const;
 
   // Highlight and text colors for TextMatches.
-  Color PlatformTextSearchHighlightColor(bool active_match,
-                                         bool in_forced_colors_mode,
-                                         ColorScheme color_scheme) const;
+  Color PlatformTextSearchHighlightColor(
+      bool active_match,
+      bool in_forced_colors_mode,
+      mojom::blink::ColorScheme color_scheme) const;
   Color PlatformTextSearchColor(bool active_match,
                                 bool in_forced_colors_mode,
-                                ColorScheme color_scheme) const;
+                                mojom::blink::ColorScheme color_scheme) const;
 
   virtual Color FocusRingColor() const;
   virtual Color PlatformFocusRingColor() const { return Color(0, 0, 0); }
@@ -138,7 +147,8 @@
 
   // System fonts and colors for CSS.
   void SystemFont(CSSValueID system_font_id, FontDescription&);
-  virtual Color SystemColor(CSSValueID, ColorScheme color_scheme) const;
+  virtual Color SystemColor(CSSValueID,
+                            mojom::blink::ColorScheme color_scheme) const;
 
   virtual void AdjustSliderThumbSize(ComputedStyle&) const;
 
@@ -178,22 +188,22 @@
  protected:
   // The platform selection color.
   virtual Color PlatformActiveSelectionBackgroundColor(
-      ColorScheme color_scheme) const;
+      mojom::blink::ColorScheme color_scheme) const;
   virtual Color PlatformInactiveSelectionBackgroundColor(
-      ColorScheme color_scheme) const;
+      mojom::blink::ColorScheme color_scheme) const;
   virtual Color PlatformActiveSelectionForegroundColor(
-      ColorScheme color_scheme) const;
+      mojom::blink::ColorScheme color_scheme) const;
   virtual Color PlatformInactiveSelectionForegroundColor(
-      ColorScheme color_scheme) const;
+      mojom::blink::ColorScheme color_scheme) const;
 
   virtual Color PlatformActiveListBoxSelectionBackgroundColor(
-      ColorScheme color_scheme) const;
+      mojom::blink::ColorScheme color_scheme) const;
   virtual Color PlatformInactiveListBoxSelectionBackgroundColor(
-      ColorScheme color_scheme) const;
+      mojom::blink::ColorScheme color_scheme) const;
   virtual Color PlatformActiveListBoxSelectionForegroundColor(
-      ColorScheme color_scheme) const;
+      mojom::blink::ColorScheme color_scheme) const;
   virtual Color PlatformInactiveListBoxSelectionForegroundColor(
-      ColorScheme color_scheme) const;
+      mojom::blink::ColorScheme color_scheme) const;
 
   // Methods for each appearance value.
   virtual void AdjustCheckboxStyle(ComputedStyle&) const;
diff --git a/third_party/blink/renderer/core/layout/layout_theme_default.cc b/third_party/blink/renderer/core/layout/layout_theme_default.cc
index ee082c7f..4f4845e 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_default.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme_default.cc
@@ -86,22 +86,22 @@
 }
 
 Color LayoutThemeDefault::PlatformActiveSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return active_selection_background_color_;
 }
 
 Color LayoutThemeDefault::PlatformInactiveSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return inactive_selection_background_color_;
 }
 
 Color LayoutThemeDefault::PlatformActiveSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return active_selection_foreground_color_;
 }
 
 Color LayoutThemeDefault::PlatformInactiveSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return inactive_selection_foreground_color_;
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_theme_default.h b/third_party/blink/renderer/core/layout/layout_theme_default.h
index 3111958..f249604c 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_default.h
+++ b/third_party/blink/renderer/core/layout/layout_theme_default.h
@@ -41,13 +41,13 @@
   String ExtraQuirksStyleSheet() override;
 
   Color PlatformActiveSelectionBackgroundColor(
-      ColorScheme color_scheme) const override;
+      mojom::blink::ColorScheme color_scheme) const override;
   Color PlatformInactiveSelectionBackgroundColor(
-      ColorScheme color_scheme) const override;
+      mojom::blink::ColorScheme color_scheme) const override;
   Color PlatformActiveSelectionForegroundColor(
-      ColorScheme color_scheme) const override;
+      mojom::blink::ColorScheme color_scheme) const override;
   Color PlatformInactiveSelectionForegroundColor(
-      ColorScheme color_scheme) const override;
+      mojom::blink::ColorScheme color_scheme) const override;
 
   IntSize SliderTickSize() const override;
   int SliderTickOffsetFromTrackCenter() const override;
diff --git a/third_party/blink/renderer/core/layout/layout_theme_mac.h b/third_party/blink/renderer/core/layout/layout_theme_mac.h
index 9e3d799..61bbb5c 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_mac.h
+++ b/third_party/blink/renderer/core/layout/layout_theme_mac.h
@@ -38,11 +38,11 @@
   }
 
   Color PlatformActiveSelectionBackgroundColor(
-      ColorScheme color_scheme) const override;
+      mojom::blink::ColorScheme color_scheme) const override;
   Color PlatformInactiveSelectionBackgroundColor(
-      ColorScheme color_scheme) const override;
+      mojom::blink::ColorScheme color_scheme) const override;
   Color PlatformActiveSelectionForegroundColor(
-      ColorScheme color_scheme) const override;
+      mojom::blink::ColorScheme color_scheme) const override;
   Color PlatformSpellingMarkerUnderlineColor() const override;
   Color PlatformGrammarMarkerUnderlineColor() const override;
   Color FocusRingColor() const override;
@@ -54,7 +54,7 @@
  protected:
   // Controls color values returned from FocusRingColor().
   bool UsesTestModeFocusRingColor() const;
-  bool IsAccentColorCustomized(ColorScheme color_scheme) const;
+  bool IsAccentColorCustomized(mojom::blink::ColorScheme color_scheme) const;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_theme_mac.mm b/third_party/blink/renderer/core/layout/layout_theme_mac.mm
index 6fd8ee5d..748da2b 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_mac.mm
+++ b/third_party/blink/renderer/core/layout/layout_theme_mac.mm
@@ -34,7 +34,8 @@
 namespace blink {
 
 namespace {
-Color GetSystemColor(MacSystemColorID color_id, ColorScheme color_scheme) {
+Color GetSystemColor(MacSystemColorID color_id,
+                     mojom::blink::ColorScheme color_scheme) {
   // In tests, a WebSandboxSupport may not be set up. Just return a dummy
   // color, in this case, black.
   auto* sandbox_support = Platform::Current()->GetSandboxSupport();
@@ -51,19 +52,19 @@
 }
 
 Color LayoutThemeMac::PlatformActiveSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return GetSystemColor(MacSystemColorID::kSelectedTextBackground,
                         color_scheme);
 }
 
 Color LayoutThemeMac::PlatformInactiveSelectionBackgroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return GetSystemColor(MacSystemColorID::kSecondarySelectedControl,
                         color_scheme);
 }
 
 Color LayoutThemeMac::PlatformActiveSelectionForegroundColor(
-    ColorScheme color_scheme) const {
+    mojom::blink::ColorScheme color_scheme) const {
   return Color::kBlack;
 }
 
@@ -75,7 +76,8 @@
   return Color(107, 107, 107);
 }
 
-bool LayoutThemeMac::IsAccentColorCustomized(ColorScheme color_scheme) const {
+bool LayoutThemeMac::IsAccentColorCustomized(
+    mojom::blink::ColorScheme color_scheme) const {
   if (@available(macOS 10.14, *)) {
     static const Color kControlBlueAccentColor =
         GetSystemColor(MacSystemColorID::kControlAccentBlueColor, color_scheme);
@@ -107,7 +109,8 @@
   }
 
   // TODO(crbug.com/929098) Need to pass an appropriate color scheme here.
-  ColorScheme color_scheme = ComputedStyle::InitialStyle().UsedColorScheme();
+  mojom::blink::ColorScheme color_scheme =
+      ComputedStyle::InitialStyle().UsedColorScheme();
 
   SkColor keyboard_focus_indicator = SkColor(
       GetSystemColor(MacSystemColorID::kKeyboardFocusIndicator, color_scheme));
diff --git a/third_party/blink/renderer/core/layout/layout_theme_mobile.h b/third_party/blink/renderer/core/layout/layout_theme_mobile.h
index 185be962..09ee09d 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_mobile.h
+++ b/third_party/blink/renderer/core/layout/layout_theme_mobile.h
@@ -44,7 +44,7 @@
   }
 
   Color PlatformActiveSelectionBackgroundColor(
-      ColorScheme color_scheme) const override {
+      mojom::blink::ColorScheme color_scheme) const override {
     return LayoutThemeMobile::kDefaultActiveSelectionBackgroundColor;
   }
 
diff --git a/third_party/blink/renderer/core/layout/layout_theme_test.cc b/third_party/blink/renderer/core/layout/layout_theme_test.cc
index 76d7557..10d1803 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme_test.cc
@@ -95,7 +95,7 @@
   ASSERT_TRUE(dark_element);
 
   const ComputedStyle* style = dark_element->GetComputedStyle();
-  EXPECT_EQ(ColorScheme::kLight, style->UsedColorScheme());
+  EXPECT_EQ(mojom::blink::ColorScheme::kLight, style->UsedColorScheme());
   EXPECT_EQ(Color(0xdd, 0xdd, 0xdd),
             style->VisitedDependentColor(GetCSSPropertyColor()));
 
@@ -106,7 +106,7 @@
   UpdateAllLifecyclePhasesForTest();
 
   style = dark_element->GetComputedStyle();
-  EXPECT_EQ(ColorScheme::kDark, style->UsedColorScheme());
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark, style->UsedColorScheme());
   EXPECT_EQ(Color(0x44, 0x44, 0x44),
             style->VisitedDependentColor(GetCSSPropertyColor()));
 }
@@ -116,7 +116,7 @@
                                              Color::kBlack, Color::kBlack);
   EXPECT_EQ(Color::kBlack,
             LayoutTheme::GetTheme().ActiveSelectionForegroundColor(
-                ColorScheme::kLight));
+                mojom::blink::ColorScheme::kLight));
   {
     // Enabling MobileLayoutTheme switches which instance is returned from
     // LayoutTheme::GetTheme(). Devtools expect SetSelectionColors() to affect
@@ -124,17 +124,17 @@
     ScopedMobileLayoutThemeForTest scope(true);
     EXPECT_EQ(Color::kBlack,
               LayoutTheme::GetTheme().ActiveSelectionForegroundColor(
-                  ColorScheme::kLight));
+                  mojom::blink::ColorScheme::kLight));
 
     LayoutTheme::GetTheme().SetSelectionColors(Color::kWhite, Color::kWhite,
                                                Color::kWhite, Color::kWhite);
     EXPECT_EQ(Color::kWhite,
               LayoutTheme::GetTheme().ActiveSelectionForegroundColor(
-                  ColorScheme::kLight));
+                  mojom::blink::ColorScheme::kLight));
   }
   EXPECT_EQ(Color::kWhite,
             LayoutTheme::GetTheme().ActiveSelectionForegroundColor(
-                ColorScheme::kLight));
+                mojom::blink::ColorScheme::kLight));
 }
 #endif  // !defined(OS_MAC)
 
diff --git a/third_party/blink/renderer/core/layout/layout_theme_win.cc b/third_party/blink/renderer/core/layout/layout_theme_win.cc
index 7c23535..6db48d7 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_win.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme_win.cc
@@ -21,8 +21,9 @@
   return *layout_theme;
 }
 
-Color LayoutThemeWin::SystemColor(CSSValueID css_value_id,
-                                  ColorScheme color_scheme) const {
+Color LayoutThemeWin::SystemColor(
+    CSSValueID css_value_id,
+    mojom::blink::ColorScheme color_scheme) const {
   blink::WebThemeEngine::SystemThemeColor theme_color;
   switch (css_value_id) {
     case CSSValueID::kActivetext:
diff --git a/third_party/blink/renderer/core/layout/layout_theme_win.h b/third_party/blink/renderer/core/layout/layout_theme_win.h
index 97c897a..20ff279 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_win.h
+++ b/third_party/blink/renderer/core/layout/layout_theme_win.h
@@ -14,7 +14,7 @@
   static scoped_refptr<LayoutTheme> Create();
 
   Color SystemColor(CSSValueID css_value_id,
-                    ColorScheme color_scheme) const override;
+                    mojom::blink::ColorScheme color_scheme) const override;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
index 788f0cc..e8a41053 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
@@ -210,6 +210,8 @@
   LayoutUnit block_size = fragment_geometry.border_box_size.block_size;
   bool is_initial_block_size_indefinite = block_size == kIndefiniteSize;
   if (is_initial_block_size_indefinite) {
+    LayoutUnit intrinsic_block_size = layout_result.IntrinsicBlockSize();
+
     if (node.IsFlexibleBox()) {
       // Flex-boxes can have their children calculate their size based in their
       // parent's final block-size. E.g.
@@ -227,8 +229,8 @@
       // </div>
       //
       // If the previous |layout_result| was produced by a space which had a
-      // fixed block-size we can't use |NGLayoutResult::IntrinsicBlockSize()|,
-      // and need to layout.
+      // fixed block-size we can't use |intrinsic_block_size| for determining
+      // the new block-size.
       //
       // TODO(ikilpatrick): Similar to %-block-size descendants we could store
       // a bit on the |NGLayoutResult| which indicates if it had a child which
@@ -238,7 +240,7 @@
       // TODO(ikilaptrick): This may occur for other layout modes, e.g.
       // grid/custom-layout/etc.
       if (old_space.IsFixedBlockSize())
-        return NGLayoutCacheStatus::kNeedsLayout;
+        intrinsic_block_size = kIndefiniteSize;
 
       // The intrinsic size of flex-boxes can depend on the %-block-size. This
       // occurs when:
@@ -246,21 +248,22 @@
       //  - A row flex-box has "height: 100%" (or similar) and children which
       //    stretch to this size.
       //
-      // Due to this we can't use cached |NGLayoutResult::IntrinsicBlockSize|
-      // value, as the following |block_size| calculation would be incorrect.
+      // Due to this we can't use the |intrinsic_block_size| value, as the
+      // following |block_size| calculation would be incorrect.
       // TODO(dgrogan): We can hit the cache here for row flexboxes when they
       // don't have stretchy children.
-      if (physical_fragment.DependsOnPercentageBlockSize()) {
-        if (new_space.PercentageResolutionBlockSize() !=
-            old_space.PercentageResolutionBlockSize())
-          return NGLayoutCacheStatus::kNeedsLayout;
-      }
+      if (physical_fragment.DependsOnPercentageBlockSize() &&
+          new_space.PercentageResolutionBlockSize() !=
+              old_space.PercentageResolutionBlockSize())
+        intrinsic_block_size = kIndefiniteSize;
     }
 
     block_size = ComputeBlockSizeForFragment(
         new_space, style, fragment_geometry.border + fragment_geometry.padding,
-        layout_result.IntrinsicBlockSize(),
-        fragment_geometry.border_box_size.inline_size);
+        intrinsic_block_size, fragment_geometry.border_box_size.inline_size);
+
+    if (block_size == kIndefiniteSize)
+      return NGLayoutCacheStatus::kNeedsLayout;
   }
 
   bool is_block_size_equal = block_size == fragment.BlockSize();
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
index 41b8498d..e729b1f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
@@ -103,6 +103,7 @@
 
     bool has_content = false;
     if (const auto* box = DynamicTo<NGPhysicalBoxFragment>(fragment)) {
+      const LayoutObject* layout_object = box->GetLayoutObject();
       if (flags_ & NGPhysicalFragment::DumpType) {
         builder_->Append("Box");
         String box_type = StringForBoxType(*fragment);
@@ -121,11 +122,10 @@
       }
       has_content = AppendOffsetAndSize(fragment, fragment_offset, has_content);
 
-      if (flags_ & NGPhysicalFragment::DumpNodeName &&
-          fragment->GetLayoutObject()) {
+      if (flags_ & NGPhysicalFragment::DumpNodeName && layout_object) {
         if (has_content)
           builder_->Append(" ");
-        builder_->Append(fragment->GetLayoutObject()->DebugName());
+        builder_->Append(layout_object->DebugName());
       }
       builder_->Append("\n");
 
@@ -138,6 +138,27 @@
         }
       }
       if (flags_ & NGPhysicalFragment::DumpSubtree) {
+        if (flags_ & NGPhysicalFragment::DumpLegacyDescendants &&
+            layout_object && !layout_object->IsLayoutNGObject()) {
+          DCHECK(box->Children().empty());
+          for (const LayoutObject* descendant = layout_object->SlowFirstChild();
+               descendant;) {
+            if (!descendant->IsLayoutNGObject()) {
+              descendant = descendant->NextInPreOrder(layout_object);
+              continue;
+            }
+            if (flags_ & NGPhysicalFragment::DumpHeaderText) {
+              AppendIndentation(indent + 2);
+              builder_->Append("(NG fragment root inside legacy subtree:)\n");
+            }
+            const LayoutBox* box_descendant = To<LayoutBox>(descendant);
+            DCHECK_EQ(box_descendant->PhysicalFragmentCount(), 1u);
+            Append(box_descendant->GetPhysicalFragment(0), base::nullopt,
+                   indent + 4);
+            descendant = descendant->NextInPreOrderAfterChildren(layout_object);
+          }
+          return;
+        }
         for (auto& child : box->Children()) {
           if (has_fragment_items && child->IsLineBox())
             continue;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
index b8c25e4..85f21c8d9 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
@@ -446,6 +446,7 @@
     DumpSelfPainting = 0x80,
     DumpNodeName = 0x100,
     DumpItems = 0x200,
+    DumpLegacyDescendants = 0x400,
     DumpAll = -1
   };
   typedef int DumpFlags;
diff --git a/third_party/blink/renderer/core/layout/scrollbars_test.cc b/third_party/blink/renderer/core/layout/scrollbars_test.cc
index 028a8c4..c802f8e 100644
--- a/third_party/blink/renderer/core/layout/scrollbars_test.cc
+++ b/third_party/blink/renderer/core/layout/scrollbars_test.cc
@@ -38,7 +38,9 @@
 
 class StubWebThemeEngine : public WebThemeEngine {
  public:
-  StubWebThemeEngine() { painted_color_scheme_.fill(ColorScheme::kLight); }
+  StubWebThemeEngine() {
+    painted_color_scheme_.fill(mojom::blink::ColorScheme::kLight);
+  }
 
   gfx::Size GetSize(Part part) override {
     switch (part) {
@@ -65,18 +67,19 @@
              State,
              const gfx::Rect&,
              const ExtraParams*,
-             blink::ColorScheme color_scheme) override {
+             mojom::blink::ColorScheme color_scheme) override {
     // Make  sure we don't overflow the array.
     DCHECK(part <= kPartProgressBar);
     painted_color_scheme_[part] = color_scheme;
   }
 
-  ColorScheme GetPaintedPartColorScheme(Part part) const {
+  mojom::blink::ColorScheme GetPaintedPartColorScheme(Part part) const {
     return painted_color_scheme_[part];
   }
 
  private:
-  std::array<ColorScheme, kPartProgressBar + 1> painted_color_scheme_;
+  std::array<mojom::blink::ColorScheme, kPartProgressBar + 1>
+      painted_color_scheme_;
 };
 
 constexpr int StubWebThemeEngine::kMinimumHorizontalLength;
@@ -3012,14 +3015,15 @@
 
   auto* theme_engine =
       static_cast<StubWebThemeEngine*>(Platform::Current()->ThemeEngine());
-  EXPECT_EQ(ColorScheme::kDark,
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark,
             theme_engine->GetPaintedPartColorScheme(
                 WebThemeEngine::kPartScrollbarHorizontalThumb));
-  EXPECT_EQ(ColorScheme::kDark,
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark,
             theme_engine->GetPaintedPartColorScheme(
                 WebThemeEngine::kPartScrollbarVerticalThumb));
-  EXPECT_EQ(ColorScheme::kDark, theme_engine->GetPaintedPartColorScheme(
-                                    WebThemeEngine::kPartScrollbarCorner));
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark,
+            theme_engine->GetPaintedPartColorScheme(
+                WebThemeEngine::kPartScrollbarCorner));
 }
 
 // Test scrollbar-gutter values with classic scrollbars and horizontal-tb text.
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index f22b3ca..5705f06 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -1849,19 +1849,6 @@
           previous_window != frame_->DomWindow(),
           frame_->DomWindow()->GetSandboxFlags(),
           security_init.FeaturePolicyHeader(), document_policy_.feature_state);
-      // Originally, before OOPIFs, the previous document associated with the
-      // frame will always already by detached at this point, so no JS unload
-      // handlers should run. However, with OOPIFs, this `LocalFrame` might
-      // still be provisional: it doesn't become swapped in until Blink notifies
-      // the embedder by calling `DidCommitNavigation()`. As a result,
-      // `DidCommitLoad()` might end up running the unload handlers for the
-      // previous *frame* when swapping it out, which might detach `this`.
-      //
-      // TODO(https://crbug.com/1153043): Fix this so that the frame is swapped
-      // in earlier, at `FrameLoader::DetachDocument()`. At that point, it is
-      // certain that the navigation will commit.
-      if (!frame_)
-        return;
     }
     // TODO(dgozman): make DidCreateScriptContext notification call currently
     // triggered by installing new document happen here, after commit.
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 98aff16..16f9a1d 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -1053,7 +1053,20 @@
     DCHECK(Client()->HasWebView());
     scoped_refptr<SecurityOrigin> security_origin =
         SecurityOrigin::Create(navigation_params->url);
-    if (!DetachDocument(security_origin.get(), &unload_timing))
+
+    // If `frame_` is provisional, this is largely a no-op other than cleaning
+    // up the initial (and unused) empty document. Otherwise, this unloads the
+    // previous Document and detaches subframes. If `DetachDocument()` returns
+    // false, JS caused `frame_` to be removed, so just return.
+    const bool is_provisional = frame_->IsProvisional();
+    if (!DetachDocument(security_origin.get(), &unload_timing)) {
+      DCHECK(!is_provisional);
+      return;
+    }
+
+    // If the frame is provisional, swap it in now. However, if `Swap()` returns
+    // false, JS caused `frame_` to be removed, so just return.
+    if (is_provisional && !frame_->SwapIn())
       return;
   }
 
@@ -1184,6 +1197,7 @@
   DCHECK_EQ(client_navigation_.get(), client_navigation);
 
   // No more events will be dispatched so detach the Document.
+  // TODO(dcheng): Why is this a conditional check?
   // TODO(yoav): Should we also be nullifying domWindow's document (or
   // domWindow) since the doc is now detached?
   frame_->GetDocument()->Shutdown();
diff --git a/third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc b/third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc
index cbec2f6..bf189b3 100644
--- a/third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc
+++ b/third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc
@@ -135,7 +135,7 @@
     const Scrollbar* vertical_scrollbar,
     const DisplayItemClient& display_item_client,
     const IntRect& corner_rect,
-    ColorScheme color_scheme) {
+    mojom::blink::ColorScheme color_scheme) {
   if (DrawingRecorder::UseCachedDrawingIfPossible(context, display_item_client,
                                                   DisplayItem::kScrollCorner))
     return;
diff --git a/third_party/blink/renderer/core/paint/custom_scrollbar_theme.h b/third_party/blink/renderer/core/paint/custom_scrollbar_theme.h
index 43cacd9..ec8a9ec3 100644
--- a/third_party/blink/renderer/core/paint/custom_scrollbar_theme.h
+++ b/third_party/blink/renderer/core/paint/custom_scrollbar_theme.h
@@ -50,7 +50,7 @@
                          const Scrollbar* vertical_scrollbar,
                          const DisplayItemClient&,
                          const IntRect& corner_rect,
-                         ColorScheme color_scheme) override;
+                         mojom::blink::ColorScheme color_scheme) override;
 
   bool ShouldCenterOnThumb(const Scrollbar& scrollbar,
                            const WebMouseEvent& event) override {
diff --git a/third_party/blink/renderer/core/paint/highlight_painting_utils.cc b/third_party/blink/renderer/core/paint/highlight_painting_utils.cc
index 5804267..ed8594f 100644
--- a/third_party/blink/renderer/core/paint/highlight_painting_utils.cc
+++ b/third_party/blink/renderer/core/paint/highlight_painting_utils.cc
@@ -35,7 +35,7 @@
 }
 
 Color ForcedSystemForegroundColor(PseudoId pseudo_id,
-                                  ColorScheme color_scheme) {
+                                  mojom::blink::ColorScheme color_scheme) {
   CSSValueID keyword = CSSValueID::kHighlighttext;
   switch (pseudo_id) {
     case kPseudoIdTargetText:
@@ -53,7 +53,7 @@
 }
 
 Color ForcedSystemBackgroundColor(PseudoId pseudo_id,
-                                  ColorScheme color_scheme) {
+                                  mojom::blink::ColorScheme color_scheme) {
   CSSValueID keyword = CSSValueID::kHighlight;
   switch (pseudo_id) {
     case kPseudoIdTargetText:
@@ -174,7 +174,7 @@
   scoped_refptr<const ComputedStyle> pseudo_style =
       HighlightPseudoStyle(node, pseudo);
 
-  ColorScheme color_scheme = style.UsedColorScheme();
+  mojom::blink::ColorScheme color_scheme = style.UsedColorScheme();
   if (pseudo_style) {
     if (!document.InForcedColorsMode() ||
         pseudo_style->ForcedColorAdjust() == EForcedColorAdjust::kNone) {
@@ -200,7 +200,7 @@
       return Color::kTransparent;
   }
 
-  ColorScheme color_scheme = style.UsedColorScheme();
+  mojom::blink::ColorScheme color_scheme = style.UsedColorScheme();
   if (scoped_refptr<const ComputedStyle> pseudo_style =
           HighlightPseudoStyle(node, pseudo)) {
     if (!document.InForcedColorsMode() ||
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index a93098f5..21236ef 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -1249,7 +1249,7 @@
   return GetLayoutBox()->StyleRef().GetScrollBehavior();
 }
 
-ColorScheme PaintLayerScrollableArea::UsedColorScheme() const {
+mojom::blink::ColorScheme PaintLayerScrollableArea::UsedColorScheme() const {
   return GetLayoutBox()->StyleRef().UsedColorScheme();
 }
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
index a7a2903..6fad682f 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
@@ -350,7 +350,7 @@
   bool ShouldPlaceVerticalScrollbarOnLeft() const override;
   int PageStep(ScrollbarOrientation) const override;
   mojom::blink::ScrollBehavior ScrollBehaviorStyle() const override;
-  ColorScheme UsedColorScheme() const override;
+  mojom::blink::ColorScheme UsedColorScheme() const override;
   cc::AnimationHost* GetCompositorAnimationHost() const override;
   CompositorAnimationTimeline* GetCompositorAnimationTimeline() const override;
   bool HasTickmarks() const override;
diff --git a/third_party/blink/renderer/core/paint/text_paint_style.h b/third_party/blink/renderer/core/paint/text_paint_style.h
index 515c9c3..0ad9c1e 100644
--- a/third_party/blink/renderer/core/paint/text_paint_style.h
+++ b/third_party/blink/renderer/core/paint/text_paint_style.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TEXT_PAINT_STYLE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_TEXT_PAINT_STYLE_H_
 
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/style/applied_text_decoration.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
@@ -24,7 +24,7 @@
   Color stroke_color;
   Color emphasis_mark_color;
   float stroke_width;
-  ColorScheme color_scheme;
+  mojom::blink::ColorScheme color_scheme;
   const ShadowList* shadow;
   base::Optional<AppliedTextDecoration> selection_text_decoration;
 
diff --git a/third_party/blink/renderer/core/paint/theme_painter_default.cc b/third_party/blink/renderer/core/paint/theme_painter_default.cc
index 3dbb624..f982084 100644
--- a/third_party/blink/renderer/core/paint/theme_painter_default.cc
+++ b/third_party/blink/renderer/core/paint/theme_painter_default.cc
@@ -523,7 +523,8 @@
       cancel_button_size, cancel_button_size);
   IntRect painting_rect = ConvertToPaintingRect(
       input_layout_box, cancel_button_object, cancel_button_rect, r);
-  ColorScheme color_scheme = cancel_button_object.StyleRef().UsedColorScheme();
+  mojom::blink::ColorScheme color_scheme =
+      cancel_button_object.StyleRef().UsedColorScheme();
   DEFINE_STATIC_REF(Image, cancel_image,
                     (Image::LoadPlatformResource(IDR_SEARCH_CANCEL)));
   DEFINE_STATIC_REF(Image, cancel_pressed_image,
@@ -558,12 +559,14 @@
             text_is_dark ? cancel_pressed_image_hc_light_mode
                          : cancel_pressed_image_dark_mode;
   } else {
-    color_scheme_adjusted_cancel_image = color_scheme == ColorScheme::kLight
-                                             ? cancel_image
-                                             : cancel_image_dark_mode;
+    color_scheme_adjusted_cancel_image =
+        color_scheme == mojom::blink::ColorScheme::kLight
+            ? cancel_image
+            : cancel_image_dark_mode;
     color_scheme_adjusted_cancel_pressed_image =
-        color_scheme == ColorScheme::kLight ? cancel_pressed_image
-                                            : cancel_pressed_image_dark_mode;
+        color_scheme == mojom::blink::ColorScheme::kLight
+            ? cancel_pressed_image
+            : cancel_pressed_image_dark_mode;
   }
   paint_info.context.DrawImage(
       To<Element>(cancel_button_object.GetNode())->IsActive()
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator_test.cc b/third_party/blink/renderer/core/scroll/scroll_animator_test.cc
index e5dbd2b6..8bd5f668 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator_test.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_test.cc
@@ -81,7 +81,7 @@
   MOCK_CONST_METHOD0(ScrollbarsCanBeActive, bool());
   MOCK_METHOD0(RegisterForAnimation, void());
   MOCK_METHOD0(ScheduleAnimation, bool());
-  MOCK_CONST_METHOD0(UsedColorScheme, ColorScheme());
+  MOCK_CONST_METHOD0(UsedColorScheme, mojom::blink::ColorScheme());
 
   bool UserInputScrollable(ScrollbarOrientation) const override { return true; }
   bool ShouldPlaceVerticalScrollbarOnLeft() const override { return false; }
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h
index 9f81f06..c15845a 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.h
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -28,7 +28,7 @@
 
 #include "base/callback_helpers.h"
 #include "cc/input/scroll_snap_data.h"
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
@@ -471,7 +471,7 @@
     return mojom::blink::ScrollBehavior::kInstant;
   }
 
-  virtual ColorScheme UsedColorScheme() const = 0;
+  virtual mojom::blink::ColorScheme UsedColorScheme() const = 0;
 
   // Subtracts space occupied by this ScrollableArea's scrollbars.
   // Does nothing if overlay scrollbars are enabled.
diff --git a/third_party/blink/renderer/core/scroll/scrollbar.cc b/third_party/blink/renderer/core/scroll/scrollbar.cc
index 3e7beab..6dad36bd 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar.cc
@@ -833,7 +833,7 @@
   return false;
 }
 
-ColorScheme Scrollbar::UsedColorScheme() const {
+mojom::blink::ColorScheme Scrollbar::UsedColorScheme() const {
   return scrollable_area_->UsedColorScheme();
 }
 
diff --git a/third_party/blink/renderer/core/scroll/scrollbar.h b/third_party/blink/renderer/core/scroll/scrollbar.h
index d4b4dcd..71d8652 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar.h
@@ -26,7 +26,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_H_
 
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/scroll/scroll_types.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
@@ -199,7 +199,7 @@
   // is the element that owns our PaintLayerScrollableArea.
   Element* StyleSource() const { return style_source_.Get(); }
 
-  ColorScheme UsedColorScheme() const;
+  mojom::blink::ColorScheme UsedColorScheme() const;
 
   virtual void Trace(Visitor*) const;
 
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_test_suite.h b/third_party/blink/renderer/core/scroll/scrollbar_test_suite.h
index 4e163575..e162dcc 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_test_suite.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_test_suite.h
@@ -71,7 +71,7 @@
   MOCK_CONST_METHOD0(VerticalScrollbar, Scrollbar*());
   MOCK_CONST_METHOD0(ScrollbarsHiddenIfOverlay, bool());
   MOCK_METHOD0(ScheduleAnimation, bool());
-  MOCK_CONST_METHOD0(UsedColorScheme, ColorScheme());
+  MOCK_CONST_METHOD0(UsedColorScheme, mojom::blink::ColorScheme());
 
   bool UserInputScrollable(ScrollbarOrientation) const override { return true; }
   bool ScrollbarsCanBeActive() const override { return true; }
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme.cc
index fdd88700..311edef 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme.cc
@@ -108,7 +108,7 @@
     const Scrollbar* vertical_scrollbar,
     const DisplayItemClient& display_item_client,
     const IntRect& corner_rect,
-    ColorScheme color_scheme) {
+    mojom::blink::ColorScheme color_scheme) {
   if (corner_rect.IsEmpty())
     return;
 
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme.h b/third_party/blink/renderer/core/scroll/scrollbar_theme.h
index a015ace..0405fe4 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme.h
@@ -97,7 +97,7 @@
                                  const Scrollbar* vertical_scrollbar,
                                  const DisplayItemClient&,
                                  const IntRect& corner_rect,
-                                 ColorScheme color_scheme);
+                                 mojom::blink::ColorScheme color_scheme);
   virtual void PaintTickmarks(GraphicsContext&,
                               const Scrollbar&,
                               const IntRect&);
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h
index 84325bb..c0188034 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h
@@ -125,7 +125,7 @@
                          const Scrollbar* vertical_scrollbar,
                          const DisplayItemClient&,
                          const IntRect& corner_rect,
-                         ColorScheme color_scheme) override;
+                         mojom::blink::ColorScheme color_scheme) override;
   void PaintThumbInternal(GraphicsContext&,
                           const Scrollbar&,
                           const IntRect&,
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
index bf96ece..4a9ca3c 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
@@ -238,15 +238,17 @@
   }
 
   params.scrollbar_extra.scrollbar_theme =
-      (scrollbar.UsedColorScheme() == ColorScheme::kDark) ? kDark : kLight;
+      (scrollbar.UsedColorScheme() == mojom::blink::ColorScheme::kDark)
+          ? mojom::blink::ColorScheme::kDark
+          : mojom::blink::ColorScheme::kLight;
   params.scrollbar_extra.is_overlay = overlay;
 
   if (overlay) {
     params.scrollbar_extra.scrollbar_theme =
         (scrollbar.GetScrollbarOverlayColorTheme() ==
          kScrollbarOverlayColorThemeLight)
-            ? kDark
-            : kLight;
+            ? mojom::blink::ColorScheme::kDark
+            : mojom::blink::ColorScheme::kLight;
   }
 
   params.scrollbar_extra.is_hovering =
@@ -296,11 +298,12 @@
     context.EndLayer();
 }
 
-void ScrollbarThemeMac::PaintScrollCorner(GraphicsContext& context,
-                                          const Scrollbar* vertical_scrollbar,
-                                          const DisplayItemClient& item,
-                                          const IntRect& rect,
-                                          ColorScheme color_scheme) {
+void ScrollbarThemeMac::PaintScrollCorner(
+    GraphicsContext& context,
+    const Scrollbar* vertical_scrollbar,
+    const DisplayItemClient& item,
+    const IntRect& rect,
+    mojom::blink::ColorScheme color_scheme) {
   if (!vertical_scrollbar) {
     ScrollbarTheme::PaintScrollCorner(context, vertical_scrollbar, item, rect,
                                       color_scheme);
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 111f154..39d7b2e1 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -2626,17 +2626,18 @@
   // Load the images of CSS properties that were deferred by LazyLoad.
   void LoadDeferredImages(Document&) const;
 
-  enum ColorScheme ComputedColorScheme() const {
-    return DarkColorScheme() ? ColorScheme::kDark : ColorScheme::kLight;
+  mojom::blink::ColorScheme ComputedColorScheme() const {
+    return DarkColorScheme() ? mojom::blink::ColorScheme::kDark
+                             : mojom::blink::ColorScheme::kLight;
   }
 
-  enum ColorScheme UsedColorScheme() const {
+  mojom::blink::ColorScheme UsedColorScheme() const {
     return RuntimeEnabledFeatures::CSSColorSchemeUARenderingEnabled()
                ? ComputedColorScheme()
-               : ColorScheme::kLight;
+               : mojom::blink::ColorScheme::kLight;
   }
 
-  enum ColorScheme UsedColorSchemeForInitialColors() const {
+  mojom::blink::ColorScheme UsedColorSchemeForInitialColors() const {
     return ComputedColorScheme();
   }
 
diff --git a/third_party/blink/renderer/core/style/computed_style_test.cc b/third_party/blink/renderer/core/style/computed_style_test.cc
index bb184154..5ccdd58 100644
--- a/third_party/blink/renderer/core/style/computed_style_test.cc
+++ b/third_party/blink/renderer/core/style/computed_style_test.cc
@@ -690,10 +690,10 @@
   light_value->Append(*CSSIdentifierValue::Create(CSSValueID::kLight));
 
   To<Longhand>(ref.GetProperty()).ApplyValue(state, *dark_value);
-  EXPECT_EQ(ColorScheme::kDark, style->UsedColorScheme());
+  EXPECT_EQ(mojom::blink::ColorScheme::kDark, style->UsedColorScheme());
 
   To<Longhand>(ref.GetProperty()).ApplyValue(state, *light_value);
-  EXPECT_EQ(ColorScheme::kLight, style->UsedColorScheme());
+  EXPECT_EQ(mojom::blink::ColorScheme::kLight, style->UsedColorScheme());
 }
 
 TEST(ComputedStyleTest, ApplyInternalLightDarkColor) {
diff --git a/third_party/blink/renderer/core/style/shadow_list.cc b/third_party/blink/renderer/core/style/shadow_list.cc
index 7d0a0a8..64ed569 100644
--- a/third_party/blink/renderer/core/style/shadow_list.cc
+++ b/third_party/blink/renderer/core/style/shadow_list.cc
@@ -52,7 +52,7 @@
 sk_sp<SkDrawLooper> ShadowList::CreateDrawLooper(
     DrawLooperBuilder::ShadowAlphaMode alpha_mode,
     const Color& current_color,
-    ColorScheme color_scheme,
+    mojom::blink::ColorScheme color_scheme,
     bool is_horizontal) const {
   DrawLooperBuilder draw_looper_builder;
   for (wtf_size_t i = Shadows().size(); i--;) {
diff --git a/third_party/blink/renderer/core/style/shadow_list.h b/third_party/blink/renderer/core/style/shadow_list.h
index fa73caa..78ee292 100644
--- a/third_party/blink/renderer/core/style/shadow_list.h
+++ b/third_party/blink/renderer/core/style/shadow_list.h
@@ -67,7 +67,7 @@
 
   sk_sp<SkDrawLooper> CreateDrawLooper(DrawLooperBuilder::ShadowAlphaMode,
                                        const Color& current_color,
-                                       ColorScheme color_scheme,
+                                       mojom::blink::ColorScheme color_scheme,
                                        bool is_horizontal = true) const;
 
  private:
diff --git a/third_party/blink/renderer/core/svg/svg_animated_color.cc b/third_party/blink/renderer/core/svg/svg_animated_color.cc
index abe32f0e..483620b 100644
--- a/third_party/blink/renderer/core/svg/svg_animated_color.cc
+++ b/third_party/blink/renderer/core/svg/svg_animated_color.cc
@@ -56,12 +56,12 @@
   return Color::kTransparent;
 }
 
-static inline ColorScheme ColorSchemeForSVGElement(
+static inline mojom::blink::ColorScheme ColorSchemeForSVGElement(
     const SVGElement* target_element) {
   DCHECK(target_element);
   if (const ComputedStyle* target_style = target_element->GetComputedStyle())
     return target_style->UsedColorScheme();
-  return ColorScheme::kLight;
+  return mojom::blink::ColorScheme::kLight;
 }
 
 void SVGColorProperty::Add(const SVGPropertyBase* other,
@@ -69,7 +69,8 @@
   DCHECK(context_element);
 
   Color fallback_color = FallbackColorForCurrentColor(context_element);
-  ColorScheme color_scheme = ColorSchemeForSVGElement(context_element);
+  mojom::blink::ColorScheme color_scheme =
+      ColorSchemeForSVGElement(context_element);
   Color from_color = To<SVGColorProperty>(other)->style_color_.Resolve(
       fallback_color, color_scheme);
   Color to_color = style_color_.Resolve(fallback_color, color_scheme);
@@ -92,7 +93,8 @@
   // Apply currentColor rules.
   DCHECK(context_element);
   Color fallback_color = FallbackColorForCurrentColor(context_element);
-  ColorScheme color_scheme = ColorSchemeForSVGElement(context_element);
+  mojom::blink::ColorScheme color_scheme =
+      ColorSchemeForSVGElement(context_element);
   Color from_color = from_style_color.Resolve(fallback_color, color_scheme);
   Color to_color = to_style_color.Resolve(fallback_color, color_scheme);
   Color to_at_end_of_duration_color =
@@ -129,7 +131,8 @@
     const SVGElement* context_element) const {
   DCHECK(context_element);
   Color fallback_color = FallbackColorForCurrentColor(context_element);
-  ColorScheme color_scheme = ColorSchemeForSVGElement(context_element);
+  mojom::blink::ColorScheme color_scheme =
+      ColorSchemeForSVGElement(context_element);
 
   Color from_color = style_color_.Resolve(fallback_color, color_scheme);
   Color to_color = To<SVGColorProperty>(to_value)->style_color_.Resolve(
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.cc
index 89c1c49c..4fecf3d 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.cc
@@ -52,7 +52,7 @@
 
 static ColorParseResult ParseColor(Color& parsed_color,
                                    const String& color_string,
-                                   ColorScheme color_scheme) {
+                                   mojom::blink::ColorScheme color_scheme) {
   if (EqualIgnoringASCIICase(color_string, "currentcolor"))
     return kParsedCurrentColor;
   const bool kUseStrictParsing = true;
@@ -72,7 +72,7 @@
   return color;
 }
 
-static ColorScheme ColorScheme(HTMLCanvasElement* canvas) {
+static mojom::blink::ColorScheme ColorScheme(HTMLCanvasElement* canvas) {
   if (canvas && canvas->isConnected()) {
     if (auto* style = canvas->GetComputedStyle())
       return style->UsedColorScheme();
diff --git a/third_party/blink/renderer/modules/peerconnection/webrtc_audio_renderer_test.cc b/third_party/blink/renderer/modules/peerconnection/webrtc_audio_renderer_test.cc
index d9cc7ff41..4d082c353 100644
--- a/third_party/blink/renderer/modules/peerconnection/webrtc_audio_renderer_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/webrtc_audio_renderer_test.cc
@@ -208,6 +208,7 @@
   }
 
   void TearDown() override {
+    base::RunLoop().RunUntilIdle();
     renderer_proxy_ = nullptr;
     renderer_ = nullptr;
     stream_descriptor_ = nullptr;
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc b/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
index 5eafbdb5b..348295f7 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_conversions.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/bindings/modules/v8/double_sequence_or_gpu_color_dict.h"
 #include "third_party/blink/renderer/bindings/modules/v8/unsigned_long_enforce_range_sequence_or_gpu_extent_3d_dict.h"
 #include "third_party/blink/renderer/bindings/modules/v8/unsigned_long_enforce_range_sequence_or_gpu_origin_3d_dict.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_index_format.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_programmable_stage_descriptor.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texture_copy_view.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texture_data_layout.h"
@@ -435,6 +436,15 @@
   return WGPUIndexFormat_Force32;
 }
 
+WGPUIndexFormat AsDawnEnum(const V8GPUIndexFormat& webgpu_enum) {
+  switch (webgpu_enum.AsEnum()) {
+    case V8GPUIndexFormat::Enum::kUint16:
+      return WGPUIndexFormat_Uint16;
+    case V8GPUIndexFormat::Enum::kUint32:
+      return WGPUIndexFormat_Uint32;
+  }
+}
+
 template <>
 WGPUPrimitiveTopology AsDawnEnum<WGPUPrimitiveTopology>(
     const WTF::String& webgpu_enum) {
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_conversions.h b/third_party/blink/renderer/modules/webgpu/dawn_conversions.h
index 4d37448..b4a688f53 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_conversions.h
+++ b/third_party/blink/renderer/modules/webgpu/dawn_conversions.h
@@ -26,6 +26,7 @@
 class GPUTextureDataLayout;
 class UnsignedLongEnforceRangeSequenceOrGPUExtent3DDict;
 class UnsignedLongEnforceRangeSequenceOrGPUOrigin3DDict;
+class V8GPUIndexFormat;
 
 // Convert WebGPU bitfield values to Dawn enums. These have the same value.
 template <typename DawnEnum>
@@ -36,6 +37,7 @@
 // Convert WebGPU string enums to Dawn enums.
 template <typename DawnEnum>
 DawnEnum AsDawnEnum(const WTF::String& webgpu_enum);
+WGPUIndexFormat AsDawnEnum(const V8GPUIndexFormat& webgpu_enum);
 
 // These conversions are used multiple times and are declared here. Conversions
 // used only once, for example for object construction, are defined
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.cc b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.cc
index 0068120..cb8381b7 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.cc
@@ -110,12 +110,11 @@
 }
 
 void GPURenderBundleEncoder::setIndexBuffer(GPUBuffer* buffer,
-                                            const WTF::String& format,
+                                            const V8GPUIndexFormat& format,
                                             uint64_t offset,
                                             uint64_t size) {
   GetProcs().renderBundleEncoderSetIndexBufferWithFormat(
-      GetHandle(), buffer->GetHandle(), AsDawnEnum<WGPUIndexFormat>(format),
-      offset, size);
+      GetHandle(), buffer->GetHandle(), AsDawnEnum(format), offset, size);
 }
 
 void GPURenderBundleEncoder::setVertexBuffer(uint32_t slot,
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h
index 2f724df..de73fc6f 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGPU_GPU_RENDER_BUNDLE_ENCODER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGPU_GPU_RENDER_BUNDLE_ENCODER_H_
 
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_index_format.h"
 #include "third_party/blink/renderer/modules/webgpu/dawn_object.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_programmable_pass_encoder.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -48,7 +49,7 @@
   void setPipeline(GPURenderPipeline* pipeline);
 
   void setIndexBuffer(GPUBuffer* buffer,
-                      const WTF::String& format,
+                      const V8GPUIndexFormat& format,
                       uint64_t offset,
                       uint64_t size);
   void setVertexBuffer(uint32_t slot,
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.cc b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.cc
index bf94a533..f175ef5 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h"
 
 #include "third_party/blink/renderer/bindings/modules/v8/double_sequence_or_gpu_color_dict.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_index_format.h"
 #include "third_party/blink/renderer/core/typed_arrays/typed_flexible_array_buffer_view.h"
 #include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_bind_group.h"
@@ -109,12 +110,11 @@
 }
 
 void GPURenderPassEncoder::setIndexBuffer(GPUBuffer* buffer,
-                                          const WTF::String& format,
+                                          const V8GPUIndexFormat& format,
                                           uint64_t offset,
                                           uint64_t size) {
   GetProcs().renderPassEncoderSetIndexBufferWithFormat(
-      GetHandle(), buffer->GetHandle(), AsDawnEnum<WGPUIndexFormat>(format),
-      offset, size);
+      GetHandle(), buffer->GetHandle(), AsDawnEnum(format), offset, size);
 }
 
 void GPURenderPassEncoder::setVertexBuffer(uint32_t slot,
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h
index 955e1db..a2b22ee 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.h
@@ -18,6 +18,7 @@
 class GPURenderBundle;
 class GPURenderPipeline;
 class GPUQuerySet;
+class V8GPUIndexFormat;
 
 class GPURenderPassEncoder : public DawnObject<WGPURenderPassEncoder>,
                              public GPUProgrammablePassEncoder {
@@ -54,7 +55,7 @@
                    float maxDepth);
   void setScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height);
   void setIndexBuffer(GPUBuffer* buffer,
-                      const WTF::String& format,
+                      const V8GPUIndexFormat& format,
                       uint64_t offset,
                       uint64_t size);
   void setVertexBuffer(uint32_t slot,
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index edef967..9c75ef6 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1616,6 +1616,7 @@
     "//mojo/public/cpp/base",
     "//mojo/public/cpp/bindings",
     "//services/network/public/mojom:mojom_headers",
+    "//third_party/blink/public/mojom:color_scheme_mojo_bindings",
     "//third_party/blink/public/mojom:embedded_frame_sink_mojo_bindings_blink",
     "//third_party/blink/renderer/platform/blob",
     "//third_party/blink/renderer/platform/heap",
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc b/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc
index 3e7a6a0..50fe5ec5 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc
@@ -362,6 +362,14 @@
     if (result_out->is_overflow)
       return ShapeToEnd(start, first_safe, range_start, range_end);
     break_opportunity.offset = range_end;
+    if (break_opportunity.non_hangable_run_end &&
+        range_end < break_opportunity.non_hangable_run_end) {
+      break_opportunity.non_hangable_run_end = base::nullopt;
+    }
+    if (IsBreakableSpace(text[range_end - 1])) {
+      break_opportunity.non_hangable_run_end =
+          FindNonHangableEnd(text, range_end - 1);
+    }
   }
   CheckBreakOffset(break_opportunity.offset, start, range_end);
 
diff --git a/third_party/blink/renderer/platform/graphics/DEPS b/third_party/blink/renderer/platform/graphics/DEPS
index 44d4fa7..978c513b 100644
--- a/third_party/blink/renderer/platform/graphics/DEPS
+++ b/third_party/blink/renderer/platform/graphics/DEPS
@@ -75,6 +75,9 @@
     "+ui/gl/buffer_format_utils.h",
   ],
   "(graphics_context|skia_utils)\.cc" : [ "+ui/base/ui_base_features.h" ],
+  "graphics_context.h": [
+    "+third_party/blink/public/mojom/frame/frame_owner_properties.mojom-blink.h",
+  ],
   "rw_buffer_test.cc": [
     "+base/threading/platform_thread.h",
   ],
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index a4b57960..0fa9d765 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -32,6 +32,7 @@
 #include "build/build_config.h"
 #include "components/paint_preview/common/paint_preview_tracker.h"
 #include "skia/ext/platform_canvas.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h"
 #include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h"
@@ -426,14 +427,14 @@
                                     float border_radius,
                                     float min_border_width,
                                     const Color& color,
-                                    ColorScheme color_scheme) {
+                                    mojom::blink::ColorScheme color_scheme) {
 #if defined(OS_MAC)
-  const Color& inner_color = color_scheme == ColorScheme::kDark
+  const Color& inner_color = color_scheme == mojom::blink::ColorScheme::kDark
                                  ? SkColorSetRGB(0x99, 0xC8, 0xFF)
                                  : color;
 #else
   const Color& inner_color =
-      color_scheme == ColorScheme::kDark ? SK_ColorWHITE : color;
+      color_scheme == mojom::blink::ColorScheme::kDark ? SK_ColorWHITE : color;
 #endif
   if (::features::IsFormControlsRefreshEnabled()) {
     // The focus ring is made of two borders which have a 2:1 ratio.
@@ -445,7 +446,7 @@
     if (min_border_width >= inside_border_width) {
       offset -= inside_border_width;
     }
-    const Color& outer_color = color_scheme == ColorScheme::kDark
+    const Color& outer_color = color_scheme == mojom::blink::ColorScheme::kDark
                                    ? SkColorSetRGB(0x10, 0x10, 0x10)
                                    : SK_ColorWHITE;
     // The outer ring is drawn first, and we overdraw to ensure no gaps or AA
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.h b/third_party/blink/renderer/platform/graphics/graphics_context.h
index f7c07b9..9c1bf446 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.h
@@ -31,7 +31,7 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "third_party/blink/public/common/css/color_scheme.h"
+#include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink-forward.h"
 #include "third_party/blink/renderer/platform/fonts/font.h"
 #include "third_party/blink/renderer/platform/graphics/dark_mode_filter.h"
 #include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
@@ -356,7 +356,7 @@
                      float border_radius,
                      float min_border_width,
                      const Color&,
-                     ColorScheme color_scheme);
+                     mojom::blink::ColorScheme color_scheme);
   void DrawFocusRing(const Path&, float width, int offset, const Color&);
 
   const PaintFlags& FillFlags() const { return ImmutableState()->FillFlags(); }
diff --git a/third_party/blink/renderer/platform/scheduler/common/features.h b/third_party/blink/renderer/platform/scheduler/common/features.h
index 103b2b14..bab6727 100644
--- a/third_party/blink/renderer/platform/scheduler/common/features.h
+++ b/third_party/blink/renderer/platform/scheduler/common/features.h
@@ -301,6 +301,12 @@
     &kPerAgentSchedulingExperiments, "signal",
     PerAgentSignal::kFirstMeaningfulPaint, &kPerAgentSignalOptions};
 
+// If enabled, base::ThreadTaskRunnerHandle::Get() and
+// base::SequencedTaskRunnerHandle::Get() returns the current active
+// per-ASG task runner instead of the per-thread task runner.
+const base::Feature kMbiOverrideTaskRunnerHandle{
+    "MbiOverrideTaskRunnerHandle", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kThrottleVisibleNotFocusedTimers{
     "ThrottleVisibleNotFocusedTimers", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h b/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h
index b12d21e..7bf92a97 100644
--- a/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h
+++ b/third_party/blink/renderer/platform/scheduler/common/scheduler_helper.h
@@ -121,6 +121,10 @@
 
   virtual void ShutdownAllQueues() {}
 
+  const scoped_refptr<base::SingleThreadTaskRunner>& default_task_runner() {
+    return default_task_runner_;
+  }
+
   base::ThreadChecker thread_checker_;
   base::sequence_manager::SequenceManager* sequence_manager_;  // NOT OWNED
 
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/agent_group_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/agent_group_scheduler_impl.cc
index 0aacf6d..6aa9bf02 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/agent_group_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/agent_group_scheduler_impl.cc
@@ -37,7 +37,7 @@
 }
 
 AgentGroupSchedulerImpl::~AgentGroupSchedulerImpl() {
-  default_task_queue_->ShutdownTaskQueue();
+  default_task_queue_->DetachFromMainThreadScheduler();
   main_thread_scheduler_.RemoveAgentGroupScheduler(this);
 }
 
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/idle_time_estimator.cc b/third_party/blink/renderer/platform/scheduler/main_thread/idle_time_estimator.cc
index ad03fa3..4092e81e 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/idle_time_estimator.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/idle_time_estimator.cc
@@ -10,11 +10,11 @@
 namespace scheduler {
 
 IdleTimeEstimator::IdleTimeEstimator(
-    const scoped_refptr<MainThreadTaskQueue>& compositor_task_runner,
+    const scoped_refptr<MainThreadTaskQueue>& compositor_task_queue,
     const base::TickClock* time_source,
     int sample_count,
     double estimation_percentile)
-    : compositor_task_queue_(compositor_task_runner),
+    : compositor_task_queue_(compositor_task_queue),
       per_frame_compositor_task_runtime_(sample_count),
       time_source_(time_source),
       estimation_percentile_(estimation_percentile),
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/idle_time_estimator.h b/third_party/blink/renderer/platform/scheduler/main_thread/idle_time_estimator.h
index db4b01b..659e914 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/idle_time_estimator.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/idle_time_estimator.h
@@ -19,7 +19,7 @@
 class PLATFORM_EXPORT IdleTimeEstimator : public base::TaskObserver {
  public:
   IdleTimeEstimator(
-      const scoped_refptr<MainThreadTaskQueue>& compositor_task_runner,
+      const scoped_refptr<MainThreadTaskQueue>& compositor_task_queue,
       const base::TickClock* time_source,
       int sample_count,
       double estimation_percentile);
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_helper.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_helper.cc
index a15e2a4..11360b0 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_helper.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_helper.cc
@@ -42,7 +42,7 @@
 
 const scoped_refptr<base::SingleThreadTaskRunner>&
 MainThreadSchedulerHelper::DefaultTaskRunner() {
-  return default_task_queue_->GetTaskRunnerWithDefaultTaskType();
+  return default_task_runner();
 }
 
 scoped_refptr<MainThreadTaskQueue>
@@ -59,7 +59,7 @@
 MainThreadSchedulerHelper::DeprecatedDefaultTaskRunner() {
   // TODO(hajimehoshi): Introduce a different task queue from the default task
   // queue and return the task runner created from it.
-  return DefaultTaskRunner();
+  return default_task_runner();
 }
 
 scoped_refptr<MainThreadTaskQueue> MainThreadSchedulerHelper::NewTaskQueue(
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index d6bdc95..e5651e4 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -376,10 +376,10 @@
 
 MainThreadSchedulerImpl::MainThreadOnly::MainThreadOnly(
     MainThreadSchedulerImpl* main_thread_scheduler_impl,
-    const scoped_refptr<MainThreadTaskQueue>& compositor_task_runner,
+    const scoped_refptr<MainThreadTaskQueue>& compositor_task_queue,
     const base::TickClock* time_source,
     base::TimeTicks now)
-    : idle_time_estimator(compositor_task_runner,
+    : idle_time_estimator(compositor_task_queue,
                           time_source,
                           kShortIdlePeriodDurationSampleCount,
                           kShortIdlePeriodDurationPercentile),
@@ -2465,30 +2465,91 @@
   return current_agent_group_scheduler_;
 }
 
-void MainThreadSchedulerImpl::SetCurrentAgentGroupScheduler(
-    WebAgentGroupScheduler* agent_group_scheduler) {
-  helper_.CheckOnValidThread();
-  if (current_agent_group_scheduler_) {
-    TRACE_EVENT_NESTABLE_ASYNC_END1(
-        TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
-        "scheduler.agent_scope", current_agent_group_scheduler_,
-        "agent_group_scheduler", current_agent_group_scheduler_);
+void MainThreadSchedulerImpl::BeginAgentGroupSchedulerScope(
+    WebAgentGroupScheduler* next_agent_group_scheduler) {
+  scoped_refptr<base::SingleThreadTaskRunner> next_task_runner;
+  const char* trace_event_scope_name;
+  void* trace_event_scope_id;
+
+  if (next_agent_group_scheduler) {
+    // If the |next_agent_group_scheduler| is not null, it means that a
+    // per-AgentSchedulingGroup task is about to start. In this case, a
+    // per-AgentGroupScheduler scope starts.
+    next_task_runner = next_agent_group_scheduler->DefaultTaskRunner(),
+    trace_event_scope_name = "scheduler.agent_scope";
+    trace_event_scope_id = next_agent_group_scheduler;
   } else {
-    TRACE_EVENT_NESTABLE_ASYNC_END0(
-        TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
-        "scheduler.thread_scope", this);
+    // If the |next_agent_group_scheduler| is null, it means that a
+    // per-thread task is about to start. In this case, a per-thread scope
+    // starts.
+    next_task_runner = helper_.DefaultTaskRunner();
+    trace_event_scope_name = "scheduler.thread_scope";
+    trace_event_scope_id = this;
   }
-  current_agent_group_scheduler_ = agent_group_scheduler;
-  if (current_agent_group_scheduler_) {
-    TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
-        TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
-        "scheduler.agent_scope", current_agent_group_scheduler_,
-        "agent_group_scheduler", current_agent_group_scheduler_);
-  } else {
-    TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
-        TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
-        "scheduler.thread_scope", this);
+
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
+      TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), trace_event_scope_name,
+      trace_event_scope_id, "agent_group_scheduler",
+      next_agent_group_scheduler);
+
+  WebAgentGroupScheduler* previous_agent_group_scheduler =
+      current_agent_group_scheduler_;
+  current_agent_group_scheduler_ = next_agent_group_scheduler;
+
+  scoped_refptr<base::SingleThreadTaskRunner> previous_task_runner =
+      base::ThreadTaskRunnerHandle::Get();
+  std::unique_ptr<base::ThreadTaskRunnerHandleOverride>
+      thread_task_runner_handle_override;
+  if (base::FeatureList::IsEnabled(kMbiOverrideTaskRunnerHandle) &&
+      next_task_runner != previous_task_runner) {
+    // per-thread and per-AgentSchedulingGroup task runner allows nested
+    // runloop. |MainThreadSchedulerImpl| guarantees that
+    // |ThreadTaskRunnerHandle::Get()| and |SequencedTaskRunnerHandle::Get()|
+    // return a proper task runner even when a nested runloop is used. Because
+    // |MainThreadSchedulerImpl::OnTaskStarted()| always overrides
+    // TTRH/STRH::Get() properly. So there is no concern about returning an
+    // unexpected task runner from TTRH/STRH::Get() in this specific case.
+    thread_task_runner_handle_override =
+        std::unique_ptr<base::ThreadTaskRunnerHandleOverride>(
+            new base::ThreadTaskRunnerHandleOverride(
+                next_task_runner,
+                /*allow_nested_runloop=*/true));
   }
+
+  main_thread_only().agent_group_scheduler_scope_stack.emplace_back(
+      AgentGroupSchedulerScope{
+          std::move(thread_task_runner_handle_override),
+          previous_agent_group_scheduler, next_agent_group_scheduler,
+          std::move(previous_task_runner), std::move(next_task_runner),
+          trace_event_scope_name, trace_event_scope_id});
+}
+
+void MainThreadSchedulerImpl::EndAgentGroupSchedulerScope() {
+  AgentGroupSchedulerScope& agent_group_scheduler_scope =
+      main_thread_only().agent_group_scheduler_scope_stack.back();
+
+  if (base::FeatureList::IsEnabled(kMbiOverrideTaskRunnerHandle)) {
+    DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(),
+              agent_group_scheduler_scope.current_task_runner);
+    DCHECK_EQ(base::SequencedTaskRunnerHandle::Get(),
+              agent_group_scheduler_scope.current_task_runner);
+  }
+  agent_group_scheduler_scope.thread_task_runner_handle_override = nullptr;
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(),
+            agent_group_scheduler_scope.previous_task_runner);
+  DCHECK_EQ(base::SequencedTaskRunnerHandle::Get(),
+            agent_group_scheduler_scope.previous_task_runner);
+
+  current_agent_group_scheduler_ =
+      agent_group_scheduler_scope.previous_agent_group_scheduler;
+
+  TRACE_EVENT_NESTABLE_ASYNC_END1(
+      TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
+      agent_group_scheduler_scope.trace_event_scope_name,
+      agent_group_scheduler_scope.trace_event_scope_id, "agent_group_scheduler",
+      agent_group_scheduler_scope.current_agent_group_scheduler);
+
+  main_thread_only().agent_group_scheduler_scope_stack.pop_back();
 }
 
 std::unique_ptr<ThreadScheduler::RendererPauseHandle>
@@ -2603,7 +2664,7 @@
     MainThreadTaskQueue* queue,
     const base::sequence_manager::Task& task,
     const TaskQueue::TaskTiming& task_timing) {
-  SetCurrentAgentGroupScheduler(queue ? queue->GetAgentGroupScheduler()
+  BeginAgentGroupSchedulerScope(queue ? queue->GetAgentGroupScheduler()
                                       : nullptr);
 
   main_thread_only().running_queues.push(queue);
@@ -2640,6 +2701,10 @@
   if (task_timing->has_wall_time() && queue && queue->GetFrameScheduler())
     queue->GetFrameScheduler()->AddTaskTime(task_timing->wall_duration());
   main_thread_only().running_queues.pop();
+
+  // The overriding TaskRunnerHandle scope ends here.
+  EndAgentGroupSchedulerScope();
+
   if (main_thread_only().nested_runloop)
     return;
 
@@ -2666,10 +2731,6 @@
 
   find_in_page_budget_pool_controller_->OnTaskCompleted(queue.get(),
                                                         task_timing);
-
-  // GetCurrentAgentGroupScheduler() should return nullptr when
-  // it's running thread global task runners.
-  SetCurrentAgentGroupScheduler(nullptr);
 }
 
 void MainThreadSchedulerImpl::RecordTaskUkm(
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index 2ef822d..b945303 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -223,8 +223,6 @@
   scoped_refptr<base::SingleThreadTaskRunner> CompositorTaskRunner() override;
   std::unique_ptr<WebAgentGroupScheduler> CreateAgentGroupScheduler() override;
   WebAgentGroupScheduler* GetCurrentAgentGroupScheduler() override;
-  void SetCurrentAgentGroupScheduler(
-      WebAgentGroupScheduler* agent_group_scheduler);
   std::unique_ptr<ThreadScheduler::RendererPauseHandle> PauseScheduler()
       override;
   base::TimeTicks MonotonicallyIncreasingVirtualTime() override;
@@ -494,6 +492,21 @@
 
   void AddAgentGroupScheduler(AgentGroupSchedulerImpl*);
 
+  struct AgentGroupSchedulerScope {
+    std::unique_ptr<base::ThreadTaskRunnerHandleOverride>
+        thread_task_runner_handle_override;
+    WebAgentGroupScheduler* previous_agent_group_scheduler;
+    WebAgentGroupScheduler* current_agent_group_scheduler;
+    scoped_refptr<base::SingleThreadTaskRunner> previous_task_runner;
+    scoped_refptr<base::SingleThreadTaskRunner> current_task_runner;
+    const char* trace_event_scope_name;
+    void* trace_event_scope_id;
+  };
+
+  void BeginAgentGroupSchedulerScope(
+      WebAgentGroupScheduler* next_agent_group_scheduler);
+  void EndAgentGroupSchedulerScope();
+
   bool IsAnyMainFrameWaitingForFirstContentfulPaint() const;
   bool IsAnyMainFrameWaitingForFirstMeaningfulPaint() const;
 
@@ -862,7 +875,7 @@
   struct MainThreadOnly {
     MainThreadOnly(
         MainThreadSchedulerImpl* main_thread_scheduler_impl,
-        const scoped_refptr<MainThreadTaskQueue>& compositor_task_runner,
+        const scoped_refptr<MainThreadTaskQueue>& compositor_task_queue,
         const base::TickClock* time_source,
         base::TimeTicks now);
     ~MainThreadOnly();
@@ -965,6 +978,8 @@
     // kNormalPriority and is updated via UpdateCompositorTaskQueuePriority().
     TraceableState<TaskQueue::QueuePriority, TracingCategoryName::kDefault>
         compositor_priority;
+
+    WTF::Vector<AgentGroupSchedulerScope> agent_group_scheduler_scope_stack;
   };
 
   struct AnyThread {
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 5057588..6199b34 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -247,6 +247,9 @@
 crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-005.html [ Failure ]
 crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-006.html [ Failure ]
 crbug.com/1155633 external/wpt/css/css-text/white-space/trailing-ideographic-space-break-spaces-007.html [ Failure ]
+crbug.com/1162836 external/wpt/css/css-text/white-space/trailing-ideographic-space-017.html [ Failure ]
+crbug.com/1162836 external/wpt/css/css-text/white-space/trailing-ideographic-space-020.html [ Failure ]
+crbug.com/1162836 external/wpt/css/css-text/white-space/trailing-ideographic-space-023.html [ Failure ]
 
 ### external/wpt/css/css-text/word-break/
 crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-004.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index c194dc8..14e5e01 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1545,10 +1545,10 @@
 crbug.com/1129834 external/wpt/css/css-text/white-space/trailing-other-space-separators-002.html [ Failure ]
 crbug.com/1129834 external/wpt/css/css-text/white-space/trailing-other-space-separators-003.html [ Failure ]
 crbug.com/1129834 external/wpt/css/css-text/white-space/trailing-other-space-separators-004.html [ Failure ]
-crbug.com/1129834 external/wpt/css/css-text/white-space/full-width-leading-spaces-004.html [ Failure ]
 crbug.com/1129834 external/wpt/css/css-text/white-space/trailing-ideographic-space-001.html [ Failure ]
 crbug.com/1129834 external/wpt/css/css-text/white-space/trailing-ideographic-space-002.html [ Failure ]
 
+crbug.com/1162836 external/wpt/css/css-text/white-space/full-width-leading-spaces-004.html [ Failure ]
 
 # Follow up the spec change for U+2010, U+2013
 crbug.com/1052717 external/wpt/css/css-text/line-break/line-break-loose-hyphens-001.html [ Failure ]
@@ -5863,3 +5863,5 @@
 crbug.com/1164459 [ Mac ] virtual/synchronous_html_parser/external/wpt/preload/download-resources.html [ Pass Timeout ]
 crbug.com/1164568 [ Mac ] external/wpt/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-allow-popups-report-to.https.html [ Pass Failure ]
 
+# Sheriff 2021-01-12
+crbug.com/1163793 http/tests/inspector-protocol/page/page-events-associated.js [ Pass Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 1e7c197..733d9af 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -109,6 +109,15 @@
              "--disable-blink-features=DecodeJpeg420ImagesToYUV"]
   },
   {
+    "prefix": "v8-off-thread-finalization",
+    "bases": [
+      "external/wpt/html/semantics/scripting-1",
+      "fast/dom",
+      "http/tests/devtools/isolated-code-cache/"
+    ],
+    "args": ["--enable-features=V8OffThreadFinalization,SmallScriptStreaming"]
+  },
+  {
     "prefix": "exotic-color-space",
     "bases": ["images"],
     "args": ["--force-color-profile=srgb",
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 9f227db..d6f23f6 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -777,6 +777,19 @@
     }
    },
    "html": {
+    "browsers": {
+     "origin": {
+      "origin-keyed-agent-clusters": {
+       "popups-crash.https.html": [
+        "dcfb5eb277489b5849f250275d6ce8197eedc224",
+        [
+         null,
+         {}
+        ]
+       ]
+      }
+     }
+    },
     "canvas": {
      "element": {
       "manual": {
@@ -1097,15 +1110,6 @@
      ]
     }
    },
-   "origin-isolation": {
-    "popups-crash.https.html": [
-     "dcfb5eb277489b5849f250275d6ce8197eedc224",
-     [
-      null,
-      {}
-     ]
-    ]
-   },
    "portals": {
     "portals-no-frame-crash.html": [
      "c87afa38c29bf98344c9d01039ed6bf9af1d5d96",
@@ -221262,7 +221266,7 @@
      []
     ],
     "idlharness-extensions.window-expected.txt": [
-     "8f261de6141c5b9a74ede23340cb3bb90ffe4a61",
+     "2add0e7fd942673ae9c2b9deb57a862e2916864e",
      []
     ],
     "idlharness.https.window-expected.txt": [
@@ -222583,6 +222587,246 @@
         []
        ]
       },
+      "origin-keyed-agent-clusters": {
+       "1-iframe": {
+        "parent-yes-child-no-port.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child-no-same.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child-no-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child-yes-port.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child-yes-same.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child-yes-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ]
+       },
+       "2-iframes": {
+        "parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ]
+       },
+       "META.yml": [
+        "f21ce69f6a710cafbb84cdbe715ffd8ae0bc75af",
+        []
+       ],
+       "OWNERS": [
+        "263ee12ddb3e9a1f485fcc61802915f7bfb14bcd",
+        []
+       ],
+       "README.md": [
+        "85ba3bce7f8740793791923a7d711b755531048a",
+        []
+       ],
+       "about-blank.https.sub.html.headers": [
+        "79a20f30fc0f486014c8b93edef7483605101504",
+        []
+       ],
+       "document-domain.sub.https.html.headers": [
+        "79a20f30fc0f486014c8b93edef7483605101504",
+        []
+       ],
+       "getter-special-cases": {
+        "csp-sandbox-no.https.html.headers": [
+         "4705ce9dedeeabf8208bf602176511c0cbe2cb76",
+         []
+        ],
+        "csp-sandbox-yes.https.html.headers": [
+         "a52bf509006a3b9a5518f4f0679b05d7cff12b42",
+         []
+        ],
+        "data-to-javascript-yes.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "data-url-yes.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "javascript-url-yes.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "removed-iframe.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "resources": {
+         "data-to-javascript-test.mjs": [
+          "3a88253ee3053465472ef7d6ecba74b92fab79ce",
+          []
+         ],
+         "data-url-test.mjs": [
+          "1a9b3be47f67b57bc8299def97cc660f6a282b9d",
+          []
+         ],
+         "helpers.mjs": [
+          "4610ffcad0a2f4be0f495a4d9e0107ecadf63eae",
+          []
+         ],
+         "javascript-url-test.mjs": [
+          "de474d8cafca9c073f306034b6c70c68d4222b31",
+          []
+         ],
+         "sandboxed-iframe-test.sub.mjs": [
+          "9357df00c5c686f0c06e7822644cff71d40dc20d",
+          []
+         ],
+         "sandboxed-same-origin-iframe-test.sub.mjs": [
+          "272f805870c932377b80b442f850d55018fdf0dd",
+          []
+         ]
+        },
+        "sandboxed-iframe-yes.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "sandboxed-same-origin-iframe-yes.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ]
+       },
+       "going-back.sub.https.html.headers": [
+        "79a20f30fc0f486014c8b93edef7483605101504",
+        []
+       ],
+       "iframe-navigation": {
+        "parent-yes-1-no-same-2-no-port.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ]
+       },
+       "insecure-http.sub.html.headers": [
+        "79a20f30fc0f486014c8b93edef7483605101504",
+        []
+       ],
+       "popups": {
+        "opener-yes-openee-no-port.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "opener-yes-openee-no-same.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "opener-yes-openee-no-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "opener-yes-openee-yes-port.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "opener-yes-openee-yes-same.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "opener-yes-openee-yes-subdomain.sub.https.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ]
+       },
+       "removing-iframes.sub.https.html.headers": [
+        "79a20f30fc0f486014c8b93edef7483605101504",
+        []
+       ],
+       "resources": {
+        "README.md": [
+         "9292fe3894e4a82f77a8877b76faa19fe6dfdb47",
+         []
+        ],
+        "crashy-popup.sub.html": [
+         "45c8d5074d22b3233d8bee4c6d1236e8899009e4",
+         []
+        ],
+        "crashy-popup.sub.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "frame.html": [
+         "537de07b140d79367161c5be16918b79e8d40048",
+         []
+        ],
+        "frame.html.headers": [
+         "79a20f30fc0f486014c8b93edef7483605101504",
+         []
+        ],
+        "helpers.mjs": [
+         "f858c5fefbca75c30a958e31ac46c264e7007337",
+         []
+        ],
+        "helpers.mjs.headers": [
+         "cb762eff806849df46dc758ef7b98b63f27f54c9",
+         []
+        ],
+        "send-header-page-script.mjs": [
+         "17b00684d643c85a30465435489ac31bceb9eb2d",
+         []
+        ],
+        "send-header-page-script.mjs.headers": [
+         "cb762eff806849df46dc758ef7b98b63f27f54c9",
+         []
+        ],
+        "send-oac-header.py": [
+         "cc8860fe75b0ab504553513ac17f1cf5ee805758",
+         []
+        ]
+       }
+      },
       "relaxing-the-same-origin-restriction": {
        "sandboxed-document_domain.html.headers": [
         "82e8023d0ba61851af5747ee2ccba154193d1875",
@@ -233878,6 +234122,10 @@
        []
       ],
       "pseudo-classes": {
+       "autofill-expected.txt": [
+        "501f68598b2549f4f62f0d929986dfd32cfab619",
+        []
+       ],
        "dir-expected.txt": [
         "dc9646444d83d4389f765d7a10af342ed2168547",
         []
@@ -235381,6 +235629,16 @@
       ]
      }
     },
+    "multiple-import-maps": {
+     "basic-expected.txt": [
+      "b56a107449385239c4e36849df466cd961dd97d4",
+      []
+     ],
+     "with-errors-expected.txt": [
+      "2ac5edb148929e6f1c9d3db59cd3989895b89a00",
+      []
+     ]
+    },
     "resources": {
      "empty.js": [
       "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
@@ -236234,7 +236492,7 @@
      []
     ],
     "gamepad-extensions.idl": [
-     "2d353b061c4b728ce61e61ed4be1f5981027865f",
+     "29ab815c411b4ce399570e9493f86559bfc0c85c",
      []
     ],
     "gamepad.idl": [
@@ -239600,246 +239858,6 @@
      []
     ]
    },
-   "origin-isolation": {
-    "1-iframe": {
-     "parent-yes-child-no-port.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child-no-same.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child-no-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child-yes-port.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child-yes-same.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child-yes-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ]
-    },
-    "2-iframes": {
-     "parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ]
-    },
-    "META.yml": [
-     "937eca52e71dffcbd50b6f9dd869cb95e1808cd5",
-     []
-    ],
-    "OWNERS": [
-     "263ee12ddb3e9a1f485fcc61802915f7bfb14bcd",
-     []
-    ],
-    "README.md": [
-     "7446ff2495d6ac03d38335817364a6ead115d61e",
-     []
-    ],
-    "about-blank.https.sub.html.headers": [
-     "79a20f30fc0f486014c8b93edef7483605101504",
-     []
-    ],
-    "document-domain.sub.https.html.headers": [
-     "79a20f30fc0f486014c8b93edef7483605101504",
-     []
-    ],
-    "getter-special-cases": {
-     "csp-sandbox-no.https.html.headers": [
-      "4705ce9dedeeabf8208bf602176511c0cbe2cb76",
-      []
-     ],
-     "csp-sandbox-yes.https.html.headers": [
-      "a52bf509006a3b9a5518f4f0679b05d7cff12b42",
-      []
-     ],
-     "data-to-javascript-yes.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "data-url-yes.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "javascript-url-yes.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "removed-iframe.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "resources": {
-      "data-to-javascript-test.mjs": [
-       "3a88253ee3053465472ef7d6ecba74b92fab79ce",
-       []
-      ],
-      "data-url-test.mjs": [
-       "d32d8521be47187b7f8db5d8d5d915e8f9d79de3",
-       []
-      ],
-      "helpers.mjs": [
-       "6cc2ed6b697956b859ade3e0c0956c804cc5f8a4",
-       []
-      ],
-      "javascript-url-test.mjs": [
-       "de474d8cafca9c073f306034b6c70c68d4222b31",
-       []
-      ],
-      "sandboxed-iframe-test.sub.mjs": [
-       "98575b8d80ecda8c294d29ca8c40199ef40f6fe9",
-       []
-      ],
-      "sandboxed-same-origin-iframe-test.sub.mjs": [
-       "272f805870c932377b80b442f850d55018fdf0dd",
-       []
-      ]
-     },
-     "sandboxed-iframe-yes.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "sandboxed-same-origin-iframe-yes.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ]
-    },
-    "going-back.sub.https.html.headers": [
-     "79a20f30fc0f486014c8b93edef7483605101504",
-     []
-    ],
-    "iframe-navigation": {
-     "parent-yes-1-no-same-2-no-port.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "parent-yes-1-no-same-2-no-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ]
-    },
-    "insecure-http.sub.html.headers": [
-     "79a20f30fc0f486014c8b93edef7483605101504",
-     []
-    ],
-    "popups": {
-     "opener-yes-openee-no-port.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "opener-yes-openee-no-same.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "opener-yes-openee-no-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "opener-yes-openee-yes-port.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "opener-yes-openee-yes-same.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "opener-yes-openee-yes-subdomain.sub.https.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ]
-    },
-    "removing-iframes.sub.https.html.headers": [
-     "79a20f30fc0f486014c8b93edef7483605101504",
-     []
-    ],
-    "resources": {
-     "README.md": [
-      "9292fe3894e4a82f77a8877b76faa19fe6dfdb47",
-      []
-     ],
-     "crashy-popup.sub.html": [
-      "f8d077c2a29ff494d502d70fc7f2cd80a443da68",
-      []
-     ],
-     "crashy-popup.sub.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "frame.html": [
-      "537de07b140d79367161c5be16918b79e8d40048",
-      []
-     ],
-     "frame.html.headers": [
-      "79a20f30fc0f486014c8b93edef7483605101504",
-      []
-     ],
-     "helpers.mjs": [
-      "02ba405c60190019b909ca84bb05c3370b912855",
-      []
-     ],
-     "helpers.mjs.headers": [
-      "cb762eff806849df46dc758ef7b98b63f27f54c9",
-      []
-     ],
-     "send-header-page-script.mjs": [
-      "515987f3f20de655f4d1bab87850e7a625fd1faa",
-      []
-     ],
-     "send-header-page-script.mjs.headers": [
-      "cb762eff806849df46dc758ef7b98b63f27f54c9",
-      []
-     ],
-     "send-origin-isolation-header.py": [
-      "8acf989f964d9964007fa84771843525580fc5fc",
-      []
-     ]
-    }
-   },
    "origin-policy": {
     "META.yml": [
      "9efb2a7d6718d1c98161329470238a165a004824",
@@ -256606,7 +256624,7 @@
        []
       ],
       "server.py": [
-       "d4472811e593e0232d3ffe86536479e68b11808f",
+       "2b5ed4a27d5125ba3474b2036fa006924a5dec1f",
        []
       ],
       "sslutils": {
@@ -258984,6 +259002,18 @@
        []
       ]
      },
+     "get_computed_label": {
+      "__init__.py": [
+       "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
+       []
+      ]
+     },
+     "get_computed_role": {
+      "__init__.py": [
+       "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
+       []
+      ]
+     },
      "get_current_url": {
       "__init__.py": [
        "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
@@ -346988,6 +347018,452 @@
         ]
        ]
       },
+      "origin-keyed-agent-clusters": {
+       "1-iframe": {
+        "parent-no-child-bad-subdomain.sub.https.html": [
+         "3a45ee6d6a8e2b0d5e449e4d98229ae37aab82ee",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child-yes-port.sub.https.html": [
+         "85fb1f64e0975434f838943364d4fe205027f95f",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child-yes-same.sub.https.html": [
+         "7ece02c81a257feff3d4ab2f2eb19545384d2928",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child-yes-subdomain-with-redirect.sub.https.html": [
+         "994f80876d5f6d31d74ed73fe2dfeef4f1575684",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child-yes-subdomain.sub.https.html": [
+         "5fc2fa29f3e851a4d3ab588a9dd8fa4b6e5d81cc",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child-yeswithparams-subdomain.sub.https.html": [
+         "3e7c8419b36a096b385f047e7192bc27d352c085",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child-no-port.sub.https.html": [
+         "f00814cfbf63b293b3c6060f3df986127a53056c",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child-no-same.sub.https.html": [
+         "307f8c48d74e5dbfa77b4627ef6e03a75884e1da",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child-no-subdomain.sub.https.html": [
+         "8c823fa36f5bc2321ca87f2e0f4d1bb5cb164c0e",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child-yes-port.sub.https.html": [
+         "5e431e6e41310bb22db730c49deb44886e02dc98",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child-yes-same.sub.https.html": [
+         "3b8c214a6186740e0224486092a8efb42083c949",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child-yes-subdomain.sub.https.html": [
+         "136a3a0bbadbffceafd3512cd8a0e2f4edf95ee2",
+         [
+          null,
+          {}
+         ]
+        ]
+       },
+       "2-iframes": {
+        "parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html": [
+         "1bb252f0ab3ad066a48b665e604ce2450f33c2ac",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html": [
+         "5b80c528f00895c81f1c2f41a5e253bd4149fd7d",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html": [
+         "bba13b82a4d74cfa41a9c3efe1695bcf9d27be99",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child1-yes-subdomain-child2-no-port.sub.https.html": [
+         "d01d1802135c797dd4dd449f4c033d7c50b6366b",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html": [
+         "9a245b3acef0821934df63efbd3b7aa80cef6e42",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html": [
+         "c308b9a17acabdcb5403a89d9f3a22e48ad4db6c",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html": [
+         "767d908c21f377fc0556d300b23549d18570fee4",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html": [
+         "45047f3ae150023df115ae4e7731356ec326e55f",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html": [
+         "202b916767c61a79f149170c851af76afcbdf088",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html": [
+         "a1316731ac2e46c3ee4427d97aaf63f983132764",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html": [
+         "46bef4b9a9e9e69189acd1307207a6da5c1d2592",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html": [
+         "39dcfc04b096f6ecaa94d7e82d5ffff3c83aa63b",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html": [
+         "b6daf91b54e0ab25901cec44076047decd0056b2",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html": [
+         "b94f9392d462e880bc96f8cf49ba188eb63a2da8",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html": [
+         "fb3fda1bf27e1576a5c6af66b99221f07b0591a6",
+         [
+          null,
+          {}
+         ]
+        ]
+       },
+       "about-blank.https.sub.html": [
+        "edc98b363e46471b3e5db54b6b5a0b827dbb0a6d",
+        [
+         null,
+         {}
+        ]
+       ],
+       "document-domain.sub.https.html": [
+        "b4535d9e548c7010c676114dfa259f5201f5ce7f",
+        [
+         null,
+         {}
+        ]
+       ],
+       "getter-special-cases": {
+        "csp-sandbox-no.https.html": [
+         "e0b5f92376287417ab099c49e0b7396dc0d87256",
+         [
+          null,
+          {}
+         ]
+        ],
+        "csp-sandbox-yes.https.html": [
+         "a2220c5acc5c4bfac7c158fd2ba4f965ad05d039",
+         [
+          null,
+          {}
+         ]
+        ],
+        "data-to-javascript-no.https.html": [
+         "06149cda8ab72081769a926d169620be9d0f5cd5",
+         [
+          null,
+          {}
+         ]
+        ],
+        "data-to-javascript-yes.https.html": [
+         "af6fea0ad979b65c74ab6ae8062c8e025b2f13a8",
+         [
+          null,
+          {}
+         ]
+        ],
+        "data-url-no.https.html": [
+         "8ae564a072a4b8338e00e3a6b8f54b7425d4f563",
+         [
+          null,
+          {}
+         ]
+        ],
+        "data-url-yes.https.html": [
+         "bcbf098a664ef20a8c083e7f43fd939ac49c5c64",
+         [
+          null,
+          {}
+         ]
+        ],
+        "javascript-url-no.https.html": [
+         "1b54ad42a4333b333bf1897ab4a47567d9c141b3",
+         [
+          null,
+          {}
+         ]
+        ],
+        "javascript-url-yes.https.html": [
+         "e2b7730dd2b3cffb5bc7f7422175ef931a538b62",
+         [
+          null,
+          {}
+         ]
+        ],
+        "removed-iframe.sub.https.html": [
+         "fcf5068908800f6fcec7d6a951d5c7b06895ba29",
+         [
+          null,
+          {}
+         ]
+        ],
+        "sandboxed-iframe-no.https.html": [
+         "29758a17b877ab0561388e2b5a92c70f4f010c56",
+         [
+          null,
+          {}
+         ]
+        ],
+        "sandboxed-iframe-yes.https.html": [
+         "5eb5d08d10f6ec621e454232c342bcdcc0d005b9",
+         [
+          null,
+          {}
+         ]
+        ],
+        "sandboxed-same-origin-iframe-no.https.html": [
+         "3ed4096f399175e133797a4116af5cca566b7dd0",
+         [
+          null,
+          {}
+         ]
+        ],
+        "sandboxed-same-origin-iframe-yes.https.html": [
+         "c7ea5f069321f6f65d114ce44f843bcbaf1c2f1a",
+         [
+          null,
+          {}
+         ]
+        ]
+       },
+       "going-back.sub.https.html": [
+        "a593619ea64a98ccc6151be606ab245016e4b885",
+        [
+         null,
+         {}
+        ]
+       ],
+       "iframe-navigation": {
+        "parent-no-1-no-same-2-yes-port.sub.https.html": [
+         "8237f2f23f9479bd1669642b7d2077e9681859e2",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-1-no-same-2-yes-subdomain.sub.https.html": [
+         "00d8c3164a699bb2daa86aac333b24a29cc51af5",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html": [
+         "803e684e1ca2d6cbad82a307f7f0d55c6c513c7e",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html": [
+         "b96d10afd13b9009e9c39d1bb5941682f751e7f3",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html": [
+         "a70ed566708ce99a77e0386f3acacdd4f309fc32",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html": [
+         "38e26301288d190e6547f0d34312aed686cab26f",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-1-no-same-2-no-port.sub.https.html": [
+         "6211845be1f74e6686ee6f47400c6d8895444199",
+         [
+          null,
+          {}
+         ]
+        ],
+        "parent-yes-1-no-same-2-no-subdomain.sub.https.html": [
+         "ead56754a7085acf8e82bddcc6cc76df6f7f7dad",
+         [
+          null,
+          {}
+         ]
+        ]
+       },
+       "insecure-http.sub.html": [
+        "6f9e5d8b73a1a811710e4450515a36f7ce9cc4d9",
+        [
+         null,
+         {}
+        ]
+       ],
+       "popups": {
+        "opener-no-openee-yes-port.sub.https.html": [
+         "a0bf569b124b449d7884ce96f8e0e46a765f13ce",
+         [
+          null,
+          {}
+         ]
+        ],
+        "opener-no-openee-yes-same.sub.https.html": [
+         "196dff1449b34b5fea5fc6369b2eda992cceee6b",
+         [
+          null,
+          {}
+         ]
+        ],
+        "opener-no-openee-yes-subdomain.sub.https.html": [
+         "f96d2273d5e73ff75581df5ba465c0cc4ffdf0f7",
+         [
+          null,
+          {}
+         ]
+        ],
+        "opener-yes-openee-no-port.sub.https.html": [
+         "51c5a208c5ef088091fc5a6019e61fac53f4d07b",
+         [
+          null,
+          {}
+         ]
+        ],
+        "opener-yes-openee-no-same.sub.https.html": [
+         "562ab40c68bf4163a540d96f447dc13287f9438f",
+         [
+          null,
+          {}
+         ]
+        ],
+        "opener-yes-openee-no-subdomain.sub.https.html": [
+         "d7d4791459eb30470fca7d77950fc9c8d79df721",
+         [
+          null,
+          {}
+         ]
+        ],
+        "opener-yes-openee-yes-port.sub.https.html": [
+         "32a5066d2eef6ba0fb3264d9b01666a71cdc30ff",
+         [
+          null,
+          {}
+         ]
+        ],
+        "opener-yes-openee-yes-same.sub.https.html": [
+         "a85decac3c1906a35399786c5a56914c047eede3",
+         [
+          null,
+          {}
+         ]
+        ],
+        "opener-yes-openee-yes-subdomain.sub.https.html": [
+         "148b39af232f3e4588f0c74187f416b8a5c395ed",
+         [
+          null,
+          {}
+         ]
+        ]
+       },
+       "removing-iframes.sub.https.html": [
+        "b83aa9f5bedbaf64c7af9215d926d86013894cab",
+        [
+         null,
+         {}
+        ]
+       ]
+      },
       "origin-of-data-document.html": [
        "448f47fa244c1134549150367a0991f79c13da86",
        [
@@ -379414,6 +379890,13 @@
      },
      "selectors": {
       "pseudo-classes": {
+       "autofill.html": [
+        "b7c3644bdbad02f7775c3ae4a341715d082870c2",
+        [
+         null,
+         {}
+        ]
+       ],
        "checked-type-change.html": [
         "661d9e4355eb46a6ea0bb64ea44daa0c9c0d2497",
         [
@@ -384050,6 +384533,22 @@
       {}
      ]
     ],
+    "multiple-import-maps": {
+     "basic.html": [
+      "9ab03ddc302af11ad7fc12d4dc8c1ed6b90084a6",
+      [
+       null,
+       {}
+      ]
+     ],
+     "with-errors.html": [
+      "a93ecaaeff5b329916bfd003959d4cb067d5ee41",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
     "not-as-classic-script.html": [
      "5f97394e4408bc8db5624e5d0c6f1b2fa1b028dd",
      [
@@ -394594,452 +395093,6 @@
      ]
     ]
    },
-   "origin-isolation": {
-    "1-iframe": {
-     "parent-no-child-bad-subdomain.sub.https.html": [
-      "27085aa151ed219718a9538ba44f720ad20c62ca",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child-yes-port.sub.https.html": [
-      "d1466e55c04136f04263bd288f7299bf33ed5a15",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child-yes-same.sub.https.html": [
-      "39d6cf996a842c6e84d9b8bec6a74dbf5d0e4a2b",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child-yes-subdomain-with-redirect.sub.https.html": [
-      "9004500b764191493112feeb26c48d61acf54fb8",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child-yes-subdomain.sub.https.html": [
-      "648c641553cdd6d6f5a0f53ce4fc94f2314e2e11",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child-yeswithparams-subdomain.sub.https.html": [
-      "400a8f72332a2620c3957bfe1aa7d6b572c927e2",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child-no-port.sub.https.html": [
-      "793c47328e103c5f63677ebdaa6818b07ce0e198",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child-no-same.sub.https.html": [
-      "f1ca9a9a7cdec13d38b42abcc93f2eb9a57242a1",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child-no-subdomain.sub.https.html": [
-      "be84ecac2d1891554bab21ae4c12cdfb59f1ebf3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child-yes-port.sub.https.html": [
-      "58516b86742e4ea3ccae560066bdf44a4fd80a9e",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child-yes-same.sub.https.html": [
-      "b08206b4478fceb24013abe39907dee5d2d95446",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child-yes-subdomain.sub.https.html": [
-      "9a426d068aef3b6115c803aa1e3828dbe6a2fbc9",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "2-iframes": {
-     "parent-no-child1-no-subdomain-child2-yes-subdomain.sub.https.html": [
-      "55c158381275a4d63e43bf7dbb8b40c366e3ba55",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html": [
-      "2c0bb614b50b35dab4086dc35d360601570084ec",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child1-no-subdomain1-child2-yes-subdomain2.sub.https.html": [
-      "e31af2061fb9ae5bb5000c7980c6637f391a444b",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child1-yes-subdomain-child2-no-port.sub.https.html": [
-      "eaccc8509cdc4d8afb2d9144af15acb9fd67ef18",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html": [
-      "5adb911ae8b466125696caf0d828db43f66afdaa",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-no-subdomain-child2-no-subdomain.sub.https.html": [
-      "e463f328f19d1c91f51c6494c182ece8ee8072ef",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-no-subdomain-child2-no-subdomain2.sub.https.html": [
-      "2d67979b68a355c0860847b9a4779cfa833647bf",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-no-subdomain-child2-yes-subdomain.sub.https.html": [
-      "ecf759cde06706311e77c0244ca796906deec83d",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-no-subdomain-child2-yes-subdomain2.sub.https.html": [
-      "0448076178aa3a2956e98116afa8bcad549cc5f6",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-no-subdomain-child2-yes-subdomainport.sub.https.html": [
-      "1b14ca48ba730791c9128da85490c606dada3f3a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-yes-subdomain-child2-no-port.sub.https.html": [
-      "fcdb13699710bf8ba5aca0d15b0e2908d60e7af1",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-yes-subdomain-child2-no-subdomain.sub.https.html": [
-      "011bcd1637b818b24d552f25fcfcd615c6e25528",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-yes-subdomain-child2-yes-subdomain.sub.https.html": [
-      "1a4dd4c9a655b56c4c94a0b8297bc0faa711154c",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-yes-subdomain-child2-yes-subdomain2.sub.https.html": [
-      "852fd2d216e3be0f0b1b18606ad46fa313a4782c",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-child1-yes-subdomain-child2-yes-subdomainport.sub.https.html": [
-      "ba8edadc188c7cc7fe8de79e95a06a824911e458",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "about-blank.https.sub.html": [
-     "edc98b363e46471b3e5db54b6b5a0b827dbb0a6d",
-     [
-      null,
-      {}
-     ]
-    ],
-    "document-domain.sub.https.html": [
-     "48f9a99b5f7dbe10b7dac7f12764029657d4ff0d",
-     [
-      null,
-      {}
-     ]
-    ],
-    "getter-special-cases": {
-     "csp-sandbox-no.https.html": [
-      "16ecb45192950d29329c6bd54a6b561f281b07a2",
-      [
-       null,
-       {}
-      ]
-     ],
-     "csp-sandbox-yes.https.html": [
-      "b9a7359974d5651f955cb65c03862a0eeb79a35d",
-      [
-       null,
-       {}
-      ]
-     ],
-     "data-to-javascript-no.https.html": [
-      "cb00ef782d0cf558ebcc28a6fa103b9826fb541d",
-      [
-       null,
-       {}
-      ]
-     ],
-     "data-to-javascript-yes.https.html": [
-      "6856441deff1d0c707dbc8b1217dd6fb718eea36",
-      [
-       null,
-       {}
-      ]
-     ],
-     "data-url-no.https.html": [
-      "5b194eaef3552fd7c7f5ba64ab3f4db648f0d457",
-      [
-       null,
-       {}
-      ]
-     ],
-     "data-url-yes.https.html": [
-      "85821be32b53245bc1356321a8bc085716a710c2",
-      [
-       null,
-       {}
-      ]
-     ],
-     "javascript-url-no.https.html": [
-      "b75cae47cdbb6c49b33044729a141ed52462af7e",
-      [
-       null,
-       {}
-      ]
-     ],
-     "javascript-url-yes.https.html": [
-      "573b1a0b712bd6c93c3931642fbe3603cc721d54",
-      [
-       null,
-       {}
-      ]
-     ],
-     "removed-iframe.sub.https.html": [
-      "8a29076e56111444a7f8d83b8d0e413291de4e51",
-      [
-       null,
-       {}
-      ]
-     ],
-     "sandboxed-iframe-no.https.html": [
-      "bc2276fede8ac74f4cb7fb9b119a8e306bc9160a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "sandboxed-iframe-yes.https.html": [
-      "5805d3421ebe2590585f0f7166d781eb979e6277",
-      [
-       null,
-       {}
-      ]
-     ],
-     "sandboxed-same-origin-iframe-no.https.html": [
-      "a1b44a1e5b34778aa7f8aa8fc64d5f0cbceced52",
-      [
-       null,
-       {}
-      ]
-     ],
-     "sandboxed-same-origin-iframe-yes.https.html": [
-      "b9339afae2e802178d55972af5033cf7050a2408",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "going-back.sub.https.html": [
-     "7e95f19366996ce0c97b87631b10d660d07c2e56",
-     [
-      null,
-      {}
-     ]
-    ],
-    "iframe-navigation": {
-     "parent-no-1-no-same-2-yes-port.sub.https.html": [
-      "85ee2f677f31dd23814276c015c39ac8abb88bef",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-1-no-same-2-yes-subdomain.sub.https.html": [
-      "bb39de648acf177ef0ee6479a4b7a8655ee8b168",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-1-no-subdomain-2-yes-subdomain.sub.https.html": [
-      "b52ca783f6f711fae40f9e01795d5072c9d36644",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-1-no-subdomain-2-yes-subdomain2.sub.https.html": [
-      "67e99cee090ad9766b3734784dec96e39dda511a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-1-subdomain-yes-2-subdomain2-no.sub.https.html": [
-      "1b7dd01947f69eb7311ef3d7235701fe229b6aa3",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-no-1-yes-subdomain-2-no-subdomain.sub.https.html": [
-      "783f5870a839b1b63f96e28a9c734e0538839547",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-1-no-same-2-no-port.sub.https.html": [
-      "461fd0ddc4b88697fb49cd3a89166f88c0310636",
-      [
-       null,
-       {}
-      ]
-     ],
-     "parent-yes-1-no-same-2-no-subdomain.sub.https.html": [
-      "6832cd03858cab38e03993b676a52ca81c594c97",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "insecure-http.sub.html": [
-     "18e0216e2627a2f81d9237be5e497aed3f22a482",
-     [
-      null,
-      {}
-     ]
-    ],
-    "popups": {
-     "opener-no-openee-yes-port.sub.https.html": [
-      "270162530b100da850d4834c049553fc01d32a84",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opener-no-openee-yes-same.sub.https.html": [
-      "0c8d89b8e66bf21ac2aa693f5dbf2131d9f8b4d0",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opener-no-openee-yes-subdomain.sub.https.html": [
-      "d1aaabd9206239469b2e7871cf6a0ab30a48bcb1",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opener-yes-openee-no-port.sub.https.html": [
-      "96b839e167f92326d72afad1b62a36b36ee25b6a",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opener-yes-openee-no-same.sub.https.html": [
-      "0dee5fae002277fdb3811524b37e8025a4118a3e",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opener-yes-openee-no-subdomain.sub.https.html": [
-      "cfec0fda1301d90bf71da00e1f93f02e71fb8119",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opener-yes-openee-yes-port.sub.https.html": [
-      "4ea37421df0ac11baa99d6fc435ca5be50af2b6b",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opener-yes-openee-yes-same.sub.https.html": [
-      "603f11d8de6cb78b96735a740330145787f5cf75",
-      [
-       null,
-       {}
-      ]
-     ],
-     "opener-yes-openee-yes-subdomain.sub.https.html": [
-      "b182d496a9278b17e26c7ee870aa9252f0a05b18",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
-    "removing-iframes.sub.https.html": [
-     "cbe13af820798a1a88300cea92ea2bf843b77933",
-     [
-      null,
-      {}
-     ]
-    ]
-   },
    "origin-policy": {
     "bad-server": {
      "bad-headers.https.html": [
@@ -399015,7 +399068,7 @@
      ]
     ],
     "avoid-prefetching-on-text-plain.html": [
-     "45564e1bfaca07f2ea21bf460132c7f18ed6a24a",
+     "b14b7e4f8a90241c30cccbcd9e5b754fde515b8b",
      [
       null,
       {}
@@ -437461,7 +437514,7 @@
    },
    "webcodecs": {
     "audio-decoder.any.js": [
-     "70110a7822ea0fb374b08a8fd5f3811a6164c3ea",
+     "a6367231b234a6e078bd96dc2e689ffb429593c6",
      [
       "webcodecs/audio-decoder.any.html",
       {
@@ -464252,6 +464305,24 @@
        ]
       ]
      },
+     "get_computed_label": {
+      "get.py": [
+       "89c0f9d2b29b410a5ccf55806c677d20b94576b1",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
+     "get_computed_role": {
+      "get.py": [
+       "06173ee6fcb2b169c11cb76e6e2955870a59e77f",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
      "get_current_url": {
       "get.py": [
        "fa2ff6a6595f165987f13ad7a7ad60fd6e3cbb44",
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-017.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-017.html
new file mode 100644
index 0000000..742e389
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-017.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-plaintext">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-none">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Disabled hypenation and unicode-bidi 'plaintext' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: plaintext;
+    hyphens: none;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>&#x3000;&#x3000;</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-018.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-018.html
new file mode 100644
index 0000000..16bb876
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-018.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-plaintext">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-none">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Disabled hypenation and unicode-bidi 'plaintext' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: plaintext;
+    hyphens: none;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>XX</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-019.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-019.html
new file mode 100644
index 0000000..0707000
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-019.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-plaintext">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-none">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Disabled hypenation and unicode-bidi 'plaintext' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: plaintext;
+    hyphens: none;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>X&#x3000;</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-020.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-020.html
new file mode 100644
index 0000000..97ae007
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-020.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-isolate">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-none">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Disabled hypenation and unicode-bidi 'isolate' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: isolate;
+    hyphens: none;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>&#x3000;&#x3000;</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-021.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-021.html
new file mode 100644
index 0000000..b75327a6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-021.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-isolate">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-none">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Disabled hypenation and unicode-bidi 'isolate' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: isolate;
+    hyphens: none;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>XX</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-022.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-022.html
new file mode 100644
index 0000000..71720c2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-022.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-isolate">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-none">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Disabled hypenation and unicode-bidi 'isolate' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: isolate;
+    hyphens: none;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>X&#x3000;</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-023.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-023.html
new file mode 100644
index 0000000..ea95ab1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-023.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-plaintext">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-auto">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Auto hypenation and unicode-bidi 'plaintext' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: plaintext;
+    hyphens: auto;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>&#x3000;&#x3000;</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-024.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-024.html
new file mode 100644
index 0000000..2a65400
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-024.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-plaintext">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-auto">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Auto hypenation and unicode-bidi 'plaintext' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: plaintext;
+    hyphens: auto;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>XX</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-025.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-025.html
new file mode 100644
index 0000000..5123e10
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/trailing-ideographic-space-025.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text level 3 Test: handling trailing ideographic space sequence</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-normal">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#valdef-unicode-bidi-plaintext">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-auto">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Auto hypenation and unicode-bidi 'plaintext' should not affect, hence the trailing ideographic spaces must hang; however, the rest of the sequence is wrapped due to the forced break.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<style>
+div {
+    font: 50px/1 Ahem;
+    width: 1ch;
+}
+span {
+    background: green;
+    color: transparent;
+
+    unicode-bidi: plaintext;
+    hyphens: auto;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<br>XX</div>
+<div><span>X&#x3000;<br>X&#x3000;</span></div>
diff --git a/third_party/blink/web_tests/external/wpt/gamepad/idlharness-extensions.window-expected.txt b/third_party/blink/web_tests/external/wpt/gamepad/idlharness-extensions.window-expected.txt
index 8f261de..2add0e7 100644
--- a/third_party/blink/web_tests/external/wpt/gamepad/idlharness-extensions.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/gamepad/idlharness-extensions.window-expected.txt
@@ -25,8 +25,19 @@
 FAIL GamepadPose interface: attribute orientation assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
 FAIL GamepadPose interface: attribute angularVelocity assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
 FAIL GamepadPose interface: attribute angularAcceleration assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadTouch interface: existence and properties of interface object assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface object length assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface object name assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: existence and properties of interface prototype object assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: attribute touchId assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: attribute surfaceId assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: attribute position assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: attribute surfaceDimensions assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
 FAIL Gamepad interface: attribute hand assert_true: The prototype object must have a property "hand" expected true got false
 FAIL Gamepad interface: attribute hapticActuators assert_true: The prototype object must have a property "hapticActuators" expected true got false
 FAIL Gamepad interface: attribute pose assert_true: The prototype object must have a property "pose" expected true got false
+FAIL Gamepad interface: attribute touchEvents assert_true: The prototype object must have a property "touchEvents" expected true got false
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/selectors/pseudo-classes/autofill-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/selectors/pseudo-classes/autofill-expected.txt
new file mode 100644
index 0000000..501f685
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/selectors/pseudo-classes/autofill-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+FAIL ":autofill" should be a valid selector Failed to execute 'querySelector' on 'Document': ':autofill' is not a valid selector.
+PASS ":-webkit-autofill" should be a valid selector
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/selectors/pseudo-classes/autofill.html b/third_party/blink/web_tests/external/wpt/html/semantics/selectors/pseudo-classes/autofill.html
new file mode 100644
index 0000000..b7c3644
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/selectors/pseudo-classes/autofill.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Selector: pseudo-classes (:autofill)</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/#pseudo-classes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_valid_selector(":autofill");
+test_valid_selector(":-webkit-autofill", [":autofill", ":-webkit-autofill"]);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/gamepad-extensions.idl b/third_party/blink/web_tests/external/wpt/interfaces/gamepad-extensions.idl
index 2d353b0..29ab815 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/gamepad-extensions.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/gamepad-extensions.idl
@@ -32,8 +32,17 @@
   readonly attribute Float32Array? angularAcceleration;
 };
 
+[Exposed=Window, SecureContext]
+interface GamepadTouch {
+  readonly attribute unsigned long touchId;
+  readonly attribute octet surfaceId;
+  readonly attribute Float32Array position;
+  readonly attribute Uint32Array? surfaceDimensions;
+};
+
 partial interface Gamepad {
   readonly attribute GamepadHand hand;
   readonly attribute FrozenArray<GamepadHapticActuator> hapticActuators;
   readonly attribute GamepadPose? pose;
+  readonly attribute FrozenArray<GamepadTouch>? touchEvents;
 };
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptserve/wptserve/server.py b/third_party/blink/web_tests/external/wpt/tools/wptserve/wptserve/server.py
index d447281..2b5ed4a2 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wptserve/wptserve/server.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wptserve/wptserve/server.py
@@ -429,7 +429,7 @@
             return False
 
         method = extract_method_header(frame.headers)
-        if method != "CONNECT":
+        if method != b"CONNECT":
             return False
 
         protocol = ""
@@ -437,7 +437,7 @@
             if key in (b':protocol', u':protocol'):
                 protocol = isomorphic_encode(value)
                 break
-        if protocol != "websocket":
+        if protocol != b"websocket":
             raise ProtocolError("Invalid protocol %s with CONNECT METHOD" % (protocol,))
 
         return True
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_label/__init__.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_label/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_label/__init__.py
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_label/get.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_label/get.py
new file mode 100644
index 0000000..89c0f9d2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_label/get.py
@@ -0,0 +1,37 @@
+import pytest
+from six import text_type
+
+from webdriver.error import NoSuchAlertException
+
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def get_computed_label(session, element):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/computedlabel".format(
+            session_id=session.session_id,
+            element_id=element))
+
+
+def test_no_browsing_context(session, closed_window):
+    response = get_computed_label(session)
+    assert_error(response, "no such window")
+
+
+def test_no_user_prompt(session):
+    response = get_computed_label(session)
+    assert_error(response, "no such alert")
+
+
+@pytest.mark.parametrize("html,tag,label", [
+    ("<button>ok</button>", "button", "ok"),
+    ("<button aria-labelledby=\"one two\"></button><div id=one>ok</div><div id=two>go</div>", "button", "ok go"),
+    ("<button aria-label=foo>bar</button>", "button", "foo"),
+    ("<label><input> foo</label>", "input", "foo"),
+    ("<label for=b>foo<label><input id=b>", "input", "foo")])
+def test_get_computed_label(session, html, tag, label):
+    session.url = inline("{0}".format(tag))
+    element = session.find.css(tag, all=False)
+    result = get_computed_label(session, element.id)
+    assert_success(result, label)
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_role/__init__.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_role/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_role/__init__.py
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_role/get.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_role/get.py
new file mode 100644
index 0000000..06173ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_computed_role/get.py
@@ -0,0 +1,35 @@
+import pytest
+from six import text_type
+
+from webdriver.error import NoSuchAlertException
+
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def get_computed_role(session, element):
+    return session.transport.send(
+        "GET", "session/{session_id}/element/{element_id}/computedrole".format(
+            session_id=session.session_id,
+            element_id=element))
+
+
+def test_no_browsing_context(session, closed_window):
+    response = get_computed_role(session, "id")
+    assert_error(response, "no such window")
+
+
+def test_no_user_prompt(session):
+    response = get_computed_role(session, "id")
+    assert_error(response, "no such alert")
+
+
+@pytest.mark.parametrize("html,tag,expected", [
+    ("<li role=menuitem>foo", "li", "menu"),
+    ("<input role=searchbox>", "input", "searchbox"),
+    ("<img role=presentation>", "img", "presentation")])
+def test_computed_roles(session, html, tag, expected):
+    session.url = inline(html)
+    element = session.find.css(tag, all=False)
+    result = get_computed_role(session, element.id)
+    assert_success(result, expected)
diff --git a/third_party/blink/web_tests/external/wpt/webusb/resources/manual.js b/third_party/blink/web_tests/external/wpt/webusb/resources/manual.js
index 869ac450..e8dc08a 100644
--- a/third_party/blink/web_tests/external/wpt/webusb/resources/manual.js
+++ b/third_party/blink/web_tests/external/wpt/webusb/resources/manual.js
@@ -36,3 +36,75 @@
     await func(test, await getDeviceForManualTest());
   }, name, properties);
 }
+
+function manual_usb_serial_test(func, name, properties) {
+  promise_test(async (test) => {
+    const device = await getDeviceForManualTest();
+    await device.open();
+    test.add_cleanup(async () => {
+      if (device.opened) {
+        await device.close();
+      }
+    });
+
+    await device.selectConfiguration(1);
+
+    let controlInterface = undefined;
+    for (const iface of device.configuration.interfaces) {
+      const alternate = iface.alternates[0];
+      if (alternate.interfaceClass == 2 &&
+          alternate.interfaceSubclass == 2 &&
+          alternate.interfaceProtocol == 0) {
+        controlInterface = iface;
+        break;
+      }
+    }
+    assert_not_equals(controlInterface, undefined,
+                      'No control interface found.');
+
+    let dataInterface = undefined;
+    for (const iface of device.configuration.interfaces) {
+      const alternate = iface.alternates[0];
+      if (alternate.interfaceClass == 10 &&
+          alternate.interfaceSubclass == 0 &&
+          alternate.interfaceProtocol == 0) {
+        dataInterface = iface;
+        break;
+      }
+    }
+    assert_not_equals(dataInterface, undefined, 'No data interface found.');
+
+    await device.claimInterface(controlInterface.interfaceNumber);
+    await device.claimInterface(dataInterface.interfaceNumber);
+
+    let inEndpoint = undefined;
+    for (const endpoint of dataInterface.alternate.endpoints) {
+      if (endpoint.type == 'bulk' && endpoint.direction == 'in') {
+        inEndpoint = endpoint;
+        break;
+      }
+    }
+    assert_not_equals(inEndpoint, undefined, 'No IN endpoint found.');
+
+    let outEndpoint = undefined;
+    for (const endpoint of dataInterface.alternate.endpoints) {
+      if (endpoint.type == 'bulk' && endpoint.direction == 'out') {
+        outEndpoint = endpoint;
+        break;
+      }
+    }
+    assert_not_equals(outEndpoint, undefined, 'No OUT endpoint found.');
+
+    // Execute a SET_CONTROL_LINE_STATE command to let the device know the
+    // host is ready to transmit and receive data.
+    await device.controlTransferOut({
+      requestType: 'class',
+      recipient: 'interface',
+      request: 0x22,
+      value: 0x01,
+      index: controlInterface.interfaceNumber,
+    });
+
+    await func(test, device, inEndpoint, outEndpoint);
+  }, name, properties);
+}
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/webusb/usbDevice_transferIn-manual.https.html b/third_party/blink/web_tests/external/wpt/webusb/usbDevice_transferIn-manual.https.html
index bd3df7d5..c0fad37 100644
--- a/third_party/blink/web_tests/external/wpt/webusb/usbDevice_transferIn-manual.https.html
+++ b/third_party/blink/web_tests/external/wpt/webusb/usbDevice_transferIn-manual.https.html
@@ -16,6 +16,7 @@
       <pre>
 void setup() {
   Serial.begin(115200);
+  Serial.setTimeout(0);
   while (!Serial) {
     ;
   }
@@ -23,78 +24,15 @@
 
 void loop() {
   if (Serial.available()) {
-    Serial.write(Serial.read());
+    char buf[1024]; // Greater than the endpoint packet size.
+    int count = Serial.readBytes(buf, sizeof buf);
+    Serial.write(buf, count);
   }
 }
       </pre>
     </p>
     <script>
-      manual_usb_test(async (t, device) => {
-        await device.open();
-        t.add_cleanup(async () => {
-          if (device.opened) {
-            await device.close();
-          }
-        });
-
-        await device.selectConfiguration(1);
-
-        let controlInterface = undefined;
-        for (const iface of device.configuration.interfaces) {
-          const alternate = iface.alternates[0];
-          if (alternate.interfaceClass == 2 &&
-              alternate.interfaceSubclass == 2 &&
-              alternate.interfaceProtocol == 0) {
-            controlInterface = iface;
-            break;
-          }
-        }
-        assert_not_equals(controlInterface, undefined,
-                          'No control interface found.');
-
-        let dataInterface = undefined;
-        for (const iface of device.configuration.interfaces) {
-          const alternate = iface.alternates[0];
-          if (alternate.interfaceClass == 10 &&
-              alternate.interfaceSubclass == 0 &&
-              alternate.interfaceProtocol == 0) {
-            dataInterface = iface;
-            break;
-          }
-        }
-        assert_not_equals(dataInterface, undefined, 'No data interface found.');
-
-        await device.claimInterface(controlInterface.interfaceNumber);
-        await device.claimInterface(dataInterface.interfaceNumber);
-
-        let inEndpoint = undefined;
-        for (const endpoint of dataInterface.alternate.endpoints) {
-          if (endpoint.type == 'bulk' && endpoint.direction == 'in') {
-            inEndpoint = endpoint;
-            break;
-          }
-        }
-        assert_not_equals(inEndpoint, undefined, 'No IN endpoint found.');
-
-        let outEndpoint = undefined;
-        for (const endpoint of dataInterface.alternate.endpoints) {
-          if (endpoint.type == 'bulk' && endpoint.direction == 'out') {
-            outEndpoint = endpoint;
-            break;
-          }
-        }
-        assert_not_equals(outEndpoint, undefined, 'No OUT endpoint found.');
-
-        // Execute a SET_CONTROL_LINE_STATE command to let the device know the
-        // host is ready to transmit and receive data.
-        await device.controlTransferOut({
-          requestType: 'class',
-          recipient: 'interface',
-          request: 0x22,
-          value: 0x01,
-          index: controlInterface.interfaceNumber,
-        });
-
+      manual_usb_serial_test(async (t, device, inEndpoint, outEndpoint) => {
         // Set up two IN transfers which should complete in order.
         const transfer1 =
             device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize);
@@ -111,13 +49,13 @@
         result = await transfer1;
         assert_equals(result.status, 'ok');
         assert_not_equals(result.data, null);
-        assert_equals(result.data.byteLength, 1);
+        assert_equals(result.data.byteLength, 1, 'byteLength');
         assert_equals(result.data.getUint8(0), 'a'.charCodeAt(0));
 
         // Set up a third IN transfer which will be canceled when the device is
         // closed at the end of the test.
         const transfer3 = promise_rejects_dom(
-            t, 'NetworkError',
+            t, 'AbortError',
             device.transferIn(inEndpoint.endpointNumber,
                               inEndpoint.packetSize));
 
@@ -131,12 +69,80 @@
         result = await transfer2;
         assert_equals(result.status, 'ok');
         assert_not_equals(result.data, null);
-        assert_equals(result.data.byteLength, 1);
+        assert_equals(result.data.byteLength, 1, 'byteLength');
         assert_equals(result.data.getUint8(0), 'b'.charCodeAt(0));
 
         await device.close();
         await transfer3;
-      }, 'Multiple IN transfers on an endpoint complete in order');
+      }, 'Multiple small IN transfers on an endpoint complete in order');
+
+      manual_usb_serial_test(async (t, device, inEndpoint, outEndpoint) => {
+        const bufferLength = outEndpoint.packetSize * 20;
+        const parallelRequests = 6;
+
+        // Keep track of the order in which transfers are submitted.
+        let enqueueSequence = 0;
+        let dequeueSequence = 0;
+        const received = new Uint8Array(bufferLength);
+        let receivedOffset = 0;
+        let done = false;
+        const transfers = [];
+
+        async function readNext(sequence) {
+          let result;
+          try {
+            result = await device.transferIn(inEndpoint.endpointNumber,
+                                             inEndpoint.packetSize);
+          } catch (e) {
+            // The last few transfers will fail when the device is closed.
+            assert_true(done);
+            assert_equals(dequeueSequence++, sequence, 'dequeueSequence done');
+            assert_equals(receivedOffset, bufferLength, 'receivedOffset');
+            assert_equals(e.name, 'AbortError');
+            return;
+          }
+
+          assert_equals(dequeueSequence++, sequence, 'dequeueSequence');
+          assert_equals(result.status, 'ok');
+          assert_not_equals(result.data, null);
+
+          const data = new Uint8Array(
+              result.data.buffer, result.data.byteOffset,
+              result.data.byteLength);
+          received.set(data, receivedOffset);
+          receivedOffset += result.data.byteLength;
+
+          // Check |done| because there might be zero-length packet completions
+          // after the data has been completely received.
+          if (!done) {
+            if (receivedOffset == bufferLength) {
+              done = true;
+              assert_array_equals(received, buffer);
+              await device.close();
+            } else {
+              await readNext(enqueueSequence++);
+            }
+          }
+        }
+
+        for (let i = 0; i < parallelRequests; ++i) {
+          transfers.push(readNext(enqueueSequence++));
+        }
+
+        // Write a large buffer to the device which will be split up into
+        // smaller packets when echoed back.
+        const buffer = new Uint8Array(bufferLength);
+        for (let i = 0; i < buffer.byteLength; ++i) {
+          buffer[i] = i;
+        }
+        let result = await device.transferOut(
+            outEndpoint.endpointNumber, buffer);
+        assert_equals(result.status, 'ok');
+        assert_equals(result.bytesWritten, buffer.byteLength);
+
+        await Promise.all(transfers);
+        assert_equals(dequeueSequence, enqueueSequence);
+      }, 'Multiple large IN transfers on an endpoint complete in order');
     </script>
   </body>
 </html>
diff --git a/third_party/blink/web_tests/http/tests/devtools/components/utilities-expected.txt b/third_party/blink/web_tests/http/tests/devtools/components/utilities-expected.txt
index 6728aff1..7bc90f76 100644
--- a/third_party/blink/web_tests/http/tests/devtools/components/utilities-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/components/utilities-expected.txt
@@ -3,8 +3,6 @@
 
 Running: orderedMergeIntersect
 
-Running: binaryIndexOfTest
-
 Running: lowerBoundTest
 
 Running: upperBoundTest
diff --git a/third_party/blink/web_tests/http/tests/devtools/components/utilities.js b/third_party/blink/web_tests/http/tests/devtools/components/utilities.js
index d9316da..cfc5a83 100644
--- a/third_party/blink/web_tests/http/tests/devtools/components/utilities.js
+++ b/third_party/blink/web_tests/http/tests/devtools/components/utilities.js
@@ -44,29 +44,6 @@
       next();
     },
 
-    function binaryIndexOfTest(next) {
-      var testArrays = [
-        [], [1], [1, 10], [1, 10, 11, 12, 13, 14, 100], [-100, -50, 0, 50, 100], [-100, -14, -13, -12, -11, -10, -1]
-      ];
-
-      function testArray(array) {
-        function comparator(a, b) {
-          return a < b ? -1 : (a > b ? 1 : 0);
-        }
-
-        for (var i = -100; i <= 100; ++i) {
-          var reference = array.indexOf(i);
-          var actual = array.binaryIndexOf(i, comparator);
-          TestRunner.assertEquals(reference, actual, 'binaryIndexOf');
-        }
-        return true;
-      }
-
-      for (var i = 0, l = testArrays.length; i < l; ++i)
-        testArray(testArrays[i]);
-      next();
-    },
-
     function lowerBoundTest(next) {
       var testArrays = [[], [1], [-1, -1, 0, 0, 0, 0, 2, 3, 4, 4, 4, 7, 9, 9, 9]];
 
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/gamepad/idlharness-extensions.window-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/gamepad/idlharness-extensions.window-expected.txt
new file mode 100644
index 0000000..8f261de
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/gamepad/idlharness-extensions.window-expected.txt
@@ -0,0 +1,32 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface Gamepad: original interface defined
+PASS Partial interface Gamepad: member names are unique
+PASS GamepadHapticActuator interface: existence and properties of interface object
+PASS GamepadHapticActuator interface object length
+PASS GamepadHapticActuator interface object name
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object's "constructor" property
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object's @@unscopables property
+PASS GamepadHapticActuator interface: attribute type
+FAIL GamepadHapticActuator interface: operation pulse(double, double) assert_own_property: interface prototype object missing non-static operation expected property "pulse" missing
+FAIL GamepadPose interface: existence and properties of interface object assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface object length assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface object name assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute hasOrientation assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute hasPosition assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute position assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute linearVelocity assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute linearAcceleration assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute orientation assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute angularVelocity assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute angularAcceleration assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL Gamepad interface: attribute hand assert_true: The prototype object must have a property "hand" expected true got false
+FAIL Gamepad interface: attribute hapticActuators assert_true: The prototype object must have a property "hapticActuators" expected true got false
+FAIL Gamepad interface: attribute pose assert_true: The prototype object must have a property "pose" expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/restrict-gamepad/external/wpt/gamepad/idlharness-extensions.window-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/restrict-gamepad/external/wpt/gamepad/idlharness-extensions.window-expected.txt
new file mode 100644
index 0000000..8f261de
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/restrict-gamepad/external/wpt/gamepad/idlharness-extensions.window-expected.txt
@@ -0,0 +1,32 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface Gamepad: original interface defined
+PASS Partial interface Gamepad: member names are unique
+PASS GamepadHapticActuator interface: existence and properties of interface object
+PASS GamepadHapticActuator interface object length
+PASS GamepadHapticActuator interface object name
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object's "constructor" property
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object's @@unscopables property
+PASS GamepadHapticActuator interface: attribute type
+FAIL GamepadHapticActuator interface: operation pulse(double, double) assert_own_property: interface prototype object missing non-static operation expected property "pulse" missing
+FAIL GamepadPose interface: existence and properties of interface object assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface object length assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface object name assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute hasOrientation assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute hasPosition assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute position assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute linearVelocity assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute linearAcceleration assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute orientation assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute angularVelocity assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute angularAcceleration assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL Gamepad interface: attribute hand assert_true: The prototype object must have a property "hand" expected true got false
+FAIL Gamepad interface: attribute hapticActuators assert_true: The prototype object must have a property "hapticActuators" expected true got false
+FAIL Gamepad interface: attribute pose assert_true: The prototype object must have a property "pose" expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/third_party/OWNERS b/third_party/blink/web_tests/third_party/OWNERS
new file mode 100644
index 0000000..db08449
--- /dev/null
+++ b/third_party/blink/web_tests/third_party/OWNERS
@@ -0,0 +1,2 @@
+file://third_party/test_fonts/OWNERS
+
diff --git a/third_party/blink/web_tests/virtual/restrict-gamepad/external/wpt/gamepad/idlharness-extensions.window-expected.txt b/third_party/blink/web_tests/virtual/restrict-gamepad/external/wpt/gamepad/idlharness-extensions.window-expected.txt
new file mode 100644
index 0000000..2add0e7
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/restrict-gamepad/external/wpt/gamepad/idlharness-extensions.window-expected.txt
@@ -0,0 +1,43 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface Gamepad: original interface defined
+PASS Partial interface Gamepad: member names are unique
+PASS GamepadHapticActuator interface: existence and properties of interface object
+PASS GamepadHapticActuator interface object length
+PASS GamepadHapticActuator interface object name
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object's "constructor" property
+PASS GamepadHapticActuator interface: existence and properties of interface prototype object's @@unscopables property
+PASS GamepadHapticActuator interface: attribute type
+FAIL GamepadHapticActuator interface: operation pulse(double, double) assert_own_property: interface prototype object missing non-static operation expected property "pulse" missing
+FAIL GamepadPose interface: existence and properties of interface object assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface object length assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface object name assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute hasOrientation assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute hasPosition assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute position assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute linearVelocity assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute linearAcceleration assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute orientation assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute angularVelocity assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadPose interface: attribute angularAcceleration assert_own_property: self does not have own property "GamepadPose" expected property "GamepadPose" missing
+FAIL GamepadTouch interface: existence and properties of interface object assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface object length assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface object name assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: existence and properties of interface prototype object assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: attribute touchId assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: attribute surfaceId assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: attribute position assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL GamepadTouch interface: attribute surfaceDimensions assert_own_property: self does not have own property "GamepadTouch" expected property "GamepadTouch" missing
+FAIL Gamepad interface: attribute hand assert_true: The prototype object must have a property "hand" expected true got false
+FAIL Gamepad interface: attribute hapticActuators assert_true: The prototype object must have a property "hapticActuators" expected true got false
+FAIL Gamepad interface: attribute pose assert_true: The prototype object must have a property "pose" expected true got false
+FAIL Gamepad interface: attribute touchEvents assert_true: The prototype object must have a property "touchEvents" expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/v8-off-thread-finalization/README.txt b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/README.txt
new file mode 100644
index 0000000..2ecd845
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/README.txt
@@ -0,0 +1,2 @@
+Virtual test suite for testing background finalization and streaming
+of small scripts.
diff --git a/third_party/blink/web_tests/virtual/v8-off-thread-finalization/external/wpt/html/semantics/scripting-1/README.txt b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/external/wpt/html/semantics/scripting-1/README.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/external/wpt/html/semantics/scripting-1/README.txt
diff --git a/third_party/blink/web_tests/virtual/v8-off-thread-finalization/fast/dom/README.txt b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/fast/dom/README.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/fast/dom/README.txt
diff --git a/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/README.txt b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/README.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/README.txt
diff --git a/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/same-origin-module-test-expected.txt b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/same-origin-module-test-expected.txt
new file mode 100644
index 0000000..6d08c710
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/same-origin-module-test-expected.txt
@@ -0,0 +1,111 @@
+Tests V8 code cache for javascript resources
+
+--- Trace events related to code caches ------
+v8.compileModule Properties:
+{
+    data : {
+        columnNumber : 1
+        lineNumber : 1
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compileModule"
+}
+Text details for v8.compileModule: v8-cache-script.cgi
+v8.compileModule Properties:
+{
+    data : {
+        columnNumber : 1
+        lineNumber : 1
+        notStreamedReason : "already used streamed data"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compileModule"
+}
+Text details for v8.compileModule: v8-cache-script.cgi
+v8.compileModule Properties:
+{
+    data : {
+        columnNumber : 1
+        lineNumber : 1
+        notStreamedReason : "module script"
+        producedCacheSize : <number>
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compileModule"
+}
+Text details for v8.compileModule: v8-cache-script.cgi
+v8.compileModule Properties:
+{
+    data : {
+        cacheConsumeOptions : "code"
+        cacheRejected : false
+        columnNumber : 1
+        consumedCacheSize : <number>
+        lineNumber : 1
+        notStreamedReason : "already used streamed data"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compileModule"
+}
+Text details for v8.compileModule: v8-cache-script.cgi
+v8.compileModule Properties:
+{
+    data : {
+        columnNumber : 1
+        lineNumber : 1
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compileModule"
+}
+Text details for v8.compileModule: v8-cache-script.cgi
+v8.compileModule Properties:
+{
+    data : {
+        cacheConsumeOptions : "code"
+        cacheRejected : false
+        columnNumber : 1
+        consumedCacheSize : <number>
+        lineNumber : 1
+        notStreamedReason : "already used streamed data"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compileModule"
+}
+Text details for v8.compileModule: v8-cache-script.cgi
+v8.compileModule Properties:
+{
+    data : {
+        cacheConsumeOptions : "code"
+        cacheRejected : false
+        columnNumber : 1
+        consumedCacheSize : <number>
+        lineNumber : 1
+        notStreamedReason : "script has code-cache available"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compileModule"
+}
+Text details for v8.compileModule: v8-cache-script.cgi
+-----------------------------------------------
+
diff --git a/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/same-origin-test-expected.txt b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/same-origin-test-expected.txt
new file mode 100644
index 0000000..c906a4c
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/same-origin-test-expected.txt
@@ -0,0 +1,118 @@
+Tests V8 code cache for javascript resources
+
+--- Trace events related to code caches ------
+Load [A] 1st time. Produce timestamp. -->
+v8.compile Properties:
+{
+    data : {
+        columnNumber : 0
+        lineNumber : 0
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-script.cgi:1
+Load [A] 2nd time. Produce code cache. -->
+v8.compile Properties:
+{
+    data : {
+        columnNumber : 0
+        lineNumber : 0
+        notStreamedReason : "already used streamed data"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-script.cgi:1
+v8.compile Properties:
+{
+    data : {
+        columnNumber : 0
+        lineNumber : 0
+        notStreamedReason : "already used streamed data"
+        producedCacheSize : <number>
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-script.cgi:1
+Load [A] 3rd time. Consume code cache. -->
+v8.compile Properties:
+{
+    data : {
+        cacheConsumeOptions : "code"
+        cacheRejected : false
+        columnNumber : 0
+        consumedCacheSize : <number>
+        lineNumber : 0
+        notStreamedReason : "already used streamed data"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-script.cgi:1
+Load [B]. Should not use the cached code. -->
+v8.compile Properties:
+{
+    data : {
+        columnNumber : 0
+        lineNumber : 0
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-script.cgi:1
+Load [A] again from MemoryCache. Should use the cached code. -->
+v8.compile Properties:
+{
+    data : {
+        cacheConsumeOptions : "code"
+        cacheRejected : false
+        columnNumber : 0
+        consumedCacheSize : <number>
+        lineNumber : 0
+        notStreamedReason : "already used streamed data"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-script.cgi:1
+Clear [A] from MemoryCache. -->
+Load [A] from Disk Cache. -->
+v8.compile Properties:
+{
+    data : {
+        cacheConsumeOptions : "code"
+        cacheRejected : false
+        columnNumber : 0
+        consumedCacheSize : <number>
+        lineNumber : 0
+        notStreamedReason : "script has code-cache available"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-script.cgi:1
+-----------------------------------------------
+
diff --git a/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/stale-revalidation-test-expected.txt b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/stale-revalidation-test-expected.txt
new file mode 100644
index 0000000..dd6b1f3
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/v8-off-thread-finalization/http/tests/devtools/isolated-code-cache/stale-revalidation-test-expected.txt
@@ -0,0 +1,82 @@
+Tests V8 code cache for resources revalidated with 304.
+
+--- Begin trace events related to code cache. ------
+1st load. Produce timestamp. -->
+v8.compile Properties:
+{
+    data : {
+        columnNumber : 0
+        lineNumber : 0
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-revalidated-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-revalidated-script.cgi:1
+2nd load. Produce code cache. -->
+v8.compile Properties:
+{
+    data : {
+        columnNumber : 0
+        lineNumber : 0
+        notStreamedReason : "already used streamed data"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-revalidated-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-revalidated-script.cgi:1
+v8.compile Properties:
+{
+    data : {
+        columnNumber : 0
+        lineNumber : 0
+        notStreamedReason : "already used streamed data"
+        producedCacheSize : <number>
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-revalidated-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-revalidated-script.cgi:1
+3rd load. Consume code cache. -->
+v8.compile Properties:
+{
+    data : {
+        cacheConsumeOptions : "code"
+        cacheRejected : false
+        columnNumber : 0
+        consumedCacheSize : <number>
+        lineNumber : 0
+        notStreamedReason : "already used streamed data"
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-revalidated-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-revalidated-script.cgi:1
+Clear the resource from the MemoryCache. -->
+Load the resource with revalidation. The code cache got dropped because its timestamp did not match the one of the request. -->
+v8.compile Properties:
+{
+    data : {
+        columnNumber : 0
+        lineNumber : 0
+        streamed : <boolean>
+        url : .../devtools/resources/v8-cache-revalidated-script.cgi
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.compile"
+}
+Text details for v8.compile: v8-cache-revalidated-script.cgi:1
+----- End trace events related to code cache. ------
+
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 66e6316..2bcb0b9 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -21099,7 +21099,8 @@
   <owner>andzaytsev@google.com</owner>
   <owner>msramek@chromium.org</owner>
   <description>
-    User clicks the manage passwords button in safety check.
+    User clicks the manage passwords button in safety check when compromised
+    passwords have been found.
   </description>
 </action>
 
@@ -21126,6 +21127,15 @@
   <description>User clicks the safe browsing row in safety check.</description>
 </action>
 
+<action name="Settings.SafetyCheck.ManageWeakPasswords">
+  <owner>rainhard@chromium.org</owner>
+  <owner>msramek@chromium.org</owner>
+  <description>
+    User clicks the manage passwords button in safety check when weak passwords
+    have been found.
+  </description>
+</action>
+
 <action name="Settings.SafetyCheck.RelaunchAfterUpdates">
   <owner>rainhard@chromium.org</owner>
   <owner>andzaytsev@google.com</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index bd1c8c4..6007237 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -4749,6 +4749,12 @@
   <int value="3" label="Initially incomplete &amp; Failure"/>
 </enum>
 
+<enum name="AutofillAwGSuggestionAvailability">
+  <int value="0" label="No suggestion"/>
+  <int value="1" label="Has suggestion, user doesn't select it"/>
+  <int value="2" label="Has suggestion, user selects it"/>
+</enum>
+
 <enum name="AutofillCardholderNameFixFlowPromptEvent">
   <int value="0" label="Shown"/>
   <int value="1" label="Accepted"/>
@@ -5800,6 +5806,12 @@
   <int value="99" label="NOT_PASSWORD"/>
 </enum>
 
+<enum name="AutofillServerPredictionAvailability">
+  <int value="0" label="Not available"/>
+  <int value="1" label="Available on session start"/>
+  <int value="2" label="Available after session start"/>
+</enum>
+
 <enum name="AutofillSessionStates">
   <int value="0" label="Unknown"/>
   <int value="1" label="No callback from framework"/>
@@ -43636,6 +43648,8 @@
   <int value="-854716639" label="TranslateAndroidManualTrigger:enabled"/>
   <int value="-854519621" label="PointerLockOptions:disabled"/>
   <int value="-853594220" label="disable-new-avatar-menu"/>
+  <int value="-853455968"
+      label="OmniboxDefaultTypedNavigationsToHttps:disabled"/>
   <int value="-850821337" label="WebContentsForceDark:enabled"/>
   <int value="-848691867" label="DesktopPWAWindowing:enabled"/>
   <int value="-847216521" label="ChromeDuplex:enabled"/>
@@ -46712,6 +46726,8 @@
   <int value="2099945365" label="OmniboxAssistantVoiceSearch:enabled"/>
   <int value="2101151142" label="disable-direct-write"/>
   <int value="2104340988" label="CrostiniUsername:disabled"/>
+  <int value="2104439359"
+      label="OmniboxDefaultTypedNavigationsToHttps:enabled"/>
   <int value="2104788328" label="use-winrt-midi-api"/>
   <int value="2106798283" label="SafetyTip:disabled"/>
   <int value="2106855416" label="HostWindowsInAppShimProcess:enabled"/>
diff --git a/tools/metrics/histograms/histograms_xml/autofill/histograms.xml b/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
index 6749739..3fe54b4 100644
--- a/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/autofill/histograms.xml
@@ -2019,6 +2019,17 @@
   <summary>Records the state of an autofill session.</summary>
 </histogram>
 
+<histogram name="Autofill.WebView.AwGIsCurrentService" enum="BooleanYesNo"
+    expires_after="2021-06-08">
+  <owner>michaelbai@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records whether Autofill with Google is the current Android autofill
+    service. It is recorded on AutofillProvider initialization. Only recorded in
+    Android P and beyond.
+  </summary>
+</histogram>
+
 <histogram name="Autofill.WebView.CreatedByActivityContext"
     enum="BooleanEnabled" expires_after="2021-06-08">
   <owner>michaelbai@chromium.org</owner>
@@ -2035,6 +2046,42 @@
   </summary>
 </histogram>
 
+<histogram name="Autofill.WebView.ServerPrediction.AwGSuggestionAvailability"
+    enum="AutofillAwGSuggestionAvailability" expires_after="2021-06-08">
+  <owner>michaelbai@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records whether Autofill with Google has suggestions and if the user selects
+    any suggestion. It is recorded when the form is submitted. Only recorded in
+    Android P and beyond.
+  </summary>
+</histogram>
+
+<histogram name="Autofill.WebView.ServerPredicton.HasValidServerPrediction"
+    enum="BooleanYesNo" expires_after="2021-06-08">
+  <owner>michaelbai@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records whether the server prediction of any field isn't NO_SERVER_DATA.
+    This histogram is recorded when the server prediction is available for the
+    current form, and only if AndroidAutofillQueryServerFieldTypes feature is
+    enabled.
+  </summary>
+</histogram>
+
+<histogram name="Autofill.WebView.ServerPredicton.PredictionAvailability"
+    enum="AutofillServerPredictionAvailability" expires_after="2021-06-08">
+  <owner>michaelbai@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records whether and when the server prediction response of current autofill
+    session is available even if query failed or there is no server data. This
+    histogram is recorded when the prediction becomes available or the new
+    session starts, and only if AndroidAutofillQueryServerFieldTypes feature is
+    enabled.
+  </summary>
+</histogram>
+
 <histogram name="Autofill.WebView.SubmissionSource"
     enum="AutofillSubmissionSource" expires_after="2021-06-08">
   <owner>michaelbai@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/file/histograms.xml b/tools/metrics/histograms/histograms_xml/file/histograms.xml
index e9341a3..0a04f794b 100644
--- a/tools/metrics/histograms/histograms_xml/file/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/file/histograms.xml
@@ -22,9 +22,9 @@
 <histograms>
 
 <histogram name="FileBrowser.ChangeDirectory.RootType"
-    enum="FileManagerRootType" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerRootType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: Counts the number of directory-changed events,
     bucketed by the RootType of the directory newly displayed.
@@ -32,9 +32,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ComputersCount" units="Computers"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: number of Computers a user has available in the
     Files app. Computed every time the File Browser is opened (including file
@@ -43,15 +43,17 @@
   </summary>
 </histogram>
 
-<histogram name="FileBrowser.Create" enum="FileDialogType" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
-  <summary>Chrome OS File Browser opening mode.</summary>
+<histogram name="FileBrowser.Create" enum="FileDialogType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
+  <summary>
+    Chrome OS File Browser: The mode in which the File Browser was opened.
+  </summary>
 </histogram>
 
-<histogram name="FileBrowser.DirectoryScan" units="ms" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+<histogram name="FileBrowser.DirectoryScan" units="ms" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: time to scan a directory. Measured on every File
     Browser directory change.
@@ -59,9 +61,9 @@
 </histogram>
 
 <histogram name="FileBrowser.DownloadDestination.IsGoogleDrive.Changed"
-    enum="BooleanEnabled" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="BooleanEnabled" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Tracks whether download destination is set to a Google Drive folder when the
     download destination is changed by the user in the settings page.
@@ -69,9 +71,9 @@
 </histogram>
 
 <histogram name="FileBrowser.DownloadDestination.IsGoogleDrive.Started"
-    enum="BooleanEnabled" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="BooleanEnabled" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Tracks whether download destination is set to a Google Drive folder on
     startup.
@@ -79,29 +81,30 @@
 </histogram>
 
 <histogram name="FileBrowser.Downloads.DirectoryPercentageOfDiskUsage"
-    units="%" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    units="%" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     The size of the space consumed by a users files in &quot;My Files&quot;
     folder and it's children, calculated as a percentage of the total disk
-    space. Caluclated on user login.
+    space. Caluclated on user login and recorded by VolumeManager.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.Downloads.DirectorySizeMiB" units="MiB"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     The total size of all of the users files stored in the &quot;My Files&quot;
-    folder and it's children. Caluclated on user login.
+    folder and it's children. Caluclated on user login and recorded by
+    VolumeManager.
   </summary>
 </histogram>
 
-<histogram name="FileBrowser.DownloadsCount" units="units" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+<histogram name="FileBrowser.DownloadsCount" units="units" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: number of files and directories in the Downloads
     directory (not including the contents of nested directories). Computed every
@@ -110,9 +113,9 @@
 </histogram>
 
 <histogram name="FileBrowser.DriveDuplicateFinder.LongComputeHash" units="ms"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     The time taken to calculate the hash of a file, only recorded if the time
     exceeds a local threshold that is currenty 5 seconds.
@@ -120,9 +123,9 @@
 </histogram>
 
 <histogram name="FileBrowser.DriveDuplicateFinder.LongSearchByHash" units="ms"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     The time taken to search for a file using it's hash value, only recorded if
     the time exceeds a local threshold that is currently 1 second.
@@ -144,9 +147,9 @@
 </histogram>
 
 <histogram name="FileBrowser.FileSystemProviderMounted"
-    enum="FileSystemProviderMountType" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileSystemProviderMountType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     The type of file system provider that has been mounted. This metric is
     emmitted on mounting of the filesystem.
@@ -154,9 +157,9 @@
 </histogram>
 
 <histogram name="FileBrowser.FolderShortcut.Add" units="units"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: this is recorded when the user adds a folder
     shortcut.
@@ -164,9 +167,9 @@
 </histogram>
 
 <histogram name="FileBrowser.FolderShortcut.Count" units="units"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: number of saved folder shorcuts. This is recorded
     when the Files app is launched.
@@ -174,9 +177,9 @@
 </histogram>
 
 <histogram name="FileBrowser.FolderShortcut.Navigate" units="units"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: this is recorded when the user clicks or selects a
     folder shortcut and is navigated to the target folder.
@@ -184,9 +187,9 @@
 </histogram>
 
 <histogram name="FileBrowser.FolderShortcut.Remove" units="units"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: this is recorded when the user removes a folder
     shortcut.
@@ -217,8 +220,8 @@
 
 <histogram name="FileBrowser.ImportController.DeviceYanked" enum="Boolean"
     expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Files App: Whether an external media device was removed during the
     upload process.
@@ -227,17 +230,17 @@
 
 <histogram name="FileBrowser.ImportController.ImportCancelled"
     enum="BooleanCanceled" expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Files App: Whether the media import process was cancelled.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.LauncherSearch.Drive" units="ms"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     The time taken to execute launcher search for drive files. Recorded when the
     complete result set is returned from drive.
@@ -245,9 +248,9 @@
 </histogram>
 
 <histogram name="FileBrowser.LauncherSearch.Local" units="ms"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     The time taken to execute launcher search for local files. Recorded when the
     complete result set has been calculated for files on the local disk.
@@ -255,9 +258,9 @@
 </histogram>
 
 <histogram name="FileBrowser.Load{FileBrowserLoad}" units="ms"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser is an built-in extension without a background page.
     Its main.html file is loaded every time the user opens a File Browser tab or
@@ -322,9 +325,9 @@
 </histogram>
 
 <histogram name="FileBrowser.Location.OnEntryExpandedOrCollapsed.NonTopLevel"
-    enum="FileManagerRootType" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerRootType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Files App: The locations (root types) of non-top-level entries
     when they are expanded or collapsed (expand icon clicked) in the directory
@@ -333,9 +336,9 @@
 </histogram>
 
 <histogram name="FileBrowser.Location.OnEntryExpandedOrCollapsed.TopLevel"
-    enum="FileManagerRootType" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerRootType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Files App: The locations (root types) of top-level entries (root
     entries) when they are expanded or collapsed (expand icon clicked) in the
@@ -344,22 +347,24 @@
 </histogram>
 
 <histogram name="FileBrowser.Location.OnEntrySelected.NonTopLevel"
-    enum="FileManagerRootType" expires_after="M79">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerRootType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Files App: The locations (root types) of non-top-level entries
-    when they are clicked in the directory tree.
+    when they are clicked in the directory tree. Contains incomplete data from
+    M80 to M87 inclusively.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.Location.OnEntrySelected.TopLevel"
-    enum="FileManagerRootType" expires_after="M79">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerRootType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Files App: The locations (root types) of top-level entries (root
-    entries) when they are clicked in the directory tree.
+    entries) when they are clicked in the directory tree. Contains incomplete
+    data from M80 to M87 inclusively.
   </summary>
 </histogram>
 
@@ -423,36 +428,36 @@
 </histogram>
 
 <histogram name="FileBrowser.MenuItemSelected" enum="FileManagerMenuCommands"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Files App: The commands selected in the menu by the files app.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.Notification.Show"
-    enum="FileManagerNotificationType" expires_after="M92">
-  <owner>fukino@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerNotificationType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: Notification types what were shown to the user.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.Notification.UserAction"
-    enum="FileManagerNotificationUserAction" expires_after="M92">
-  <owner>fukino@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerNotificationUserAction" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: User actions responding to a notification.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.OpenFiles.RootType" enum="FileManagerRootType"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: The locations (root types) of files which are opened
     by the file picker.
@@ -460,9 +465,9 @@
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.DisplayTime" units="ms"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Photo Editor: time to display an image. Measured from the moment
     the user selected the image till the moment it is displayed (not counting
@@ -471,46 +476,46 @@
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.FileType" enum="PhotoEditorFileType"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>Chrome OS Photo Editor: the type of the file opened.</summary>
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.LoadMode" enum="PhotoEditorLoadMode"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>Chrome OS Photo Editor: the way the image has been loaded.</summary>
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.LoadTime" units="ms"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>Chrome OS Photo Editor: time to load an image from a file.</summary>
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.SaveResult"
-    enum="PhotoEditorSaveResult" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="PhotoEditorSaveResult" expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Photo Editor: the result of a file save operation.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.SaveTime" units="ms"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>Chrome OS Photo Editor: time to save an image to a file.</summary>
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.Size.MB" units="MBytes"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Photo Editor: size of an image file in megabytes. Measured on
     every image load.
@@ -518,9 +523,9 @@
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.Size.MPix" units="MPixels"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Photo Editor: size of an image in megapixels. Measured on every
     image load.
@@ -528,53 +533,53 @@
 </histogram>
 
 <histogram name="FileBrowser.PhotoEditor.Tool" enum="PhotoEditorToolType"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>Chrome OS Photo Editor: the button which the user clicked.</summary>
 </histogram>
 
 <histogram name="FileBrowser.QuickView.DialogType" enum="FileDialogType"
-    expires_after="M90">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     File dialog type (e.g. Full page, Save as file) when quick view is launched.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.QuickView.FileType" enum="ViewFileType"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>File types that were tried to be opened with quick view.</summary>
 </histogram>
 
 <histogram name="FileBrowser.QuickView.FileTypeOnLaunch" enum="ViewFileType"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>File types that were selected when quick view is launched.</summary>
 </histogram>
 
 <histogram name="FileBrowser.QuickView.VolumeType" enum="FileManagerVolumeType"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>The volume type where quick view is opened.</summary>
 </histogram>
 
 <histogram name="FileBrowser.QuickView.WayToOpen"
-    enum="FileManagerQuickViewWayToOpen" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerQuickViewWayToOpen" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>How quick view was opened.</summary>
 </histogram>
 
 <histogram name="FileBrowser.Recent.LoadArcMedia" units="ms"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Time to load a recently modified file list from Media Views. It is triggered
     when the user opens or reloads Recent view in the Files app.
@@ -582,9 +587,9 @@
 </histogram>
 
 <histogram name="FileBrowser.Recent.LoadCrostini" units="ms"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Time to load a recently modified file list from Crostini. It is triggered
     when the user opens or reloads Recent view in the Files app.
@@ -592,27 +597,27 @@
 </histogram>
 
 <histogram name="FileBrowser.Recent.LoadDownloads" units="ms"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Time to load a recently modified file list from Downloads. It is triggered
     when the user opens or reloads Recent view in the Files app.
   </summary>
 </histogram>
 
-<histogram name="FileBrowser.Recent.LoadDrive" units="ms" expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+<histogram name="FileBrowser.Recent.LoadDrive" units="ms" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Time to load a recently modified file list from Drive. It is triggered when
     the user opens or reloads Recent view in the Files app.
   </summary>
 </histogram>
 
-<histogram name="FileBrowser.Recent.LoadTotal" units="ms" expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+<histogram name="FileBrowser.Recent.LoadTotal" units="ms" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Time to load a recently modified file list from all sources. It is triggered
     when the user opens or reloads Recent view in the Files app.
@@ -647,18 +652,18 @@
 </histogram>
 
 <histogram name="FileBrowser.SuggestApps.CloseDialog"
-    enum="SuggestAppsDialogCloseReason" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="SuggestAppsDialogCloseReason" expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: the reason why the suggest apps dialog was closed.
   </summary>
 </histogram>
 
 <histogram name="FileBrowser.SuggestApps.Install"
-    enum="SuggestAppsDialogInstall" expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="SuggestAppsDialogInstall" expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: whether the Webstore item user selected was
     successfully installed or not.
@@ -666,9 +671,9 @@
 </histogram>
 
 <histogram name="FileBrowser.SuggestApps.Load" enum="SuggestAppsDialogLoad"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: whether the initialization of the dialog succeeded
     or not.
@@ -676,9 +681,9 @@
 </histogram>
 
 <histogram name="FileBrowser.SuggestApps.LoadTime" units="ms"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M92">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: time to load the suggest apps dialog. Measured
     between the moment window appears and the moment all the contents in the
@@ -687,9 +692,9 @@
 </histogram>
 
 <histogram name="FileBrowser.TeamDrivesCount" units="Team Drives"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: number of Team Drives a user has available in the
     Files app. Computed every time the File Browser is opened (including file
@@ -699,9 +704,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ToggleFileListType" enum="FileManagerListType"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS Files App: Recorded when the Grid View/List View toggle menu icon
     is selected.
@@ -709,9 +714,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingFileType" enum="ViewFileType"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     File types that were tried to be viewed through browser. This is recorded
     when the user tries to view a file from the Files app.
@@ -719,9 +724,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingFileType.Offline" enum="ViewFileType"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     File types that were tried to be viewed through browser while the user is
     offline. This is recorded when the user tries to view a file from the Files
@@ -730,9 +735,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingFileType.Online" enum="ViewFileType"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     File types that were tried to be viewed through browser while the user is
     online. This is recorded when the user tries to view a file from the Files
@@ -741,9 +746,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingRootType" enum="FileManagerRootType"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: The locations (root types) of files which are opened
     in stand-alone mode. This does not include files opened in file picker mode.
@@ -751,9 +756,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingRootType.Offline"
-    enum="FileManagerRootType" expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerRootType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: The locations (root types) of files which are opened
     in stand-alone mode while the user is offline. This does not include files
@@ -762,9 +767,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingRootType.Online" enum="FileManagerRootType"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: The locations (root types) of files which are opened
     in stand-alone mode while the user is online. This does not include files
@@ -773,9 +778,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingTaskType" enum="FileManagerTaskType"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: The type of the handler to be used to open files.
     This is recorded when the user tries to view a file from the Files app.
@@ -783,9 +788,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingTaskType.Offline"
-    enum="FileManagerTaskType" expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    enum="FileManagerTaskType" expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: The type of the handler to be used to open files
     when the user is offline. This is recorded when the user tries to view a
@@ -794,9 +799,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ViewingTaskType.Online" enum="FileManagerTaskType"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: The type of the handler to be used to open files
     when the user is online. This is recorded when the user tries to view a file
@@ -805,9 +810,9 @@
 </histogram>
 
 <histogram name="FileBrowser.VolumeType" enum="FileManagerVolumeType"
-    expires_after="M92">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: counts the number of times volumes are mounted for
     each volume type.
@@ -815,9 +820,9 @@
 </histogram>
 
 <histogram name="FileBrowser.ZipFileTask" enum="FileManagerZipHandlerType"
-    expires_after="M89">
-  <owner>slangley@chromium.org</owner>
-  <owner>weifangsun@chromium.org</owner>
+    expires_after="M98">
+  <owner>simmonsjosh@google.com</owner>
+  <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
     Chrome OS File Browser: counts the number of times ZIP file was opened or
     created, categorized by component extensions and its operation types.
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index c491856e..f5ccc08 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -12016,7 +12016,12 @@
   <suffix name="Family-link-notice.Done" label=""/>
   <suffix name="Fingerprint-setup.Next" label=""/>
   <suffix name="Gaia-signin.Back" label=""/>
-  <suffix name="Gaia-signin.CloseDialog" label=""/>
+  <suffix name="Gaia-signin.Cancel" label=""/>
+  <suffix name="Gaia-signin.CloseDialog" label="">
+    <obsolete>
+      Removed in M89.
+    </obsolete>
+  </suffix>
   <suffix name="Gaia-signin.EnterpriseEnroll" label=""/>
   <suffix name="Gaia-signin.StartConsumerKiosk" label=""/>
   <suffix name="Gesture-navigation.Next" label=""/>
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 9219e87..5625c03 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -12676,6 +12676,27 @@
   <summary>Number of unread entries in reading list.</summary>
 </histogram>
 
+<histogram name="ReadingList.WebUI.LoadCompletedTime" units="ms"
+    expires_after="M92">
+  <owner>corising@chromium.org</owner>
+  <owner>chrome-desktop-ui-sea@google.com</owner>
+  <summary>
+    The amount of time between the render frame host StartProvisionalLoad event
+    and the render frame DocumentOnLoadCompleted event for the Read Later WebUI
+    page.
+  </summary>
+</histogram>
+
+<histogram name="ReadingList.WebUI.LoadDocumentTime" units="ms"
+    expires_after="M92">
+  <owner>corising@chromium.org</owner>
+  <owner>chrome-desktop-ui-sea@google.com</owner>
+  <summary>
+    The amount of time between the render frame host StartProvisionalLoad and
+    DidFinishDocumentLoad events for the Read Later WebUI page.
+  </summary>
+</histogram>
+
 <histogram name="RecoveryComponent.Event" enum="RecoveryComponentEvent"
     expires_after="M77">
   <owner>robertshield@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/service/histograms.xml b/tools/metrics/histograms/histograms_xml/service/histograms.xml
index 2078bc6..b3d6db30 100644
--- a/tools/metrics/histograms/histograms_xml/service/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/service/histograms.xml
@@ -371,6 +371,9 @@
 
 <histogram name="ServiceWorker.GetAllOriginsInfoTime" units="ms"
     expires_after="2021-01-24">
+  <obsolete>
+    Removed 2021-01-12.
+  </obsolete>
   <owner>bashi@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/settings/histograms.xml b/tools/metrics/histograms/histograms_xml/settings/histograms.xml
index 4ab4cffb..3ac2564 100644
--- a/tools/metrics/histograms/histograms_xml/settings/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/settings/histograms.xml
@@ -186,7 +186,7 @@
   <summary>
     Which user actions were taken in safety check. Recorded every time a user
     does an interaction in safety check. Value 5 and 6 got added with M86, 7-9
-    with M87, and 10 with M88.
+    with M87, 10 with M88, and 11 with M89.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/histograms_xml/v8/histograms.xml b/tools/metrics/histograms/histograms_xml/v8/histograms.xml
index a56c1789..5a21d97 100644
--- a/tools/metrics/histograms/histograms_xml/v8/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/v8/histograms.xml
@@ -337,8 +337,9 @@
 </histogram>
 
 <histogram name="V8.DebugFeatureUsage" enum="V8DebugFeature"
-    expires_after="2021-01-10">
+    expires_after="2022-01-10">
   <owner>yangguo@chromium.org</owner>
+  <owner>bmeurer@chromium.org</owner>
   <summary>
     Debugger feature used at least once per isolate, recorded on first use.
   </summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 79b6faa..74eb1c5 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,12 +1,12 @@
 {
     "trace_processor_shell": {
         "win": {
-            "hash": "26aa7f0361bf6a3fbf5627c8c8699301ab51c6e6",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/026943fd29be9ac85975ddb8afdd2853bb03b3da/trace_processor_shell.exe"
+            "hash": "f3800b3c8e2857a159879265b67126c6ab2495e7",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/53a231c0ae868366179bd560b68a2f5f4b166541/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "f0efd147e6c4c6f1b295bb5755a0e927fd94da5b",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/026943fd29be9ac85975ddb8afdd2853bb03b3da/trace_processor_shell"
+            "hash": "fd0d9f70fe09c168bbb999ade77e6f9e4c991c3c",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/53a231c0ae868366179bd560b68a2f5f4b166541/trace_processor_shell"
         },
         "linux": {
             "hash": "9de25afa2969d17640815fe3b83e4427b0526564",
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index b543710..2d9cda3 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -5,12 +5,12 @@
 #include "ui/accessibility/ax_tree.h"
 
 #include <stddef.h>
-
 #include <algorithm>
 #include <numeric>
 #include <utility>
 
 #include "base/auto_reset.h"
+#include "base/callback_helpers.h"
 #include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/containers/contains.h"
diff --git a/ui/base/clipboard/clipboard_ozone.cc b/ui/base/clipboard/clipboard_ozone.cc
index 1140e65..4975a153 100644
--- a/ui/base/clipboard/clipboard_ozone.cc
+++ b/ui/base/clipboard/clipboard_ozone.cc
@@ -201,7 +201,6 @@
   void PerformRequestAndWaitForResult(ClipboardBuffer buffer,
                                       Request* request) {
     DCHECK(request);
-    DCHECK(!abort_timer_.IsRunning());
     DCHECK(!pending_request_);
 
     pending_request_ = request;
@@ -228,16 +227,12 @@
     request->finish_closure = run_loop.QuitClosure();
 
     // Set a timeout timer after which the request will be aborted.
-    abort_timer_.Start(FROM_HERE, kRequestTimeout, this,
-                       &AsyncClipboardOzone::AbortStalledRequest);
+    base::OneShotTimer abort_timer;
+    abort_timer.Start(FROM_HERE, kRequestTimeout, this,
+                      &AsyncClipboardOzone::CompleteRequest);
     run_loop.Run();
   }
 
-  void AbortStalledRequest() {
-    if (pending_request_ && pending_request_->finish_closure)
-      std::move(pending_request_->finish_closure).Run();
-  }
-
   void DispatchReadRequest(ClipboardBuffer buffer, Request* request) {
     auto callback = base::BindOnce(&AsyncClipboardOzone::OnTextRead,
                                    weak_factory_.GetWeakPtr());
@@ -275,7 +270,7 @@
   void CompleteRequest() {
     if (!pending_request_)
       return;
-    abort_timer_.Stop();
+
     if (pending_request_->finish_closure)
       std::move(pending_request_->finish_closure).Run();
     pending_request_ = nullptr;
@@ -295,9 +290,6 @@
   // A current pending request being processed.
   Request* pending_request_ = nullptr;
 
-  // Aborts |pending_request| after Request::timeout.
-  base::RepeatingTimer abort_timer_;
-
   // Provides communication to a system clipboard under ozone level.
   PlatformClipboard* const platform_clipboard_ = nullptr;
 
diff --git a/ui/events/ozone/evdev/event_device_info.cc b/ui/events/ozone/evdev/event_device_info.cc
index 637942d..797489b 100644
--- a/ui/events/ozone/evdev/event_device_info.cc
+++ b/ui/events/ozone/evdev/event_device_info.cc
@@ -50,6 +50,10 @@
     {0x1050, 0x0010},  // Yubico.com Yubikey
     {0x1050, 0x0407},  // Yubico.com Yubikey 4 OTP+U2F+CCID
     {0x1bcf, 0x08a0},  // Kensington Pro Fit Full-size
+    {0x256c, 0x006d},  // Huion HS64
+    {0x28bd, 0x0914},  // XP-Pen Star G640
+    {0x28bd, 0x091f},  // XP-Pen Artist 12 Pro
+    {0x28bd, 0x0928},  // XP-Pen Deco mini7W
 };
 
 constexpr struct {
diff --git a/ui/file_manager/file_manager/background/js/BUILD.gn b/ui/file_manager/file_manager/background/js/BUILD.gn
index fb3480a..ae5f2b50 100644
--- a/ui/file_manager/file_manager/background/js/BUILD.gn
+++ b/ui/file_manager/file_manager/background/js/BUILD.gn
@@ -1020,7 +1020,10 @@
     "//ui/file_manager/file_manager/common/js:util.m",
     "//ui/webui/resources/js:assert.m",
   ]
-  visibility += [ "//ui/file_manager/file_manager/foreground/js:navigation_list_model_unittest.m" ]
+  visibility += [
+    "//ui/file_manager/file_manager/foreground/js:navigation_list_model_unittest.m",
+    "//ui/file_manager/file_manager/foreground/js:providers_model_unittest.m",
+  ]
 
   extra_deps = [ ":modulize" ]
 }
diff --git a/ui/file_manager/file_manager/foreground/js/BUILD.gn b/ui/file_manager/file_manager/foreground/js/BUILD.gn
index b1fdf499..302fe1e 100644
--- a/ui/file_manager/file_manager/foreground/js/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/BUILD.gn
@@ -39,13 +39,21 @@
     ":empty_folder_controller.m",
     ":fake_android_app_list_model.m",
     ":file_list_model.m",
+    ":file_type_filters_controller.m",
     ":file_watcher.m",
     ":folder_shortcuts_data_model.m",
+    ":launch_param.m",
+    ":metrics_start.m",
+    ":mock_actions_model.m",
     ":mock_directory_model.m",
     ":mock_folder_shortcut_data_model.m",
     ":mock_navigation_list_model.m",
     ":navigation_list_model.m",
+    ":navigation_uma.m",
+    ":providers_model.m",
+    ":spinner_controller.m",
     ":thumbnail_loader.m",
+    ":web_store_utils.m",
   ]
 }
 
@@ -185,6 +193,16 @@
   ]
 }
 
+js_library("mock_actions_model.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/mock_actions_model.m.js" ]
+  deps = [
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js/cr:event_target.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("fake_file_selection_handler") {
   testonly = true
   deps = [
@@ -463,6 +481,17 @@
   ]
 }
 
+js_library("navigation_uma.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/navigation_uma.m.js" ]
+  deps = [
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/file_manager/common/js:metrics.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("directory_tree_naming_controller") {
   deps = [
     ":directory_model",
@@ -708,10 +737,28 @@
   ]
 }
 
-js_unittest("file_type_filters_controller_unittest") {
+js_library("file_type_filters_controller.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/file_type_filters_controller.m.js" ]
   deps = [
-    ":file_type_filters_controller",
-    "//ui/webui/resources/js:webui_resource_test",
+    ":directory_model.m",
+    "//ui/file_manager/externs:files_app_entry_interfaces.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("file_type_filters_controller_unittest.m") {
+  deps = [
+    ":directory_model.m",
+    ":file_type_filters_controller.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:mock_chrome",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:files_app_entry_interfaces.m",
+    "//ui/file_manager/file_manager/common/js:files_app_entry_types.m",
+    "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js/cr:event_target.m",
   ]
 }
 
@@ -827,6 +874,16 @@
   ]
 }
 
+js_library("launch_param.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/launch_param.m.js" ]
+  deps = [
+    ":dialog_type.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("list_thumbnail_loader") {
   deps = [
     ":directory_model",
@@ -892,6 +949,13 @@
   deps = [ "//ui/file_manager/file_manager/common/js:metrics" ]
 }
 
+js_library("metrics_start.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/metrics_start.m.js" ]
+  deps = [ "//ui/file_manager/file_manager/common/js:metrics.m" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("naming_controller") {
   deps = [
     ":directory_contents",
@@ -962,13 +1026,28 @@
   deps = [ "//ui/webui/resources/js:assert" ]
 }
 
-js_unittest("providers_model_unittest") {
+js_library("providers_model.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/providers_model.m.js" ]
   deps = [
-    ":providers_model",
-    "//ui/file_manager/base/js:mock_chrome",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/file_manager/file_manager/background/js:mock_volume_manager",
-    "//ui/webui/resources/js:load_time_data",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/webui/resources/js:assert.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("providers_model_unittest.m") {
+  deps = [
+    ":providers_model.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:mock_chrome.m",
+    "//ui/file_manager/base/js:test_error_reporting.m",
+    "//ui/file_manager/base/js:volume_manager_types.m",
+    "//ui/file_manager/externs:volume_manager.m",
+    "//ui/file_manager/file_manager/background/js:mock_volume_manager.m",
+    "//ui/file_manager/file_manager/background/js:volume_info_impl.m",
+    "//ui/file_manager/file_manager/common/js:mock_entry.m",
   ]
 }
 
@@ -1038,12 +1117,18 @@
 js_library("spinner_controller") {
 }
 
-js_unittest("spinner_controller_unittest") {
+js_library("spinner_controller.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/spinner_controller.m.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("spinner_controller_unittest.m") {
   deps = [
-    ":spinner_controller",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/webui/resources/js:assert",
-    "//ui/webui/resources/js:util",
+    ":spinner_controller.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:test_error_reporting.m",
+    "//ui/webui/resources/js:assert.m",
   ]
 }
 
@@ -1126,6 +1211,13 @@
   deps = [ ":constants" ]
 }
 
+js_library("web_store_utils.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/web_store_utils.m.js" ]
+  deps = [ ":constants.m" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("webui_command_extender") {
   deps = [
     "//ui/webui/resources/js:cr",
@@ -1137,7 +1229,10 @@
   deps = [
     ":directory_contents_unittest.m",
     ":file_list_model_unittest.m",
+    ":file_type_filters_controller_unittest.m",
     ":navigation_list_model_unittest.m",
+    ":providers_model_unittest.m",
+    ":spinner_controller_unittest.m",
     ":thumbnail_loader_unittest.m",
   ]
   js_module = true
@@ -1157,11 +1252,8 @@
     ":file_manager_commands_unittest",
     ":file_tasks_unittest",
     ":file_transfer_controller_unittest",
-    ":file_type_filters_controller_unittest",
     ":import_controller_unittest",
     ":list_thumbnail_loader_unittest",
-    ":providers_model_unittest",
-    ":spinner_controller_unittest",
     ":task_controller_unittest",
   ]
   mocks = [ "$externs_path/file_manager_private.js" ]
@@ -1182,11 +1274,19 @@
     "file_list_model.js",
     "file_watcher.js",
     "folder_shortcuts_data_model.js",
+    "launch_param.js",
     "mock_directory_model.js",
     "mock_folder_shortcut_data_model.js",
     "mock_navigation_list_model.js",
     "navigation_list_model.js",
+    "providers_model.js",
     "thumbnail_loader.js",
+    "web_store_utils.js",
+    "file_type_filters_controller.js",
+    "navigation_uma.js",
+    "metrics_start.js",
+    "mock_actions_model.js",
+    "spinner_controller.js",
   ]
 
   namespace_rewrites = cr_namespace_rewrites
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index 58f9b893..dbd9dfa 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -1775,12 +1775,21 @@
     this.addsItems_;
   }
 
+  /** @override */
   execute(event, fileManager) {
     if (this.addsItems_ === undefined) {
       return;
     }
 
-    const entries = fileManager.selectionHandler.selection.entries;
+    // Filter out entries from unsupported volumes.
+    const allowedVolumeTypes = HoldingSpaceUtil.getAllowedVolumeTypes();
+    const entries =
+        fileManager.selectionHandler.selection.entries.filter(entry => {
+          const volumeInfo = fileManager.volumeManager.getVolumeInfo(entry);
+          return volumeInfo &&
+              allowedVolumeTypes.includes(volumeInfo.volumeType);
+        });
+
     chrome.fileManagerPrivate.toggleAddedToHoldingSpace(
         entries, this.addsItems_);
 
@@ -1800,17 +1809,25 @@
     }
 
     const allowedVolumeTypes = HoldingSpaceUtil.getAllowedVolumeTypes();
-    const currentVolumeInfo = fileManager.directoryModel.getCurrentVolumeInfo();
-    if (!currentVolumeInfo ||
-        !allowedVolumeTypes.includes(currentVolumeInfo.volumeType)) {
-      event.canExecute = false;
-      command.setHidden(true);
-      return;
+    const currentRootType = fileManager.directoryModel.getCurrentRootType();
+    if (!util.isRecentRootType(currentRootType)) {
+      const volumeInfo = fileManager.directoryModel.getCurrentVolumeInfo();
+      if (!volumeInfo || !allowedVolumeTypes.includes(volumeInfo.volumeType)) {
+        event.canExecute = false;
+        command.setHidden(true);
+        return;
+      }
     }
 
-    const entries = fileManager.selectionHandler.selection.entries;
+    // Filter out entries from unsupported volumes.
+    const entries =
+        fileManager.selectionHandler.selection.entries.filter(entry => {
+          const volumeInfo = fileManager.volumeManager.getVolumeInfo(entry);
+          return volumeInfo &&
+              allowedVolumeTypes.includes(volumeInfo.volumeType);
+        });
 
-    if (!entries || entries.length === 0) {
+    if (entries.length === 0) {
       event.canExecute = false;
       command.setHidden(true);
       return;
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands_unittest.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands_unittest.js
index e576458..65f96b1 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands_unittest.js
@@ -30,3 +30,205 @@
   }
 }
 
+/**
+ * Checks that the `toggle-holding-space` command is appropriately enabled/
+ * disabled given the current selection state and executes as expected.
+ */
+function testToggleHoldingSpaceCommand() {
+  // Verify `toggle-holding-space` command exists.
+  const command = CommandHandler.getCommand('toggle-holding-space');
+  assertNotEqual(command, undefined);
+
+  // Enable the holding space feature and provide strings.
+  loadTimeData.data = {
+    HOLDING_SPACE_ENABLED: true,
+    HOLDING_SPACE_PIN_TO_SHELF_COMMAND_LABEL: 'Pin to shelf',
+    HOLDING_SPACE_UNPIN_TO_SHELF_COMMAND_LABEL: 'Unpin to shelf',
+  };
+
+  // Mock private API.
+  chrome.fileManagerPrivate.getHoldingSpaceState = (callback) => {
+    callback({itemUrls: []});
+  };
+
+  // Mock volume manager.
+  const volumeManager = new MockVolumeManager();
+
+  // Create `DOWNLOADS` volume.
+  const downloadsVolumeInfo = volumeManager.createVolumeInfo(
+      VolumeManagerCommon.VolumeType.DOWNLOADS, 'downloadsVolumeId',
+      'Downloads volume');
+  const downloadsFileSystem = downloadsVolumeInfo.fileSystem;
+
+  // Create `REMOVABLE` volume.
+  const removableVolumeInfo = volumeManager.createVolumeInfo(
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removableVolumeId',
+      'Removable volume');
+  const removableFileSystem = removableVolumeInfo.fileSystem;
+
+  // Mock file/folder entries.
+  const audioFileEntry = new MockEntry(downloadsFileSystem, '/audio.mp3');
+  const downloadFileEntry = new MockEntry(downloadsFileSystem, '/download.txt');
+  const folderEntry = MockDirectoryEntry.create(downloadsFileSystem, '/folder');
+  const imageFileEntry = new MockEntry(downloadsFileSystem, '/image.png');
+  const removableFileEntry =
+      new MockEntry(removableFileSystem, '/removable.txt');
+  const videoFileEntry = new MockEntry(downloadsFileSystem, 'video.mp4');
+
+  // Define test cases.
+  const testCases = [
+    {
+      description: 'Tests empty selection in `Downloads`',
+      currentRootType: VolumeManagerCommon.RootType.DOWNLOADS,
+      currentVolumeInfo: {
+        volumeType: VolumeManagerCommon.VolumeType.DOWNLOADS,
+      },
+      selection: [],
+      expect: {
+        canExecute: false,
+        hidden: true,
+      },
+    },
+    {
+      description: 'Tests selection from supported volume in `Downloads`',
+      currentRootType: VolumeManagerCommon.RootType.DOWNLOADS,
+      currentVolumeInfo: {
+        volumeType: VolumeManagerCommon.VolumeType.DOWNLOADS,
+      },
+      selection: [downloadFileEntry],
+      expect: {
+        canExecute: true,
+        hidden: false,
+        entries: [downloadFileEntry],
+      },
+    },
+    {
+      description:
+          'Tests folder selection from supported volume in `Downloads`',
+      currentRootType: VolumeManagerCommon.RootType.DOWNLOADS,
+      currentVolumeInfo: {
+        volumeType: VolumeManagerCommon.VolumeType.DOWNLOADS,
+      },
+      selection: [folderEntry],
+      expect: {
+        canExecute: true,
+        hidden: false,
+        entries: [folderEntry],
+      },
+    },
+    {
+      description: 'Tests selection from supported volume in `Recent`',
+      currentRootType: VolumeManagerCommon.RootType.RECENT,
+      currentVolumeInfo: null,
+      selection: [downloadFileEntry],
+      expect: {
+        canExecute: true,
+        hidden: false,
+        entries: [downloadFileEntry],
+      },
+    },
+    {
+      description: 'Test selection from unsupported volume in `Recent`',
+      currentRootType: VolumeManagerCommon.RootType.RECENT,
+      currentVolumeInfo: null,
+      selection: [removableFileEntry],
+      expect: {
+        canExecute: false,
+        hidden: true,
+      },
+    },
+    {
+      description: 'Test selection from mix of volumes in `Recent`',
+      currentRootType: VolumeManagerCommon.RootType.RECENT,
+      currentVolumeInfo: null,
+      selection: [audioFileEntry, removableFileEntry, downloadFileEntry],
+      expect: {
+        canExecute: true,
+        hidden: false,
+        entries: [audioFileEntry, downloadFileEntry],
+      },
+    },
+    {
+      description: 'Tests selection from supported volume in `Recent Audio`',
+      currentRootType: VolumeManagerCommon.RootType.RECENT_AUDIO,
+      currentVolumeInfo: null,
+      selection: [audioFileEntry],
+      expect: {
+        canExecute: true,
+        hidden: false,
+        entries: [audioFileEntry],
+      },
+    },
+    {
+      description: 'Tests selection from supported volume in `Recent Images`',
+      currentRootType: VolumeManagerCommon.RootType.RECENT_AUDIO,
+      currentVolumeInfo: null,
+      selection: [imageFileEntry],
+      expect: {
+        canExecute: true,
+        hidden: false,
+        entries: [imageFileEntry],
+      },
+    },
+    {
+      description: 'Tests selection from supported volume in `Recent Videos`',
+      currentRootType: VolumeManagerCommon.RootType.RECENT_AUDIO,
+      currentVolumeInfo: null,
+      selection: [videoFileEntry],
+      expect: {
+        canExecute: true,
+        hidden: false,
+        entries: [videoFileEntry],
+      },
+    },
+  ];
+
+  // Run test cases.
+  for (const testCase of testCases) {
+    console.log('Starting test case... ' + testCase.description);
+
+    // Mock `Event`.
+    const event = {
+      canExecute: true,
+      command: {
+        hidden: false,
+        setHidden: (hidden) => {
+          event.command.hidden = hidden;
+        },
+      },
+    };
+
+    // Mock `FileManager`.
+    const fileManager = {
+      directoryModel: {
+        getCurrentRootType: () => testCase.currentRootType,
+        getCurrentVolumeInfo: () => testCase.currentVolumeInfo,
+      },
+      selectionHandler: {
+        selection: {entries: testCase.selection},
+      },
+      volumeManager: volumeManager,
+    };
+
+    // Verify `command.canExecute()` results in expected `event` state.
+    command.canExecute(event, fileManager);
+    assertEquals(event.canExecute, testCase.expect.canExecute);
+    assertEquals(event.command.hidden, testCase.expect.hidden);
+
+    if (!event.canExecute || event.command.hidden) {
+      continue;
+    }
+
+    // Mock private API.
+    let didInteractWithMockPrivateApi = false;
+    chrome.fileManagerPrivate.toggleAddedToHoldingSpace = (entries, isAdd) => {
+      didInteractWithMockPrivateApi = true;
+      assertArrayEquals(entries, testCase.expect.entries);
+      assertTrue(isAdd);
+    };
+
+    // Verify `command.execute()` results in expected mock API interactions.
+    command.execute(event, fileManager);
+    assertTrue(didInteractWithMockPrivateApi);
+  }
+}
diff --git a/ui/file_manager/file_manager/foreground/js/file_type_filters_controller.js b/ui/file_manager/file_manager/foreground/js/file_type_filters_controller.js
index f7d8e94..83ca4b1 100644
--- a/ui/file_manager/file_manager/foreground/js/file_type_filters_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/file_type_filters_controller.js
@@ -2,11 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {FakeEntry} from '../../../externs/files_app_entry_interfaces.m.js';
+// #import {DirectoryModel} from './directory_model.m.js';
+// #import {str, util} from '../../common/js/util.m.js';
+
 /**
  * This class controls wires file-type filter UI and the filter settings in
  * Recents view.
  */
-class FileTypeFiltersController {
+/* #export */ class FileTypeFiltersController {
   /**
    * @param {!HTMLElement} fileTypeFilterContainer
    * @param {!DirectoryModel} directoryModel
diff --git a/ui/file_manager/file_manager/foreground/js/file_type_filters_controller_unittest.js b/ui/file_manager/file_manager/foreground/js/file_type_filters_controller_unittest.m.js
similarity index 79%
rename from ui/file_manager/file_manager/foreground/js/file_type_filters_controller_unittest.js
rename to ui/file_manager/file_manager/foreground/js/file_type_filters_controller_unittest.m.js
index 41d5b53..4b7ad71 100644
--- a/ui/file_manager/file_manager/foreground/js/file_type_filters_controller_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/file_type_filters_controller_unittest.m.js
@@ -2,6 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://test/chai_assert.js';
+
+import {installMockChrome} from '../../../base/js/mock_chrome.m.js';
+import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+import {FakeEntry} from '../../../externs/files_app_entry_interfaces.m.js';
+import {EntryList, FakeEntryImpl} from '../../common/js/files_app_entry_types.m.js';
+
+import {DirectoryModel} from './directory_model.m.js';
+import {FileTypeFiltersController} from './file_type_filters_controller.m.js';
+
 /**
  * @type {!HTMLElement}
  */
@@ -27,15 +39,36 @@
  */
 let fileTypeFiltersController;
 
-function setUp() {
+export function setUp() {
   // Mock loadTimeData strings.
-  window.loadTimeData.data = {
+  loadTimeData.data = {
     MEDIA_VIEW_AUDIO_ROOT_LABEL: 'Audio',
     MEDIA_VIEW_IMAGES_ROOT_LABEL: 'Images',
     MEDIA_VIEW_VIDEOS_ROOT_LABEL: 'Videos',
   };
 
-  class MockDirectoryModel extends cr.EventTarget {
+  /**
+   * Mock chrome APIs.
+   * @type {!Object}
+   */
+  const mockChrome = {
+    fileManagerPrivate: {
+      SourceRestriction: {
+        ANY_SOURCE: 'any_source',
+        NATIVE_SOURCE: 'native_source',
+      },
+      RecentFileType: {
+        ALL: 'all',
+        AUDIO: 'audio',
+        IMAGE: 'image',
+        VIDEO: 'video',
+      },
+    },
+  };
+
+  installMockChrome(mockChrome);
+
+  class MockDirectoryModel extends EventTarget {
     constructor() {
       super();
 
@@ -83,7 +116,7 @@
  * Tests that creating FileTypeFiltersController generates three buttons in the
  * given container element.
  */
-function testCreatedButtonLabels() {
+export function testCreatedButtonLabels() {
   const buttons = container.children;
   assertEquals(buttons.length, 3);
 
@@ -95,7 +128,7 @@
 /**
  * Tests that initial states of all buttons inside container are inactive.
  */
-function testButtonInitialActiveState() {
+export function testButtonInitialActiveState() {
   const buttons = container.children;
   assertEquals(buttons.length, 3);
 
@@ -107,7 +140,7 @@
 /**
  * Tests that click events toggle button state (inactive -> active -> inactive).
  */
-function testButtonToggleState() {
+export function testButtonToggleState() {
   const buttons = container.children;
   assertEquals(buttons.length, 3);
 
@@ -123,7 +156,7 @@
  * If button_1 is clicked then button_0 is active, button_0 becomes inactive and
  * button_1 becomes active.
  */
-function testOnlyOneButtonCanActive() {
+export function testOnlyOneButtonCanActive() {
   const buttons = container.children;
   assertEquals(buttons.length, 3);
 
@@ -148,7 +181,7 @@
  * Tests that container element is visible only when the current directory is
  * Recents view.
  */
-function testContainerIsShownOnlyInRecents() {
+export function testContainerIsShownOnlyInRecents() {
   container.hidden = true;
   directoryModel.changeDirectoryEntry(recentEntry);
   assertFalse(container.hidden);
@@ -160,7 +193,7 @@
  * Tests that button's active state is reset to inactive when the user leaves
  * Recents view.
  */
-function testActiveButtonIsResetOnLeavingRecents() {
+export function testActiveButtonIsResetOnLeavingRecents() {
   const buttons = container.children;
   assertEquals(buttons.length, 3);
 
@@ -182,7 +215,7 @@
  * recentFileType property, and DirectoryModel.rescan() is called after the
  * Recent entry's property is modified.
  */
-function testAppliedFilters() {
+export function testAppliedFilters() {
   const buttons = container.children;
   assertEquals(buttons.length, 3);
 
diff --git a/ui/file_manager/file_manager/foreground/js/launch_param.js b/ui/file_manager/file_manager/foreground/js/launch_param.js
index 3cf673f..5b0bd5f 100644
--- a/ui/file_manager/file_manager/foreground/js/launch_param.js
+++ b/ui/file_manager/file_manager/foreground/js/launch_param.js
@@ -2,15 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {AllowedPaths} from '../../../base/js/volume_manager_types.m.js';
+// #import {DialogType} from './dialog_type.m.js';
+
 /**
  * @typedef {{
  *   overrideCwsContainerUrlForTest: (string|undefined),
  *   overrideCwsContainerOriginForTest: (string|undefined)
  * }}
  */
-let SuggestAppDialogState;
+/* #export */ let SuggestAppDialogState;
 
-class LaunchParam {
+/* #export */ class LaunchParam {
   /**
    * @param {!Object} unformatted Unformatted option.
    */
diff --git a/ui/file_manager/file_manager/foreground/js/metrics_start.js b/ui/file_manager/file_manager/foreground/js/metrics_start.js
index 8695c097..6427034 100644
--- a/ui/file_manager/file_manager/foreground/js/metrics_start.js
+++ b/ui/file_manager/file_manager/foreground/js/metrics_start.js
@@ -8,5 +8,7 @@
  * define the metrics namespace).
  */
 
+// #import {metrics} from '../../../common/js/metrics.m.js';
+
 metrics.startInterval('Load.Total');
 metrics.startInterval('Load.Script');
diff --git a/ui/file_manager/file_manager/foreground/js/mock_actions_model.js b/ui/file_manager/file_manager/foreground/js/mock_actions_model.js
index cbb4b4a..546d322 100644
--- a/ui/file_manager/file_manager/foreground/js/mock_actions_model.js
+++ b/ui/file_manager/file_manager/foreground/js/mock_actions_model.js
@@ -2,12 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-class MockActionModel {
+// clang-format off
+// #import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.m.js';
+// #import {dispatchSimpleEvent} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
+/* #export */ class MockActionModel extends cr.EventTarget {
   /**
    * @param {string} title
    * @param {Array<!Entry>} entries
    */
   constructor(title, entries) {
+    super();
+
     this.title = title;
     this.entries = entries;
     this.actionsModel = null;
@@ -20,11 +27,11 @@
   onCanExecute() {}
 
   onExecute() {
-    cr.dispatchSimpleEvent('invalidated', this.actionsModel);
+    cr.dispatchSimpleEvent(this, 'invalidated', true);
   }
 }
 
-class MockActionsModel extends cr.EventTarget {
+/* #export */ class MockActionsModel extends cr.EventTarget {
   constructor(actions) {
     super();
 
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_uma.js b/ui/file_manager/file_manager/foreground/js/navigation_uma.js
index 4e94fbc..d9601c62 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_uma.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_uma.js
@@ -2,11 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+// #import {VolumeManager} from '../../../externs/volume_manager.m.js';
+// #import {metrics} from '../../../common/js/metrics.m.js';
+// clang-format on
+
 /**
  * UMA exporter for navigation in the Files app.
  *
  */
-class NavigationUma {
+/* #export */ class NavigationUma {
   /**
    * @param {!VolumeManager} volumeManager
    *
diff --git a/ui/file_manager/file_manager/foreground/js/providers_model.js b/ui/file_manager/file_manager/foreground/js/providers_model.js
index 5066dc2..de1edc2 100644
--- a/ui/file_manager/file_manager/foreground/js/providers_model.js
+++ b/ui/file_manager/file_manager/foreground/js/providers_model.js
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// #import {VolumeManager} from '../../../externs/volume_manager.m.js';
+// clang-format on
 
 /**
  * An item in the model. Represents a single providing extension.
@@ -96,7 +101,7 @@
  * providing extensions as well as performing operations on them, such as
  * requesting a new mount point.
  */
-class ProvidersModel {
+/* #export */ class ProvidersModel {
   /**
    * @param {!VolumeManager} volumeManager
    */
diff --git a/ui/file_manager/file_manager/foreground/js/providers_model_unittest.js b/ui/file_manager/file_manager/foreground/js/providers_model_unittest.m.js
similarity index 62%
rename from ui/file_manager/file_manager/foreground/js/providers_model_unittest.js
rename to ui/file_manager/file_manager/foreground/js/providers_model_unittest.m.js
index a00d7eb..609f7e1 100644
--- a/ui/file_manager/file_manager/foreground/js/providers_model_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/providers_model_unittest.m.js
@@ -2,6 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {assertEquals} from 'chrome://test/chai_assert.js';
+
+import {installMockChrome, MockCommandLinePrivate} from '../../../base/js/mock_chrome.m.js';
+import {reportPromise} from '../../../base/js/test_error_reporting.m.js';
+import {VolumeManagerCommon} from '../../../base/js/volume_manager_types.m.js';
+import {VolumeManager} from '../../../externs/volume_manager.m.js';
+import {MockVolumeManager} from '../../background/js/mock_volume_manager.m.js';
+import {VolumeInfoImpl} from '../../background/js/volume_info_impl.m.js';
+import {MockDirectoryEntry, MockFileSystem} from '../../common/js/mock_entry.m.js';
+
+import {ProvidersModel} from './providers_model.m.js';
+
 /**
  * Providing extension which has a mounted file system and doesn't support
  * multiple mounts.
@@ -103,6 +115,9 @@
   multipleMounts: true,
 };
 
+/** @type {!VolumeManager} */
+let volumeManager;
+
 function addProvidedVolume(volumeManager, providerId, volumeId) {
   const fileSystem = new MockFileSystem(volumeId, 'filesystem:' + volumeId);
   fileSystem.entries['/'] = MockDirectoryEntry.create(fileSystem, '');
@@ -129,7 +144,7 @@
   volumeManager.volumeInfoList.add(volumeInfo);
 }
 
-function setUp() {
+export function setUp() {
   window.loadTimeData.getString = id => id;
 
   // Create and install a mock fileManagerPrivate API for fetching the list of
@@ -152,9 +167,8 @@
   installMockChrome(mockChrome);
   new MockCommandLinePrivate();
 
-  // Create and install a volume manager.
-  const volumeManager = new MockVolumeManager();
-  MockVolumeManager.installMockSingleton(volumeManager);
+  // Install mock volume manager.
+  volumeManager = new MockVolumeManager();
 
   // Add provided test volumes.
   const single = MOUNTED_SINGLE_PROVIDING_EXTENSION.providerId;
@@ -163,79 +177,68 @@
   addProvidedVolume(volumeManager, multiple, 'volume-2');
 }
 
-function testGetInstalledProviders(callback) {
+export function testGetInstalledProviders(callback) {
+  const model = new ProvidersModel(volumeManager);
   reportPromise(
-      volumeManagerFactory.getInstance()
-          .then(volumeManager => {
-            const model = new ProvidersModel(volumeManager);
-            return model.getInstalledProviders();
-          })
-          .then(providers => {
-            assertEquals(5, providers.length);
-            assertEquals(
-                MOUNTED_SINGLE_PROVIDING_EXTENSION.providerId,
-                providers[0].providerId);
-            assertEquals(
-                MOUNTED_SINGLE_PROVIDING_EXTENSION.iconSet,
-                providers[0].iconSet);
-            assertEquals(
-                MOUNTED_SINGLE_PROVIDING_EXTENSION.name, providers[0].name);
-            assertEquals(
-                MOUNTED_SINGLE_PROVIDING_EXTENSION.configurable,
-                providers[0].configurable);
-            assertEquals(
-                MOUNTED_SINGLE_PROVIDING_EXTENSION.watchable,
-                providers[0].watchable);
-            assertEquals(
-                MOUNTED_SINGLE_PROVIDING_EXTENSION.multipleMounts,
-                providers[0].multipleMounts);
-            assertEquals(
-                MOUNTED_SINGLE_PROVIDING_EXTENSION.source, providers[0].source);
+      model.getInstalledProviders().then(providers => {
+        assertEquals(5, providers.length);
+        assertEquals(
+            MOUNTED_SINGLE_PROVIDING_EXTENSION.providerId,
+            providers[0].providerId);
+        assertEquals(
+            MOUNTED_SINGLE_PROVIDING_EXTENSION.iconSet, providers[0].iconSet);
+        assertEquals(
+            MOUNTED_SINGLE_PROVIDING_EXTENSION.name, providers[0].name);
+        assertEquals(
+            MOUNTED_SINGLE_PROVIDING_EXTENSION.configurable,
+            providers[0].configurable);
+        assertEquals(
+            MOUNTED_SINGLE_PROVIDING_EXTENSION.watchable,
+            providers[0].watchable);
+        assertEquals(
+            MOUNTED_SINGLE_PROVIDING_EXTENSION.multipleMounts,
+            providers[0].multipleMounts);
+        assertEquals(
+            MOUNTED_SINGLE_PROVIDING_EXTENSION.source, providers[0].source);
 
-            assertEquals(
-                NOT_MOUNTED_SINGLE_PROVIDING_EXTENSION.providerId,
-                providers[1].providerId);
-            assertEquals(
-                MOUNTED_MULTIPLE_PROVIDING_EXTENSION.providerId,
-                providers[2].providerId);
-            assertEquals(
-                NOT_MOUNTED_FILE_PROVIDING_EXTENSION.providerId,
-                providers[3].providerId);
-            assertEquals(
-                NOT_MOUNTED_DEVICE_PROVIDING_EXTENSION.providerId,
-                providers[4].providerId);
+        assertEquals(
+            NOT_MOUNTED_SINGLE_PROVIDING_EXTENSION.providerId,
+            providers[1].providerId);
+        assertEquals(
+            MOUNTED_MULTIPLE_PROVIDING_EXTENSION.providerId,
+            providers[2].providerId);
+        assertEquals(
+            NOT_MOUNTED_FILE_PROVIDING_EXTENSION.providerId,
+            providers[3].providerId);
+        assertEquals(
+            NOT_MOUNTED_DEVICE_PROVIDING_EXTENSION.providerId,
+            providers[4].providerId);
 
-            assertEquals(
-                NOT_MOUNTED_SINGLE_PROVIDING_EXTENSION.iconSet,
-                providers[1].iconSet);
-            assertEquals(
-                MOUNTED_MULTIPLE_PROVIDING_EXTENSION.iconSet,
-                providers[2].iconSet);
-            assertEquals(
-                NOT_MOUNTED_FILE_PROVIDING_EXTENSION.iconSet,
-                providers[3].iconSet);
-            assertEquals(
-                NOT_MOUNTED_DEVICE_PROVIDING_EXTENSION.iconSet,
-                providers[4].iconSet);
-          }),
+        assertEquals(
+            NOT_MOUNTED_SINGLE_PROVIDING_EXTENSION.iconSet,
+            providers[1].iconSet);
+        assertEquals(
+            MOUNTED_MULTIPLE_PROVIDING_EXTENSION.iconSet, providers[2].iconSet);
+        assertEquals(
+            NOT_MOUNTED_FILE_PROVIDING_EXTENSION.iconSet, providers[3].iconSet);
+        assertEquals(
+            NOT_MOUNTED_DEVICE_PROVIDING_EXTENSION.iconSet,
+            providers[4].iconSet);
+      }),
       callback);
 }
 
-function testGetMountableProviders(callback) {
+export function testGetMountableProviders(callback) {
+  const model = new ProvidersModel(volumeManager);
   reportPromise(
-      volumeManagerFactory.getInstance()
-          .then(volumeManager => {
-            const model = new ProvidersModel(volumeManager);
-            return model.getMountableProviders();
-          })
-          .then(providers => {
-            assertEquals(2, providers.length);
-            assertEquals(
-                NOT_MOUNTED_SINGLE_PROVIDING_EXTENSION.providerId,
-                providers[0].providerId);
-            assertEquals(
-                MOUNTED_MULTIPLE_PROVIDING_EXTENSION.providerId,
-                providers[1].providerId);
-          }),
+      model.getMountableProviders().then(providers => {
+        assertEquals(2, providers.length);
+        assertEquals(
+            NOT_MOUNTED_SINGLE_PROVIDING_EXTENSION.providerId,
+            providers[0].providerId);
+        assertEquals(
+            MOUNTED_MULTIPLE_PROVIDING_EXTENSION.providerId,
+            providers[1].providerId);
+      }),
       callback);
 }
diff --git a/ui/file_manager/file_manager/foreground/js/spinner_controller.js b/ui/file_manager/file_manager/foreground/js/spinner_controller.js
index 4d92c44..cd9361c 100644
--- a/ui/file_manager/file_manager/foreground/js/spinner_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/spinner_controller.js
@@ -7,7 +7,7 @@
  * is called 3 times, the hide callback has to be called 3 times to make the
  * spinner invisible.
  */
-class SpinnerController {
+/* #export */ class SpinnerController {
   /** @param {!Element} element */
   constructor(element) {
     /**
diff --git a/ui/file_manager/file_manager/foreground/js/spinner_controller_unittest.js b/ui/file_manager/file_manager/foreground/js/spinner_controller_unittest.m.js
similarity index 88%
rename from ui/file_manager/file_manager/foreground/js/spinner_controller_unittest.js
rename to ui/file_manager/file_manager/foreground/js/spinner_controller_unittest.m.js
index e5b12861..68afffe 100644
--- a/ui/file_manager/file_manager/foreground/js/spinner_controller_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/spinner_controller_unittest.m.js
@@ -2,6 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {assertFalse, assertTrue} from 'chrome://test/chai_assert.js';
+
+import {reportPromise} from '../../../base/js/test_error_reporting.m.js';
+
+import {SpinnerController} from './spinner_controller.m.js';
+
 /**
  * @type {Element}
  */
@@ -22,7 +29,7 @@
   });
 }
 
-function setUp() {
+export function setUp() {
   spinner = document.createElement('div');
   spinner.id = 'spinner';
   spinner.textContent = 'LOADING...';
@@ -33,7 +40,7 @@
   controller.setBlinkDurationForTesting(100);
 }
 
-function testBlink(callback) {
+export function testBlink(callback) {
   assertTrue(spinner.hidden);
   controller.blink();
 
@@ -49,7 +56,7 @@
       callback);
 }
 
-function testShow(callback) {
+export function testShow(callback) {
   assertTrue(spinner.hidden);
   const hideCallback = controller.show();
 
@@ -73,7 +80,7 @@
       callback);
 }
 
-function testShowDuringBlink(callback) {
+export function testShowDuringBlink(callback) {
   assertTrue(spinner.hidden);
   controller.blink();
   const hideCallback = controller.show();
@@ -103,7 +110,7 @@
       callback);
 }
 
-function testStackedShows(callback) {
+export function testStackedShows(callback) {
   assertTrue(spinner.hidden);
 
   const hideCallbacks = [];
diff --git a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
index 5f0c8fb..f69daea 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
@@ -25,9 +25,16 @@
   uses_js_modules = true
   deps = [
     ":directory_tree.m",
+    ":drag_selector.m",
     ":empty_folder.m",
     ":file_list_selection_model.m",
+    ":file_manager_dialog_base.m",
     ":files_alert_dialog.m",
+    ":files_confirm_dialog.m",
+    ":files_menu.m",
+    ":multi_menu.m",
+    ":multi_menu_button.m",
+    ":suggest_apps_dialog.m",
     "table:table.m",
     "table:table_column.m",
     "table:table_column_model.m",
@@ -264,6 +271,14 @@
   externs_list = [ "//ui/file_manager/externs/drag_target.js" ]
 }
 
+js_library("drag_selector.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/drag_selector.m.js" ]
+  deps = [ "//ui/webui/resources/js/cr/ui:list.m" ]
+  externs_list = [ "//ui/file_manager/externs/drag_target.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("empty_folder") {
   deps = [ "//ui/webui/resources/js:util" ]
 }
@@ -324,11 +339,22 @@
   externs_list = [ "$externs_path/chrome_extensions.js" ]
 }
 
-js_unittest("file_manager_dialog_base_unittest") {
+js_library("file_manager_dialog_base.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base.m.js" ]
   deps = [
-    ":file_manager_dialog_base",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/webui/resources/js:webui_resource_test",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js/cr/ui:dialogs.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("file_manager_dialog_base_unittest.m") {
+  deps = [
+    ":file_manager_dialog_base.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/base/js:test_error_reporting.m",
+    "//ui/webui/resources/js:assert.m",
   ]
 }
 
@@ -465,6 +491,16 @@
   deps = [ "//ui/webui/resources/js/cr/ui:dialogs" ]
 }
 
+js_library("files_confirm_dialog.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/files_confirm_dialog.m.js" ]
+  deps = [
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js/cr/ui:dialogs.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("files_menu") {
   deps = [
     "//ui/webui/resources/js:assert",
@@ -474,6 +510,18 @@
   externs_list = [ "//ui/file_manager/externs/paper_elements.js" ]
 }
 
+js_library("files_menu.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/files_menu.m.js" ]
+  deps = [
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js/cr/ui:menu.m",
+    "//ui/webui/resources/js/cr/ui:menu_item.m",
+  ]
+  externs_list = [ "//ui/file_manager/externs/paper_elements.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("gear_menu") {
   deps = [ "//ui/file_manager/file_manager/common/js:util" ]
 }
@@ -534,6 +582,20 @@
   externs_list = [ "$externs_path/pending.js" ]
 }
 
+js_library("multi_menu.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/multi_menu.m.js" ]
+
+  deps = [
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:event_tracker.m",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:menu.m",
+    "//ui/webui/resources/js/cr/ui:menu_item.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_library("multi_menu_button") {
   deps = [
     # TODO(files-ng): remove util dep when the files-ng flag is removed.
@@ -547,13 +609,32 @@
   externs_list = [ "$externs_path/pending.js" ]
 }
 
-js_unittest("multi_menu_unittest") {
+js_library("multi_menu_button.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/multi_menu_button.m.js" ]
   deps = [
-    ":multi_menu",
-    ":multi_menu_button",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/file_manager/file_manager/common/js:util",
-    "//ui/webui/resources/js:webui_resource_test",
+    ":multi_menu.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js:event_tracker.m",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:menu.m",
+    "//ui/webui/resources/js/cr/ui:menu_button.m",
+    "//ui/webui/resources/js/cr/ui:menu_item.m",
+    "//ui/webui/resources/js/cr/ui:position_util.m",
+  ]
+
+  extra_deps = [ ":modulize" ]
+}
+
+js_unittest("multi_menu_unittest.m") {
+  deps = [
+    ":multi_menu_button.m",
+    "//chrome/test/data/webui:chai_assert",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/webui/resources/js:assert.m",
+    "//ui/webui/resources/js/cr:ui.m",
+    "//ui/webui/resources/js/cr/ui:command.m",
+    "//ui/webui/resources/js/cr/ui:menu.m",
   ]
 }
 
@@ -615,10 +696,31 @@
       [ "//ui/file_manager/externs/chrome_webstore_widget_private.js" ]
 }
 
+js_library("suggest_apps_dialog.m") {
+  sources = [ "$root_gen_dir/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.m.js" ]
+  deps = [
+    ":file_manager_dialog_base.m",
+    "//ui/file_manager/file_manager/common/js:metrics.m",
+    "//ui/file_manager/file_manager/common/js:util.m",
+    "//ui/file_manager/file_manager/cws_widget:cws_widget_container.m",
+    "//ui/file_manager/file_manager/cws_widget:cws_widget_container_platform_delegate.m",
+    "//ui/file_manager/file_manager/foreground/js:launch_param.m",
+    "//ui/file_manager/file_manager/foreground/js:providers_model.m",
+    "//ui/file_manager/file_manager/foreground/js:web_store_utils.m",
+    "//ui/webui/resources/js:assert.m",
+  ]
+  externs_list =
+      [ "//ui/file_manager/externs/chrome_webstore_widget_private.js" ]
+
+  extra_deps = [ ":modulize" ]
+}
+
 js_test_gen_html("js_test_gen_html_modules") {
   deps = [
     ":directory_tree_unittest.m",
     ":file_list_selection_model_unittest.m",
+    ":file_manager_dialog_base_unittest.m",
+    ":multi_menu_unittest.m",
   ]
   js_module = true
 
@@ -636,22 +738,31 @@
   deps = [
     ":actions_submenu_unittest",
     ":breadcrumb_unittest",
-    ":file_manager_dialog_base_unittest",
     ":file_table_list_unittest",
     ":file_table_unittest",
     ":file_tap_handler_unittest",
     ":install_linux_package_dialog_unittest",
-    ":multi_menu_unittest",
   ]
 }
 
 js_modulizer("modulize") {
   input_files = [
     "directory_tree.js",
+    "drag_selector.js",
     "empty_folder.js",
     "file_list_selection_model.js",
+    "file_manager_dialog_base.js",
     "files_alert_dialog.js",
+    "files_confirm_dialog.js",
+    "files_menu.js",
+    "multi_menu.js",
+    "multi_menu_button.js",
+    "suggest_apps_dialog.js",
   ]
 
   namespace_rewrites = cr_namespace_rewrites
+  namespace_rewrites += [
+    "cr.ui.MultiMenu|MultiMenu",
+    "cr.ui.FilesMenuItem|FilesMenuItem",
+  ]
 }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/drag_selector.js b/ui/file_manager/file_manager/foreground/js/ui/drag_selector.js
index 0de8d341..b661d0b3 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/drag_selector.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/drag_selector.js
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-class DragSelector {
+// #import {List} from 'chrome://resources/js/cr/ui/list.m.js';
+
+/* #export */ class DragSelector {
   /**
    * Drag selector used on the file list or the grid table.
    */
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base.js b/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base.js
index a9e33e5..63818e9 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base.js
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {util} from '../../../common/js/util.m.js';
+// #import {BaseDialog} from 'chrome://resources/js/cr/ui/dialogs.m.js';
+
 /**
  * This class is an extended class, to manage the status of the dialogs.
  */
-class FileManagerDialogBase extends cr.ui.dialogs.BaseDialog {
+/* #export */ class FileManagerDialogBase extends cr.ui.dialogs.BaseDialog {
   /**
    * @param {HTMLElement} parentNode Parent node of the dialog.
    */
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base_unittest.m.js
similarity index 75%
rename from ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base_unittest.js
rename to ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base_unittest.m.js
index 55ba581..30dcfc5 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_manager_dialog_base_unittest.m.js
@@ -2,7 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-function setUp() {
+import {assertInstanceof} from 'chrome://resources/js/assert.m.js';
+import {assertFalse} from 'chrome://test/chai_assert.js';
+import {waitUntil} from '../../../../base/js/test_error_reporting.m.js';
+import {FileManagerDialogBase} from './file_manager_dialog_base.m.js';
+
+export function setUp() {
   // Polyfill chrome.app.window.current().
   /** @suppress {duplicate,checkTypes,const} */
   chrome.app = {window: {current: () => null}};
@@ -12,7 +17,7 @@
   };
 }
 
-async function testShowDialogAfterHide(done) {
+export async function testShowDialogAfterHide(done) {
   const container =
       assertInstanceof(document.createElement('div'), HTMLElement);
 
diff --git a/ui/file_manager/file_manager/foreground/js/ui/files_confirm_dialog.js b/ui/file_manager/file_manager/foreground/js/ui/files_confirm_dialog.js
index 7d0a56c..1b07e2a 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/files_confirm_dialog.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/files_confirm_dialog.js
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {util} from '../../../common/js/util.m.js';
+// #import {ConfirmDialog} from 'chrome://resources/js/cr/ui/dialogs.m.js';
+
 /**
  * Confirm dialog.
  */
-class FilesConfirmDialog extends cr.ui.dialogs.ConfirmDialog {
+/* #export */ class FilesConfirmDialog extends cr.ui.dialogs.ConfirmDialog {
   /**
    * @param {!Element} parentElement
    */
diff --git a/ui/file_manager/file_manager/foreground/js/ui/files_menu.js b/ui/file_manager/file_manager/foreground/js/ui/files_menu.js
index f8e69d9..dbeb14cc 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/files_menu.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/files_menu.js
@@ -2,11 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {Menu} from 'chrome://resources/js/cr/ui/menu.m.js';
+// #import {assertInstanceof} from 'chrome://resources/js/assert.m.js';
+// #import {MenuItem} from 'chrome://resources/js/cr/ui/menu_item.m.js';
+
 cr.define('cr.ui', () => {
   /**
    * Menu item with ripple animation.
    */
-  class FilesMenuItem extends cr.ui.MenuItem {
+  /* #export */ class FilesMenuItem extends cr.ui.MenuItem {
     constructor() {
       super();
 
@@ -204,6 +208,7 @@
     }
   }
 
+  // #cr_define_end
   return {
     FilesMenuItem: FilesMenuItem,
   };
diff --git a/ui/file_manager/file_manager/foreground/js/ui/multi_menu.js b/ui/file_manager/file_manager/foreground/js/ui/multi_menu.js
index 2352b28..52c1023 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/multi_menu.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/multi_menu.js
@@ -2,6 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// #import {MenuItem} from 'chrome://resources/js/cr/ui/menu_item.m.js';
+// #import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
+// #import {assertInstanceof} from 'chrome://resources/js/assert.m.js';
+// #import {Menu} from 'chrome://resources/js/cr/ui/menu.m.js';
+// #import {decorate} from 'chrome://resources/js/cr/ui.m.js';
+
 cr.define('cr.ui', () => {
   /**
    * Creates a menu that supports sub-menus.
@@ -14,7 +20,7 @@
    * @extends {cr.ui.Menu}
    * @implements {EventListener}
    */
-  class MultiMenu {
+  /* #export */ class MultiMenu {
     constructor() {
       /**
        * Whether a sub-menu is positioned on the left of its parent.
@@ -207,9 +213,7 @@
     positionSubMenu_(item, subMenu) {
       const style = subMenu.style;
 
-      if (util.isFilesNg()) {
-        style.marginTop = '0';  // crbug.com/1066727
-      }
+      style.marginTop = '0';  // crbug.com/1066727
 
       // The sub-menu needs to sit aligned to the top and side of
       // the menu-item passed in. It also needs to fit inside the viewport
@@ -299,7 +303,7 @@
      * @private
      */
     findMenuItem(node) {
-      const MenuItem = cr.ui.MenuItem;
+      /* #ignore */ const MenuItem = cr.ui.MenuItem;
       while (node && node.parentNode !== this && !(node instanceof MenuItem)) {
         node = node.parentNode;
       }
@@ -453,5 +457,6 @@
   }
 
   // Export
+  // #cr_define_end
   return {MultiMenu};
 });
diff --git a/ui/file_manager/file_manager/foreground/js/ui/multi_menu_button.js b/ui/file_manager/file_manager/foreground/js/ui/multi_menu_button.js
index 567c3eb7..a4a4f95b 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/multi_menu_button.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/multi_menu_button.js
@@ -2,16 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// #import {Menu} from 'chrome://resources/js/cr/ui/menu.m.js';
+// #import {AnchorType, positionPopupAroundElement} from 'chrome://resources/js/cr/ui/position_util.m.js';
+// #import {util} from '../../../common/js/util.m.js';
+// #import {HideType} from 'chrome://resources/js/cr/ui/menu_button.m.js';
+// #import {MenuItem} from 'chrome://resources/js/cr/ui/menu_item.m.js';
+// #import {MultiMenu} from './multi_menu.m.js';
+// #import {decorate} from 'chrome://resources/js/cr/ui.m.js';
+// #import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
+// clang-format on
+
 cr.define('cr.ui', () => {
-  /** @const */
-  const HideType = cr.ui.HideType;
+  /* #ignore */ /** @const */ const HideType = cr.ui.HideType;
 
   /**
    * A button that displays a MultiMenu (menu with sub-menus).
    * @extends {HTMLButtonElement}
    * @implements {EventListener}
    */
-  class MultiMenuButton {
+  /* #export */ class MultiMenuButton {
     constructor() {
       /**
        * Property that hosts sub-menus for filling with overflow items.
@@ -445,5 +456,6 @@
   MultiMenuButton.prototype.__proto__ = HTMLButtonElement.prototype;
 
   // Export
+  // #cr_define_end
   return {MultiMenuButton};
 });
diff --git a/ui/file_manager/file_manager/foreground/js/ui/multi_menu_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/multi_menu_unittest.m.js
similarity index 87%
rename from ui/file_manager/file_manager/foreground/js/ui/multi_menu_unittest.js
rename to ui/file_manager/file_manager/foreground/js/ui/multi_menu_unittest.m.js
index d2948ac..29bec58 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/multi_menu_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/multi_menu_unittest.m.js
@@ -2,25 +2,45 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-'use strict';
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {decorate} from 'chrome://resources/js/cr/ui.m.js';
+import {Command} from 'chrome://resources/js/cr/ui/command.m.js';
+import {Menu} from 'chrome://resources/js/cr/ui/menu.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://test/chai_assert.js';
 
-/** @type {cr.ui.MultiMenuButton} */
+import {util} from '../../../common/js/util.m.js';
+
+import {MultiMenuButton} from './multi_menu_button.m.js';
+
+/** @type {MultiMenuButton} */
 let menubutton;
 
-/** @type {cr.ui.Menu} */
+/** @type {Menu} */
 let topMenu;
 
-/** @type {cr.ui.Menu} */
+/** @type {Menu} */
 let subMenu;
 
-/** @type {cr.ui.Menu} */
+/** @type {Menu} */
 let secondSubMenu;
 
+/** @type {number} */
+let initialWindowHeight;
+
 // Set up test components.
-function setUp() {
+export function setUp() {
   // Internals of WebUI reference this property when processing
   // keyboard events, so we need to prepare it to stop asserts.
   loadTimeData.data = {'SHORTCUT_ENTER': 'Enter'};
+
+  // Multiple tests rely on the window height, reset between tests to avoid
+  // interference.
+  if (!initialWindowHeight) {
+    initialWindowHeight = window.innerHeight;
+  }
+  window.innerHeight = initialWindowHeight;
+
   // Install cr.ui <command> elements and <cr-menu>s on the page.
   document.body.innerHTML = [
     '<style>',
@@ -63,12 +83,11 @@
   ].join('');
 
   // Initialize cr.ui.Command with the <command>s.
-  cr.ui.decorate('command', cr.ui.Command);
-  menubutton =
-      util.queryDecoratedElement('#test-menu-button', cr.ui.MultiMenuButton);
-  topMenu = util.queryDecoratedElement('#menu', cr.ui.Menu);
-  subMenu = util.queryDecoratedElement('#sub-menu', cr.ui.Menu);
-  secondSubMenu = util.queryDecoratedElement('#second-sub-menu', cr.ui.Menu);
+  decorate('command', Command);
+  menubutton = util.queryDecoratedElement('#test-menu-button', MultiMenuButton);
+  topMenu = util.queryDecoratedElement('#menu', Menu);
+  subMenu = util.queryDecoratedElement('#sub-menu', Menu);
+  secondSubMenu = util.queryDecoratedElement('#second-sub-menu', Menu);
 }
 
 /**
@@ -137,7 +156,7 @@
  * Tests that making the top level menu visible doesn't
  * cause the sub-menu to become visible.
  */
-function testShowMenuDoesntShowSubMenu() {
+export function testShowMenuDoesntShowSubMenu() {
   menubutton.showMenu(true);
   // Check the top level menu is not hidden.
   assertFalse(topMenu.hasAttribute('hidden'));
@@ -149,7 +168,7 @@
  * Tests that a 'mouseover' event on top of normal menu-items
  * doesn't cause the sub-menu to become visible.
  */
-function testMouseOverNormalItemsDoesntShowSubMenu() {
+export function testMouseOverNormalItemsDoesntShowSubMenu() {
   menubutton.showMenu(true);
   sendMouseOver('#default-task');
   assertTrue(subMenu.hasAttribute('hidden'));
@@ -161,7 +180,7 @@
  * Tests that 'mouseover' on a menu-item with 'show-submenu' command
  * causes the sub-menu to become visible.
  */
-function testMouseOverHostMenuShowsSubMenu() {
+export function testMouseOverHostMenuShowsSubMenu() {
   menubutton.showMenu(true);
   sendMouseOver('#host-sub-menu');
   assertFalse(subMenu.hasAttribute('hidden'));
@@ -171,7 +190,7 @@
  * Tests that 'mouseout' with the mouse over the top level
  * menu causes the sub-menu to hide.
  */
-function testMouseoutFromHostMenuItemToHostMenu() {
+export function testMouseoutFromHostMenuItemToHostMenu() {
   menubutton.showMenu(true);
   sendMouseOver('#host-sub-menu');
   assertFalse(subMenu.hasAttribute('hidden'));
@@ -186,7 +205,7 @@
  * Tests that 'mouseout' with the mouse over the sub-menu
  * doesn't hide the sub-menu.
  */
-function testMouseoutFromHostMenuToSubMenu() {
+export function testMouseoutFromHostMenuToSubMenu() {
   menubutton.showMenu(true);
   sendMouseOver('#host-sub-menu');
   assertFalse(subMenu.hasAttribute('hidden'));
@@ -200,7 +219,7 @@
  * Tests that selecting a menu-item with a 'show-submenu' command
  * doesn't cause the sub-menu to become visible.
  */
-function testSelectHostMenuItem() {
+export function testSelectHostMenuItem() {
   menubutton.showMenu(true);
   topMenu.selectedIndex = 2;
   const hostItem = document.querySelector('#host-sub-menu');
@@ -216,7 +235,7 @@
  * (Note: in an application, this would happen from a command
  * being executed rather than a direct showSubMenu() call.)
  */
-function testSelectHostMenuItemAndCallShowSubMenu() {
+export function testSelectHostMenuItemAndCallShowSubMenu() {
   testSelectHostMenuItem();
   menubutton.menu.showSubMenu();
   assertFalse(subMenu.hasAttribute('hidden'));
@@ -226,7 +245,7 @@
  * Tests that a mouse click outside of a menu and sub-menu causes
  * both menus to hide.
  */
-function testClickOutsideVisibleMenuAndSubMenu() {
+export function testClickOutsideVisibleMenuAndSubMenu() {
   testSelectHostMenuItemAndCallShowSubMenu();
   const event = new MouseEvent('mousedown', {
     bubbles: true,
@@ -245,7 +264,7 @@
  * Tests that shrinking the window height will limit
  * the height of the sub-menu.
  */
-function testShrinkWindowSizesSubMenu() {
+export function testShrinkWindowSizesSubMenu() {
   testSelectHostMenuItemAndCallShowSubMenu();
   const subMenuPosition = subMenu.getBoundingClientRect();
   // Reduce window innerHeight so sub-menu won't fit.
@@ -264,7 +283,7 @@
  * Tests that growing the window height will increase
  * the height of the sub-menu.
  */
-function testGrowWindowSizesSubMenu() {
+export function testGrowWindowSizesSubMenu() {
   // Remember the full size of the sub-menu
   testSelectHostMenuItemAndCallShowSubMenu();
   const subMenuPosition = subMenu.getBoundingClientRect();
@@ -303,7 +322,7 @@
 /**
  * Tests that arrow navigates from main menu to sub-menu.
  */
-function testNavigateFromMenuToSubMenu() {
+export function testNavigateFromMenuToSubMenu() {
   prepareForKeyboardNavigation();
   // Check that the hosting menu-item is not selected.
   const hostItem = document.querySelector('#host-sub-menu');
@@ -317,7 +336,7 @@
  * Tests that arrow left moves back to the top level menu
  * only when the selected sub-menu item is the first one.
  */
-function testNavigateFromSubMenuToParentMenu() {
+export function testNavigateFromSubMenuToParentMenu() {
   testNavigateFromMenuToSubMenu();
   // Use the arrow key to go to the next sub-menu item.
   sendKeyDown('#test-menu-button', 'ArrowDown');
@@ -346,7 +365,7 @@
  * Tests that arrow up on the top level menu hides the
  * sub menu when the sub-menu is visible.
  */
-function testTopMenuArrowUpDismissesSubMenu() {
+export function testTopMenuArrowUpDismissesSubMenu() {
   prepareForKeyboardNavigation();
   // Check that the hosting menu-item is not selected.
   const hostItem = document.querySelector('#host-sub-menu');
@@ -365,7 +384,7 @@
  * Tests that the top level menu is resized when the parent
  * window is too small to fit in without clipping.
  */
-function testShrinkWindowSizesTopMenu() {
+export function testShrinkWindowSizesTopMenu() {
   menubutton.showMenu(true);
   const menuPosition = topMenu.getBoundingClientRect();
   // Reduce window innerHeight so the menu won't fit.
@@ -380,7 +399,7 @@
 /**
  * Tests that mousedown the menu button grabs focus.
  */
-function testFocusMenuButtonWithMouse() {
+export function testFocusMenuButtonWithMouse() {
   // Set focus on a div element.
   //* @type {HTMLElement} */
   const divElement = document.querySelector('#focus-div');
@@ -424,7 +443,7 @@
 /**
  * Tests that opening a sub menu hides any showing sub menu.
  */
-function testShowSubMenuHidesExisting() {
+export function testShowSubMenuHidesExisting() {
   testMouseOverHostMenuShowsSubMenu();
   sendMouseOver('#host-second-sub-menu');
   // Check the previously shown sub menu is hidden.
diff --git a/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js b/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
index e5fe23a..b03e88c 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
@@ -3,16 +3,28 @@
 // found in the LICENSE file.
 
 /**
- * SuggestAppsDialog contains a list box to select an app to be opened the file
- * with. This dialog should be used as action picker for file operations.
+ * @fileoverview SuggestAppsDialog contains a list box to select an app to be
+ * opened the file with. This dialog should be used as action picker for file
+ * operations.
  */
 
+// clang-format off
+// #import {SuggestAppDialogState} from '../launch_param.m.js';
+// #import {ProvidersModel} from '../providers_model.m.js';
+// #import {CWSWidgetContainerPlatformDelegate} from '../../../cws_widget/cws_widget_container_platform_delegate.m.js';
+// #import {FileManagerDialogBase} from './file_manager_dialog_base.m.js';
+// #import {CWSWidgetContainer} from '../../../cws_widget/cws_widget_container.m.js';
+// #import {webStoreUtils} from '../web_store_utils.m.js';
+// #import {str, util} from '../../../common/js/util.m.js';
+// #import {metrics} from '../../common/js/metrics.m.js';
+// #import {assert} from 'chrome://resources/js/assert.m.js';
+// clang-format on
 
 /**
  * Creates dialog in DOM tree.
  *
  */
-class SuggestAppsDialog extends FileManagerDialogBase {
+/* #export */ class SuggestAppsDialog extends FileManagerDialogBase {
   /**
    * @param {!ProvidersModel} providersModel Model for providers.
    * @param {!HTMLElement} parentNode Node to be parent for this dialog.
diff --git a/ui/file_manager/file_manager/foreground/js/web_store_utils.js b/ui/file_manager/file_manager/foreground/js/web_store_utils.js
index 75c2f5ff8..8f5682c 100644
--- a/ui/file_manager/file_manager/foreground/js/web_store_utils.js
+++ b/ui/file_manager/file_manager/foreground/js/web_store_utils.js
@@ -3,6 +3,13 @@
 // found in the LICENSE file.
 
 /**
+ * @fileoverview
+ * @suppress {uselessCode} Temporary suppress because of the line exporting.
+ */
+
+// #import {constants} from './constants.m.js';
+
+/**
  * Namespace for web store utility functions.
  * @namespace
  */
@@ -53,3 +60,6 @@
   }
   return url;
 };
+
+// eslint-disable-next-line semi,no-extra-semi
+/* #export */ {webStoreUtils};
diff --git a/ui/message_center/views/message_popup_view.cc b/ui/message_center/views/message_popup_view.cc
index 7b40bb5f..c3ed17bb 100644
--- a/ui/message_center/views/message_popup_view.cc
+++ b/ui/message_center/views/message_popup_view.cc
@@ -15,6 +15,7 @@
 #include "ui/message_center/views/message_view.h"
 #include "ui/message_center/views/message_view_factory.h"
 #include "ui/views/layout/fill_layout.h"
+#include "ui/views/metadata/metadata_impl_macros.h"
 #include "ui/views/widget/widget.h"
 
 #if defined(OS_WIN)
@@ -182,10 +183,6 @@
   node_data->role = ax::mojom::Role::kAlertDialog;
 }
 
-const char* MessagePopupView::GetClassName() const {
-  return "MessagePopupView";
-}
-
 void MessagePopupView::OnDisplayChanged() {
   OnWorkAreaChanged();
 }
@@ -224,4 +221,7 @@
   return GetWidget() && !GetWidget()->IsClosed();
 }
 
+BEGIN_METADATA(MessagePopupView, views::WidgetDelegateView)
+END_METADATA
+
 }  // namespace message_center
diff --git a/ui/message_center/views/message_popup_view.h b/ui/message_center/views/message_popup_view.h
index 9b5476c5d..edd309b 100644
--- a/ui/message_center/views/message_popup_view.h
+++ b/ui/message_center/views/message_popup_view.h
@@ -21,8 +21,12 @@
 class MESSAGE_CENTER_EXPORT MessagePopupView : public views::WidgetDelegateView,
                                                public views::WidgetObserver {
  public:
+  METADATA_HEADER(MessagePopupView);
+
   MessagePopupView(const Notification& notification,
                    MessagePopupCollection* popup_collection);
+  MessagePopupView(const MessagePopupView&) = delete;
+  MessagePopupView& operator=(const MessagePopupView&) = delete;
   ~MessagePopupView() override;
 
   // Update notification contents to |notification|. Virtual for unit testing.
@@ -54,7 +58,6 @@
   void OnMouseExited(const ui::MouseEvent& event) override;
   void ChildPreferredSizeChanged(views::View* child) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-  const char* GetClassName() const override;
   void OnDisplayChanged() override;
   void OnWorkAreaChanged() override;
   void OnFocus() override;
@@ -70,7 +73,7 @@
 
  protected:
   // For unit testing.
-  MessagePopupView(MessagePopupCollection* popup_collection);
+  explicit MessagePopupView(MessagePopupCollection* popup_collection);
 
  private:
   // True if the view has a widget and the widget is not closed.
@@ -87,8 +90,6 @@
   bool is_active_ = false;
 
   ScopedObserver<views::Widget, views::WidgetObserver> observer_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(MessagePopupView);
 };
 
 }  // namespace message_center
diff --git a/ui/webui/resources/cr_components/certificate_manager/certificate_provisioning_details_dialog.html b/ui/webui/resources/cr_components/certificate_manager/certificate_provisioning_details_dialog.html
index 10d45a0..6aae7084 100644
--- a/ui/webui/resources/cr_components/certificate_manager/certificate_provisioning_details_dialog.html
+++ b/ui/webui/resources/cr_components/certificate_manager/certificate_provisioning_details_dialog.html
@@ -29,25 +29,41 @@
   </div>
   <div slot="body">
     <div class="two-line">
-      <div class="label">[[i18n('certificateProvisioningProfileName')]]</div>
-      <div class="value">[[model.certProfileName]]</div>
+      <div class="label" aria-describedby="certProfileName">
+        [[i18n('certificateProvisioningProfileName')]]
+      </div>
+      <div class="value" id="certProfileName" aria-hidden="true">
+        [[model.certProfileName]]
+      </div>
     </div>
     <div class="two-line">
-      <div class="label">[[i18n('certificateProvisioningProfileId')]]</div>
-      <div class="value">[[model.certProfileId]]</div>
+      <div class="label" aria-describedby="certProfileId">
+        [[i18n('certificateProvisioningProfileId')]]
+      </div>
+      <div class="value" id="certProfileId" aria-hidden="true">
+        [[model.certProfileId]]
+      </div>
     </div>
     <div class="button-box">
       <div class="two-line flex">
-        <div class="label">[[i18n('certificateProvisioningStatus')]]</div>
-        <span class="value">[[model.status]]</span>
+        <div class="label" aria-describedby="status">
+          [[i18n('certificateProvisioningStatus')]]
+        </div>
+        <span class="value" id="status" aria-hidden="true">
+          [[model.status]]
+        </span>
       </div>
       <cr-button id="refresh" role="button" on-click="onRefresh_">
         [[i18n('certificateProvisioningRefresh')]]
       </cr-button>
     </div>
     <div class="two-line">
-      <div class="label">[[i18n('certificateProvisioningLastUpdate')]]</div>
-      <div class="value">[[model.timeSinceLastUpdate]]</div>
+      <div class="label" aria-describedby="timeSinceLastUpdate">
+        [[i18n('certificateProvisioningLastUpdate')]]
+      </div>
+      <div class="value" id="timeSinceLastUpdate" aria-hidden="true">
+        [[model.timeSinceLastUpdate]]
+      </div>
     </div>
     <hr></hr>
     <cr-expand-button expanded="{{advancedExpanded_}}"
@@ -56,12 +72,20 @@
     </cr-expand-button>
     <iron-collapse id="advancedInfo" opened="[[advancedExpanded_]]">
       <div class="two-line">
-        <div class="label">[[i18n('certificateProvisioningStatusId')]]</div>
-        <div class="value">[[model.stateId]]</div>
+        <div class="label" aria-describedby="stateId">
+          [[i18n('certificateProvisioningStatusId')]]
+        </div>
+        <div class="value" id="stateId" aria-hidden="true">
+          [[model.stateId]]
+        </div>
       </div>
       <div class="two-line">
-        <div class="label">[[i18n('certificateProvisioningPublicKey')]]</div>
-        <div class="value">[[model.publicKey]]</div>
+        <div class="label" aria-describedby="publicKey">
+          [[i18n('certificateProvisioningPublicKey')]]
+        </div>
+        <div class="value" id="publicKey" aria-hidden="true">
+          [[model.publicKey]]
+        </div>
       </div>
     </iron-collapse>
   </div>
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_list_item.html b/ui/webui/resources/cr_components/chromeos/network/network_list_item.html
index 8a58ff8d7..a1bfbb7d 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_list_item.html
+++ b/ui/webui/resources/cr_components/chromeos/network/network_list_item.html
@@ -24,11 +24,26 @@
       }
 
       #divOuter {
-        margin-bottom: 8px;
-        margin-top: 8px;
-        min-height: 48px;
+        height: var(--cr-network-row-height, 48px);
         overflow: auto;
+        padding-bottom: var(--cr-network-row-padding-bottom, 0);
         padding-inline-end: var(--cr-icon-ripple-padding);
+        padding-top: var(--cr-network-row-padding-bottom, 0);
+      }
+
+      :host([is-e-sim-pending-profile_]) #divText {
+        opacity: 0.4;
+      }
+
+      :host(:not([is-e-sim-pending-profile_])) #divIcon {
+        height: 24px;
+        width: 24px;
+      }
+
+      :host([is-e-sim-pending-profile_]) #divIcon {
+        height: 20px;
+        opacity: 0.4;
+        width: 20px;
       }
 
       #divDetail {
@@ -53,24 +68,10 @@
         color: var(--google-green-500);
       }
 
-      iron-icon :not(.esim-pending-profile) {
-        height: 24px;
-        width: 24px;
-      }
-
-      iron-icon .esim-pending-profile {
-        height: 20px;
-        width: 20px;
-      }
-
       cr-policy-indicator {
         padding: 0 var(--cr-controlled-by-spacing);
       }
 
-      .esim-pending-profile {
-        opacity: 0.4;
-      }
-
       #wrapper {
         height: 100%;
       }
@@ -106,11 +107,9 @@
           </network-icon>
         </template>
         <template is="dom-if" if="[[item.polymerIcon]]">
-          <iron-icon icon="[[item.polymerIcon]]"
-              class$="[[getItemClassName_(item, item.customItemType]]"></iron-icon>
+          <iron-icon id="divIcon" icon="[[item.polymerIcon]]"></iron-icon>
         </template>
-        <div id="divText" class$="layout horizontal flex
-            [[getItemClassName_(item, item.customItemType)]]">
+        <div id="divText" class="layout horizontal flex">
           <div id="networkName" aria-hidden="true">
             [[getItemName_(item)]]
           </div>
@@ -144,7 +143,7 @@
             </cr-icon-button>
           </div>
         </template>
-        <template is="dom-if" if="[[isESimPendingProfile_(item, item.customItemType)]]" restamp>
+        <template is="dom-if" if="[[isESimPendingProfile_]]" restamp>
           <cr-button id="installButton"
               aria-label$="[[getItemName_(item)]], $i18n{networkListItemDownload}"
               on-click="onInstallButtonClick_">
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_list_item.js b/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
index f106d20..faeb93d 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_list_item.js
@@ -72,6 +72,17 @@
     },
 
     /**
+     * Whether the network item is a cellular one and is of an esim
+     * pending profile.
+     */
+    isESimPendingProfile_: {
+      type: Boolean,
+      reflectToAttribute: true,
+      value: false,
+      computed: 'computeIsESimPendingProfile_(item, item.customItemType)',
+    },
+
+    /**
      * The cached ConnectionState for the network.
      * @type {!chromeos.networkConfig.mojom.ConnectionStateType|undefined}
      */
@@ -368,7 +379,7 @@
             'networkListItemLabelWifi', index, total, this.getItemName_(),
             secured, this.item.typeState.wifi.signalStrength);
       default:
-        if (this.isESimPendingProfile_()) {
+        if (this.isESimPendingProfile_) {
           if (this.subtitle_) {
             return this.i18n(
                 'networkListItemLabelESimPendingProfileWithProviderName', index,
@@ -504,7 +515,7 @@
     if (this.isSubpageButtonVisible_(this.networkState, this.showButtons) &&
         this.$$('#subpage-button') === this.shadowRoot.activeElement) {
       this.fireShowDetails_(event);
-    } else if (this.isESimPendingProfile_()) {
+    } else if (this.isESimPendingProfile_) {
       this.onInstallButtonClick_();
     } else if (this.item.hasOwnProperty('customItemName')) {
       this.fire('custom-item-selected', this.item);
@@ -572,21 +583,13 @@
    * @return {boolean}
    * @private
    */
-  isESimPendingProfile_() {
+  computeIsESimPendingProfile_() {
     return !!this.item && this.item.hasOwnProperty('customItemType') &&
         this.item.customItemType ===
         NetworkList.CustomItemType.ESIM_PENDING_PROFILE;
   },
 
   /**
-   * @return {string}
-   * @private
-   */
-  getItemClassName_() {
-    return this.isESimPendingProfile_() ? 'esim-pending-profile' : '';
-  },
-
-  /**
    * @return {boolean}
    * @private
    */
diff --git a/weblayer/browser/subresource_filter_browsertest.cc b/weblayer/browser/subresource_filter_browsertest.cc
index b0f77ef..5995af45 100644
--- a/weblayer/browser/subresource_filter_browsertest.cc
+++ b/weblayer/browser/subresource_filter_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "base/json/json_reader.h"
 #include "build/build_config.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
 #include "components/subresource_filter/content/browser/fake_safe_browsing_database_manager.h"
 #include "components/subresource_filter/content/browser/ruleset_service.h"
@@ -15,6 +16,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "weblayer/browser/browser_process.h"
+#include "weblayer/browser/host_content_settings_map_factory.h"
 #include "weblayer/browser/subresource_filter_client_impl.h"
 #include "weblayer/browser/tab_impl.h"
 #include "weblayer/grit/weblayer_resources.h"
@@ -89,8 +91,7 @@
  protected:
   void SetRulesetToDisallowURLsWithPathSuffix(const std::string& suffix) {
     subresource_filter::testing::TestRulesetPair test_ruleset_pair;
-    subresource_filter::testing::TestRulesetCreator test_ruleset_creator;
-    test_ruleset_creator.CreateRulesetToDisallowURLsWithPathSuffix(
+    test_ruleset_creator_.CreateRulesetToDisallowURLsWithPathSuffix(
         suffix, &test_ruleset_pair);
 
     subresource_filter::testing::TestRulesetPublisher test_ruleset_publisher(
@@ -114,6 +115,9 @@
                 ->client());
     client_impl->set_database_manager_for_testing(std::move(database_manager));
   }
+
+ private:
+  subresource_filter::testing::TestRulesetCreator test_ruleset_creator_;
 };
 
 // Tests that the ruleset service is available.
@@ -294,6 +298,68 @@
   EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents->GetMainFrame()));
 }
 
+IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
+                       ContentSettingsAllowlist_DoNotActivate) {
+  auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents();
+
+  GURL test_url(
+      embedded_test_server()->GetURL("/frame_with_included_script.html"));
+
+  ASSERT_NO_FATAL_FAILURE(
+      SetRulesetToDisallowURLsWithPathSuffix("included_script.js"));
+  ActivateSubresourceFilterInWebContentsForURL(web_contents, test_url);
+
+  NavigateAndWaitForCompletion(test_url, shell());
+  EXPECT_FALSE(WasParsedScriptElementLoaded(web_contents->GetMainFrame()));
+
+  content::WebContentsConsoleObserver console_observer(web_contents);
+  console_observer.SetPattern(subresource_filter::kActivationConsoleMessage);
+
+  // Simulate explicitly allowlisting via content settings.
+  HostContentSettingsMap* settings_map =
+      HostContentSettingsMapFactory::GetForBrowserContext(
+          web_contents->GetBrowserContext());
+  settings_map->SetContentSettingDefaultScope(
+      test_url, test_url, ContentSettingsType::ADS, CONTENT_SETTING_ALLOW);
+
+  NavigateAndWaitForCompletion(test_url, shell());
+  EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents->GetMainFrame()));
+
+  // No message for allowlisted url.
+  EXPECT_TRUE(console_observer.messages().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
+                       ContentSettingsAllowlistGlobal_DoNotActivate) {
+  auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents();
+
+  GURL test_url(
+      embedded_test_server()->GetURL("/frame_with_included_script.html"));
+
+  ASSERT_NO_FATAL_FAILURE(
+      SetRulesetToDisallowURLsWithPathSuffix("included_script.js"));
+  ActivateSubresourceFilterInWebContentsForURL(web_contents, test_url);
+
+  NavigateAndWaitForCompletion(test_url, shell());
+  EXPECT_FALSE(WasParsedScriptElementLoaded(web_contents->GetMainFrame()));
+
+  content::WebContentsConsoleObserver console_observer(web_contents);
+  console_observer.SetPattern(subresource_filter::kActivationConsoleMessage);
+
+  // Simulate globally allowing ads via content settings.
+  HostContentSettingsMap* settings_map =
+      HostContentSettingsMapFactory::GetForBrowserContext(
+          web_contents->GetBrowserContext());
+  settings_map->SetDefaultContentSetting(ContentSettingsType::ADS,
+                                         CONTENT_SETTING_ALLOW);
+
+  NavigateAndWaitForCompletion(test_url, shell());
+  EXPECT_TRUE(WasParsedScriptElementLoaded(web_contents->GetMainFrame()));
+
+  // No message for loads that are not activated.
+  EXPECT_TRUE(console_observer.messages().empty());
+}
+
 #if defined(OS_ANDROID)
 // Test that the ads blocked infobar is presented when visiting a page where the
 // subresource filter blocks resources from being loaded and is removed when
diff --git a/weblayer/browser/subresource_filter_client_impl.cc b/weblayer/browser/subresource_filter_client_impl.cc
index ba3eebb..a99f17ce 100644
--- a/weblayer/browser/subresource_filter_client_impl.cc
+++ b/weblayer/browser/subresource_filter_client_impl.cc
@@ -9,6 +9,7 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
+#include "components/subresource_filter/content/browser/profile_interaction_manager.h"
 #include "components/subresource_filter/content/browser/ruleset_service.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
 #include "components/subresource_filter/core/common/activation_decision.h"
@@ -18,6 +19,7 @@
 #include "content/public/browser/render_frame_host.h"
 #include "weblayer/browser/browser_process.h"
 #include "weblayer/browser/safe_browsing/safe_browsing_service.h"
+#include "weblayer/browser/subresource_filter_profile_context_factory.h"
 
 #if defined(OS_ANDROID)
 #include "components/safe_browsing/android/remote_database_manager.h"
@@ -53,7 +55,12 @@
 #if defined(OS_ANDROID)
       web_contents_(web_contents),
 #endif
-      database_manager_(GetDatabaseManagerFromSafeBrowsingService()) {
+      database_manager_(GetDatabaseManagerFromSafeBrowsingService()),
+      profile_interaction_manager_(
+          std::make_unique<subresource_filter::ProfileInteractionManager>(
+              web_contents,
+              SubresourceFilterProfileContextFactory::GetForBrowserContext(
+                  web_contents->GetBrowserContext()))) {
 }
 
 SubresourceFilterClientImpl::~SubresourceFilterClientImpl() = default;
@@ -87,16 +94,6 @@
 #endif
 }
 
-subresource_filter::mojom::ActivationLevel
-SubresourceFilterClientImpl::OnPageActivationComputed(
-    content::NavigationHandle* navigation_handle,
-    subresource_filter::mojom::ActivationLevel initial_activation_level,
-    subresource_filter::ActivationDecision* decision) {
-  DCHECK(navigation_handle->IsInMainFrame());
-
-  return initial_activation_level;
-}
-
 void SubresourceFilterClientImpl::OnAdsViolationTriggered(
     content::RenderFrameHost* rfh,
     subresource_filter::mojom::AdsViolation triggered_violation) {}
@@ -106,4 +103,9 @@
   return database_manager_;
 }
 
+subresource_filter::ProfileInteractionManager*
+SubresourceFilterClientImpl::GetProfileInteractionManager() {
+  return profile_interaction_manager_.get();
+}
+
 }  // namespace weblayer
diff --git a/weblayer/browser/subresource_filter_client_impl.h b/weblayer/browser/subresource_filter_client_impl.h
index 1debbf2..cceb5d2 100644
--- a/weblayer/browser/subresource_filter_client_impl.h
+++ b/weblayer/browser/subresource_filter_client_impl.h
@@ -43,15 +43,13 @@
 
   // SubresourceFilterClient:
   void ShowNotification() override;
-  subresource_filter::mojom::ActivationLevel OnPageActivationComputed(
-      content::NavigationHandle* navigation_handle,
-      subresource_filter::mojom::ActivationLevel initial_activation_level,
-      subresource_filter::ActivationDecision* decision) override;
   void OnAdsViolationTriggered(
       content::RenderFrameHost* rfh,
       subresource_filter::mojom::AdsViolation triggered_violation) override;
   const scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
   GetSafeBrowsingDatabaseManager() override;
+  subresource_filter::ProfileInteractionManager* GetProfileInteractionManager()
+      override;
   void OnReloadRequested() override;
 
   // Sets the SafeBrowsingDatabaseManager instance used to |database_manager|.
@@ -70,6 +68,8 @@
   std::unique_ptr<subresource_filter::ContentSubresourceFilterThrottleManager>
       throttle_manager_;
   scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager_;
+  std::unique_ptr<subresource_filter::ProfileInteractionManager>
+      profile_interaction_manager_;
 };
 
 }  // namespace weblayer