diff --git a/.vpython3 b/.vpython3
index 1f38037..39206c2 100644
--- a/.vpython3
+++ b/.vpython3
@@ -28,7 +28,7 @@
 #   components/policy/test_support/policy_testserver.py
 wheel: <
   name: "infra/python/wheels/protobuf-py2_py3"
-  version: "version:3.6.1"
+  version: "version:3.15.8"
 >
 
 # TODO(https://crbug.com/898348): Add in necessary wheels as Python3 versions
diff --git a/DEPS b/DEPS
index bb6c3250..f2cc73c 100644
--- a/DEPS
+++ b/DEPS
@@ -209,7 +209,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '42bee2d3d2a6c39d0c7956f137f4b42da7ee7552',
+  'skia_revision': 'c6260f974261ee9fb5dc9573669559d728a856e8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -221,7 +221,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': '88156d2686c8df127d1e5ba675221573a8a13b4c',
+  'angle_revision': 'f871545d293f8fd55357eb2bcdaacea3cca9569f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -280,7 +280,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '64fcc4794c4e36bc60a2af6107e388bb6f6c166e',
+  'catapult_revision': '7da48e9f874a0eec2d81aed2426c9da0af0d25ff',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -328,7 +328,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': 'ce3d4a5b094dce131453da9a5ef422671f20ad7d',
+  'dawn_revision': 'b2527e6d9c03a4560ee5aff27c61de3b2a87b63d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -895,7 +895,7 @@
   },
 
   'src/third_party/breakpad/breakpad':
-    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + '3bea2815bfea6e641d50aad15bde2c494ef8f34b',
+    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + 'c484031f1f199ee53567241426efffee49008f82',
 
   'src/third_party/byte_buddy': {
       'packages': [
@@ -966,7 +966,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '3ffca4bed79f3b5336a93193c571484df09b1004',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'dd0076703b55ea14f15666c62759ea242906e4b7',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1438,7 +1438,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'ZV7RwS9jPyaEWlNUpA8m6n3XwWC5ORRQMw8T_kUjsYEC'
+              'version': 'uDQJbkoDWGwLYtnDu3A7LnRVwsKkaFQkUWtChrVO_hYC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1547,7 +1547,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '22ba62ffe79c3881581ab430368bf3764d9533eb',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@108d5055b4a3068b1c71cb592a09698d4139c15b',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@02a51202ebfe6eae1f8af87e97f69bb6486f42e5',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '732a76d9d3c70d6aa487216495eeb28518349c3a',
@@ -1635,7 +1635,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@210b0f0b51851d02d93d3424947d578c8bbbd09d',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3e2ef08e4c5aa8a73b56bb12c6bc9c34845f0ed4',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_browser_context.cc b/android_webview/browser/aw_browser_context.cc
index 41493cd..dc4935c1 100644
--- a/android_webview/browser/aw_browser_context.cc
+++ b/android_webview/browser/aw_browser_context.cc
@@ -173,7 +173,7 @@
   form_database_service_ =
       std::make_unique<AwFormDatabaseService>(context_storage_path_);
 
-  EnsureResourceContextInitialized(this);
+  EnsureResourceContextInitialized();
 
   // This constructor is entered during the creation of ContentBrowserClient,
   // before browser threads are created. Therefore any checks to enforce
@@ -182,7 +182,7 @@
 
 AwBrowserContext::~AwBrowserContext() {
   DCHECK_EQ(this, g_browser_context);
-  BrowserContext::NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
   SimpleKeyMap::GetInstance()->Dissociate(this);
   ShutdownStoragePartitions();
 
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index d6bee0db..ae17246 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -835,8 +835,7 @@
         url::kFileScheme,
         content::CreateFileURLLoaderFactory(
             aw_browser_context->GetPath(),
-            content::BrowserContext::GetSharedCorsOriginAccessList(
-                aw_browser_context)));
+            aw_browser_context->GetSharedCorsOriginAccessList()));
   }
 }
 
diff --git a/android_webview/browser/aw_feature_list_creator.cc b/android_webview/browser/aw_feature_list_creator.cc
index 6ae6f29..f63386f 100644
--- a/android_webview/browser/aw_feature_list_creator.cc
+++ b/android_webview/browser/aw_feature_list_creator.cc
@@ -218,7 +218,7 @@
   // able to break seed downloads. See https://crbug.com/801771 for more info.
   variations::SafeSeedManager ignored_safe_seed_manager(local_state_.get());
 
-  // Populate FieldTrialList. Since low_entropy_provider is null, it will fall
+  // Populate FieldTrialList. Since |low_entropy_provider| is null, it will fall
   // back to the provider we previously gave to FieldTrialList, which is a low
   // entropy provider. The X-Client-Data header is not reported on WebView, so
   // we pass an empty object as the |low_entropy_source_value|.
@@ -228,8 +228,8 @@
       GetSwitchDependentFeatureOverrides(
           *base::CommandLine::ForCurrentProcess()),
       /*low_entropy_provider=*/nullptr, std::make_unique<base::FeatureList>(),
-      aw_field_trials_.get(), &ignored_safe_seed_manager,
-      /*low_entropy_source_value=*/absl::nullopt);
+      metrics_client->metrics_state_manager(), aw_field_trials_.get(),
+      &ignored_safe_seed_manager, /*low_entropy_source_value=*/absl::nullopt);
 }
 
 void AwFeatureListCreator::CreateLocalState() {
diff --git a/android_webview/docs/test-instructions.md b/android_webview/docs/test-instructions.md
index f8f4e33..f602b37 100644
--- a/android_webview/docs/test-instructions.md
+++ b/android_webview/docs/test-instructions.md
@@ -163,6 +163,42 @@
     -f=AwContentsTest#testClearCacheInQuickSuccession
 ```
 
+#### Debugging hangs in instrumentation tests
+
+If an instrumentation test is hanging, it's possible to get a callstack from the
+browser process. This requires running on a device with root.
+
+It's not possible to get a callstack from the renderer because the sandbox will
+prevent the trace file from being written. A workaround if you want to see the
+renderer threads is to run in single-process mode by adding
+`@OnlyRunIn(SINGLE_PROCESS)` above the test.
+
+##### conventions
+
+| Label     |                                                                |
+|-----------|----------------------------------------------------------------|
+|  (shell)  | in your workstation's shell                                    |
+|  (phone)  | inside the phone's shell which you entered through `adb shell` |
+
+```sh
+# Find the pid
+$ (shell) adb root
+$ (shell) adb shell
+
+# Get the main WebView Shell pid, e.g. org.chromium.android_webview.shell and
+# not org.chromium.android_webview.shell:sandboxed_process0
+$ (phone) ps -A | grep org.chromium.android_webview.shell
+# Generate a callstack (this won't kill the process)
+$ (phone) kill -3 pid
+# Look for the latest trace
+$ (phone) ls /data/anr/ -l
+# Copy the trace locally
+$ (shell) adb pull /data/anr/trace_01 /tmp/t1
+# Generate a callstack. Run this from the source directory.
+$ (shell) third_party/android_platform/development/scripts/stack --output-directory=out/Release /tmp/t1
+```
+
+
 ## External tests
 
 As WebView is an Android system component, we have some tests defined outside of
diff --git a/ash/app_list/views/search_result_list_view.cc b/ash/app_list/views/search_result_list_view.cc
index dc7955d..608bc0e 100644
--- a/ash/app_list/views/search_result_list_view.cc
+++ b/ash/app_list/views/search_result_list_view.cc
@@ -41,6 +41,10 @@
 constexpr base::TimeDelta kImpressionThreshold =
     base::TimeDelta::FromSeconds(3);
 
+// TODO(crbug.com/1199206): Move this into SharedAppListConfig once the UI for
+// categories is more developed.
+constexpr size_t kMaxResultsWithCategoricalSearch = 12;
+
 SearchResultIdWithPositionIndices GetSearchResultsForLogging(
     std::vector<SearchResultView*> search_result_views) {
   SearchResultIdWithPositionIndices results;
@@ -53,6 +57,12 @@
   return results;
 }
 
+size_t GetMaxSearchResultListItems() {
+  if (app_list_features::IsCategoricalSearchEnabled())
+    return kMaxResultsWithCategoricalSearch;
+  return SharedAppListConfig::instance().max_search_result_list_items();
+}
+
 }  // namespace
 
 SearchResultListView::SearchResultListView(AppListMainView* main_view,
@@ -65,7 +75,7 @@
       views::BoxLayout::Orientation::kVertical));
 
   size_t result_count =
-      SharedAppListConfig::instance().max_search_result_list_items() +
+      GetMaxSearchResultListItems() +
       SharedAppListConfig::instance().max_assistant_search_result_list_items();
 
   for (size_t i = 0; i < result_count; ++i) {
@@ -240,8 +250,7 @@
                    result.result_type() !=
                        AppListSearchResultType::kAssistantText;
           }),
-          /*max_results=*/
-          SharedAppListConfig::instance().max_search_result_list_items());
+          GetMaxSearchResultListItems());
 
   std::vector<SearchResult*> assistant_results = GetAssistantResults();
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 737c272..01aa35b 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -629,7 +629,7 @@
         Mic is muted.
       </message>
       <message name="IDS_ASH_STATUS_TRAY_MIC_STATE_MUTED_BY_HW_SWITCH" desc="The text used as a tooltip for microphone mute button in Chrome OS quick settings when the mic gain button is muted because the device microphone mute switch is turned on. Used as a status text for IDS_ASH_STATUS_TRAY_MIC_GAIN.">
-        Device microphone button is muted.
+        Device's microphone button is turned off.
       </message>
       <message name="IDS_ASH_STATUS_AREA_TOAST_MIC_OFF" desc="The text in the toast that shows up when the mic is muted using a physical key or shortcut.">
         Microphone is off
@@ -3551,7 +3551,7 @@
         The Alt + click keyboard shortcut has changed. To use your keyboard to right-click, press the <ph name="LAUNCHER_KEY_NAME">$1<ex>Launcher</ex></ph> key + click.
       </message>
       <message name="IDS_ASH_SHORTCUT_DEPRECATION_FKEY" desc="Message telling user to use search/launcher + top row key instead of Search + a digit key to emulate a F-Key. The name of the launcher key varies by device and may be either Search or Launcher depending on the glyph on the keyboard.">
-        The <ph name="LAUNCHER_KEY_NAME">$1<ex>Launcher</ex></ph> + Number keyboard shortcut has changed. To use F-Keys, press the <ph name="LAUNCHER_KEY_NAME">$1<ex>Launcher</ex></ph> key + a key on the top row.
+        The <ph name="LAUNCHER_KEY_NAME">$1<ex>Launcher</ex></ph> + Number keyboard shortcut has changed. To use function keys, press the <ph name="LAUNCHER_KEY_NAME">$1<ex>Launcher</ex></ph> key + a key on the top row.
       </message>
       <message name="IDS_ASH_SHORTCUT_DEPRECATION_SEARCH_PERIOD_INSERT" desc="Message telling user to use search/launcher + shift + backspace instead of search/launcher + period to emulate the delete key. The name of the launcher key varies by device and may be either Search or Launcher depending on the glyph on the keyboard.">
         The <ph name="LAUNCHER_KEY_NAME">$1<ex>Launcher</ex></ph> + Period keyboard shortcut has changed. To use the Insert key, press the <ph name="LAUNCHER_KEY_NAME">$1<ex>Launcher</ex></ph> key + Shift + Backspace.
diff --git a/ash/ash_strings_grd/IDS_ASH_SHORTCUT_DEPRECATION_FKEY.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SHORTCUT_DEPRECATION_FKEY.png.sha1
index 4bde087d..f69f7b4 100644
--- a/ash/ash_strings_grd/IDS_ASH_SHORTCUT_DEPRECATION_FKEY.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_SHORTCUT_DEPRECATION_FKEY.png.sha1
@@ -1 +1 @@
-bfaf3221862d7f7675642f34eb15031142f93a6a
\ No newline at end of file
+1b9cd6f04d6d61a092be4fb5481a2866374609b9
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_MIC_STATE_MUTED_BY_HW_SWITCH.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_MIC_STATE_MUTED_BY_HW_SWITCH.png.sha1
index e86556f..6ff77de 100644
--- a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_MIC_STATE_MUTED_BY_HW_SWITCH.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_MIC_STATE_MUTED_BY_HW_SWITCH.png.sha1
@@ -1 +1 @@
-29406d3c186815f2d4699cb71e6b9fe597d78ff2
\ No newline at end of file
+c7d932abd2fba64bb2ffc019400a528a02154451
\ No newline at end of file
diff --git a/ash/content/common/resources/BUILD.gn b/ash/content/common/resources/BUILD.gn
index 50f4961..aa41ff7 100644
--- a/ash/content/common/resources/BUILD.gn
+++ b/ash/content/common/resources/BUILD.gn
@@ -13,6 +13,7 @@
 preprocessed_gen_manifest = "preprocessed_gen_manifest.json"
 
 polymer_element_files = [
+  "navigation_icons.js",
   "navigation_selector.js",
   "navigation_view_panel.js",
 ]
@@ -50,6 +51,7 @@
 
 js_library("navigation_selector") {
   deps = [
+    ":navigation_icons",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
 }
@@ -61,6 +63,12 @@
   ]
 }
 
+js_library("navigation_icons") {
+  deps = [
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+}
+
 preprocess_if_expr("preprocess_generated") {
   deps = [ ":web_components" ]
   in_folder = target_gen_dir
diff --git a/ash/content/common/resources/navigation_icons.html b/ash/content/common/resources/navigation_icons.html
new file mode 100644
index 0000000..58dd798
--- /dev/null
+++ b/ash/content/common/resources/navigation_icons.html
@@ -0,0 +1,8 @@
+<iron-iconset-svg name="navigation-selector" size="20">
+  <svg>
+    <defs>
+      <!-- TODO(jimmyxgong): Replace this stub icon with actual icons. -->
+      <g id="laptop-chromebook" viewBox="0 0 24 24"><path d="M22 18V3H2v15H0v2h24v-2h-2zm-8 0h-4v-1h4v1zm6-3H4V5h16v10z"></path></g>
+    </defs>
+  </svg>
+</iron-iconset-svg>
diff --git a/ash/content/common/resources/navigation_icons.js b/ash/content/common/resources/navigation_icons.js
new file mode 100644
index 0000000..b401f8b
--- /dev/null
+++ b/ash/content/common/resources/navigation_icons.js
@@ -0,0 +1,10 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
+
+import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+const template = html`{__html_template__}`;
+document.head.appendChild(template.content);
\ No newline at end of file
diff --git a/ash/content/common/resources/navigation_selector.html b/ash/content/common/resources/navigation_selector.html
index c175093..cc526678 100644
--- a/ash/content/common/resources/navigation_selector.html
+++ b/ash/content/common/resources/navigation_selector.html
@@ -31,6 +31,17 @@
   color: var(--google-blue-600);
 }
 
+iron-icon {
+  --iron-icon-fill-color: var(--google-grey-700);
+  margin-inline-end: 16px;
+  pointer-events: none;
+  vertical-align: top;
+}
+
+.navigation-item.selected > iron-icon {
+  --iron-icon-fill-color: var(--google-blue-600);
+}
+
 cr-expand-button {
   padding-inline-start: 20px;
 }
@@ -42,6 +53,8 @@
     <template id="menuItems" is="dom-repeat" items="{{menuItems}}">
       <template is="dom-if" if="[[!isCollapsible_(item)]]">
         <div class="navigation-item" tabindex="0" on-click="onSelected_">
+          <iron-icon class="icon" icon="[[item.selectorItem.icon]]" alt="">
+          </iron-icon>
           [[item.selectorItem.name]]
         </div>
       </template>
@@ -54,6 +67,8 @@
               items="{{item.properties.subMenuItems}}">
             <div class="navigation-item nested-item" tabindex="0"
                 on-click="onNestedSelected_">
+              <iron-icon class="icon" icon="[[getIcon_(item)]]" alt="">
+              </iron-icon>
               [[item.name]]
             </div>
           </template>
diff --git a/ash/content/common/resources/navigation_selector.js b/ash/content/common/resources/navigation_selector.js
index e5a92f2..17f4dd20 100644
--- a/ash/content/common/resources/navigation_selector.js
+++ b/ash/content/common/resources/navigation_selector.js
@@ -4,15 +4,18 @@
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import './navigation_icons.js';
 import 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
+import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 
 /**
  * @typedef {{
  *   name: string,
  *   pageIs: string,
+ *   icon: string,
  * }}
  */
 export let SelectorItem;
@@ -140,6 +143,15 @@
   isCollapsible_(item) {
     return item.properties.isCollapsible;
   }
+
+  /**
+   * @param {!SelectorItem} item
+   * @return {string}
+   * @protected
+   */
+  getIcon_(item) {
+    return item.icon;
+  }
 }
 
 customElements.define(NavigationSelectorElement.is, NavigationSelectorElement);
\ No newline at end of file
diff --git a/ash/content/common/resources/navigation_view_panel.js b/ash/content/common/resources/navigation_view_panel.js
index ca701f1..6fd24bd 100644
--- a/ash/content/common/resources/navigation_view_panel.js
+++ b/ash/content/common/resources/navigation_view_panel.js
@@ -54,10 +54,12 @@
   /**
    * @param {string} name
    * @param {string} pageIs
+   * @param {string} icon
    * @param {!Array<SelectorItem>} subItems
    */
-  addSelector(name, pageIs, subItems=[]) {
-    let item = /** @type {SelectorItem} */ ({'name': name, 'pageIs': pageIs});
+  addSelector(name, pageIs, icon='', subItems=[]) {
+    let item = /** @type {SelectorItem} */ ({
+        'name': name, 'pageIs': pageIs, 'icon': icon});
     let property = /** @type {SelectorProperties} */ ({
         'isCollapsible': subItems.length,
         'isExpanded': false,
@@ -107,7 +109,7 @@
     Array.from(components).map((c) => {
       const functionCall = c[functionName];
       if (typeof functionCall === "function") {
-        functionCall({detail: params});
+        functionCall.call(c, {detail: params});
       }
     });
   }
diff --git a/ash/content/shimless_rma/resources/BUILD.gn b/ash/content/shimless_rma/resources/BUILD.gn
index ec236eb..6d6204b 100644
--- a/ash/content/shimless_rma/resources/BUILD.gn
+++ b/ash/content/shimless_rma/resources/BUILD.gn
@@ -68,6 +68,7 @@
   deps = [
     ":shimless_rma_types",
     "//ash/content/common/resources:fake_method_resolver",
+    "//ash/content/common/resources:fake_observables",
     "//ui/webui/resources/js:cr.m",
   ]
 }
diff --git a/ash/content/shimless_rma/resources/fake_shimless_rma_service.js b/ash/content/shimless_rma/resources/fake_shimless_rma_service.js
index 9f9f004..de01f163 100644
--- a/ash/content/shimless_rma/resources/fake_shimless_rma_service.js
+++ b/ash/content/shimless_rma/resources/fake_shimless_rma_service.js
@@ -3,20 +3,22 @@
 // found in the LICENSE file.
 
 import {FakeMethodResolver} from 'chrome://resources/ash/common/fake_method_resolver.js';
+import {FakeObservables} from 'chrome://resources/ash/common/fake_observables.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 
-import {Component, ComponentRepairState, ComponentType, CurrentState, NextState, PrevState, RmadErrorCode, RmaState, ShimlessRmaServiceInterface, State} from './shimless_rma_types.js';
+import {CalibrationComponent, CalibrationObserver, Component, ComponentRepairState, ComponentType, CurrentState, ErrorObserver, HardwareWriteProtectionStateObserver, NextState, PowerCableStateObserver, PrevState, ProvisioningObserver, ProvisioningStep, RmadErrorCode, RmaState, ShimlessRmaServiceInterface, State} from './shimless_rma_types.js';
 
 /** @implements {ShimlessRmaServiceInterface} */
 export class FakeShimlessRmaService {
   constructor() {
     this.methods_ = new FakeMethodResolver();
+    this.observables_ = new FakeObservables();
 
     /**
      * The list of states for this RMA flow.
      * @private {!Array<!State>}
      */
-    this.states_ =  [];
+    this.states_ = [];
 
     /**
      * The index into states_ for the current fake state.
@@ -165,28 +167,28 @@
   }
 
   /**
-   * @return {!Promise<!{version: !string}>}
+   * @return {!Promise<!{version: string}>}
    */
   getCurrentChromeVersion() {
     return this.methods_.resolveMethod('getCurrentChromeVersion');
   }
 
   /**
-   * @param {!string} version
+   * @param {string} version
    */
   setGetCurrentChromeVersionResult(version) {
     this.methods_.setResult('getCurrentChromeVersion', {version: version});
   }
 
   /**
-   * @return {!Promise<!{updateAvailable: !boolean}>}
+   * @return {!Promise<!{updateAvailable: boolean}>}
    */
   checkForChromeUpdates() {
     return this.methods_.resolveMethod('checkForChromeUpdates');
   }
 
   /**
-   * @param {!boolean} available
+   * @param {boolean} available
    */
   setCheckForChromeUpdatesResult(available) {
     this.methods_.setResult(
@@ -224,14 +226,14 @@
   }
 
   /**
-   * @return {!Promise<!{available: !boolean}>}
+   * @return {!Promise<!{available: boolean}>}
    */
   manualDisableWriteProtectAvailable() {
     return this.methods_.resolveMethod('manualDisableWriteProtectAvailable');
   }
 
   /**
-   * @param {!boolean} available}
+   * @param {boolean} available
    */
   setManualDisableWriteProtectAvailableResult(available) {
     this.methods_.setResult(
@@ -248,7 +250,7 @@
   }
 
   /**
-   * @param {!string} code
+   * @param {string} code
    * @return {!Promise<!NextState>}
    */
   rsuDisableWriteProtect(code) {
@@ -293,14 +295,14 @@
   }
 
   /**
-   * @return {!Promise<!{required: !boolean}>}
+   * @return {!Promise<!{required: boolean}>}
    */
   reimageRequired() {
     return this.methods_.resolveMethod('reimageRequired');
   }
 
   /**
-   * @param {!boolean} required
+   * @param {boolean} required
    */
   setReimageRequiredResult(required) {
     this.methods_.setResult('reimageRequired', {required: required});
@@ -331,42 +333,42 @@
   }
 
   /**
-   * @return {!Promise<!{regions: !Array<!string>}>}
+   * @return {!Promise<!{regions: !Array<string>}>}
    */
   getRegionList() {
     return this.methods_.resolveMethod('getRegionList');
   }
 
   /**
-   * @param {!Array<!string>} regions
+   * @param {!Array<string>} regions
    */
   setGetRegionListResult(regions) {
     this.methods_.setResult('getRegionList', {regions: regions});
   }
 
   /**
-   * @return {!Promise<!{skus: !Array<!string>}>}
+   * @return {!Promise<!{skus: !Array<string>}>}
    */
   getSkuList() {
     return this.methods_.resolveMethod('getSkuList');
   }
 
   /**
-   * @param {!Array<!string>} skus
+   * @param {!Array<string>} skus
    */
   setGetSkuListResult(skus) {
     this.methods_.setResult('getSkuList', {skus: skus});
   }
 
   /**
-   * @return {!Promise<!{serialNumber: !string}>}
+   * @return {!Promise<!{serialNumber: string}>}
    */
   getOriginalSerialNumber() {
     return this.methods_.resolveMethod('getOriginalSerialNumber');
   }
 
   /**
-   * @param {!string} serialNumber
+   * @param {string} serialNumber
    */
   setGetOriginalSerialNumberResult(serialNumber) {
     this.serialNumber_ = serialNumber;
@@ -375,7 +377,7 @@
   }
 
   /**
-   * @return {!Promise<!{serialNumber: !string}>}
+   * @return {!Promise<!{serialNumber: string}>}
    */
   getSerialNumber() {
     this.methods_.setResult(
@@ -384,7 +386,7 @@
   }
 
   /**
-   * @param {!string} serialNumber
+   * @param {string} serialNumber
    * @return {!Promise<!{error: !RmadErrorCode}>}
    */
   setSerialNumber(serialNumber) {
@@ -403,14 +405,14 @@
   }
 
   /**
-   * @return {!Promise<!{regionIndex: !number}>}
+   * @return {!Promise<!{regionIndex: number}>}
    */
   getOriginalRegion() {
     return this.methods_.resolveMethod('getOriginalRegion');
   }
 
   /**
-   * @param {!number} regionIndex
+   * @param {number} regionIndex
    */
   setGetOriginalRegionResult(regionIndex) {
     this.regionIndex_ = regionIndex;
@@ -418,7 +420,7 @@
   }
 
   /**
-   * @return {!Promise<!{regionIndex: !number}>}
+   * @return {!Promise<!{regionIndex: number}>}
    */
   getRegion() {
     this.methods_.setResult('getRegion', {regionIndex: this.regionIndex_});
@@ -426,7 +428,7 @@
   }
 
   /**
-   * @param {!number} regionIndex
+   * @param {number} regionIndex
    * @return {!Promise<!{error: !RmadErrorCode}>}
    */
   setRegion(regionIndex) {
@@ -446,14 +448,14 @@
   }
 
   /**
-   * @return {!Promise<!{skuIndex: !number}>}
+   * @return {!Promise<!{skuIndex: number}>}
    */
   getOriginalSku() {
     return this.methods_.resolveMethod('getOriginalSku');
   }
 
   /**
-   * @param {!number} skuIndex
+   * @param {number} skuIndex
    */
   setGetOriginalSkuResult(skuIndex) {
     this.skuIndex_ = skuIndex;
@@ -461,7 +463,7 @@
   }
 
   /**
-   * @return {!Promise<!{skuIndex: !number}>}
+   * @return {!Promise<!{skuIndex: number}>}
    */
   getSku() {
     this.methods_.setResult('getSku', {skuIndex: this.skuIndex_});
@@ -469,7 +471,7 @@
   }
 
   /**
-   * @param {!number} skuIndex
+   * @param {number} skuIndex
    * @return {!Promise<!{error: !RmadErrorCode}>}
    */
   setSku(skuIndex) {
@@ -503,10 +505,173 @@
   }
 
   /**
+   * Implements ShimlessRmaServiceInterface.ObserveError.
+   * @param {!ErrorObserver} remote
+   */
+  observeError(remote) {
+    this.observables_.observe('ErrorObserver_onError', (error) => {
+      remote.onError(
+          /** @type {!RmadErrorCode} */ (error));
+    });
+  }
+
+  /**
+   * Implements ShimlessRmaServiceInterface.ObserveCalibration.
+   * @param {!CalibrationObserver} remote
+   */
+  observeCalibration(remote) {
+    this.observables_.observe(
+        'CalibrationObserver_onCalibrationUpdated', (component, progress) => {
+          remote.onCalibrationUpdated(
+              /** @type {!CalibrationComponent} */ (component),
+              /** @type {number} */ (progress));
+        });
+  }
+
+  /**
+   * Implements ShimlessRmaServiceInterface.ObserveProvisioning.
+   * @param {!ProvisioningObserver} remote
+   */
+  observeProvisioning(remote) {
+    this.observables_.observe(
+        'ProvisioningObserver_onProvisioningUpdated', (step, progress) => {
+          remote.onProvisioningUpdated(
+              /** @type {!ProvisioningStep} */ (step),
+              /** @type {number} */ (progress));
+        });
+  }
+
+  /**
+   * Implements ShimlessRmaServiceInterface.ObserveHardwareWriteProtectionState.
+   * @param {!HardwareWriteProtectionStateObserver} remote
+   */
+  observeHardwareWriteProtectionState(remote) {
+    this.observables_.observe(
+        'HardwareWriteProtectionStateObserver_onHardwareWriteProtectionStateChanged',
+        (enabled) => {
+          remote.onHardwareWriteProtectionStateChanged(
+              /** @type {boolean} */ (enabled));
+        });
+  }
+
+  /**
+   * Implements ShimlessRmaServiceInterface.ObservePowerCableState.
+   * @param {!PowerCableStateObserver} remote
+   */
+  observePowerCableState(remote) {
+    this.observables_.observe(
+        'PowerCableStateObserver_onPowerCableStateChanged', (pluggedIn) => {
+          remote.onPowerCableStateChanged(/** @type {boolean} */ (pluggedIn));
+        });
+  }
+
+  /**
+   * Causes the error observer to fire after a delay.
+   * @param {!RmadErrorCode} error
+   * @param {number} delayMs
+   */
+  triggerErrorObserver(error, delayMs) {
+    return this.triggerObserverAfterMs('ErrorObserver_onError', error, delayMs);
+  }
+
+  /**
+   * Causes the calibration observer to fire after a delay.
+   * @param {!CalibrationComponent} component
+   * @param {number} progress
+   * @param {number} delayMs
+   */
+  triggerCalibrationObserver(component, progress, delayMs) {
+    return this.triggerObserverAfterMs(
+        'CalibrationObserver_onCalibrationUpdated', [component, progress],
+        delayMs);
+  }
+
+  /**
+   * Causes the provisioning observer to fire after a delay.
+   * @param {!ProvisioningStep} step
+   * @param {number} progress
+   * @param {number} delayMs
+   */
+  triggerProvisioningObserver(step, progress, delayMs) {
+    return this.triggerObserverAfterMs(
+        'ProvisioningObserver_onProvisioningUpdated', [step, progress],
+        delayMs);
+  }
+
+  /**
+   * Causes the hardware write protection observer to fire after a delay.
+   * @param {boolean} enabled
+   * @param {number} delayMs
+   */
+  triggerHardwareWriteProtectionObserver(enabled, delayMs) {
+    return this.triggerObserverAfterMs(
+        'HardwareWriteProtectionStateObserver_onHardwareWriteProtectionStateChanged',
+        enabled, delayMs);
+  }
+
+  /**
+   * Causes the power cable observer to fire after a delay.
+   * @param {boolean} pluggedIn
+   * @param {number} delayMs
+   */
+  triggerPowerCableObserver(pluggedIn, delayMs) {
+    return this.triggerObserverAfterMs(
+        'PowerCableStateObserver_onPowerCableStateChanged', pluggedIn, delayMs);
+  }
+
+  /**
+   * Causes an observer to fire after a delay.
+   * @param {string} method
+   * @param {!T} result
+   * @param {number} delayMs
+   * @template T
+   */
+  triggerObserverAfterMs(method, result, delayMs) {
+    let setDataTriggerAndResolve = function (service, resolve) {
+      service.observables_.setObservableData(method, [result]);
+      service.observables_.trigger(method);
+      resolve();
+    }
+    return new Promise((resolve) => {
+      if (delayMs === 0) {
+        setDataTriggerAndResolve(this, resolve);
+      } else {
+        setTimeout(() => {
+          setDataTriggerAndResolve(this, resolve);
+        }, delayMs);
+      }
+    });
+  }
+
+  /**
+   * Disables all observers and resets provider to its initial state.
+   */
+  reset() {
+    this.registerMethods_();
+    this.registerObservables_();
+
+    this.states_ = [];
+    this.stateIndex_ = 0;
+
+    // This state data is more complicated so the behavior of the get/set
+    // methods is a little different than other fakes in that they don't return
+    // undefined by default.
+    this.components_ = [];
+    this.serialNumber_ = '';
+    this.setSetSerialNumberResult(RmadErrorCode.kOk);
+    this.regionIndex_ = 0;
+    this.setSetRegionResult(RmadErrorCode.kOk);
+    this.skuIndex_ = 0;
+    this.setSetSkuResult(RmadErrorCode.kOk);
+  }
+
+  /**
    * Setup method resolvers.
    * @private
    */
   registerMethods_() {
+    this.methods_ = new FakeMethodResolver();
+
     this.methods_.register('getCurrentState');
     this.methods_.register('getNextState');
     this.methods_.register('getPrevState');
@@ -548,30 +713,26 @@
   }
 
   /**
-   * Disables all observers and resets provider to its initial state.
+   * Setup observables.
+   * @private
    */
-  reset() {
-    this.methods_ = new FakeMethodResolver();
-    this.registerMethods_();
-
-    this.states_ = [];
-    this.stateIndex_ = 0;
-
-    // This state data is more complicated so the behavior of the get/set
-    // methods is a little different than other fakes in that they don't return
-    // undefined by default.
-    this.components_ = [];
-    this.serialNumber_ = '';
-    this.setSetSerialNumberResult(RmadErrorCode.kOk);
-    this.regionIndex_ = 0;
-    this.setSetRegionResult(RmadErrorCode.kOk);
-    this.skuIndex_ = 0;
-    this.setSetSkuResult(RmadErrorCode.kOk);
+  registerObservables_() {
+    if (this.observables_) {
+      this.observables_.stopAllTriggerIntervals();
+    }
+    this.observables_ = new FakeObservables();
+    this.observables_.register('ErrorObserver_onError');
+    this.observables_.register('CalibrationObserver_onCalibrationUpdated');
+    this.observables_.register('ProvisioningObserver_onProvisioningUpdated');
+    this.observables_.register(
+        'HardwareWriteProtectionStateObserver_onHardwareWriteProtectionStateChanged');
+    this.observables_.register(
+        'PowerCableStateObserver_onPowerCableStateChanged');
   }
 
   /**
    * @private
-   * @param {!string} method
+   * @param {string} method
    * @param {!RmaState} expectedState
    * @returns {!Promise<!NextState>}
    */
@@ -615,7 +776,7 @@
    * Sets the value that will be returned when calling state specific functions
    * that progress state. e.g. setSameOwner()
    * @private
-   * @param {!string} method
+   * @param {string} method
    * @param {!RmaState} state
    * @param {!RmadErrorCode} error
    */
diff --git a/ash/content/shimless_rma/resources/shimless_rma.js b/ash/content/shimless_rma/resources/shimless_rma.js
index 6b5a268..412cc61 100644
--- a/ash/content/shimless_rma/resources/shimless_rma.js
+++ b/ash/content/shimless_rma/resources/shimless_rma.js
@@ -71,6 +71,8 @@
   ready() {
     super.ready();
     this.shimlessRmaService_ = getShimlessRmaService();
+
+    // Get the initial state.
     this.fetchState_().then((state) => this.loadState_(state));
   }
 
diff --git a/ash/content/shimless_rma/resources/shimless_rma_types.js b/ash/content/shimless_rma/resources/shimless_rma_types.js
index 63276ca..493bb6fd 100644
--- a/ash/content/shimless_rma/resources/shimless_rma_types.js
+++ b/ash/content/shimless_rma/resources/shimless_rma_types.js
@@ -137,31 +137,60 @@
 /**
  * @typedef {{state: !RmaState, error: !RmadErrorCode}}
  */
- export let State;
-
- /**
-  * @typedef {{currentState: !RmaState, error: !RmadErrorCode}}
-  */
- export let CurrentState;
-
- /**
-  * @typedef {{nextState: !RmaState, error: !RmadErrorCode}}
-  */
- export let NextState;
-
- /**
-  * @typedef {{prevState: !RmaState, error: !RmadErrorCode}}
-  */
- export let PrevState;
+export let State;
 
 /**
- * Type alias for NetworkListObserver.
- * @typedef {{
- *   onError: !function(!RmadErrorCode)
- * }}
+ * @typedef {{currentState: !RmaState, error: !RmadErrorCode}}
+ */
+export let CurrentState;
+
+/**
+ * @typedef {{nextState: !RmaState, error: !RmadErrorCode}}
+ */
+export let NextState;
+
+/**
+ * @typedef {{prevState: !RmaState, error: !RmadErrorCode}}
+ */
+export let PrevState;
+
+/**
+ * Type alias for ErrorObserver.
+ * @typedef {{onError: !function(!RmadErrorCode)}}
  */
 export let ErrorObserver;
 
+/**
+ * Type alias for CalibrationProgressObserver.
+ * @typedef {{
+ *   onCalibrationUpdated: !function(!CalibrationComponent, number)
+ * }}
+ */
+export let CalibrationObserver;
+
+/**
+ * Type alias for ProvisioningProgressObserver.
+ * @typedef {{
+ *   onProvisioningUpdated: !function(!ProvisioningStep, number)
+ * }}
+ */
+export let ProvisioningObserver;
+
+/**
+ * Type alias for HardwareWriteProtectionState.
+ * @typedef {{
+ *   onHardwareWriteProtectionStateChanged: !function(boolean)
+ * }}
+ */
+export let HardwareWriteProtectionStateObserver;
+
+/**
+ * Type alias for PowerCableState.
+ * @typedef {{
+ *   onPowerCableStateChanged: !function(boolean)
+ * }}
+ */
+export let PowerCableStateObserver;
 
 /**
  * Type of ShimlessRmaServiceInterface.setStates function.
diff --git a/ash/content/shortcut_customization_ui/resources/shortcut_customization_app.js b/ash/content/shortcut_customization_ui/resources/shortcut_customization_app.js
index e0ec642..c4a4a137 100644
--- a/ash/content/shortcut_customization_ui/resources/shortcut_customization_app.js
+++ b/ash/content/shortcut_customization_ui/resources/shortcut_customization_app.js
@@ -28,11 +28,15 @@
   ready() {
     super.ready();
     this.$.navigationPanel.addSelector('Chrome OS',
-                                       'chromeos-shortcuts-page');
-    this.$.navigationPanel.addSelector('Browser', 'browser-shortcuts-page');
-    this.$.navigationPanel.addSelector('Android', 'android-shortcuts-page');
+                                       'chromeos-shortcuts-page',
+                                       'navigation-selector:laptop-chromebook');
+    this.$.navigationPanel.addSelector('Browser', 'browser-shortcuts-page',
+                                       'navigation-selector:laptop-chromebook');
+    this.$.navigationPanel.addSelector('Android', 'android-shortcuts-page',
+                                       'navigation-selector:laptop-chromebook');
     this.$.navigationPanel.addSelector('Accessibility',
-                                       'accessibility-shortcuts-page');
+                                       'accessibility-shortcuts-page',
+                                       'navigation-selector:laptop-chromebook');
   }
 }
 
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 3e4fc4f0..55de90b 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -1639,17 +1639,26 @@
   primary_big_view_ = main_view_->AddChildView(std::move(primary_big_view));
   auto* spacing_middle =
       main_view_->AddChildView(std::make_unique<NonAccessibleView>());
-  users_list_ = main_view_->AddChildView(
-      BuildScrollableUsersListView(users, LoginDisplayStyle::kSmall));
+  users_list_ =
+      main_view_->AddChildView(std::make_unique<ScrollableUsersListView>(
+          users,
+          base::BindRepeating(&LockContentsView::SwapToBigUser,
+                              base::Unretained(this)),
+          LoginDisplayStyle::kSmall));
   auto* spacing_right =
       main_view_->AddChildView(std::make_unique<NonAccessibleView>());
 
   // Set width for the |spacing_*| views.
   AddDisplayLayoutAction(base::BindRepeating(
       [](views::View* host_view, views::View* big_user_view,
-         views::View* users_list, views::View* spacing_left,
+         ScrollableUsersListView* users_list, views::View* spacing_left,
          views::View* spacing_middle, views::View* spacing_right,
          bool landscape) {
+        // `users_list` has margins that depend on the current orientation.
+        // Update these here so that the following calculations see the correct
+        // bounds.
+        users_list->UpdateUserViewHostLayoutInsets();
+
         int total_width = host_view->GetPreferredSize().width();
         int available_width =
             total_width - (big_user_view->GetPreferredSize().width() +
@@ -1694,8 +1703,12 @@
   fill = main_view_->AddChildView(std::make_unique<NonAccessibleView>());
   main_layout->SetFlexForView(fill, 1);
 
-  users_list_ = main_view_->AddChildView(
-      BuildScrollableUsersListView(users, LoginDisplayStyle::kExtraSmall));
+  users_list_ =
+      main_view_->AddChildView(std::make_unique<ScrollableUsersListView>(
+          users,
+          base::BindRepeating(&LockContentsView::SwapToBigUser,
+                              base::Unretained(this)),
+          LoginDisplayStyle::kExtraSmall));
 
   // User list size may change after a display metric change.
   AddDisplayLayoutAction(base::BindRepeating(
@@ -2248,20 +2261,6 @@
   return users_list_->GetUserView(user);
 }
 
-std::unique_ptr<ScrollableUsersListView>
-LockContentsView::BuildScrollableUsersListView(
-    const std::vector<LoginUserInfo>& users,
-    LoginDisplayStyle display_style) {
-  auto user_list_view = std::make_unique<ScrollableUsersListView>(
-      users,
-      base::BindRepeating(&LockContentsView::SwapToBigUser,
-                          base::Unretained(this)),
-      display_style);
-  user_list_view->ClipHeightTo(user_list_view->contents()->size().height(),
-                               size().height());
-  return user_list_view;
-}
-
 void LockContentsView::SetDisplayStyle(DisplayStyle style) {
   const bool show_expanded_view =
       style == DisplayStyle::kExclusivePublicAccountExpandedView;
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index 2e5c68b..a569ff1 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -390,11 +390,6 @@
   // Returns the user view for |user|.
   LoginUserView* TryToFindUserView(const AccountId& user);
 
-  // Returns scrollable view with initialized size and rows for all |users|.
-  std::unique_ptr<ScrollableUsersListView> BuildScrollableUsersListView(
-      const std::vector<LoginUserInfo>& users,
-      LoginDisplayStyle display_style);
-
   // Change the visibility of child views based on the |style|.
   void SetDisplayStyle(DisplayStyle style);
 
diff --git a/ash/login/ui/lock_contents_view_unittest.cc b/ash/login/ui/lock_contents_view_unittest.cc
index be64b497..0711dd9 100644
--- a/ash/login/ui/lock_contents_view_unittest.cc
+++ b/ash/login/ui/lock_contents_view_unittest.cc
@@ -278,9 +278,9 @@
       mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock,
       DataDispatcher(),
       std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()));
+  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
   LockContentsView::TestApi lock_contents(contents);
   SetUserCount(3);
-  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
   // Returns the distance between the auth user view and the user view.
   auto calculate_distance = [&]() {
@@ -298,11 +298,13 @@
           widget->GetNativeWindow());
   for (int i = 2; i < 10; ++i) {
     SetUserCount(i);
+    SCOPED_TRACE(testing::Message() << "User count: " << i);
 
     // Start at 0 degrees (landscape).
     display_manager()->SetDisplayRotation(
         display.id(), display::Display::ROTATE_0,
         display::Display::RotationSource::ACTIVE);
+    widget->LayoutRootViewIfNecessary();
     int distance_0deg = calculate_distance();
     EXPECT_NE(distance_0deg, 0);
 
@@ -310,16 +312,18 @@
     display_manager()->SetDisplayRotation(
         display.id(), display::Display::ROTATE_90,
         display::Display::RotationSource::ACTIVE);
+    widget->LayoutRootViewIfNecessary();
     int distance_90deg = calculate_distance();
-    EXPECT_GT(distance_0deg, distance_90deg);
+    EXPECT_LT(distance_90deg, distance_0deg);
 
     // Rotate the display back to 0 degrees (landscape).
     display_manager()->SetDisplayRotation(
         display.id(), display::Display::ROTATE_0,
         display::Display::RotationSource::ACTIVE);
-    int distance_180deg = calculate_distance();
-    EXPECT_EQ(distance_0deg, distance_180deg);
-    EXPECT_NE(distance_0deg, distance_90deg);
+    widget->LayoutRootViewIfNecessary();
+    int distance_0deg_2 = calculate_distance();
+    EXPECT_EQ(distance_0deg_2, distance_0deg);
+    EXPECT_NE(distance_0deg_2, distance_90deg);
   }
 }
 
@@ -329,10 +333,10 @@
       mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock,
       DataDispatcher(),
       std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()));
+  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
   SetUserCount(9);
   ScrollableUsersListView* users_list =
       LockContentsView::TestApi(contents).users_list();
-  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
   // Users list in extra small layout should adjust its height to parent.
   EXPECT_EQ(contents->height(), users_list->height());
@@ -366,10 +370,10 @@
       mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock,
       DataDispatcher(),
       std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()));
+  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
   SetUserCount(4);
   ScrollableUsersListView* users_list =
       LockContentsView::TestApi(contents).users_list();
-  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
   // Calculate top spacing between users list and lock screen contents.
   auto top_margin = [&]() {
@@ -547,11 +551,11 @@
       mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock,
       DataDispatcher(),
       std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()));
+  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
   LockContentsView::TestApi lock_contents(contents);
   SetUserCount(5);
   ScrollableUsersListView::TestApi users_list(lock_contents.users_list());
   EXPECT_EQ(users().size() - 1, users_list.user_views().size());
-  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
   LoginBigUserView* auth_view = lock_contents.primary_big_view();
 
@@ -1896,9 +1900,9 @@
       mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock,
       DataDispatcher(),
       std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()));
+  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
   LockContentsView::TestApi contents_test_api(contents);
   AddUsers(3);
-  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
   LoginPasswordView* password_view =
       LoginAuthUserView::TestApi(
@@ -1922,8 +1926,8 @@
   auto* contents = new LockContentsView(
       mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock,
       DataDispatcher(), std::move(fake_detachable_base_model));
-  SetUserCount(3);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
+  SetUserCount(3);
 
   LockContentsView::TestApi test_api(contents);
   LoginBigUserView* primary_view = test_api.primary_big_view();
@@ -2672,8 +2676,8 @@
       mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLogin,
       DataDispatcher(),
       std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()));
-  SetUserCount(3);
   SetWidget(CreateWidgetWithContent(contents));
+  SetUserCount(3);
 
   LockContentsView::TestApi lock_contents(contents);
   ScrollableUsersListView::TestApi users_list(lock_contents.users_list());
diff --git a/ash/login/ui/scrollable_users_list_view.cc b/ash/login/ui/scrollable_users_list_view.cc
index 9be492f..aa3ce621 100644
--- a/ash/login/ui/scrollable_users_list_view.cc
+++ b/ash/login/ui/scrollable_users_list_view.cc
@@ -353,6 +353,16 @@
   return nullptr;
 }
 
+void ScrollableUsersListView::UpdateUserViewHostLayoutInsets() {
+  DCHECK(GetWidget());
+  bool should_show_landscape =
+      login_views_utils::ShouldShowLandscape(GetWidget());
+  LayoutParams layout_params = BuildLayoutForStyle(display_style_);
+  user_view_host_layout_->set_inside_border_insets(
+      should_show_landscape ? layout_params.insets_landscape
+                            : layout_params.insets_portrait);
+}
+
 void ScrollableUsersListView::Layout() {
   DCHECK(user_view_host_layout_);
 
@@ -364,13 +374,7 @@
       PreferredSizeChanged();
   }
 
-  // Update the user view layout.
-  bool should_show_landscape =
-      login_views_utils::ShouldShowLandscape(GetWidget());
-  LayoutParams layout_params = BuildLayoutForStyle(display_style_);
-  user_view_host_layout_->set_inside_border_insets(
-      should_show_landscape ? layout_params.insets_landscape
-                            : layout_params.insets_portrait);
+  UpdateUserViewHostLayoutInsets();
 
   // Layout everything.
   ScrollView::Layout();
diff --git a/ash/login/ui/scrollable_users_list_view.h b/ash/login/ui/scrollable_users_list_view.h
index a5e7ee9..11034f2 100644
--- a/ash/login/ui/scrollable_users_list_view.h
+++ b/ash/login/ui/scrollable_users_list_view.h
@@ -63,6 +63,10 @@
   // Returns user view with |account_id| if it exists or nullptr otherwise.
   LoginUserView* GetUserView(const AccountId& account_id);
 
+  // Updates the insets for the `user_view_host_layout_` based on whether the
+  // view is in landscape or portrait mode.
+  void UpdateUserViewHostLayoutInsets();
+
   // views::View:
   void Layout() override;
   void OnPaintBackground(gfx::Canvas* canvas) override;
diff --git a/ash/public/cpp/holding_space/holding_space_image.cc b/ash/public/cpp/holding_space/holding_space_image.cc
index adb4ce41..8ff85e1 100644
--- a/ash/public/cpp/holding_space/holding_space_image.cc
+++ b/ash/public/cpp/holding_space/holding_space_image.cc
@@ -115,6 +115,7 @@
   gfx::Size size;
   switch (type) {
     case HoldingSpaceItem::Type::kArcDownload:
+    case HoldingSpaceItem::Type::kDiagnosticsLog:
     case HoldingSpaceItem::Type::kDownload:
     case HoldingSpaceItem::Type::kNearbyShare:
     case HoldingSpaceItem::Type::kPinnedFile:
diff --git a/ash/public/cpp/holding_space/holding_space_item.cc b/ash/public/cpp/holding_space/holding_space_item.cc
index 46c2a58..86d50ee0 100644
--- a/ash/public/cpp/holding_space/holding_space_item.cc
+++ b/ash/public/cpp/holding_space/holding_space_item.cc
@@ -70,6 +70,7 @@
 bool HoldingSpaceItem::IsDownload(HoldingSpaceItem::Type type) {
   switch (type) {
     case Type::kArcDownload:
+    case Type::kDiagnosticsLog:
     case Type::kDownload:
       return true;
     case Type::kNearbyShare:
@@ -198,6 +199,7 @@
     case Type::kScreenshot:
       return true;
     case Type::kArcDownload:
+    case Type::kDiagnosticsLog:
     case Type::kDownload:
     case Type::kNearbyShare:
     case Type::kPinnedFile:
diff --git a/ash/public/cpp/holding_space/holding_space_item.h b/ash/public/cpp/holding_space/holding_space_item.h
index 528ce97..8abf670 100644
--- a/ash/public/cpp/holding_space/holding_space_item.h
+++ b/ash/public/cpp/holding_space/holding_space_item.h
@@ -38,7 +38,8 @@
     kScreenRecording = 4,
     kArcDownload = 5,
     kPrintedPdf = 6,
-    kMaxValue = kPrintedPdf,
+    kDiagnosticsLog = 7,
+    kMaxValue = kDiagnosticsLog,
   };
 
   HoldingSpaceItem(const HoldingSpaceItem&) = delete;
diff --git a/ash/public/cpp/holding_space/holding_space_metrics.cc b/ash/public/cpp/holding_space/holding_space_metrics.cc
index beff27b6..583b3dd 100644
--- a/ash/public/cpp/holding_space/holding_space_metrics.cc
+++ b/ash/public/cpp/holding_space/holding_space_metrics.cc
@@ -70,6 +70,8 @@
   switch (type) {
     case HoldingSpaceItem::Type::kArcDownload:
       return "ArcDownload";
+    case HoldingSpaceItem::Type::kDiagnosticsLog:
+      return "DiagnosticsLog";
     case HoldingSpaceItem::Type::kDownload:
       return "Download";
     case HoldingSpaceItem::Type::kNearbyShare:
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index 9448bf4..e62d97c 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -9,6 +9,8 @@
   icon_directory = "."
 
   sources = [
+    "add_cellular_network.icon",
+    "add_cellular_network_rtl.icon",
     "always_show_shelf.icon",
     "auto_hide.icon",
     "autoclick.icon",
diff --git a/components/vector_icons/add_cellular_network.icon b/ash/resources/vector_icons/add_cellular_network.icon
similarity index 100%
rename from components/vector_icons/add_cellular_network.icon
rename to ash/resources/vector_icons/add_cellular_network.icon
diff --git a/ash/resources/vector_icons/add_cellular_network_rtl.icon b/ash/resources/vector_icons/add_cellular_network_rtl.icon
new file mode 100644
index 0000000..7f2fa1f
--- /dev/null
+++ b/ash/resources/vector_icons/add_cellular_network_rtl.icon
@@ -0,0 +1,28 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 3, 16,
+CUBIC_TO, 3, 16.55f, 3.45f, 17, 4, 17,
+H_LINE_TO, 11,
+CUBIC_TO, 10.37f, 16.17f, 10, 15.13f, 10, 14,
+CUBIC_TO, 10, 12.77f, 10.44f, 11.64f, 11.18f, 10.77f,
+LINE_TO, 4.71f, 4.29f,
+CUBIC_TO, 4.08f, 3.66f, 3, 4.11f, 3, 5,
+V_LINE_TO, 16,
+CLOSE,
+MOVE_TO, 16, 17,
+V_LINE_TO, 15,
+H_LINE_TO, 18,
+V_LINE_TO, 13,
+H_LINE_TO, 16,
+V_LINE_TO, 11,
+H_LINE_TO, 14,
+V_LINE_TO, 13,
+H_LINE_TO, 12,
+V_LINE_TO, 15,
+H_LINE_TO, 14,
+V_LINE_TO, 17,
+H_LINE_TO, 16,
+CLOSE
\ No newline at end of file
diff --git a/ash/system/holding_space/downloads_section.cc b/ash/system/holding_space/downloads_section.cc
index 464c5f3..08c769b 100644
--- a/ash/system/holding_space/downloads_section.cc
+++ b/ash/system/holding_space/downloads_section.cc
@@ -130,6 +130,7 @@
     : HoldingSpaceItemViewsSection(delegate,
                                    /*supported_types=*/
                                    {HoldingSpaceItem::Type::kArcDownload,
+                                    HoldingSpaceItem::Type::kDiagnosticsLog,
                                     HoldingSpaceItem::Type::kDownload,
                                     HoldingSpaceItem::Type::kNearbyShare,
                                     HoldingSpaceItem::Type::kPrintedPdf},
diff --git a/ash/system/holding_space/holding_space_tray_unittest.cc b/ash/system/holding_space/holding_space_tray_unittest.cc
index 7e74880..74d2fb6 100644
--- a/ash/system/holding_space/holding_space_tray_unittest.cc
+++ b/ash/system/holding_space/holding_space_tray_unittest.cc
@@ -955,6 +955,7 @@
     All,
     HoldingSpaceTrayDownloadsSectionTest,
     ::testing::Values(HoldingSpaceItem::Type::kArcDownload,
+                      HoldingSpaceItem::Type::kDiagnosticsLog,
                       HoldingSpaceItem::Type::kDownload,
                       HoldingSpaceItem::Type::kNearbyShare,
                       HoldingSpaceItem::Type::kPrintedPdf));
diff --git a/ash/system/network/cellular_setup_notifier.cc b/ash/system/network/cellular_setup_notifier.cc
index ded72dfd..b5b18d8e 100644
--- a/ash/system/network/cellular_setup_notifier.cc
+++ b/ash/system/network/cellular_setup_notifier.cc
@@ -8,6 +8,7 @@
 #include "ash/public/cpp/network_config_service.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/public/cpp/system_tray_client.h"
+#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -183,7 +184,7 @@
           message_center::RichNotificationData(),
           base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
               base::BindRepeating(&OnCellularSetupNotificationClicked)),
-          vector_icons::kAddCellularNetworkIcon,
+          kAddCellularNetworkIcon,
           message_center::SystemNotificationWarningLevel::NORMAL);
 
   message_center::MessageCenter* message_center =
diff --git a/ash/system/network/network_section_header_view.cc b/ash/system/network/network_section_header_view.cc
index ab86945a..4cb0e16 100644
--- a/ash/system/network/network_section_header_view.cc
+++ b/ash/system/network/network_section_header_view.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/metrics/user_metrics_recorder.h"
 #include "ash/public/cpp/system_tray_client.h"
+#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -353,10 +354,12 @@
   int tooltip_message_id = IsCellularDeviceInhibited()
                                ? IDS_ASH_STATUS_TRAY_INHIBITED_CELLULAR
                                : IDS_ASH_STATUS_TRAY_ADD_CELLULAR_LABEL;
+  const gfx::VectorIcon& icon = base::i18n::IsRTL() ? kAddCellularNetworkRtlIcon
+                                                    : kAddCellularNetworkIcon;
   add_esim_button_ = new TopShortcutButton(
       base::BindRepeating(&MobileSectionHeaderView::AddCellularButtonPressed,
                           base::Unretained(this)),
-      vector_icons::kAddCellularNetworkIcon, tooltip_message_id);
+      icon, tooltip_message_id);
 
   add_esim_button_->SetEnabled(enabled && !IsCellularDeviceInhibited());
 
diff --git a/ash/wm/container_finder.cc b/ash/wm/container_finder.cc
index 499c66ade..171d5f0 100644
--- a/ash/wm/container_finder.cc
+++ b/ash/wm/container_finder.cc
@@ -12,6 +12,7 @@
 #include "ash/wm/always_on_top_controller.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
+#include "components/full_restore/full_restore_utils.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/gfx/geometry/rect.h"
@@ -91,6 +92,15 @@
     target_root = FindContainerRoot(bounds_in_screen);
   }
 
+  // For full restore, the window may be created before the associated full
+  // restore data can be retrieved. In this case, we will place it in a hidden
+  // container and will move it to a desk container when the full restore data
+  // can be retrieved. An example would be ARC windows, which can be created
+  // before their associated tasks are, which are required to retrieve full
+  // restore data.
+  if (window->GetProperty(full_restore::kParentToHiddenContainerKey))
+    return target_root->GetChildById(kShellWindowId_UnparentedControlContainer);
+
   switch (window->GetType()) {
     case aura::client::WINDOW_TYPE_NORMAL:
     case aura::client::WINDOW_TYPE_POPUP:
diff --git a/ash/wm/full_restore/full_restore_controller.cc b/ash/wm/full_restore/full_restore_controller.cc
index a09b753..9cc11d3 100644
--- a/ash/wm/full_restore/full_restore_controller.cc
+++ b/ash/wm/full_restore/full_restore_controller.cc
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
+#include "ash/wm/container_finder.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -24,6 +25,7 @@
 #include "components/full_restore/full_restore_utils.h"
 #include "components/prefs/pref_service.h"
 #include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/window_parenting_client.h"
 #include "ui/aura/window.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
@@ -63,6 +65,12 @@
 constexpr AppType kSupportedAppTypes[3] = {
     AppType::BROWSER, AppType::CHROME_APP, AppType::ARC_APP};
 
+std::unique_ptr<full_restore::WindowInfo> GetWindowInfo(aura::Window* window) {
+  return g_read_window_callback_for_testing
+             ? g_read_window_callback_for_testing.Run(window)
+             : full_restore::GetWindowInfo(window);
+}
+
 // Returns the sibling of `window` that `window` should be stacked below based
 // on restored activation indices. Returns nullptr if `window` does not need
 // to be moved in the z-ordering. Should be called after `window` is added as
@@ -199,15 +207,38 @@
 
 void FullRestoreController::OnWidgetInitialized(views::Widget* widget) {
   DCHECK(widget);
+
+  aura::Window* window = widget->GetNativeWindow();
+  if (window->GetProperty(full_restore::kParentToHiddenContainerKey))
+    return;
+
   UpdateAndObserveWindow(widget->GetNativeWindow());
 }
 
 void FullRestoreController::OnARCTaskReadyForUnparentedWindow(
     aura::Window* window) {
   DCHECK(window);
-  window->SetProperty(full_restore::kParentToHiddenContainerKey, false);
+  DCHECK(window->GetProperty(full_restore::kParentToHiddenContainerKey));
 
-  // TODO(crbug.com/1205148): Reparent and call `UpdateAndObserveWindow()`.
+  std::unique_ptr<full_restore::WindowInfo> window_info = GetWindowInfo(window);
+  if (window_info) {
+    const int desk_id = window_info->desk_id
+                            ? int{*window_info->desk_id}
+                            : aura::client::kUnassignedWorkspace;
+    window->SetProperty(aura::client::kWindowWorkspaceKey, desk_id);
+    window->SetProperty(aura::client::kVisibleOnAllWorkspacesKey,
+                        window_info->visible_on_all_workspaces.has_value());
+  }
+
+  // Now that the hidden container key is cleared,
+  // `aura::client::ParentWindowWithContext` should parent `window` to a valid
+  // desk container.
+  window->SetProperty(full_restore::kParentToHiddenContainerKey, false);
+  aura::client::ParentWindowWithContext(window,
+                                        /*context=*/window->GetRootWindow(),
+                                        window->GetBoundsInScreen());
+
+  UpdateAndObserveWindow(window);
 }
 
 void FullRestoreController::OnWindowStackingChanged(aura::Window* window) {
diff --git a/ash/wm/full_restore/full_restore_controller_unittest.cc b/ash/wm/full_restore/full_restore_controller_unittest.cc
index cbaffc0..6e6f54e 100644
--- a/ash/wm/full_restore/full_restore_controller_unittest.cc
+++ b/ash/wm/full_restore/full_restore_controller_unittest.cc
@@ -119,7 +119,8 @@
       aura::Window* root_window = Shell::GetPrimaryRootWindow(),
       absl::optional<int32_t> restore_window_id = absl::nullopt,
       chromeos::WindowStateType window_state_type =
-          chromeos::WindowStateType::kNormal) {
+          chromeos::WindowStateType::kNormal,
+      bool is_taskless_arc_app = false) {
     // Full restore widgets are inactive when created as we do not want to take
     // activation from a possible activated window, and we want to stack them in
     // a certain order.
@@ -140,9 +141,17 @@
                                        *restore_window_id);
     }
 
+    if (is_taskless_arc_app) {
+      widget_builder
+          .SetWindowProperty(full_restore::kParentToHiddenContainerKey, true)
+          .SetWindowProperty(aura::client::kAppType,
+                             static_cast<int>(AppType::ARC_APP));
+    }
+
     views::Widget* widget = widget_builder.BuildOwnedByNativeWidget();
     SetResizable(widget);
-    FullRestoreController::Get()->OnWidgetInitialized(widget);
+    if (!is_taskless_arc_app)
+      FullRestoreController::Get()->OnWidgetInitialized(widget);
     if (window_state_type != chromeos::WindowStateType::kMinimized)
       widget->Show();
     return widget;
@@ -152,7 +161,8 @@
   // `fake_full_restore_file_`. Returns nullptr if there is not an entry that
   // matches `restore_window_id`.
   views::Widget* CreateTestFullRestoredWidgetFromRestoreId(
-      int32_t restore_window_id) {
+      int32_t restore_window_id,
+      bool is_taskless_arc_app) {
     if (!fake_full_restore_file_.contains(restore_window_id))
       return nullptr;
 
@@ -162,9 +172,16 @@
     const int32_t activation_index = info->activation_index.value_or(-1);
     const auto window_state_type =
         info->window_state_type.value_or(chromeos::WindowStateType::kNormal);
-    return CreateTestFullRestoredWidget(activation_index, bounds,
-                                        Shell::GetPrimaryRootWindow(),
-                                        restore_window_id, window_state_type);
+    return CreateTestFullRestoredWidget(
+        activation_index, bounds, Shell::GetPrimaryRootWindow(),
+        restore_window_id, window_state_type, is_taskless_arc_app);
+  }
+
+  views::Widget* CreateTestFullRestoredWidgetFromRestoreId(
+      int32_t restore_window_id) {
+    return CreateTestFullRestoredWidgetFromRestoreId(
+        restore_window_id,
+        /*is_taskless_arc_app=*/false);
   }
 
   void VerifyStackingOrder(aura::Window* parent,
@@ -179,21 +196,48 @@
   // Adds an entry to the fake full restore file. Calling
   // If `CreateTestFullRestoreWidget` is called with a matching
   // `restore_window_id`, it will read and set the values set here.
-  void AddEntryToFakeFile(
-      int restore_window_id,
-      const gfx::Rect& bounds,
-      chromeos::WindowStateType window_state_type,
-      int32_t activation_index = -1,
-      int64_t display_id = WindowTreeHostManager::GetPrimaryDisplayId()) {
+  void AddEntryToFakeFile(int restore_window_id,
+                          const gfx::Rect& bounds,
+                          chromeos::WindowStateType window_state_type,
+                          int32_t activation_index,
+                          int64_t display_id,
+                          int32_t desk_id) {
     DCHECK(!fake_full_restore_file_.contains(restore_window_id));
     auto window_info = std::make_unique<full_restore::WindowInfo>();
     window_info->current_bounds = bounds;
     window_info->window_state_type = window_state_type;
     window_info->activation_index = activation_index;
     window_info->display_id = display_id;
+    window_info->desk_id = desk_id;
     fake_full_restore_file_[restore_window_id].info = std::move(window_info);
   }
 
+  void AddEntryToFakeFile(int restore_window_id,
+                          const gfx::Rect& bounds,
+                          chromeos::WindowStateType window_state_type,
+                          int32_t activation_index,
+                          int64_t display_id) {
+    AddEntryToFakeFile(restore_window_id, bounds, window_state_type,
+                       activation_index, display_id, /*desk_id=*/1);
+  }
+
+  void AddEntryToFakeFile(int restore_window_id,
+                          const gfx::Rect& bounds,
+                          chromeos::WindowStateType window_state_type) {
+    AddEntryToFakeFile(
+        restore_window_id, bounds, window_state_type, /*activation_index=*/-1,
+        WindowTreeHostManager::GetPrimaryDisplayId(), /*desk_id=*/1);
+  }
+
+  void AddEntryToFakeFile(int restore_window_id,
+                          const gfx::Rect& bounds,
+                          chromeos::WindowStateType window_state_type,
+                          int32_t desk_id) {
+    AddEntryToFakeFile(restore_window_id, bounds, window_state_type,
+                       /*activation_index=*/-1,
+                       WindowTreeHostManager::GetPrimaryDisplayId(), desk_id);
+  }
+
   // AshTestBase:
   void SetUp() override {
     scoped_feature_list_.InitAndEnableFeature(features::kFullRestore);
@@ -955,4 +999,102 @@
   EXPECT_EQ(0u, GetRestorePropertyClearCallbacks().size());
 }
 
+// Tests full restore behavior for when a ARC window is created without an
+// associated task.
+TEST_F(FullRestoreControllerTest, ArcAppWindowCreatedWithoutTask) {
+  constexpr int kRestoreId = 1;
+
+  // Create enough desks so that we can parent to the expected desk.
+  auto* desks_controller = DesksController::Get();
+  for (int i = 0; i < 4; ++i)
+    desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+
+  // Add a normal window to the fake file. The target desk is desk 3.
+  AddEntryToFakeFile(kRestoreId, gfx::Rect(400, 400),
+                     chromeos::WindowStateType::kNormal, 3);
+
+  aura::Window* root_window = Shell::GetPrimaryRootWindow();
+
+  // Restore the window, it should go to the invisible unparented container for
+  // now.
+  auto* restored_window = CreateTestFullRestoredWidgetFromRestoreId(
+                              kRestoreId, /*is_taskless_arc_app=*/true)
+                              ->GetNativeWindow();
+  EXPECT_EQ(Shell::GetContainer(root_window,
+                                kShellWindowId_UnparentedControlContainer),
+            restored_window->parent());
+
+  // Simulate having the task ready. Our `restored_window` should now be
+  // parented to the desk associated with desk 3, which is desk D.
+  FullRestoreController::Get()->OnARCTaskReadyForUnparentedWindow(
+      restored_window);
+  EXPECT_EQ(Shell::GetContainer(root_window, kShellWindowId_DeskContainerD),
+            restored_window->parent());
+}
+
+// Tests that parenting ARC windows to hidden container works in the multi
+// display scenario, including if a display gets disconnected partway through.
+TEST_F(FullRestoreControllerTest, ArcAppWindowCreatedWithoutTaskMultiDisplay) {
+  UpdateDisplay("800x800,801+0-800x800");
+
+  const int64_t primary_id = WindowTreeHostManager::GetPrimaryDisplayId();
+  const int64_t second_id = display_manager()->GetDisplayAt(1).id();
+  display::ManagedDisplayInfo primary_info =
+      display_manager()->GetDisplayInfo(primary_id);
+  display::ManagedDisplayInfo second_info =
+      display_manager()->GetDisplayInfo(second_id);
+
+  aura::Window* primary_root_window = Shell::GetPrimaryRootWindow();
+  aura::Window* secondary_root_window = Shell::GetAllRootWindows()[1];
+
+  // Create enough desks so that we can parent to the expected desk.
+  auto* desks_controller = DesksController::Get();
+  for (int i = 0; i < 4; ++i)
+    desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+
+  // Add two normal windows to the fake file. The target desk is desk 3 and the
+  // target display is the secondary one.
+  constexpr int kRestoreId1 = 1;
+  constexpr int kRestoreId2 = 2;
+  AddEntryToFakeFile(kRestoreId1, gfx::Rect(900, 0, 400, 400),
+                     chromeos::WindowStateType::kNormal, 3);
+  AddEntryToFakeFile(kRestoreId2, gfx::Rect(900, 0, 400, 400),
+                     chromeos::WindowStateType::kNormal, 3);
+
+  // Restore the first window, it should go to the invisible unparented
+  // container for the secondary display until the ARC task is ready.
+  auto* restored_window1 = CreateTestFullRestoredWidgetFromRestoreId(
+                               kRestoreId1, /*is_taskless_arc_app=*/true)
+                               ->GetNativeWindow();
+  EXPECT_EQ(Shell::GetContainer(secondary_root_window,
+                                kShellWindowId_UnparentedControlContainer),
+            restored_window1->parent());
+  FullRestoreController::Get()->OnARCTaskReadyForUnparentedWindow(
+      restored_window1);
+  EXPECT_EQ(
+      Shell::GetContainer(secondary_root_window, kShellWindowId_DeskContainerD),
+      restored_window1->parent());
+
+  // Restore the second window, it should also go to the invisible unparented
+  // container for the secondary display.
+  auto* restored_window2 = CreateTestFullRestoredWidgetFromRestoreId(
+                               kRestoreId2, /*is_taskless_arc_app=*/true)
+                               ->GetNativeWindow();
+  EXPECT_EQ(Shell::GetContainer(secondary_root_window,
+                                kShellWindowId_UnparentedControlContainer),
+            restored_window2->parent());
+
+  // Remove the secondary display. When the ARC task is ready, it should go to
+  // container associated with desk 3 on the primary display.
+  std::vector<display::ManagedDisplayInfo> display_info_list;
+  display_info_list.push_back(primary_info);
+  display_manager()->OnNativeDisplaysChanged(display_info_list);
+
+  FullRestoreController::Get()->OnARCTaskReadyForUnparentedWindow(
+      restored_window2);
+  EXPECT_EQ(
+      Shell::GetContainer(primary_root_window, kShellWindowId_DeskContainerD),
+      restored_window2->parent());
+}
+
 }  // namespace ash
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index 1825874..844910c 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -34,6 +34,7 @@
 #include "chromeos/ui/base/window_pin_type.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "chromeos/ui/base/window_state_type.h"
+#include "components/full_restore/full_restore_utils.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/layout_manager.h"
 #include "ui/aura/window.h"
@@ -74,6 +75,13 @@
          container_id == kShellWindowId_ArcVirtualKeyboardContainer;
 }
 
+// ARC windows will not be in a top level container until they are associated
+// with a task. We still want a WindowState created for these windows as they
+// will be moved to a top level container soon.
+bool IsTemporarilyHiddenForFullrestore(aura::Window* window) {
+  return window->GetProperty(full_restore::kParentToHiddenContainerKey);
+}
+
 // A tentative class to set the bounds on the window.
 // TODO(oshima): Once all logic is cleaned up, move this to the real layout
 // manager with proper friendship.
@@ -917,8 +925,13 @@
 
   DCHECK(window->parent());
 
-  if (!IsToplevelContainer(window->parent()))
+  // WindowState is only for windows in top level container, unless they are
+  // temporarily hidden when launched by full restore. The will be reparented to
+  // a top level container soon, and need a WindowState.
+  if (!IsToplevelContainer(window->parent()) &&
+      !IsTemporarilyHiddenForFullrestore(window)) {
     return nullptr;
+  }
 
   state = new WindowState(window);
   window->SetProperty(kWindowStateKey, state);
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 2e85cd0e..df24024 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1280,6 +1280,7 @@
       "files/file_path_watcher_linux.cc",
       "files/file_path_watcher_linux.h",
       "files/file_util_linux.cc",
+      "files/scoped_file_linux.cc",
       "process/internal_linux.cc",
       "process/internal_linux.h",
       "process/memory_linux.cc",
@@ -1936,6 +1937,8 @@
         "allocator/partition_allocator/starscan/starscan_fwd.h",
         "allocator/partition_allocator/starscan/stats_collector.cc",
         "allocator/partition_allocator/starscan/stats_collector.h",
+        "allocator/partition_allocator/starscan/write_protector.cc",
+        "allocator/partition_allocator/starscan/write_protector.h",
         "allocator/partition_allocator/thread_cache.cc",
         "allocator/partition_allocator/thread_cache.h",
         "allocator/partition_allocator/yield_processor.h",
@@ -3326,7 +3329,10 @@
   }
 
   if (is_linux || is_chromeos) {
-    sources += [ "debug/proc_maps_linux_unittest.cc" ]
+    sources += [
+      "debug/proc_maps_linux_unittest.cc",
+      "files/scoped_file_linux_unittest.cc",
+    ]
   }
 
   if (is_mac) {
diff --git a/base/allocator/allocator_shim.h b/base/allocator/allocator_shim.h
index 4ec8919..080029d 100644
--- a/base/allocator/allocator_shim.h
+++ b/base/allocator/allocator_shim.h
@@ -167,7 +167,7 @@
 #endif
 
 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && PA_ALLOW_PCSCAN
-BASE_EXPORT void EnablePCScan();
+BASE_EXPORT void EnablePCScan(bool dcscan);
 #endif
 
 }  // namespace allocator
diff --git a/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc b/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc
index 049fda0..dc834318c 100644
--- a/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc
+++ b/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc
@@ -484,8 +484,10 @@
 #endif  // BUILDFLAG(ENABLE_RUNTIME_BACKUP_REF_PTR_CONTROL)
 
 #if PA_ALLOW_PCSCAN
-void EnablePCScan() {
-  internal::PCScan::Initialize();
+void EnablePCScan(bool dcscan) {
+  internal::PCScan::Initialize(
+      dcscan ? internal::PCScan::WantedWriteProtectionMode::kEnabled
+             : internal::PCScan::WantedWriteProtectionMode::kDisabled);
   internal::PCScan::RegisterScannableRoot(Allocator());
   if (Allocator() != AlignedAllocator())
     internal::PCScan::RegisterScannableRoot(AlignedAllocator());
diff --git a/base/allocator/partition_allocator/partition_alloc_config.h b/base/allocator/partition_allocator/partition_alloc_config.h
index 26aa0be..254476f9 100644
--- a/base/allocator/partition_allocator/partition_alloc_config.h
+++ b/base/allocator/partition_allocator/partition_alloc_config.h
@@ -34,6 +34,12 @@
 #define PA_STARSCAN_NEON_SUPPORTED
 #endif
 
+#if defined(PA_HAS_64_BITS_POINTERS) && \
+    (defined(OS_LINUX) || defined(OS_ANDROID))
+// TODO(bikineev): Enable for ChromeOS.
+#define PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED
+#endif
+
 // POSIX is not only UNIX, e.g. macOS and other OSes. We do use Linux-specific
 // features such as futex(2).
 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
diff --git a/base/allocator/partition_allocator/partition_alloc_features.cc b/base/allocator/partition_allocator/partition_alloc_features.cc
index 75dda64..00c093a34 100644
--- a/base/allocator/partition_allocator/partition_alloc_features.cc
+++ b/base/allocator/partition_allocator/partition_alloc_features.cc
@@ -54,5 +54,8 @@
 #endif  // defined(PA_PCSCAN_STACK_SUPPORTED)
 };
 
+const Feature kPartitionAllocDCScan{"PartitionAllocDCScan",
+                                    FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace base
diff --git a/base/allocator/partition_allocator/partition_alloc_features.h b/base/allocator/partition_allocator/partition_alloc_features.h
index d4f290da..baff1f55 100644
--- a/base/allocator/partition_allocator/partition_alloc_features.h
+++ b/base/allocator/partition_allocator/partition_alloc_features.h
@@ -28,6 +28,7 @@
 
 extern const BASE_EXPORT Feature kPartitionAllocPCScanMUAwareScheduler;
 extern const BASE_EXPORT Feature kPartitionAllocPCScanStackScanning;
+extern const BASE_EXPORT Feature kPartitionAllocDCScan;
 
 extern const BASE_EXPORT Feature kPartitionAllocLazyCommit;
 
diff --git a/base/allocator/partition_allocator/partition_page.h b/base/allocator/partition_allocator/partition_page.h
index a19d74ae..c927888 100644
--- a/base/allocator/partition_allocator/partition_page.h
+++ b/base/allocator/partition_allocator/partition_page.h
@@ -367,8 +367,8 @@
       reinterpret_cast<uintptr_t>(maybe_inner_ptr) & kSuperPageBaseMask);
   auto* extent = reinterpret_cast<PartitionSuperPageExtentEntry<thread_safe>*>(
       PartitionSuperPageToMetadataArea(super_page_ptr));
-  PA_DCHECK(
-      IsWithinSuperPagePayload(maybe_inner_ptr, extent->root->IsScanEnabled()));
+  PA_DCHECK(IsWithinSuperPagePayload(maybe_inner_ptr,
+                                     extent->root->IsQuarantineAllowed()));
 #endif
   auto* slot_span =
       SlotSpanMetadata<thread_safe>::FromSlotInnerPtr(maybe_inner_ptr);
diff --git a/base/allocator/partition_allocator/starscan/pcscan.cc b/base/allocator/partition_allocator/starscan/pcscan.cc
index 0b843724..fe343661 100644
--- a/base/allocator/partition_allocator/starscan/pcscan.cc
+++ b/base/allocator/partition_allocator/starscan/pcscan.cc
@@ -9,8 +9,8 @@
 namespace base {
 namespace internal {
 
-void PCScan::Initialize() {
-  PCScanInternal::Instance().Initialize();
+void PCScan::Initialize(WantedWriteProtectionMode wpmode) {
+  PCScanInternal::Instance().Initialize(wpmode);
 }
 
 void PCScan::RegisterScannableRoot(Root* root) {
@@ -68,8 +68,8 @@
   ReinitPCScanMetadataAllocatorForTesting();          // IN-TEST
 }
 
-void PCScan::ReinitForTesting() {
-  PCScanInternal::Instance().ReinitForTesting();  // IN-TEST
+void PCScan::ReinitForTesting(WantedWriteProtectionMode wpmode) {
+  PCScanInternal::Instance().ReinitForTesting(wpmode);  // IN-TEST
 }
 
 void PCScan::FinishScanForTesting() {
diff --git a/base/allocator/partition_allocator/starscan/pcscan.h b/base/allocator/partition_allocator/starscan/pcscan.h
index 1c43430..23035c0 100644
--- a/base/allocator/partition_allocator/starscan/pcscan.h
+++ b/base/allocator/partition_allocator/starscan/pcscan.h
@@ -52,11 +52,18 @@
     kEager,
   };
 
+  // Based on the provided mode, PCScan will try to use a certain
+  // WriteProtector, if supported by the system.
+  enum class WantedWriteProtectionMode : uint8_t {
+    kDisabled,
+    kEnabled,
+  };
+
   PCScan(const PCScan&) = delete;
   PCScan& operator=(const PCScan&) = delete;
 
   // Initializes PCScan and prepares internal data structures.
-  static void Initialize();
+  static void Initialize(WantedWriteProtectionMode);
 
   // Registers a root for scanning.
   static void RegisterScannableRoot(Root* root);
@@ -132,7 +139,7 @@
   static void FinishScanForTesting();
 
   // Reinitialize internal structures (e.g. card table).
-  static void ReinitForTesting();
+  static void ReinitForTesting(WantedWriteProtectionMode);
 
   size_t epoch() const { return scheduler_.epoch(); }
 
diff --git a/base/allocator/partition_allocator/starscan/pcscan_internal.cc b/base/allocator/partition_allocator/starscan/pcscan_internal.cc
index 1467042e..c2a1db4f 100644
--- a/base/allocator/partition_allocator/starscan/pcscan_internal.cc
+++ b/base/allocator/partition_allocator/starscan/pcscan_internal.cc
@@ -22,6 +22,7 @@
 #include "base/allocator/partition_allocator/partition_address_space.h"
 #include "base/allocator/partition_allocator/partition_alloc.h"
 #include "base/allocator/partition_allocator/partition_alloc_check.h"
+#include "base/allocator/partition_allocator/partition_alloc_config.h"
 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
 #include "base/allocator/partition_allocator/partition_alloc_features.h"
 #include "base/allocator/partition_allocator/partition_page.h"
@@ -303,14 +304,11 @@
     typename Root::ScopedGuard guard(root->lock_);
 
     // Take a snapshot of all super pages and scannable slot spans.
-    // TODO(bikineev): Consider making current_extent lock-free and moving it
-    // to the concurrent thread.
     for (auto* super_page_extent = root->first_extent; super_page_extent;
          super_page_extent = super_page_extent->next) {
       for (char* super_page = super_page_extent->super_page_base;
            super_page != super_page_extent->super_pages_end;
            super_page += kSuperPageSize) {
-        // TODO(bikineev): Consider following freelists instead of slot spans.
         const size_t visited_slot_spans = IterateSlotSpans<ThreadSafe>(
             super_page, true /*with_quarantine*/,
             [this](SlotSpan* slot_span) -> bool {
@@ -424,11 +422,13 @@
       }
     }
     ~SyncScope() {
-      // First, notify other scanning threads that this thread is done.
+      // First, notify the scanning thread that this thread is done.
       NotifyThreads();
+      // Then, unprotect all scanned pages, if needed.
+      task_.UnprotectPartitions();
       if (context == Context::kScanner) {
         // The scanner thread must wait here until all safepoints leave.
-        // Otherwise, sweeping may free a page that can be accessed by a
+        // Otherwise, sweeping may free a page that can later be accessed by a
         // descheduled mutator.
         WaitForOtherThreads();
       }
@@ -491,6 +491,9 @@
   // Clear quarantined objects and prepare card table for fast lookup
   void ClearQuarantinedObjectsAndPrepareCardTable();
 
+  // Unprotect all slot spans from all partitions.
+  void UnprotectPartitions();
+
   // Sweeps (frees) unreachable quarantined entries. Returns the size of swept
   // objects.
   void SweepQuarantine();
@@ -507,6 +510,8 @@
   std::mutex mutex_;
   std::condition_variable condvar_;
   std::atomic<size_t> number_of_scanning_threads_{0u};
+  // We can unprotect only once to reduce context-switches.
+  std::once_flag unprotect_once_flag_;
   PCScan& pcscan_;
 };
 
@@ -608,6 +613,19 @@
   });
 }
 
+void PCScanTask::UnprotectPartitions() {
+  std::call_once(unprotect_once_flag_, [this] {
+    auto& pcscan = PCScanInternal::Instance();
+    const auto unprotect = [&pcscan](const auto& slot_span) {
+      pcscan.UnprotectPages(
+          reinterpret_cast<uintptr_t>(slot_span.begin),
+          (slot_span.end - slot_span.begin) * sizeof(uintptr_t));
+    };
+    snapshot_.large_scan_areas_worklist().VisitNonConcurrently(unprotect);
+    snapshot_.scan_areas_worklist().VisitNonConcurrently(unprotect);
+  });
+}
+
 class PCScanScanLoop final : public ScanLoop<PCScanScanLoop> {
   friend class ScanLoop<PCScanScanLoop>;
 
@@ -696,7 +714,11 @@
   // is scanned contains quarantined objects.
   PCScanSnapshot::LargeScanAreasWorklist::RandomizedView large_scan_areas(
       snapshot_.large_scan_areas_worklist());
-  large_scan_areas.Visit([this, &scan_loop](auto scan_area) {
+  auto& pcscan = PCScanInternal::Instance();
+  large_scan_areas.Visit([this, &scan_loop, &pcscan](auto scan_area) {
+    // Protect slot span before scanning it.
+    pcscan.ProtectPages(reinterpret_cast<uintptr_t>(scan_area.begin),
+                        (scan_area.end - scan_area.begin) * sizeof(uintptr_t));
     // The bitmap is (a) always guaranteed to exist and (b) the same for all
     // objects in a given slot span.
     // TODO(chromium:1129751): Check mutator bitmap as well if performance
@@ -722,7 +744,10 @@
   // Scan areas with regular size slots.
   PCScanSnapshot::ScanAreasWorklist::RandomizedView scan_areas(
       snapshot_.scan_areas_worklist());
-  scan_areas.Visit([&scan_loop](auto scan_area) {
+  scan_areas.Visit([&scan_loop, &pcscan](auto scan_area) {
+    // Protect slot span before scanning it.
+    pcscan.ProtectPages(reinterpret_cast<uintptr_t>(scan_area.begin),
+                        (scan_area.end - scan_area.begin) * sizeof(uintptr_t));
     scan_loop.Run(scan_area.begin, scan_area.end);
   });
 
@@ -970,10 +995,18 @@
 
 PCScanInternal::~PCScanInternal() = default;
 
-void PCScanInternal::Initialize() {
+void PCScanInternal::Initialize(PCScan::WantedWriteProtectionMode wpmode) {
   PA_DCHECK(!is_initialized_);
   CommitCardTable();
-  PCScan::SetClearType(PCScan::ClearType::kLazy);
+#if defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+  if (wpmode == PCScan::WantedWriteProtectionMode::kEnabled)
+    write_protector_ = std::make_unique<UserFaultFDWriteProtector>();
+  else
+    write_protector_ = std::make_unique<NoWriteProtector>();
+#else
+  write_protector_ = std::make_unique<NoWriteProtector>();
+#endif  // defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+  PCScan::SetClearType(write_protector_->SupportedClearType());
   is_initialized_ = true;
 }
 
@@ -1137,6 +1170,23 @@
   return it != stack_tops_.end() ? it->second : nullptr;
 }
 
+void PCScanInternal::ProtectPages(uintptr_t begin, size_t size) {
+  // Slot-span sizes are multiple of system page size. However, the ranges that
+  // are recorded are not, since in the snapshot we only record the used
+  // payload. Therefore we align up the incoming range by 4k. The unused part of
+  // slot-spans doesn't need to be protected (the allocator will enter the
+  // safepoint before trying to allocate from it).
+  PA_DCHECK(write_protector_.get());
+  write_protector_->ProtectPages(
+      begin, (size + SystemPageSize() - 1) & ~(SystemPageSize() - 1));
+}
+
+void PCScanInternal::UnprotectPages(uintptr_t begin, size_t size) {
+  PA_DCHECK(write_protector_.get());
+  write_protector_->UnprotectPages(
+      begin, (size + SystemPageSize() - 1) & ~(SystemPageSize() - 1));
+}
+
 void PCScanInternal::ClearRootsForTesting() {
   // Set all roots as non-scannable and non-quarantinable.
   for (auto* root : scannable_roots_) {
@@ -1150,9 +1200,9 @@
   nonscannable_roots_.ClearForTesting();  // IN-TEST
 }
 
-void PCScanInternal::ReinitForTesting() {
+void PCScanInternal::ReinitForTesting(PCScan::WantedWriteProtectionMode mode) {
   is_initialized_ = false;
-  Initialize();
+  Initialize(mode);
 }
 
 void PCScanInternal::FinishScanForTesting() {
diff --git a/base/allocator/partition_allocator/starscan/pcscan_internal.h b/base/allocator/partition_allocator/starscan/pcscan_internal.h
index 991add4..22545dd 100644
--- a/base/allocator/partition_allocator/starscan/pcscan_internal.h
+++ b/base/allocator/partition_allocator/starscan/pcscan_internal.h
@@ -12,6 +12,7 @@
 #include "base/allocator/partition_allocator/starscan/metadata_allocator.h"
 #include "base/allocator/partition_allocator/starscan/pcscan.h"
 #include "base/allocator/partition_allocator/starscan/starscan_fwd.h"
+#include "base/allocator/partition_allocator/starscan/write_protector.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/no_destructor.h"
 
@@ -69,7 +70,7 @@
 
   ~PCScanInternal();
 
-  void Initialize();
+  void Initialize(PCScan::WantedWriteProtectionMode);
   bool is_initialized() const { return is_initialized_; }
 
   void PerformScan(PCScan::InvocationMode);
@@ -107,9 +108,12 @@
 
   void* GetCurrentThreadStackTop() const;
 
-  void ClearRootsForTesting();  // IN-TEST
-  void ReinitForTesting();      // IN-TEST
-  void FinishScanForTesting();  // IN-TEST
+  void ProtectPages(uintptr_t begin, size_t size);
+  void UnprotectPages(uintptr_t begin, size_t size);
+
+  void ClearRootsForTesting();                               // IN-TEST
+  void ReinitForTesting(PCScan::WantedWriteProtectionMode);  // IN-TEST
+  void FinishScanForTesting();                               // IN-TEST
 
  private:
   friend base::NoDestructor<PCScanInternal>;
@@ -138,6 +142,8 @@
   const char* process_name_ = nullptr;
   const SimdSupport simd_support_;
 
+  std::unique_ptr<WriteProtector> write_protector_;
+
   bool is_initialized_ = false;
 };
 
diff --git a/base/allocator/partition_allocator/starscan/pcscan_unittest.cc b/base/allocator/partition_allocator/starscan/pcscan_unittest.cc
index 82b0433..fa689980 100644
--- a/base/allocator/partition_allocator/starscan/pcscan_unittest.cc
+++ b/base/allocator/partition_allocator/starscan/pcscan_unittest.cc
@@ -44,7 +44,7 @@
     PartitionAllocGlobalInit([](size_t) { LOG(FATAL) << "Out of memory"; });
     // Previous test runs within the same process decommit GigaCage, therefore
     // we need to make sure that the card table is recommitted for each run.
-    PCScan::ReinitForTesting();
+    PCScan::ReinitForTesting(PCScan::WantedWriteProtectionMode::kDisabled);
     allocator_.init({PartitionOptions::AlignedAlloc::kAllowed,
                      PartitionOptions::ThreadCache::kDisabled,
                      PartitionOptions::Quarantine::kAllowed,
diff --git a/base/allocator/partition_allocator/starscan/raceful_worklist.h b/base/allocator/partition_allocator/starscan/raceful_worklist.h
index b51ed0d..d73fdd61 100644
--- a/base/allocator/partition_allocator/starscan/raceful_worklist.h
+++ b/base/allocator/partition_allocator/starscan/raceful_worklist.h
@@ -60,6 +60,9 @@
 
   void Push(const T& t) { data_.push_back(Node(t)); }
 
+  template <typename Function>
+  void VisitNonConcurrently(Function) const;
+
  private:
   Underlying data_;
   std::atomic<bool> fully_visited_{false};
@@ -67,6 +70,13 @@
 
 template <typename T>
 template <typename Function>
+void RacefulWorklist<T>::VisitNonConcurrently(Function f) const {
+  for (const auto& t : data_)
+    f(t.value);
+}
+
+template <typename T>
+template <typename Function>
 void RacefulWorklist<T>::RandomizedView::Visit(Function f) {
   auto& data = worklist_.data_;
   std::vector<typename Underlying::iterator,
diff --git a/base/allocator/partition_allocator/starscan/write_protector.cc b/base/allocator/partition_allocator/starscan/write_protector.cc
new file mode 100644
index 0000000..cb0d0b0b
--- /dev/null
+++ b/base/allocator/partition_allocator/starscan/write_protector.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/allocator/partition_allocator/starscan/write_protector.h"
+
+#include <mutex>
+#include <thread>
+
+#include "base/allocator/partition_allocator/address_pool_manager.h"
+#include "base/allocator/partition_allocator/partition_address_space.h"
+#include "base/allocator/partition_allocator/partition_alloc_check.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+
+#if defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+#include <fcntl.h>
+#include <linux/userfaultfd.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#endif  // defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+
+namespace base {
+namespace internal {
+
+PCScan::ClearType NoWriteProtector::SupportedClearType() const {
+  return PCScan::ClearType::kLazy;
+}
+
+#if defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+namespace {
+void UserFaultFDThread(int uffd) {
+  PA_DCHECK(-1 != uffd);
+
+  static constexpr char kThreadName[] = "PCScanPFHandler";
+  base::PlatformThread::SetName(kThreadName);
+
+  while (true) {
+    // Pool on the uffd descriptor for page fault events.
+    pollfd pollfd{.fd = uffd, .events = POLLIN};
+    const int nready = HANDLE_EINTR(poll(&pollfd, 1, -1));
+    PA_CHECK(-1 != nready);
+
+    // Get page fault info.
+    uffd_msg msg;
+    const int nread = HANDLE_EINTR(read(uffd, &msg, sizeof(msg)));
+    PA_CHECK(0 != nread);
+
+    // We only expect page faults.
+    PA_DCHECK(UFFD_EVENT_PAGEFAULT == msg.event);
+    // We have subscribed only to wp-fault events.
+    PA_DCHECK(UFFD_PAGEFAULT_FLAG_WP & msg.arg.pagefault.flags);
+
+    // Enter the safepoint. Concurrent faulted writes will wait until safepoint
+    // finishes.
+    PCScan::JoinScanIfNeeded();
+  }
+}
+}  // namespace
+
+UserFaultFDWriteProtector::UserFaultFDWriteProtector()
+    : uffd_(syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK)) {
+  if (uffd_ == -1) {
+    LOG(WARNING) << "userfaultfd is not supported by the current kernel";
+    return;
+  }
+
+  PA_PCHECK(-1 != uffd_);
+
+  uffdio_api uffdio_api;
+  uffdio_api.api = UFFD_API;
+  uffdio_api.features = 0;
+  PA_CHECK(-1 != ioctl(uffd_, UFFDIO_API, &uffdio_api));
+  PA_CHECK(UFFD_API == uffdio_api.api);
+
+  // Register the giga-cage to listen uffd events.
+  struct uffdio_register uffdio_register;
+  uffdio_register.range.start = PartitionAddressSpace::BRPPoolBase();
+  uffdio_register.range.len = AddressPoolManager::kBRPPoolMaxSize;
+  uffdio_register.mode = UFFDIO_REGISTER_MODE_WP;
+  PA_CHECK(-1 != ioctl(uffd_, UFFDIO_REGISTER, &uffdio_register));
+
+  // Start uffd thread.
+  std::thread(UserFaultFDThread, uffd_).detach();
+}
+
+namespace {
+enum class UserFaultFDWPMode {
+  kProtect,
+  kUnprotect,
+};
+
+void UserFaultFDWPSet(int uffd,
+                      uintptr_t begin,
+                      size_t length,
+                      UserFaultFDWPMode mode) {
+  PA_DCHECK(0 == (begin % SystemPageSize()));
+  PA_DCHECK(0 == (length % SystemPageSize()));
+
+  uffdio_writeprotect wp;
+  wp.range.start = begin;
+  wp.range.len = length;
+  wp.mode =
+      (mode == UserFaultFDWPMode::kProtect) ? UFFDIO_WRITEPROTECT_MODE_WP : 0;
+  PA_PCHECK(-1 != ioctl(uffd, UFFDIO_WRITEPROTECT, &wp));
+}
+}  // namespace
+
+void UserFaultFDWriteProtector::ProtectPages(uintptr_t begin, size_t length) {
+  if (IsSupported())
+    UserFaultFDWPSet(uffd_, begin, length, UserFaultFDWPMode::kProtect);
+}
+
+void UserFaultFDWriteProtector::UnprotectPages(uintptr_t begin, size_t length) {
+  if (IsSupported())
+    UserFaultFDWPSet(uffd_, begin, length, UserFaultFDWPMode::kUnprotect);
+}
+
+PCScan::ClearType UserFaultFDWriteProtector::SupportedClearType() const {
+  return IsSupported() ? PCScan::ClearType::kEager : PCScan::ClearType::kLazy;
+}
+
+bool UserFaultFDWriteProtector::IsSupported() const {
+  return uffd_ != -1;
+}
+
+#endif  // defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+
+}  // namespace internal
+}  // namespace base
diff --git a/base/allocator/partition_allocator/starscan/write_protector.h b/base/allocator/partition_allocator/starscan/write_protector.h
new file mode 100644
index 0000000..5817b1e
--- /dev/null
+++ b/base/allocator/partition_allocator/starscan/write_protector.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_STARSCAN_WRITE_PROTECTOR_H_
+#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_STARSCAN_WRITE_PROTECTOR_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <mutex>
+
+#include "base/allocator/partition_allocator/starscan/metadata_allocator.h"
+#include "base/allocator/partition_allocator/starscan/pcscan.h"
+#include "base/allocator/partition_allocator/starscan/raceful_worklist.h"
+#include "build/build_config.h"
+
+namespace base {
+namespace internal {
+
+// Interface for page protection/unprotection. This is used in DCScan to catch
+// concurrent mutator writes. Protection is done when the scanner starts
+// scanning a range. Unprotection happens at the end of the scanning phase.
+class WriteProtector : public AllocatedOnPCScanMetadataPartition {
+ public:
+  virtual ~WriteProtector() = default;
+
+  virtual void ProtectPages(uintptr_t begin, size_t length) = 0;
+  virtual void UnprotectPages(uintptr_t begin, size_t length) = 0;
+
+  virtual PCScan::ClearType SupportedClearType() const = 0;
+};
+
+class NoWriteProtector final : public WriteProtector {
+ public:
+  void ProtectPages(uintptr_t, size_t) final {}
+  void UnprotectPages(uintptr_t, size_t) final {}
+  PCScan::ClearType SupportedClearType() const final;
+};
+
+#if defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+class UserFaultFDWriteProtector final : public WriteProtector {
+ public:
+  UserFaultFDWriteProtector();
+
+  UserFaultFDWriteProtector(const UserFaultFDWriteProtector&) = delete;
+  UserFaultFDWriteProtector& operator=(const UserFaultFDWriteProtector&) =
+      delete;
+
+  void ProtectPages(uintptr_t, size_t) final;
+  void UnprotectPages(uintptr_t, size_t) final;
+
+  PCScan::ClearType SupportedClearType() const final;
+
+ private:
+  bool IsSupported() const;
+
+  const int uffd_ = 0;
+};
+#endif  // defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+
+}  // namespace internal
+}  // namespace base
+
+#endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_STARSCAN_WRITE_PROTECTOR_H_
diff --git a/base/files/scoped_file.h b/base/files/scoped_file.h
index ee49fd7e..427dd3ed 100644
--- a/base/files/scoped_file.h
+++ b/base/files/scoped_file.h
@@ -26,11 +26,21 @@
   static void Release(const ScopedGeneric<int, ScopedFDCloseTraits>&, int);
 };
 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+// On ChromeOS and Linux we guard FD lifetime with a global table and hook into
+// libc close() to perform checks.
+struct BASE_EXPORT ScopedFDCloseTraits : public ScopedGenericOwnershipTracking {
+#else
 struct BASE_EXPORT ScopedFDCloseTraits {
+#endif
   static int InvalidValue() {
     return -1;
   }
   static void Free(int fd);
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+  static void Acquire(const ScopedGeneric<int, ScopedFDCloseTraits>&, int);
+  static void Release(const ScopedGeneric<int, ScopedFDCloseTraits>&, int);
+#endif
 };
 #endif
 
@@ -44,6 +54,36 @@
 
 }  // namespace internal
 
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+namespace subtle {
+
+// Enables or disables enforcement of FD ownership as tracked by ScopedFD
+// objects. Enforcement is disabled by default since it proves unwieldy in some
+// test environments, but tracking is always done. It's best to enable this as
+// early as possible in a process's lifetime.
+void BASE_EXPORT EnableFDOwnershipEnforcement(bool enabled);
+
+// Resets ownership state of all FDs. The only permissible use of this API is
+// in a forked child process between the fork() and a subsequent exec() call.
+//
+// For one issue, it is common to mass-close most open FDs before calling
+// exec(), to avoid leaking FDs into the new executable's environment. For
+// processes which have enabled FD ownership enforcement, this reset operation
+// is necessary before performing such closures.
+//
+// Furthermore, fork()+exec() may be used in a multithreaded context, and
+// because fork() is not atomic, the FD ownership state in the child process may
+// be inconsistent with the actual set of opened file descriptors once fork()
+// returns in the child process.
+//
+// It is therefore especially important to call this ASAP after fork() in the
+// child process if any FD manipulation will be done prior to the subsequent
+// exec call.
+void BASE_EXPORT ResetFDOwnership();
+
+}  // namespace subtle
+#endif
+
 // -----------------------------------------------------------------------------
 
 #if defined(OS_POSIX) || defined(OS_FUCHSIA)
@@ -64,6 +104,12 @@
 // Automatically closes |FILE*|s.
 typedef std::unique_ptr<FILE, internal::ScopedFILECloser> ScopedFILE;
 
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+// Queries the ownership status of an FD, i.e. whether it is currently owned by
+// a ScopedFD in the calling process.
+bool BASE_EXPORT IsFDOwned(int fd);
+#endif  // defined(OS_CHROMEOS) || defined(OS_LINUX)
+
 }  // namespace base
 
 #endif  // BASE_FILES_SCOPED_FILE_H_
diff --git a/base/files/scoped_file_linux.cc b/base/files/scoped_file_linux.cc
new file mode 100644
index 0000000..26bc18e6
--- /dev/null
+++ b/base/files/scoped_file_linux.cc
@@ -0,0 +1,91 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/scoped_file.h"
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+
+#include "base/compiler_specific.h"
+#include "base/debug/stack_trace.h"
+#include "base/immediate_crash.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+
+namespace {
+
+// We want to avoid any kind of allocations in our close() implementation, so we
+// use a fixed-size table. Given our common FD limits and the preference for new
+// FD allocations to use the lowest available descriptor, this should be
+// sufficient to guard most FD lifetimes. The worst case scenario if someone
+// attempts to own a higher FD is that we don't track it.
+const int kMaxTrackedFds = 4096;
+
+std::atomic_bool g_is_ownership_enforced{false};
+std::array<std::atomic_bool, kMaxTrackedFds> g_is_fd_owned;
+
+NOINLINE void CrashOnFdOwnershipViolation() {
+  RAW_LOG(ERROR, "Crashing due to FD ownership violation:\n");
+  base::debug::StackTrace().Print();
+  IMMEDIATE_CRASH();
+}
+
+bool CanTrack(int fd) {
+  return fd >= 0 && fd < kMaxTrackedFds;
+}
+
+void UpdateAndCheckFdOwnership(int fd, bool owned) {
+  if (CanTrack(fd) && g_is_fd_owned[fd].exchange(owned) == owned &&
+      g_is_ownership_enforced) {
+    CrashOnFdOwnershipViolation();
+  }
+}
+
+}  // namespace
+
+namespace base {
+namespace internal {
+
+// static
+void ScopedFDCloseTraits::Acquire(const ScopedFD& owner, int fd) {
+  UpdateAndCheckFdOwnership(fd, /*owned=*/true);
+}
+
+// static
+void ScopedFDCloseTraits::Release(const ScopedFD& owner, int fd) {
+  UpdateAndCheckFdOwnership(fd, /*owned=*/false);
+}
+
+}  // namespace internal
+
+namespace subtle {
+
+void EnableFDOwnershipEnforcement(bool enabled) {
+  g_is_ownership_enforced = enabled;
+}
+
+void ResetFDOwnership() {
+  std::fill(g_is_fd_owned.begin(), g_is_fd_owned.end(), false);
+}
+
+}  // namespace subtle
+
+bool IsFDOwned(int fd) {
+  return CanTrack(fd) && g_is_fd_owned[fd];
+}
+
+}  // namespace base
+
+extern "C" {
+
+int __close(int);
+
+__attribute__((visibility("default"), noinline)) int close(int fd) {
+  if (base::IsFDOwned(fd) && g_is_ownership_enforced)
+    CrashOnFdOwnershipViolation();
+  return __close(fd);
+}
+
+}  // extern "C"
diff --git a/base/files/scoped_file_linux_unittest.cc b/base/files/scoped_file_linux_unittest.cc
new file mode 100644
index 0000000..7cac627
--- /dev/null
+++ b/base/files/scoped_file_linux_unittest.cc
@@ -0,0 +1,54 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/scoped_file.h"
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class ScopedFDOwnershipTrackingTest : public testing::Test {
+ public:
+  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+  void TearDown() override { ASSERT_TRUE(temp_dir_.Delete()); }
+
+  ScopedFD OpenFD() {
+    FilePath dont_care;
+    return CreateAndOpenFdForTemporaryFileInDir(temp_dir_.GetPath(),
+                                                &dont_care);
+  }
+
+ private:
+  ScopedTempDir temp_dir_;
+};
+
+TEST_F(ScopedFDOwnershipTrackingTest, BasicTracking) {
+  ScopedFD fd = OpenFD();
+  EXPECT_TRUE(IsFDOwned(fd.get()));
+  int fd_value = fd.get();
+  fd.reset();
+  EXPECT_FALSE(IsFDOwned(fd_value));
+}
+
+#if defined(GTEST_HAS_DEATH_TEST)
+
+TEST_F(ScopedFDOwnershipTrackingTest, NoDoubleOwnership) {
+  ScopedFD fd = OpenFD();
+  subtle::EnableFDOwnershipEnforcement(true);
+  EXPECT_DEATH(ScopedFD(fd.get()), "");
+}
+
+TEST_F(ScopedFDOwnershipTrackingTest, CrashOnUnownedClose) {
+  ScopedFD fd = OpenFD();
+  subtle::EnableFDOwnershipEnforcement(true);
+  EXPECT_DEATH(close(fd.get()), "");
+}
+
+#endif  // defined(GTEST_HAS_DEATH_TEST)
+
+}  // namespace
+}  // namespace base
diff --git a/base/process/launch_posix.cc b/base/process/launch_posix.cc
index 944b1d70..86fb840 100644
--- a/base/process/launch_posix.cc
+++ b/base/process/launch_posix.cc
@@ -358,19 +358,27 @@
     // might do things like block waiting for threads that don't even exist
     // in the child.
 
-    // If a child process uses the readline library, the process block forever.
-    // In BSD like OSes including OS X it is safe to assign /dev/null as stdin.
-    // See http://crbug.com/56596.
-    base::ScopedFD null_fd(HANDLE_EINTR(open("/dev/null", O_RDONLY)));
-    if (!null_fd.is_valid()) {
-      RAW_LOG(ERROR, "Failed to open /dev/null");
-      _exit(127);
-    }
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+    // See comments on the ResetFDOwnership() declaration in
+    // base/files/scoped_file.h regarding why this is called early here.
+    subtle::ResetFDOwnership();
+#endif
 
-    int new_fd = HANDLE_EINTR(dup2(null_fd.get(), STDIN_FILENO));
-    if (new_fd != STDIN_FILENO) {
-      RAW_LOG(ERROR, "Failed to dup /dev/null for stdin");
-      _exit(127);
+    {
+      // If a child process uses the readline library, the process block
+      // forever. In BSD like OSes including OS X it is safe to assign /dev/null
+      // as stdin. See http://crbug.com/56596.
+      base::ScopedFD null_fd(HANDLE_EINTR(open("/dev/null", O_RDONLY)));
+      if (!null_fd.is_valid()) {
+        RAW_LOG(ERROR, "Failed to open /dev/null");
+        _exit(127);
+      }
+
+      int new_fd = HANDLE_EINTR(dup2(null_fd.get(), STDIN_FILENO));
+      if (new_fd != STDIN_FILENO) {
+        RAW_LOG(ERROR, "Failed to dup /dev/null for stdin");
+        _exit(127);
+      }
     }
 
     if (options.new_process_group) {
@@ -550,6 +558,12 @@
       // DANGER: no calls to malloc or locks are allowed from now on:
       // http://crbug.com/36678
 
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+      // See comments on the ResetFDOwnership() declaration in
+      // base/files/scoped_file.h regarding why this is called early here.
+      subtle::ResetFDOwnership();
+#endif
+
       // Obscure fork() rule: in the child, if you don't end up doing exec*(),
       // you call _exit() instead of exit(). This is because _exit() does not
       // call any previously-registered (in the parent) exit handlers, which
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 8862a80..6a5b8b2 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-4.20210519.2.1
+4.20210519.3.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 8862a80..6a5b8b2 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-4.20210519.2.1
+4.20210519.3.1
diff --git a/buildtools/checkdeps/java_checker.py b/buildtools/checkdeps/java_checker.py
index 75f41f1..93ae49de 100644
--- a/buildtools/checkdeps/java_checker.py
+++ b/buildtools/checkdeps/java_checker.py
@@ -81,6 +81,9 @@
       # Skip unwanted subdirectories. TODO(husky): it would be better to do
       # this via the skip_child_includes flag in DEPS files. Maybe hoist this
       # prescan logic into checkdeps.py itself?
+      root = root.decode('utf-8')
+      dirs = [d.decode('utf-8') for d in dirs]
+      files = [f.decode('utf-8') for f in files]
       dirs[:] = [d for d in dirs if not self._IgnoreDir(d)]
       for f in files:
         if f.endswith('.java'):
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index c2de7c1..57c9cc3c 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -949,6 +949,7 @@
   "java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java",
   "java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxTheme.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java",
+  "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerFactory.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteDelegate.java",
   "java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
index 3f7a03a2..e7e62825 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/DEPS
@@ -5,6 +5,7 @@
   "+chrome/android/java/src/org/chromium/chrome/browser/omnibox",
 
   "+chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java",
+  "+chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java",
 
   "+base/android/java/src/org/chromium/base",
   "+chrome/browser/feature_engagement",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
index a485512d..b6b9d7b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -140,8 +140,7 @@
             OverrideUrlLoadingDelegate overrideUrlLoadingDelegate,
             BackKeyBehaviorDelegate backKeyBehavior, SearchEngineLogoUtils searchEngineLogoUtils,
             @NonNull Runnable launchAssistanceSettingsAction,
-            @NonNull PageInfoAction pageInfoAction, @NonNull Callback<Profile> spareRendererCreator,
-            @NonNull Callback<Tab> bringTabToFrontCallback,
+            @NonNull PageInfoAction pageInfoAction, @NonNull Callback<Tab> bringTabToFrontCallback,
             @NonNull SaveOfflineButtonState saveOfflineButtonState, @NonNull OmniboxUma omniboxUma,
             @NonNull Supplier<TabWindowManager> tabWindowManagerSupplier,
             @NonNull BookmarkState bookmarkState) {
@@ -169,7 +168,7 @@
         mAutocompleteCoordinator = new AutocompleteCoordinator(mLocationBarLayout, this, this,
                 mUrlCoordinator, activityLifecycleDispatcher, modalDialogManagerSupplier,
                 activityTabSupplier, shareDelegateSupplier, locationBarDataProvider,
-                spareRendererCreator, bringTabToFrontCallback, tabWindowManagerSupplier,
+                profileObservableSupplier, bringTabToFrontCallback, tabWindowManagerSupplier,
                 bookmarkState);
         StatusView statusView = mLocationBarLayout.findViewById(R.id.location_bar_status);
         mStatusCoordinator = new StatusCoordinator(isTablet(), statusView, mUrlCoordinator,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
index 5c13335a..f61b4dbb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -856,7 +856,6 @@
 
     private void setProfile(Profile profile) {
         if (profile == null || !mNativeInitialized) return;
-        mAutocompleteCoordinator.setAutocompleteProfile(profile);
         mOmniboxPrerender.initializeForProfile(profile);
 
         mLocationBarLayout.setShowIconsWhenUrlFocused(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
index 64f7602..1754c8e4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
@@ -20,16 +20,31 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.embedder_support.util.UrlUtilities;
+import org.chromium.components.metrics.OmniboxEventProtos.OmniboxEventProto.PageClassification;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.AutocompleteResult;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.url.GURL;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Bridge to the native AutocompleteControllerAndroid.
+ *
+ * The bridge is created and maintained by the AutocompleteControllerAndroid native class.
+ * The Native class is created on request for supplied profiles and remains available until the
+ * Profile gets destroyed, making this instance follow the same life cycle.
+ *
+ * Instances of this class should not be acquired directly; instead, when a profile-specific
+ * AutocompleteController is required, please acquire one using the AutocompleteControllerFactory.
+ *
+ * When User Profile gets destroyed, native class gets destroyed as well, and during the
+ * destruction calls the #notifyNativeDestroyed() method, which signals the Java
+ * AutocompleteController is no longer valid, and removes it from the AutocompleteControllerFactory
+ * cache.
  */
 public class AutocompleteController {
     // Maximum number of voice suggestions to show.
@@ -43,10 +58,11 @@
     private static final int OMNIBOX_SPARE_RENDERER_DELAY_MS = 1000;
 
     private final @NonNull Callback<Profile> mSpareRendererCreator;
-    private long mNativeAutocompleteControllerAndroid;
-    private OnSuggestionsReceivedListener mListener;
+    private final @NonNull Profile mProfile;
+    private final @NonNull Runnable mControllerDestroyedCallback;
+    private final long mNativeAutocompleteControllerAndroid;
+    private final Set<OnSuggestionsReceivedListener> mListeners = new HashSet<>();
 
-    private Profile mProfile;
     private @NonNull AutocompleteResult mAutocompleteResult = AutocompleteResult.EMPTY_RESULT;
 
     /**
@@ -57,52 +73,38 @@
                 AutocompleteResult autocompleteResult, String inlineAutocompleteText);
     }
 
-    public AutocompleteController(@NonNull Callback<Profile> spareRendererCreator) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    protected AutocompleteController(@NonNull Profile profile,
+            @NonNull Callback<Profile> spareRendererCreator,
+            @NonNull Runnable controllerDestroyedCallback) {
+        assert profile != null : "Invalid profile used to construct AutocompleteController";
+        mProfile = profile;
+        mNativeAutocompleteControllerAndroid =
+                AutocompleteControllerJni.get().init(AutocompleteController.this, profile);
+
+        // Note: this may fire when building integration tests that mock JNI calls.
+        // When mocking JNI calls, please make sure to supply a Mock AutocompleteController to
+        // AutocompleteControllerFactory.
+        assert mNativeAutocompleteControllerAndroid != 0 : "Could not acquire Native Controller.";
         mSpareRendererCreator = spareRendererCreator;
+        mControllerDestroyedCallback = controllerDestroyedCallback;
     }
 
     /**
      * @param listener The listener to be notified when new suggestions are available.
      */
-    public void setOnSuggestionsReceivedListener(@NonNull OnSuggestionsReceivedListener listener) {
-        mListener = listener;
-    }
-
-    void destroy() {
-        if (mNativeAutocompleteControllerAndroid != 0) {
-            AutocompleteControllerJni.get().releaseJavaObject(mNativeAutocompleteControllerAndroid);
-        }
-        mNativeAutocompleteControllerAndroid = 0;
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void addOnSuggestionsReceivedListener(@NonNull OnSuggestionsReceivedListener listener) {
+        mListeners.add(listener);
     }
 
     /**
-     * Resets the underlying autocomplete controller based on the specified profile. This function
-     * returns early if there are no profile changes.
-     *
-     * <p>This will implicitly stop the autocomplete suggestions, so
-     * {@link #start(Profile, String, String, boolean)} must be called again to start them flowing
-     * again.  This should not be an issue as changing profiles should not normally occur while
-     * waiting on omnibox suggestions.
-     *
-     * @param profile The profile to reset the AutocompleteController with.
+     * @param listener A previously registered new suggestions listener to be removed.
      */
-    public void setProfile(Profile profile) {
-        assert mListener != null : "Ensure a listener is set prior to calling.";
-        if (mProfile == profile) {
-            mNativeAutocompleteControllerAndroid =
-                    AutocompleteControllerJni.get().init(AutocompleteController.this, profile);
-            return;
-        }
-
-        mProfile = profile;
-        stop(true);
-        if (profile == null) {
-            mNativeAutocompleteControllerAndroid = 0;
-            return;
-        }
-
-        mNativeAutocompleteControllerAndroid =
-                AutocompleteControllerJni.get().init(AutocompleteController.this, profile);
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void removeOnSuggestionsReceivedListener(
+            @NonNull OnSuggestionsReceivedListener listener) {
+        mListeners.remove(listener);
     }
 
     /**
@@ -121,10 +123,10 @@
     public void start(Profile profile, String url, int pageClassification, String text,
             int cursorPosition, boolean preventInlineAutocomplete, @Nullable String queryTileId,
             boolean isQueryStartedFromTiles) {
-        assert mListener != null : "Ensure a listener is set prior to calling.";
+        // TODO(crbug.com/1138587): investigate whether we need profiles and drop the null check.
         if (profile == null || TextUtils.isEmpty(url)) return;
 
-        setProfile(profile);
+        assert profile == mProfile;
 
         // Initializing the native counterpart might still fail.
         if (mNativeAutocompleteControllerAndroid != 0) {
@@ -169,7 +171,6 @@
      */
     public void startZeroSuggest(
             Profile profile, String omniboxText, String url, int pageClassification, String title) {
-        assert mListener != null : "Ensure a listener is set prior to calling.";
         if (profile == null || TextUtils.isEmpty(url)) return;
 
         // Proactively start up a renderer, to reduce the time to display search results,
@@ -179,6 +180,7 @@
         // renderer will be started after the next navigation if the delay is too long, but the
         // spare renderer will probably get used anyways by a later navigation.
         if (!profile.isOffTheRecord() && !UrlUtilities.isNTPUrl(url)
+                && pageClassification != PageClassification.ANDROID_SEARCH_WIDGET_VALUE
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.OMNIBOX_SPARE_RENDERER)) {
             // It is ok for this to get called multiple times since all the requests will get
             // de-duplicated to the first one.
@@ -193,7 +195,7 @@
                             ChromeFeatureList.OMNIBOX_SPARE_RENDERER,
                             "omnibox_spare_renderer_delay_ms", OMNIBOX_SPARE_RENDERER_DELAY_MS));
         }
-        setProfile(profile);
+        assert profile == mProfile;
 
         if (mNativeAutocompleteControllerAndroid != 0) {
             AutocompleteControllerJni.get().onOmniboxFocused(mNativeAutocompleteControllerAndroid,
@@ -210,7 +212,6 @@
      *         empty result set.
      */
     public void stop(boolean clear) {
-        assert mListener != null : "Ensure a listener is set prior to calling.";
         if (mNativeAutocompleteControllerAndroid != 0) {
             AutocompleteControllerJni.get().stop(
                     mNativeAutocompleteControllerAndroid, AutocompleteController.this, clear);
@@ -251,18 +252,19 @@
     @CalledByNative
     protected void onSuggestionsReceived(
             AutocompleteResult autocompleteResult, String inlineAutocompleteText) {
-        assert mListener != null : "Ensure a listener is set prior generating suggestions.";
         final AutocompleteResult originalResult = autocompleteResult;
 
         mAutocompleteResult = autocompleteResult;
 
         // Notify callbacks of suggestions.
-        mListener.onSuggestionsReceived(autocompleteResult, inlineAutocompleteText);
+        for (OnSuggestionsReceivedListener listener : mListeners) {
+            listener.onSuggestionsReceived(autocompleteResult, inlineAutocompleteText);
+        }
     }
 
     @CalledByNative
     private void notifyNativeDestroyed() {
-        mNativeAutocompleteControllerAndroid = 0;
+        mControllerDestroyedCallback.run();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerFactory.java
new file mode 100644
index 0000000..520f5ed
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerFactory.java
@@ -0,0 +1,71 @@
+// 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.omnibox.suggestions;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.chromium.chrome.browser.WarmupManager;
+import org.chromium.chrome.browser.profiles.Profile;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The factory building/retrieving the AutocompleteController objects.
+ *
+ * Facilitates construction or retrieval of AutocompleteController instances associated with
+ * particular user Profiles.
+ * The factory may be shared between multiple individual activities, typically Search and regular
+ * Chrome activities. To prevent the instances from being separately constructed, this factory
+ * operates within singleton realms, replicating the KeyedServiceFactory used on the Native (C++)
+ * side.
+ */
+public class AutocompleteControllerFactory {
+    /** A map associating individual Profile objects with their corresponding Controllers */
+    private static Map<Profile, AutocompleteController> sControllers = new HashMap<>();
+
+    /** AutocompleteController instance returned for testing purposes. */
+    private static AutocompleteController sAutocompleteControllerForTesting;
+
+    /**
+     * Retrieve the AutocompleteController associated with the supplied profile.
+     *
+     * @param profile Profile to request AutocompleteController for.
+     * @return AutocompleteController for supplied profile.
+     */
+    static AutocompleteController getController(Profile profile) {
+        if (sAutocompleteControllerForTesting != null) return sAutocompleteControllerForTesting;
+
+        AutocompleteController controller = sControllers.get(profile);
+        if (controller != null) return controller;
+
+        controller = new AutocompleteController(profile,
+                WarmupManager.getInstance()::createSpareRenderProcessHost,
+                () -> removeController(profile));
+
+        sControllers.put(profile, controller);
+        return controller;
+    }
+
+    /**
+     * Remove the controller associated with supplied profile.
+     *
+     * @param profile Profile for which the controller should be dropped.
+     */
+    private static void removeController(@NonNull Profile profile) {
+        sControllers.remove(profile);
+    }
+
+    /**
+     * Set the instance of AutocompleteController that will be used for testing purposes.
+     * Supplied instance will always be returned, regardless of profile information.
+     *
+     * @param controller The controller to return, or null to remove test instance.
+     */
+    public static void setControllerForTesting(@Nullable AutocompleteController controller) {
+        sAutocompleteControllerForTesting = controller;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
index 36a537c3..b6847390 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
@@ -18,6 +18,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.StrictModeContext;
+import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
@@ -63,10 +64,12 @@
  * Coordinator that handles the interactions with the autocomplete system.
  */
 public class AutocompleteCoordinator implements UrlFocusChangeListener, UrlTextChangeListener {
-    private final ViewGroup mParent;
-    private OmniboxQueryTileCoordinator mQueryTileCoordinator;
-    private AutocompleteMediator mMediator;
-    private OmniboxSuggestionsDropdown mDropdown;
+    private final @NonNull ViewGroup mParent;
+    private final @NonNull ObservableSupplier<Profile> mProfileSupplier;
+    private final @NonNull Callback<Profile> mProfileChangeCallback;
+    private final @NonNull OmniboxQueryTileCoordinator mQueryTileCoordinator;
+    private final @NonNull AutocompleteMediator mMediator;
+    private @Nullable OmniboxSuggestionsDropdown mDropdown;
 
     public AutocompleteCoordinator(@NonNull ViewGroup parent,
             @NonNull AutocompleteDelegate delegate,
@@ -77,7 +80,7 @@
             @NonNull Supplier<Tab> activityTabSupplier,
             @Nullable Supplier<ShareDelegate> shareDelegateSupplier,
             @NonNull LocationBarDataProvider locationBarDataProvider,
-            @NonNull Callback<Profile> spareRendererCreator,
+            @NonNull ObservableSupplier<Profile> profileObservableSupplier,
             @NonNull Callback<Tab> bringToForegroundCallback,
             @NonNull Supplier<TabWindowManager> tabWindowManagerSupplier,
             @NonNull BookmarkState bookmarkState) {
@@ -93,10 +96,9 @@
 
         mQueryTileCoordinator = new OmniboxQueryTileCoordinator(context, this::onTileSelected);
         mMediator = new AutocompleteMediator(context, delegate, urlBarEditingTextProvider,
-                new AutocompleteController(spareRendererCreator), listModel, new Handler(),
-                lifecycleDispatcher, modalDialogManagerSupplier, activityTabSupplier,
-                shareDelegateSupplier, locationBarDataProvider, bringToForegroundCallback,
-                tabWindowManagerSupplier, bookmarkState);
+                listModel, new Handler(), lifecycleDispatcher, modalDialogManagerSupplier,
+                activityTabSupplier, shareDelegateSupplier, locationBarDataProvider,
+                bringToForegroundCallback, tabWindowManagerSupplier, bookmarkState);
         mMediator.initDefaultProcessors(mQueryTileCoordinator::setTiles);
 
         listModel.set(SuggestionListProperties.OBSERVER, mMediator);
@@ -107,6 +109,10 @@
         LazyConstructionPropertyMcp.create(listModel, SuggestionListProperties.VISIBLE,
                 viewProvider, SuggestionListViewBinder::bind);
 
+        mProfileSupplier = profileObservableSupplier;
+        mProfileChangeCallback = this::setAutocompleteProfile;
+        mProfileSupplier.addObserver(mProfileChangeCallback);
+
         // https://crbug.com/966227 Set initial layout direction ahead of inflating the suggestions.
         updateSuggestionListLayoutDirection();
     }
@@ -115,10 +121,9 @@
      * Clean up resources used by this class.
      */
     public void destroy() {
+        mProfileSupplier.removeObserver(mProfileChangeCallback);
         mQueryTileCoordinator.destroy();
-        mQueryTileCoordinator = null;
         mMediator.destroy();
-        mMediator = null;
     }
 
     private ViewProvider<SuggestionListViewHolder> createViewProvider(
@@ -234,6 +239,7 @@
      * Updates the profile used for generating autocomplete suggestions.
      * @param profile The profile to be used.
      */
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     public void setAutocompleteProfile(Profile profile) {
         mMediator.setAutocompleteProfile(profile);
         mQueryTileCoordinator.setProfile(profile);
@@ -388,12 +394,6 @@
         return mDropdown;
     }
 
-    /** @param controller The instance of AutocompleteController to be used. */
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    public void setAutocompleteControllerForTest(AutocompleteController controller) {
-        mMediator.setAutocompleteControllerForTest(controller);
-    }
-
     /** @return The current receiving OnSuggestionsReceived events. */
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     public OnSuggestionsReceivedListener getSuggestionsReceivedListenerForTest() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index 6bf35911..7f12d7c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -152,7 +152,6 @@
 
     public AutocompleteMediator(@NonNull Context context, @NonNull AutocompleteDelegate delegate,
             @NonNull UrlBarEditingTextStateProvider textProvider,
-            @NonNull AutocompleteController autocompleteController,
             @NonNull PropertyModel listPropertyModel, @NonNull Handler handler,
             @NonNull ActivityLifecycleDispatcher lifecycleDispatcher,
             @NonNull Supplier<ModalDialogManager> modalDialogManagerSupplier,
@@ -169,15 +168,13 @@
         mLifecycleDispatcher = lifecycleDispatcher;
         mLifecycleDispatcher.register(this);
         mModalDialogManagerSupplier = modalDialogManagerSupplier;
-        mAutocomplete = autocompleteController;
-        mAutocomplete.setOnSuggestionsReceivedListener(this);
         mHandler = handler;
         mDataProvider = locationBarDataProvider;
         mBringTabToFrontCallback = bringTabToFrontCallback;
         mTabWindowManagerSupplier = tabWindowManagerSupplier;
         mSuggestionModels = mListPropertyModel.get(SuggestionListProperties.SUGGESTION_MODELS);
-        mDropdownViewInfoListBuilder = new DropdownItemViewInfoListBuilder(
-                mAutocomplete, activityTabSupplier, bookmarkState);
+        mDropdownViewInfoListBuilder =
+                new DropdownItemViewInfoListBuilder(activityTabSupplier, bookmarkState);
         mDropdownViewInfoListBuilder.setShareDelegateSupplier(shareDelegateSupplier);
         mDropdownViewInfoListManager = new DropdownItemViewInfoListManager(mSuggestionModels);
     }
@@ -201,7 +198,7 @@
     public void destroy() {
         if (mAutocomplete != null) {
             stopAutocomplete(false);
-            mAutocomplete.destroy();
+            mAutocomplete.removeOnSuggestionsReceivedListener(this);
         }
         mDropdownViewInfoListBuilder.destroy();
         if (mLifecycleDispatcher != null) {
@@ -387,7 +384,13 @@
      * @param profile The profile to be used.
      */
     void setAutocompleteProfile(Profile profile) {
-        mAutocomplete.setProfile(profile);
+        if (mAutocomplete != null) {
+            stopAutocomplete(true);
+            mAutocomplete.removeOnSuggestionsReceivedListener(this);
+        }
+        mAutocomplete = AutocompleteControllerFactory.getController(profile);
+
+        mAutocomplete.addOnSuggestionsReceivedListener(this);
         mDropdownViewInfoListBuilder.setProfile(profile);
     }
 
@@ -927,18 +930,6 @@
     }
 
     /**
-     * Sets the autocomplete controller for the location bar.
-     *
-     * @param controller The controller that will handle autocomplete/omnibox suggestions.
-     * @note Only used for testing.
-     */
-    public void setAutocompleteControllerForTest(AutocompleteController controller) {
-        if (mAutocomplete != null) stopAutocomplete(true);
-        mAutocomplete = controller;
-        mDropdownViewInfoListBuilder.setAutocompleteControllerForTest(controller);
-    }
-
-    /**
      * Respond to Suggestion list height change and update list of presented suggestions.
      *
      * This typically happens as a result of soft keyboard being shown or hidden.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
index 9a8adf7..208580a0d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
@@ -53,7 +53,6 @@
 
     private final @NonNull List<SuggestionProcessor> mPriorityOrderedSuggestionProcessors;
     private final @NonNull Supplier<Tab> mActivityTabSupplier;
-    private @NonNull AutocompleteController mAutocompleteController;
 
     private @Nullable HeaderProcessor mHeaderProcessor;
     private @Nullable Supplier<ShareDelegate> mShareDelegateSupplier;
@@ -65,11 +64,10 @@
     private boolean mEnableAdaptiveSuggestionsCount;
     private boolean mBuiltListHasFullyConcealedElements;
 
-    DropdownItemViewInfoListBuilder(AutocompleteController controller,
+    DropdownItemViewInfoListBuilder(
             @NonNull Supplier<Tab> tabSupplier, BookmarkState bookmarkState) {
         mPriorityOrderedSuggestionProcessors = new ArrayList<>();
         mDropdownHeight = DROPDOWN_HEIGHT_UNKNOWN;
-        mAutocompleteController = controller;
         mActivityTabSupplier = tabSupplier;
         mBookmarkState = bookmarkState;
     }
@@ -361,13 +359,4 @@
         assert false : "No default handler for suggestions";
         return null;
     }
-
-    /**
-     * Change the AutocompleteController instance that will be used by this class.
-     *
-     * @param controller New AutocompleteController to use.
-     */
-    void setAutocompleteControllerForTest(@NonNull AutocompleteController controller) {
-        mAutocompleteController = controller;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
index 19f5f67d..963095a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
@@ -179,6 +179,7 @@
             loadUrl(url, transition, postDataType, postData);
             return true;
         };
+        // clang-format off
         mLocationBarCoordinator = new LocationBarCoordinator(mSearchBox, anchorView,
                 mProfileSupplier, PrivacyPreferencesManagerImpl.getInstance(),
                 mSearchBoxDataProvider, null, new WindowDelegate(getWindow()), getWindowAndroid(),
@@ -187,9 +188,10 @@
                 getLifecycleDispatcher(), overrideUrlLoadingDelegate, /*backKeyBehavior=*/this,
                 SearchEngineLogoUtils.getInstance(), /*launchAssistanceSettingsAction=*/() -> {},
                 /*pageInfoAction=*/(tab, permission) -> {},
-                /*spareRendererCreator=*/(profile) -> {}, IntentHandler::bringTabToFront,
+                IntentHandler::bringTabToFront,
                 /*saveOfflineButtonState=*/(tab) -> false, /*omniboxUma*/(url, transition) -> {},
                 TabWindowManagerSingleton::getInstance, /*bookmarkState=*/(url) -> false);
+        // clang-format on
         mLocationBarCoordinator.setUrlBarFocusable(true);
         mLocationBarCoordinator.setShouldShowMicButtonWhenUnfocused(true);
         mLocationBarCoordinator.getOmniboxStub().addUrlFocusChangeListener(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 47b27f5..073a020 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -39,7 +39,6 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.IntentHandler;
-import org.chromium.chrome.browser.WarmupManager;
 import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton;
 import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantPreferenceFragment;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge;
@@ -541,7 +540,6 @@
                     new BackKeyBehaviorDelegate() {}, SearchEngineLogoUtils.getInstance(),
                     () -> AutofillAssistantPreferenceFragment.launchSettings(mActivity),
                     toolbarPageInfo::show,
-                    WarmupManager.getInstance()::createSpareRenderProcessHost,
                     IntentHandler::bringTabToFront, DownloadUtils::isAllowedToDownloadPage,
                     NewTabPageUma::recordOmniboxNavigation, TabWindowManagerSingleton::getInstance,
                     (url) -> mBookmarkBridgeSupplier.hasValue()
@@ -908,7 +906,9 @@
                     || mLocationBar.getOmniboxStub().isUrlBarFocused()) {
                 return;
             }
-            setUrlBarFocus(true, OmniboxFocusReason.FOCUS_ON_NEW_TAB);
+            // Note: this is executed during native initialization.
+            // Defer Omnibox focus, giving Autocomplete time to finish initialization.
+            mHandler.post(() -> setUrlBarFocus(true, OmniboxFocusReason.FOCUS_ON_NEW_TAB));
             if (shouldShowCursorInLocationBar()) {
                 mLocationBar.showUrlBarCursorWithoutFocusAnimations();
             }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoProfileDestroyerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoProfileDestroyerIntegrationTest.java
index 406f62e..dfacabf0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoProfileDestroyerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoProfileDestroyerIntegrationTest.java
@@ -24,6 +24,8 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController;
+import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteControllerFactory;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.chrome.browser.tab.Tab;
@@ -50,10 +52,15 @@
     @Mock
     ProfileManager.Observer mMockProfileManagerObserver;
 
+    @Mock
+    AutocompleteController mAutocompleteController;
+
     @Before
     public void setUp() throws InterruptedException {
         MockitoAnnotations.initMocks(this);
 
+        AutocompleteControllerFactory.setControllerForTesting(mAutocompleteController);
+
         mActivityTestRule.startMainActivityOnBlankPage();
         ProfileManager.addObserver(mMockProfileManagerObserver);
         mIncognitoTabModel = mActivityTestRule.getActivity().getTabModelSelector().getModel(true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
index 8875e46d..750ca18e6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
@@ -57,6 +57,7 @@
 import org.chromium.components.omnibox.AutocompleteResult;
 import org.chromium.components.query_tiles.QueryTile;
 import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
@@ -208,10 +209,17 @@
         mListModel = new PropertyModel(SuggestionListProperties.ALL_KEYS);
         mListModel.set(SuggestionListProperties.SUGGESTION_MODELS, mSuggestionModels);
 
+        AutocompleteControllerFactory.setControllerForTesting(mAutocompleteController);
+        // clang-format off
         mMediator = new AutocompleteMediator(ContextUtils.getApplicationContext(),
-                mAutocompleteDelegate, mTextStateProvider, mAutocompleteController, mListModel,
+                mAutocompleteDelegate, mTextStateProvider, mListModel,
                 mHandler, mLifecycleDispatcher, () -> mModalDialogManager, null, null,
                 mLocationBarDataProvider, tab -> {}, null, url -> false);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mMediator.setAutocompleteProfile(mProfile);
+        });
+        // clang-format on
         mMediator.getDropdownItemViewInfoListBuilderForTest().registerSuggestionProcessor(
                 mMockProcessor);
         mMediator.getDropdownItemViewInfoListBuilderForTest().setHeaderProcessorForTest(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilderUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilderUnitTest.java
index b021b73..37938a3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilderUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilderUnitTest.java
@@ -81,8 +81,7 @@
                 .thenAnswer((mock) -> new PropertyModel(SuggestionCommonProperties.ALL_KEYS));
         when(mMockHeaderProcessor.getViewTypeId()).thenReturn(OmniboxSuggestionUiType.HEADER);
 
-        mBuilder = new DropdownItemViewInfoListBuilder(
-                mAutocompleteController, () -> null, (url) -> false);
+        mBuilder = new DropdownItemViewInfoListBuilder(() -> null, (url) -> false);
         mBuilder.registerSuggestionProcessor(mMockSuggestionProcessor);
         mBuilder.setHeaderProcessorForTest(mMockHeaderProcessor);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
index 35db870..70ceebf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesTest.java
@@ -4,6 +4,12 @@
 
 package org.chromium.chrome.browser.omnibox.suggestions.mostvisited;
 
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
 import android.support.test.InstrumentationRegistry;
 import android.view.KeyEvent;
 import android.view.View;
@@ -18,6 +24,10 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -29,15 +39,17 @@
 import org.chromium.chrome.browser.omnibox.LocationBarLayout;
 import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType;
 import org.chromium.chrome.browser.omnibox.UrlBar;
+import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteController;
+import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteControllerFactory;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
 import org.chromium.chrome.browser.omnibox.suggestions.carousel.BaseCarouselSuggestionView;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.OmniboxTestUtils;
-import org.chromium.chrome.test.util.OmniboxTestUtils.TestAutocompleteController;
 import org.chromium.chrome.test.util.WaitForFocusHelper;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.omnibox.AutocompleteMatch.NavsuggestTile;
@@ -81,10 +93,19 @@
     public final BlankCTATabInitialStateRule mInitialStateRule =
             new BlankCTATabInitialStateRule(sActivityTestRule, false);
 
+    @Mock
+    private Profile mProfile;
+
+    @Mock
+    private AutocompleteController mController;
+
+    @Captor
+    private ArgumentCaptor<AutocompleteController.OnSuggestionsReceivedListener> mListener;
+
     private ChromeTabbedActivity mActivity;
     private UrlBar mUrlBar;
     private LocationBarLayout mLocationBarLayout;
-    private TestAutocompleteController mController;
+
     private AutocompleteCoordinator mAutocomplete;
     private EmbeddedTestServer mTestServer;
     private Tab mTab;
@@ -96,6 +117,7 @@
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         sActivityTestRule.waitForActivityNativeInitializationComplete();
         mActivity = sActivityTestRule.getActivity();
         mLocationBarLayout = mActivity.findViewById(R.id.location_bar);
@@ -108,9 +130,15 @@
         ChromeTabUtils.waitForTabPageLoaded(mTab, null);
 
         // Set up a fake AutocompleteController that will supply the suggestions.
-        mController = new OmniboxTestUtils.TestAutocompleteController(
-                mAutocomplete.getSuggestionsReceivedListenerForTest());
-        mAutocomplete.setAutocompleteControllerForTest(mController);
+        AutocompleteControllerFactory.setControllerForTesting(mController);
+
+        // clang-format off
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mAutocomplete.setAutocompleteProfile(mProfile);
+        });
+        // clang-format on
+
+        verify(mController).addOnSuggestionsReceivedListener(mListener.capture());
 
         setUpSuggestionsToShow();
         focusOmniboxAndWaitForSuggestions();
@@ -160,7 +188,12 @@
         autocompleteResult.getGroupsDetails().put(
                 1, new AutocompleteResult.GroupDetails("See also", false));
 
-        mController.addAutocompleteResult(PAGE_URL, PAGE_URL, autocompleteResult);
+        doAnswer(invocation -> {
+            mListener.getValue().onSuggestionsReceived(autocompleteResult, PAGE_URL);
+            return null;
+        })
+                .when(mController)
+                .startZeroSuggest(any(), eq(PAGE_URL), any(), anyInt(), any());
     }
 
     private void focusOmniboxAndWaitForSuggestions() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerTest.java
index d4b7624..4e92a30 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerTest.java
@@ -413,9 +413,11 @@
         public TestAutocompleteCoordinator(ViewGroup parent, AutocompleteDelegate delegate,
                 OmniboxSuggestionsDropdownEmbedder dropdownEmbedder,
                 UrlBarEditingTextStateProvider urlBarEditingTextProvider) {
+            // clang-format off
             super(parent, delegate, dropdownEmbedder, urlBarEditingTextProvider,
                     mLifecycleDispatcher, () -> mModalDialogManager, null, null, mDataProvider,
-                    (profile) -> {}, (tab) -> {}, null, (url) -> false);
+                    mProfileSupplier, (tab) -> {}, null, (url) -> false);
+            // clang-format on
         }
 
         @Override
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/ui/controller/webapps/WebappDisclosureControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/ui/controller/webapps/WebappDisclosureControllerTest.java
index d997af23..7cd81e1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/ui/controller/webapps/WebappDisclosureControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/ui/controller/webapps/WebappDisclosureControllerTest.java
@@ -27,6 +27,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.android.util.concurrent.RoboExecutorService;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
 
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -49,6 +50,8 @@
  */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
+// TODO(crbug.com/1210371): Change to use paused looper. See crbug for details.
+@LooperMode(LooperMode.Mode.LEGACY)
 public class WebappDisclosureControllerTest {
     private static final String UNBOUND_PACKAGE = "unbound";
     private static final String BOUND_PACKAGE = WebApkConstants.WEBAPK_PACKAGE_PREFIX + ".bound";
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/PolicyLoadListenerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/PolicyLoadListenerUnitTest.java
index fbc350b..b150a41 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/PolicyLoadListenerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/firstrun/PolicyLoadListenerUnitTest.java
@@ -19,6 +19,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowProcess;
 
@@ -169,6 +170,8 @@
     }
 
     @Test
+    // TODO(crbug.com/1210371): Change to use paused loop. See crbug for details.
+    @LooperMode(LooperMode.Mode.LEGACY)
     public void testDestroyAfterStart_PolicyInitializedInterleaved() {
         Assert.assertNull(LOADING_NOT_FINISHED, mPolicyLoadListener.onAvailable(mListener));
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelperTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelperTest.java
index 9c7a607..8f1b1e5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelperTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelperTest.java
@@ -9,6 +9,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import org.robolectric.annotation.LooperMode;
 
 import android.app.Activity;
 
@@ -51,6 +52,8 @@
     /** Tests that connection attempts are delayed. */
     @Test
     @Feature({"GCore"})
+    // TODO(crbug.com/1210371): Change to use paused loop. See crbug for details.
+    @LooperMode(LooperMode.Mode.LEGACY)
     public void connectionAttemptDelayTest() {
         GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
index 3a045aa..b628920 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
@@ -23,6 +23,7 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.RealObject;
 import org.robolectric.shadows.ShadowLooper;
 
@@ -60,6 +61,8 @@
             ChromeSurveyControllerFlowTest.ShadowSurveyInfoBar.class,
             ChromeSurveyControllerFlowTest.ShadowInfoBarContainer.class
         })
+//TODO(crbug.com/1210371): Rewrite using paused loop. See crbug for details.
+@LooperMode(LooperMode.Mode.LEGACY)
 public class ChromeSurveyControllerFlowTest {
     // clang-format on
     private static final String TEST_TRIGGER_ID = "test_trigger_id";
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 48f0e355..8d508920 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -315,8 +315,8 @@
   <message name="IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_MESSAGE" desc="Message displayed during loading page in eSIM setup flow when looking for eSIM profiles.">
     Looking for available profiles...
   </message>
-  <message name="IDS_CELLULAR_SETUP_ESIM_CONNECTION_WARNING" desc="Message displayed during loading page in eSIM setup flow when looking for eSIM profiles informing the user that cellular setup may disconnect their current cellular network connection.">
-    This may cause your mobile network to briefly disconnect
+  <message name="IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_DURING_ACTIVE_CELLULAR_CONNECTION_MESSAGE" desc="Message displayed during loading page in eSIM setup flow when looking for eSIM profiles informing the user that cellular setup may disconnect their current cellular network connection.">
+    Looking for available profiles. This may cause your mobile network to disconnect for a few minutes.
   </message>
   <message name="IDS_CELLULAR_SETUP_PROFILE_LIST_PAGE_MESSAGE" desc="Label informing the user that they are able to select multiple eSIM profiles for downloading.">
     We've found multiple profiles available to download. Select the ones you would like to download before proceeding.
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_CONNECTION_WARNING.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_CONNECTION_WARNING.png.sha1
deleted file mode 100644
index de7f9a53..0000000
--- a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_CONNECTION_WARNING.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-9d01cc22fe3233eaf079c7ae82057ad2a0b2e829
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_DURING_ACTIVE_CELLULAR_CONNECTION_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_DURING_ACTIVE_CELLULAR_CONNECTION_MESSAGE.png.sha1
new file mode 100644
index 0000000..aed39a7
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_DURING_ACTIVE_CELLULAR_CONNECTION_MESSAGE.png.sha1
@@ -0,0 +1 @@
+c30a1b8bae5ded451e6a8efd6ca98dc993604ce2
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_MESSAGE.png.sha1
index 1665de8..311eac4 100644
--- a/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_MESSAGE.png.sha1
+++ b/chrome/app/chromeos_strings_grdp/IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_MESSAGE.png.sha1
@@ -1 +1 @@
-50d98ca9d86f9d8fe780771b4de96d4edeb154dc
\ No newline at end of file
+ac77a7010623072630a82e3903bad1d220a10f0e
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 05d993db..c6d09b2 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -1235,6 +1235,9 @@
         <message name="IDS_TAKE_SCREENSHOT" desc="The text label of the Take Screenshot menu item">
           T&amp;ake screenshot
         </message>
+        <message name="IDS_RESTORE_ALL_TABS" desc="The text label of the Restore All Tabs menu item.">
+          R&amp;estore all tabs
+        </message>
         <message name="IDS_RESTORE_TAB" desc="The text label of the Restore Tab menu item">
           R&amp;eopen closed tab
         </message>
@@ -1273,6 +1276,9 @@
         <message name="IDS_TAKE_SCREENSHOT" desc="The text label of the Take Screenshot menu item">
           T&amp;ake Screenshot
         </message>
+        <message name="IDS_RESTORE_ALL_TABS" desc="In Title Case: The text label of the Restore All Tabs menu item.">
+          R&amp;estore All Tabs
+        </message>
         <message name="IDS_RESTORE_TAB" desc="In Title Case: The text label of the Restore Tab menu item">
           R&amp;eopen Closed Tab
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_RESTORE_ALL_TABS.png.sha1 b/chrome/app/generated_resources_grd/IDS_RESTORE_ALL_TABS.png.sha1
new file mode 100644
index 0000000..61a5b433
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_RESTORE_ALL_TABS.png.sha1
@@ -0,0 +1 @@
+2f5e9d03f9f0de8965d079c981475b7c8c00e9f7
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 82833c7..0605834 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3397,12 +3397,12 @@
   } else {
     #!is_android
     sources += [
-      "accessibility/caption_controller.cc",
-      "accessibility/caption_controller.h",
-      "accessibility/caption_controller_factory.cc",
-      "accessibility/caption_controller_factory.h",
       "accessibility/invert_bubble_prefs.cc",
       "accessibility/invert_bubble_prefs.h",
+      "accessibility/live_caption_controller.cc",
+      "accessibility/live_caption_controller.h",
+      "accessibility/live_caption_controller_factory.cc",
+      "accessibility/live_caption_controller_factory.h",
       "accessibility/live_caption_speech_recognition_host.cc",
       "accessibility/live_caption_speech_recognition_host.h",
       "apps/app_service/app_icon_factory.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index df2c8c33..c75598f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -7057,10 +7057,6 @@
      FEATURE_VALUE_TYPE(net::features::kSplitCacheByNetworkIsolationKey)},
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    {"enable-scalable-status-area", flag_descriptions::kScalableStatusAreaName,
-     flag_descriptions::kScalableStatusAreaDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kScalableStatusArea)},
-
     {"enable-show-date-in-tray", flag_descriptions::kShowDateInTrayName,
      flag_descriptions::kShowDateInTrayDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kShowDateInTrayButton)},
diff --git a/chrome/browser/accessibility/caption_controller_factory.cc b/chrome/browser/accessibility/caption_controller_factory.cc
deleted file mode 100644
index 28085388..0000000
--- a/chrome/browser/accessibility/caption_controller_factory.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/accessibility/caption_controller_factory.h"
-
-#include "build/build_config.h"
-#include "chrome/browser/accessibility/caption_controller.h"
-#include "chrome/browser/profiles/incognito_helpers.h"
-#include "chrome/browser/profiles/profile.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
-
-namespace captions {
-
-// static
-CaptionController* CaptionControllerFactory::GetForProfile(Profile* profile) {
-  return static_cast<CaptionController*>(
-      GetInstance()->GetServiceForBrowserContext(profile, true));
-}
-
-// static
-CaptionController* CaptionControllerFactory::GetForProfileIfExists(
-    Profile* profile) {
-  return static_cast<CaptionController*>(
-      GetInstance()->GetServiceForBrowserContext(profile, /*create=*/false));
-}
-
-// static
-CaptionControllerFactory* CaptionControllerFactory::GetInstance() {
-  static base::NoDestructor<CaptionControllerFactory> factory;
-  return factory.get();
-}
-
-CaptionControllerFactory::CaptionControllerFactory()
-    : BrowserContextKeyedServiceFactory(
-          "CaptionController",
-          BrowserContextDependencyManager::GetInstance()) {}
-
-CaptionControllerFactory::~CaptionControllerFactory() = default;
-
-content::BrowserContext* CaptionControllerFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  return chrome::GetBrowserContextRedirectedInIncognito(context);
-}
-
-KeyedService* CaptionControllerFactory::BuildServiceInstanceFor(
-    content::BrowserContext* context) const {
-  return new CaptionController(
-      Profile::FromBrowserContext(context)->GetPrefs());
-}
-
-}  // namespace captions
diff --git a/chrome/browser/accessibility/caption_controller_factory.h b/chrome/browser/accessibility/caption_controller_factory.h
deleted file mode 100644
index 1caf078..0000000
--- a/chrome/browser/accessibility/caption_controller_factory.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ACCESSIBILITY_CAPTION_CONTROLLER_FACTORY_H_
-#define CHROME_BROWSER_ACCESSIBILITY_CAPTION_CONTROLLER_FACTORY_H_
-
-#include "base/no_destructor.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-
-class Profile;
-
-namespace captions {
-
-class CaptionController;
-
-// Factory to get or create an instance of CaptionController from a Profile.
-class CaptionControllerFactory : public BrowserContextKeyedServiceFactory {
- public:
-  static CaptionController* GetForProfile(Profile* profile);
-
-  static CaptionController* GetForProfileIfExists(Profile* profile);
-
-  static CaptionControllerFactory* GetInstance();
-
- private:
-  friend base::NoDestructor<CaptionControllerFactory>;
-
-  CaptionControllerFactory();
-  ~CaptionControllerFactory() override;
-
-  // BrowserContextKeyedServiceFactory:
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* profile) const override;
-};
-
-}  // namespace captions
-
-#endif  // CHROME_BROWSER_ACCESSIBILITY_CAPTION_CONTROLLER_FACTORY_H_
diff --git a/chrome/browser/accessibility/caption_controller.cc b/chrome/browser/accessibility/live_caption_controller.cc
similarity index 85%
rename from chrome/browser/accessibility/caption_controller.cc
rename to chrome/browser/accessibility/live_caption_controller.cc
index 5e67ea81..0fbf024 100644
--- a/chrome/browser/accessibility/caption_controller.cc
+++ b/chrome/browser/accessibility/live_caption_controller.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/accessibility/caption_controller.h"
+#include "chrome/browser/accessibility/live_caption_controller.h"
 
 #include <memory>
 
@@ -39,10 +39,10 @@
 
 namespace captions {
 
-CaptionController::CaptionController(PrefService* profile_prefs)
+LiveCaptionController::LiveCaptionController(PrefService* profile_prefs)
     : profile_prefs_(profile_prefs) {}
 
-CaptionController::~CaptionController() {
+LiveCaptionController::~LiveCaptionController() {
   if (enabled_) {
     enabled_ = false;
     StopLiveCaption();
@@ -50,7 +50,7 @@
 }
 
 // static
-void CaptionController::RegisterProfilePrefs(
+void LiveCaptionController::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
   registry->RegisterBooleanPref(
       prefs::kLiveCaptionEnabled, false,
@@ -61,7 +61,7 @@
                                user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
 }
 
-void CaptionController::Init() {
+void LiveCaptionController::Init() {
   base::UmaHistogramBoolean("Accessibility.LiveCaption.FeatureEnabled",
                             media::IsLiveCaptionFeatureEnabled());
 
@@ -82,11 +82,11 @@
 
   pref_change_registrar_->Add(
       prefs::kLiveCaptionEnabled,
-      base::BindRepeating(&CaptionController::OnLiveCaptionEnabledChanged,
+      base::BindRepeating(&LiveCaptionController::OnLiveCaptionEnabledChanged,
                           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kLiveCaptionLanguageCode,
-      base::BindRepeating(&CaptionController::OnLiveCaptionLanguageChanged,
+      base::BindRepeating(&LiveCaptionController::OnLiveCaptionLanguageChanged,
                           base::Unretained(this)));
 
   enabled_ = IsLiveCaptionEnabled();
@@ -100,11 +100,11 @@
   // callback is not called on destroyed object.
   content::BrowserAccessibilityState::GetInstance()
       ->AddUIThreadHistogramCallback(base::BindOnce(
-          &CaptionController::UpdateAccessibilityCaptionHistograms,
+          &LiveCaptionController::UpdateAccessibilityCaptionHistograms,
           weak_ptr_factory_.GetWeakPtr()));
 }
 
-void CaptionController::OnLiveCaptionEnabledChanged() {
+void LiveCaptionController::OnLiveCaptionEnabledChanged() {
   bool enabled = IsLiveCaptionEnabled();
   if (enabled == enabled_)
     return;
@@ -119,19 +119,19 @@
   }
 }
 
-void CaptionController::OnLiveCaptionLanguageChanged() {
+void LiveCaptionController::OnLiveCaptionLanguageChanged() {
   if (enabled_)
     speech::SodaInstaller::GetInstance()->InstallLanguage(
         profile_prefs_->GetString(prefs::kLiveCaptionLanguageCode),
         g_browser_process->local_state());
 }
 
-bool CaptionController::IsLiveCaptionEnabled() {
+bool LiveCaptionController::IsLiveCaptionEnabled() {
   PrefService* profile_prefs = profile_prefs_;
   return profile_prefs->GetBoolean(prefs::kLiveCaptionEnabled);
 }
 
-void CaptionController::StartLiveCaption() {
+void LiveCaptionController::StartLiveCaption() {
   DCHECK(enabled_);
   if (!base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption)) {
     CreateUI();
@@ -151,13 +151,13 @@
   }
 }
 
-void CaptionController::StopLiveCaption() {
+void LiveCaptionController::StopLiveCaption() {
   DCHECK(!enabled_);
   speech::SodaInstaller::GetInstance()->RemoveObserver(this);
   DestroyUI();
 }
 
-void CaptionController::OnSodaInstalled() {
+void LiveCaptionController::OnSodaInstalled() {
   // Live Caption should always be enabled when this is called. If Live Caption
   // has been disabled, then this should not be observing the SodaInstaller
   // anymore.
@@ -166,7 +166,7 @@
   CreateUI();
 }
 
-void CaptionController::CreateUI() {
+void LiveCaptionController::CreateUI() {
   DCHECK(enabled_);
   if (is_ui_constructed_)
     return;
@@ -185,13 +185,13 @@
     DCHECK(!pref_change_registrar_->IsObserved(pref_name));
     pref_change_registrar_->Add(
         pref_name,
-        base::BindRepeating(&CaptionController::OnCaptionStyleUpdated,
+        base::BindRepeating(&LiveCaptionController::OnCaptionStyleUpdated,
                             base::Unretained(this)));
   }
   OnCaptionStyleUpdated();
 }
 
-void CaptionController::DestroyUI() {
+void LiveCaptionController::DestroyUI() {
   DCHECK(!enabled_);
   if (!is_ui_constructed_)
     return;
@@ -208,11 +208,11 @@
   }
 }
 
-void CaptionController::UpdateAccessibilityCaptionHistograms() {
+void LiveCaptionController::UpdateAccessibilityCaptionHistograms() {
   base::UmaHistogramBoolean("Accessibility.LiveCaption", enabled_);
 }
 
-bool CaptionController::DispatchTranscription(
+bool LiveCaptionController::DispatchTranscription(
     LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host,
     const media::mojom::SpeechRecognitionResultPtr& result) {
   if (!caption_bubble_controller_)
@@ -221,14 +221,14 @@
       live_caption_speech_recognition_host, result);
 }
 
-void CaptionController::OnError(
+void LiveCaptionController::OnError(
     LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host) {
   if (!caption_bubble_controller_)
     return;
   caption_bubble_controller_->OnError(live_caption_speech_recognition_host);
 }
 
-void CaptionController::OnAudioStreamEnd(
+void LiveCaptionController::OnAudioStreamEnd(
     LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host) {
   if (!caption_bubble_controller_)
     return;
@@ -236,12 +236,12 @@
       live_caption_speech_recognition_host);
 }
 
-void CaptionController::OnLanguageIdentificationEvent(
+void LiveCaptionController::OnLanguageIdentificationEvent(
     const media::mojom::LanguageIdentificationEventPtr& event) {
   // TODO(crbug.com/1175357): Implement the UI for language identification.
 }
 
-void CaptionController::OnCaptionStyleUpdated() {
+void LiveCaptionController::OnCaptionStyleUpdated() {
   // Metrics are recorded when passing the caption prefs to the browser, so do
   // not duplicate them here.
   caption_style_ = GetCaptionStyleFromUserSettings(profile_prefs_,
diff --git a/chrome/browser/accessibility/caption_controller.h b/chrome/browser/accessibility/live_caption_controller.h
similarity index 75%
rename from chrome/browser/accessibility/caption_controller.h
rename to chrome/browser/accessibility/live_caption_controller.h
index e0cd6c6..101f639f 100644
--- a/chrome/browser/accessibility/caption_controller.h
+++ b/chrome/browser/accessibility/live_caption_controller.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ACCESSIBILITY_CAPTION_CONTROLLER_H_
-#define CHROME_BROWSER_ACCESSIBILITY_CAPTION_CONTROLLER_H_
+#ifndef CHROME_BROWSER_ACCESSIBILITY_LIVE_CAPTION_CONTROLLER_H_
+#define CHROME_BROWSER_ACCESSIBILITY_LIVE_CAPTION_CONTROLLER_H_
 
 #include <memory>
 
@@ -31,22 +31,22 @@
 class LiveCaptionSpeechRecognitionHost;
 
 ///////////////////////////////////////////////////////////////////////////////
-// Caption Controller
+// Live Caption Controller
 //
 //  The controller of the live caption feature. It enables the captioning
-//  service when the preference is enabled. The caption controller is a
-//  KeyedService. There exists one caption controller per profile and it lasts
-//  for the duration of the session. The caption controller owns the live
-//  caption UI, which is a caption bubble controller.
+//  service when the preference is enabled. The live caption controller is a
+//  KeyedService. There exists one live caption controller per profile and it
+//  lasts for the duration of the session. The live caption controller owns the
+//  live caption UI, which is a caption bubble controller.
 //
-class CaptionController : public KeyedService,
-                          public speech::SodaInstaller::Observer,
-                          public ui::NativeThemeObserver {
+class LiveCaptionController : public KeyedService,
+                              public speech::SodaInstaller::Observer,
+                              public ui::NativeThemeObserver {
  public:
-  explicit CaptionController(PrefService* profile_prefs);
-  ~CaptionController() override;
-  CaptionController(const CaptionController&) = delete;
-  CaptionController& operator=(const CaptionController&) = delete;
+  explicit LiveCaptionController(PrefService* profile_prefs);
+  ~LiveCaptionController() override;
+  LiveCaptionController(const LiveCaptionController&) = delete;
+  LiveCaptionController& operator=(const LiveCaptionController&) = delete;
 
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
@@ -72,8 +72,8 @@
       LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host);
 
  private:
-  friend class CaptionControllerFactory;
-  friend class CaptionControllerTest;
+  friend class LiveCaptionControllerFactory;
+  friend class LiveCaptionControllerTest;
 
   // SodaInstaller::Observer:
   void OnSodaInstalled() override;
@@ -113,9 +113,9 @@
   // ensures that the UI is not constructed or deconstructed twice.
   bool is_ui_constructed_ = false;
 
-  base::WeakPtrFactory<CaptionController> weak_ptr_factory_{this};
+  base::WeakPtrFactory<LiveCaptionController> weak_ptr_factory_{this};
 };
 
 }  // namespace captions
 
-#endif  // CHROME_BROWSER_ACCESSIBILITY_CAPTION_CONTROLLER_H_
+#endif  // CHROME_BROWSER_ACCESSIBILITY_LIVE_CAPTION_CONTROLLER_H_
diff --git a/chrome/browser/accessibility/caption_controller_browsertest.cc b/chrome/browser/accessibility/live_caption_controller_browsertest.cc
similarity index 91%
rename from chrome/browser/accessibility/caption_controller_browsertest.cc
rename to chrome/browser/accessibility/live_caption_controller_browsertest.cc
index b434f95..54e616c 100644
--- a/chrome/browser/accessibility/caption_controller_browsertest.cc
+++ b/chrome/browser/accessibility/live_caption_controller_browsertest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/accessibility/caption_controller.h"
+#include "chrome/browser/accessibility/live_caption_controller.h"
 
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
@@ -10,7 +10,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/accessibility/caption_controller_factory.h"
+#include "chrome/browser/accessibility/live_caption_controller_factory.h"
 #include "chrome/browser/accessibility/live_caption_speech_recognition_host.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/browser_process.h"
@@ -68,12 +68,13 @@
   return profile_manager->GetProfileByPath(profile_path);
 }
 
-class CaptionControllerTest : public InProcessBrowserTest {
+class LiveCaptionControllerTest : public InProcessBrowserTest {
  public:
-  CaptionControllerTest() = default;
-  ~CaptionControllerTest() override = default;
-  CaptionControllerTest(const CaptionControllerTest&) = delete;
-  CaptionControllerTest& operator=(const CaptionControllerTest&) = delete;
+  LiveCaptionControllerTest() = default;
+  ~LiveCaptionControllerTest() override = default;
+  LiveCaptionControllerTest(const LiveCaptionControllerTest&) = delete;
+  LiveCaptionControllerTest& operator=(const LiveCaptionControllerTest&) =
+      delete;
 
   // InProcessBrowserTest overrides:
   void SetUp() override {
@@ -94,12 +95,12 @@
       speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
   }
 
-  CaptionController* GetController() {
+  LiveCaptionController* GetController() {
     return GetControllerForProfile(browser()->profile());
   }
 
-  CaptionController* GetControllerForProfile(Profile* profile) {
-    return CaptionControllerFactory::GetForProfile(profile);
+  LiveCaptionController* GetControllerForProfile(Profile* profile) {
+    return LiveCaptionControllerFactory::GetForProfile(profile);
   }
 
   CaptionBubbleController* GetBubbleController() {
@@ -184,7 +185,7 @@
       live_caption_speech_recognition_host_;
 };
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest, ProfilePrefsAreRegistered) {
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, ProfilePrefsAreRegistered) {
   EXPECT_FALSE(
       browser()->profile()->GetPrefs()->GetBoolean(prefs::kLiveCaptionEnabled));
 
@@ -198,7 +199,7 @@
 #endif  // !defined(OS_CHROMEOS)
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest,
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
                        ProfilePrefsAreRegistered_Incognito) {
   // Set live caption enabled on the regular profile.
   SetLiveCaptionEnabled(true);
@@ -228,7 +229,7 @@
 #endif  // !defined(OS_CHROMEOS)
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest, LiveCaptionEnabledChanged) {
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, LiveCaptionEnabledChanged) {
   EXPECT_EQ(nullptr, GetBubbleController());
   EXPECT_FALSE(HasBubbleController());
 
@@ -241,7 +242,7 @@
   EXPECT_FALSE(HasBubbleController());
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest,
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
                        LiveCaptionEnabledChanged_BubbleVisible) {
   SetLiveCaptionEnabled(true);
   // Make the bubble visible by dispatching a transcription.
@@ -254,7 +255,7 @@
   EXPECT_FALSE(HasBubbleController());
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest, OnSodaInstalled) {
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnSodaInstalled) {
   EXPECT_FALSE(HasBubbleController());
   browser()->profile()->GetPrefs()->SetBoolean(prefs::kLiveCaptionEnabled,
                                                true);
@@ -265,7 +266,7 @@
   EXPECT_TRUE(HasBubbleController());
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest, DispatchTranscription) {
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, DispatchTranscription) {
   bool success = DispatchTranscription("A baby spider is called a spiderling.");
   EXPECT_FALSE(success);
   EXPECT_FALSE(HasBubbleController());
@@ -286,7 +287,7 @@
   EXPECT_FALSE(HasBubbleController());
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest, OnError) {
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnError) {
   OnError();
   EXPECT_FALSE(HasBubbleController());
 
@@ -299,7 +300,7 @@
   EXPECT_FALSE(HasBubbleController());
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest, OnAudioStreamEnd) {
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnAudioStreamEnd) {
   OnAudioStreamEnd();
   EXPECT_FALSE(HasBubbleController());
 
@@ -317,7 +318,7 @@
 
 #if !BUILDFLAG(IS_CHROMEOS_ASH)  // No multi-profile on ChromeOS.
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest,
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
                        LiveCaptionEnabledChanged_MultipleProfiles) {
   Profile* profile1 = browser()->profile();
   Profile* profile2 = CreateProfile();
@@ -347,7 +348,7 @@
   EXPECT_FALSE(HasBubbleControllerOnProfile(profile2));
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest,
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
                        DispatchTranscription_MultipleProfiles) {
   Profile* profile1 = browser()->profile();
   Profile* profile2 = CreateProfile();
@@ -375,7 +376,7 @@
       "Mosquitos were around at the time of the dinosaurs.", profile2);
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest, OnError_MultipleProfiles) {
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest, OnError_MultipleProfiles) {
   Profile* profile1 = browser()->profile();
   Profile* profile2 = CreateProfile();
 
@@ -393,7 +394,7 @@
   ExpectIsWidgetVisibleOnProfile(true, profile2);
 }
 
-IN_PROC_BROWSER_TEST_F(CaptionControllerTest,
+IN_PROC_BROWSER_TEST_F(LiveCaptionControllerTest,
                        OnAudioStreamEnd_MultipleProfiles) {
   Profile* profile1 = browser()->profile();
   Profile* profile2 = CreateProfile();
diff --git a/chrome/browser/accessibility/live_caption_controller_factory.cc b/chrome/browser/accessibility/live_caption_controller_factory.cc
new file mode 100644
index 0000000..36551a3
--- /dev/null
+++ b/chrome/browser/accessibility/live_caption_controller_factory.cc
@@ -0,0 +1,53 @@
+// 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 "chrome/browser/accessibility/live_caption_controller_factory.h"
+
+#include "build/build_config.h"
+#include "chrome/browser/accessibility/live_caption_controller.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+namespace captions {
+
+// static
+LiveCaptionController* LiveCaptionControllerFactory::GetForProfile(
+    Profile* profile) {
+  return static_cast<LiveCaptionController*>(
+      GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+// static
+LiveCaptionController* LiveCaptionControllerFactory::GetForProfileIfExists(
+    Profile* profile) {
+  return static_cast<LiveCaptionController*>(
+      GetInstance()->GetServiceForBrowserContext(profile, /*create=*/false));
+}
+
+// static
+LiveCaptionControllerFactory* LiveCaptionControllerFactory::GetInstance() {
+  static base::NoDestructor<LiveCaptionControllerFactory> factory;
+  return factory.get();
+}
+
+LiveCaptionControllerFactory::LiveCaptionControllerFactory()
+    : BrowserContextKeyedServiceFactory(
+          "LiveCaptionController",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+LiveCaptionControllerFactory::~LiveCaptionControllerFactory() = default;
+
+content::BrowserContext* LiveCaptionControllerFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return chrome::GetBrowserContextRedirectedInIncognito(context);
+}
+
+KeyedService* LiveCaptionControllerFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new LiveCaptionController(
+      Profile::FromBrowserContext(context)->GetPrefs());
+}
+
+}  // namespace captions
diff --git a/chrome/browser/accessibility/live_caption_controller_factory.h b/chrome/browser/accessibility/live_caption_controller_factory.h
new file mode 100644
index 0000000..72cde4b
--- /dev/null
+++ b/chrome/browser/accessibility/live_caption_controller_factory.h
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ACCESSIBILITY_LIVE_CAPTION_CONTROLLER_FACTORY_H_
+#define CHROME_BROWSER_ACCESSIBILITY_LIVE_CAPTION_CONTROLLER_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class Profile;
+
+namespace captions {
+
+class LiveCaptionController;
+
+// Factory to get or create an instance of LiveCaptionController from a Profile.
+class LiveCaptionControllerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static LiveCaptionController* GetForProfile(Profile* profile);
+
+  static LiveCaptionController* GetForProfileIfExists(Profile* profile);
+
+  static LiveCaptionControllerFactory* GetInstance();
+
+ private:
+  friend base::NoDestructor<LiveCaptionControllerFactory>;
+
+  LiveCaptionControllerFactory();
+  ~LiveCaptionControllerFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* profile) const override;
+};
+
+}  // namespace captions
+
+#endif  // CHROME_BROWSER_ACCESSIBILITY_LIVE_CAPTION_CONTROLLER_FACTORY_H_
diff --git a/chrome/browser/accessibility/live_caption_speech_recognition_host.cc b/chrome/browser/accessibility/live_caption_speech_recognition_host.cc
index a488705..7407b3b 100644
--- a/chrome/browser/accessibility/live_caption_speech_recognition_host.cc
+++ b/chrome/browser/accessibility/live_caption_speech_recognition_host.cc
@@ -7,8 +7,8 @@
 #include <memory>
 #include <utility>
 
-#include "chrome/browser/accessibility/caption_controller.h"
-#include "chrome/browser/accessibility/caption_controller_factory.h"
+#include "chrome/browser/accessibility/live_caption_controller.h"
+#include "chrome/browser/accessibility/live_caption_controller_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
@@ -36,35 +36,36 @@
 }
 
 LiveCaptionSpeechRecognitionHost::~LiveCaptionSpeechRecognitionHost() {
-  CaptionController* caption_controller = GetCaptionController();
-  if (caption_controller)
-    caption_controller->OnAudioStreamEnd(this);
+  LiveCaptionController* live_caption_controller = GetLiveCaptionController();
+  if (live_caption_controller)
+    live_caption_controller->OnAudioStreamEnd(this);
 }
 
 void LiveCaptionSpeechRecognitionHost::OnSpeechRecognitionRecognitionEvent(
     media::mojom::SpeechRecognitionResultPtr result,
     OnSpeechRecognitionRecognitionEventCallback reply) {
-  CaptionController* caption_controller = GetCaptionController();
-  if (!caption_controller) {
+  LiveCaptionController* live_caption_controller = GetLiveCaptionController();
+  if (!live_caption_controller) {
     std::move(reply).Run(false);
     return;
   }
-  std::move(reply).Run(caption_controller->DispatchTranscription(this, result));
+  std::move(reply).Run(
+      live_caption_controller->DispatchTranscription(this, result));
 }
 
 void LiveCaptionSpeechRecognitionHost::OnLanguageIdentificationEvent(
     media::mojom::LanguageIdentificationEventPtr event) {
-  CaptionController* caption_controller = GetCaptionController();
-  if (!caption_controller)
+  LiveCaptionController* live_caption_controller = GetLiveCaptionController();
+  if (!live_caption_controller)
     return;
 
-  caption_controller->OnLanguageIdentificationEvent(std::move(event));
+  live_caption_controller->OnLanguageIdentificationEvent(std::move(event));
 }
 
 void LiveCaptionSpeechRecognitionHost::OnSpeechRecognitionError() {
-  CaptionController* caption_controller = GetCaptionController();
-  if (caption_controller)
-    caption_controller->OnError(this);
+  LiveCaptionController* live_caption_controller = GetLiveCaptionController();
+  if (live_caption_controller)
+    live_caption_controller->OnError(this);
 }
 
 void LiveCaptionSpeechRecognitionHost::RenderFrameDeleted(
@@ -83,7 +84,8 @@
   return web_contents;
 }
 
-CaptionController* LiveCaptionSpeechRecognitionHost::GetCaptionController() {
+LiveCaptionController*
+LiveCaptionSpeechRecognitionHost::GetLiveCaptionController() {
   content::WebContents* web_contents = GetWebContents();
   if (!web_contents)
     return nullptr;
@@ -91,7 +93,7 @@
       Profile::FromBrowserContext(web_contents->GetBrowserContext());
   if (!profile)
     return nullptr;
-  return CaptionControllerFactory::GetForProfile(profile);
+  return LiveCaptionControllerFactory::GetForProfile(profile);
 }
 
 }  // namespace captions
diff --git a/chrome/browser/accessibility/live_caption_speech_recognition_host.h b/chrome/browser/accessibility/live_caption_speech_recognition_host.h
index dcc3e06..3db8765 100644
--- a/chrome/browser/accessibility/live_caption_speech_recognition_host.h
+++ b/chrome/browser/accessibility/live_caption_speech_recognition_host.h
@@ -15,7 +15,7 @@
 
 namespace captions {
 
-class CaptionController;
+class LiveCaptionController;
 
 ///////////////////////////////////////////////////////////////////////////////
 //  Live Caption Speech Recognition Host
@@ -59,9 +59,9 @@
   void RenderFrameDeleted(content::RenderFrameHost* frame_host) override;
 
  private:
-  // Returns the CaptionController for frame_host_. Returns nullptr if it does
-  // not exist.
-  CaptionController* GetCaptionController();
+  // Returns the LiveCaptionController for frame_host_. Returns nullptr if it
+  // does not exist.
+  LiveCaptionController* GetLiveCaptionController();
 
   content::RenderFrameHost* frame_host_;
 };
diff --git a/chrome/browser/apps/app_service/publishers/standalone_browser_apps.cc b/chrome/browser/apps/app_service/publishers/standalone_browser_apps.cc
index baebe0ad..95af91f 100644
--- a/chrome/browser/apps/app_service/publishers/standalone_browser_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/standalone_browser_apps.cc
@@ -45,7 +45,7 @@
   app->show_in_launcher = apps::mojom::OptionalBool::kTrue;
   app->show_in_shelf = apps::mojom::OptionalBool::kTrue;
   app->show_in_search = apps::mojom::OptionalBool::kTrue;
-  app->show_in_management = apps::mojom::OptionalBool::kFalse;
+  app->show_in_management = apps::mojom::OptionalBool::kTrue;
   return app;
 }
 
diff --git a/chrome/browser/ash/accessibility/switch_access_browsertest.cc b/chrome/browser/ash/accessibility/switch_access_browsertest.cc
index ba06316b..d578394c 100644
--- a/chrome/browser/ash/accessibility/switch_access_browsertest.cc
+++ b/chrome/browser/ash/accessibility/switch_access_browsertest.cc
@@ -122,8 +122,6 @@
   std::unique_ptr<ExtensionConsoleErrorObserver> console_observer_;
 };
 
-// TODO(anastasi): Add a test for typing with the virtual keyboard.
-
 IN_PROC_BROWSER_TEST_F(SwitchAccessTest, ConsumesKeyEvents) {
   EnableSwitchAccess({'1', 'A'} /* select */, {'2', 'B'} /* next */,
                      {'3', 'C'} /* previous */);
@@ -254,4 +252,38 @@
   WaitForFocusRing("primary", "button", "Keyboard");
 }
 
+IN_PROC_BROWSER_TEST_F(SwitchAccessTest, TypeIntoVirtualKeyboard) {
+  EnableSwitchAccess({'1', 'A'} /* select */, {'2', 'B'} /* next */,
+                     {'3', 'C'} /* previous */);
+
+  // Load a webpage with a text box.
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GURL("data:text/html,<input autofocus aria-label=MyTextField>"));
+
+  // Wait for switch access to focus on the text field.
+  WaitForFocusRing("primary", "textField", "MyTextField");
+
+  // Send "select", which opens the switch access menu.
+  SendVirtualKeyPress(ui::KeyboardCode::VKEY_1);
+
+  // Wait for the switch access menu to appear and for focus to land on
+  // the first item, the "keyboard" button.
+  //
+  // Note that we don't try to also call WaitForSwitchAccessMenuAndGetActions
+  // here because by the time it returns, we may have already received the focus
+  // ring for the menu and so the following WaitForFocusRing would fail / loop
+  // forever.
+  WaitForFocusRing("primary", "button", "Keyboard");
+
+  // Send "select", which opens the virtual keyboard.
+  SendVirtualKeyPress(ui::KeyboardCode::VKEY_1);
+
+  // Finally, we should land on a keyboard key.
+  WaitForFocusRing("primary", "keyboard", "");
+
+  // Actually typing and verifying text field value should be covered by
+  // js-based tests that have the ability to ask the text field for its value.
+}
+
 }  // namespace ash
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 4b4d66f..45ea243 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3717,7 +3717,7 @@
         additional_backends) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   storage::ExternalMountPoints* external_mount_points =
-      content::BrowserContext::GetMountPoints(browser_context);
+      browser_context->GetMountPoints();
   DCHECK(external_mount_points);
   auto backend = std::make_unique<chromeos::FileSystemBackend>(
       Profile::FromBrowserContext(browser_context),
@@ -4985,6 +4985,56 @@
 #endif
 }
 
+void ChromeContentBrowserClient::WillCreateWebTransport(
+    content::RenderFrameHost* frame,
+    const GURL& url,
+    WillCreateWebTransportCallback callback) {
+#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
+  if (!frame) {
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
+  int frame_tree_node_id = frame->GetFrameTreeNodeId();
+  content::WebContents* web_contents =
+      content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
+  DCHECK(web_contents);
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  DCHECK(profile);
+  auto checker = std::make_unique<safe_browsing::WebApiHandshakeChecker>(
+      base::BindOnce(
+          &ChromeContentBrowserClient::GetSafeBrowsingUrlCheckerDelegate,
+          base::Unretained(this),
+          safe_browsing::IsSafeBrowsingEnabled(*profile->GetPrefs()),
+          /*should_check_on_sb_disabled=*/false,
+          safe_browsing::GetURLAllowlistByPolicy(profile->GetPrefs())),
+      base::BindRepeating(&content::WebContents::FromFrameTreeNodeId,
+                          frame_tree_node_id),
+      frame_tree_node_id);
+  auto* raw_checker = checker.get();
+  raw_checker->Check(
+      url,
+      base::BindOnce(
+          &ChromeContentBrowserClient::SafeBrowsingWebApiHandshakeChecked,
+          weak_factory_.GetWeakPtr(), std::move(checker), std::move(callback)));
+#else
+  std::move(callback).Run(absl::nullopt);
+#endif
+}
+
+void ChromeContentBrowserClient::SafeBrowsingWebApiHandshakeChecked(
+    std::unique_ptr<safe_browsing::WebApiHandshakeChecker> checker,
+    WillCreateWebTransportCallback callback,
+    safe_browsing::WebApiHandshakeChecker::CheckResult result) {
+  if (result == safe_browsing::WebApiHandshakeChecker::CheckResult::kProceed) {
+    std::move(callback).Run(absl::nullopt);
+  } else {
+    std::move(callback).Run(network::mojom::WebTransportError::New(
+        net::ERR_ABORTED, quic::QUIC_INTERNAL_ERROR,
+        "SafeBrowsing check failed", false));
+  }
+}
+
 bool ChromeContentBrowserClient::WillCreateRestrictedCookieManager(
     network::mojom::RestrictedCookieManagerRole role,
     content::BrowserContext* browser_context,
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 0413cd7a..dc56756c 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -23,6 +23,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/startup_data.h"
+#include "components/safe_browsing/content/browser/web_api_handshake_checker.h"
 #include "content/public/browser/child_process_security_policy.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/web_contents.h"
@@ -519,6 +520,10 @@
       const absl::optional<std::string>& user_agent,
       mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
           handshake_client) override;
+  void WillCreateWebTransport(content::RenderFrameHost* frame,
+                              const GURL& url,
+                              WillCreateWebTransportCallback callback) override;
+
   bool WillCreateRestrictedCookieManager(
       network::mojom::RestrictedCookieManagerRole role,
       content::BrowserContext* browser_context,
@@ -793,6 +798,11 @@
       bool is_enterprise_lookup_enabled,
       bool is_consumer_lookup_enabled);
 
+  void SafeBrowsingWebApiHandshakeChecked(
+      std::unique_ptr<safe_browsing::WebApiHandshakeChecker> checker,
+      WillCreateWebTransportCallback callback,
+      safe_browsing::WebApiHandshakeChecker::CheckResult result);
+
 #if !defined(OS_ANDROID)
   void OnKeepaliveTimerFired(
       std::unique_ptr<ScopedKeepAlive> keep_alive_handle);
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 9eff32e6..ae8d3f98 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2205,8 +2205,6 @@
     "extensions/install_limiter.h",
     "extensions/install_limiter_factory.cc",
     "extensions/install_limiter_factory.h",
-    "extensions/launcher_search_provider.cc",
-    "extensions/launcher_search_provider.h",
     "extensions/media_player_event_router.cc",
     "extensions/media_player_event_router.h",
     "extensions/permissions_updater_delegate_chromeos.cc",
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_apitest.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_apitest.cc
index af0a8f2..50fa8be 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_apitest.cc
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/test/shell_test_api.h"
 #include "base/macros.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/ash/arc/arc_util.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager.h"
@@ -27,6 +28,7 @@
 #include "components/arc/session/connection_holder.h"
 #include "components/arc/test/connection_holder_util.h"
 #include "components/arc/test/fake_app_instance.h"
+#include "components/feature_engagement/public/feature_constants.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
 #include "components/policy/core/common/mock_configuration_policy_provider.h"
 #include "components/policy/core/common/policy_map.h"
@@ -56,6 +58,17 @@
     // due to app pin syncing code. Sync isn't relevant to this test, so skip
     // pinned app sync. https://crbug.com/1085597
     SkipPinnedAppsFromSyncForTest();
+
+    // Switching to tablet mode can cause in-product help (IPH) to show. The IPH
+    // bubble can affect window activation when a browser window closes: if
+    // browser A has the IPH bubble, and browser B is active, closing browser B
+    // will lead to the IPH bubble being activated instead of browser A. This
+    // affects some tests that expect browser A to be active.
+    //
+    // Disable the IPH to avoid this issue. TODO(crbug.com/1209011): fix the
+    // issue more generally and remove this feature override.
+    scoped_feature_list_.InitAndDisableFeature(
+        feature_engagement::kIPHWebUITabStripFeature);
   }
   ~AutotestPrivateApiTest() override = default;
 
@@ -81,6 +94,8 @@
   ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
   DISALLOW_COPY_AND_ASSIGN(AutotestPrivateApiTest);
 };
 
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api_test.cc b/chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api_test.cc
index 7ff9744..e7484962 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api_test.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api_test.cc
@@ -181,10 +181,9 @@
 
   // Creates new, test mount point.
   void AddTmpMountPoint(const std::string& extension_id) {
-    BrowserContext::GetMountPoints(browser()->profile())
-        ->RegisterFileSystem("tmp", storage::kFileSystemTypeLocal,
-                             storage::FileSystemMountOption(),
-                             tmp_mount_point_);
+    browser()->profile()->GetMountPoints()->RegisterFileSystem(
+        "tmp", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(),
+        tmp_mount_point_);
   }
 
   base::FilePath GetFullPathOnTmpMountPoint(
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
index 6e79975..58c82c07 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -158,10 +158,9 @@
         kTestFileContent));
   }
 
-  ASSERT_TRUE(
-      content::BrowserContext::GetMountPoints(profile)->RegisterFileSystem(
-          kLocalMountPointName, storage::kFileSystemTypeLocal,
-          storage::FileSystemMountOption(), root));
+  ASSERT_TRUE(profile->GetMountPoints()->RegisterFileSystem(
+      kLocalMountPointName, storage::kFileSystemTypeLocal,
+      storage::FileSystemMountOption(), root));
   file_manager::VolumeManager::Get(profile)->AddVolumeForTesting(
       root, file_manager::VOLUME_TYPE_TESTING, chromeos::DEVICE_TYPE_UNKNOWN,
       false /* read_only */);
diff --git a/chrome/browser/chromeos/extensions/launcher_search_provider.cc b/chrome/browser/chromeos/extensions/launcher_search_provider.cc
deleted file mode 100644
index ff9c94a..0000000
--- a/chrome/browser/chromeos/extensions/launcher_search_provider.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/extensions/launcher_search_provider.h"
-
-#include <memory>
-#include <utility>
-
-#include "chrome/common/extensions/api/launcher_search_provider.h"
-#include "content/public/browser/render_frame_host.h"
-
-namespace extensions {
-
-LauncherSearchProviderSetSearchResultsFunction::
-    ~LauncherSearchProviderSetSearchResultsFunction() {}
-
-ExtensionFunction::ResponseAction
-LauncherSearchProviderSetSearchResultsFunction::Run() {
-  return RespondNow(NoArguments());
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/chromeos/extensions/launcher_search_provider.h b/chrome/browser/chromeos/extensions/launcher_search_provider.h
deleted file mode 100644
index a9713474..0000000
--- a/chrome/browser/chromeos/extensions/launcher_search_provider.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_CHROMEOS_EXTENSIONS_LAUNCHER_SEARCH_PROVIDER_H_
-#define CHROME_BROWSER_CHROMEOS_EXTENSIONS_LAUNCHER_SEARCH_PROVIDER_H_
-
-#include "extensions/browser/extension_function.h"
-
-namespace extensions {
-
-// Implements chrome.launcherSearchProvider.setSearchResults method.
-// TODO(crbug.com/1208731): This is unused but needs to be defined while the
-// launcher files extension still exists. It should be removed along with the
-// extension.
-class LauncherSearchProviderSetSearchResultsFunction
-    : public ExtensionFunction {
- public:
-  DECLARE_EXTENSION_FUNCTION("launcherSearchProvider.setSearchResults",
-                             LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS)
- protected:
-  ~LauncherSearchProviderSetSearchResultsFunction() override;
-  ResponseAction Run() override;
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_CHROMEOS_EXTENSIONS_LAUNCHER_SEARCH_PROVIDER_H_
diff --git a/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc b/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
index 6b978f4..62279d6 100644
--- a/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
+++ b/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
@@ -425,10 +425,9 @@
 
   // FileSystemExtensionApiTestBase override.
   void AddTestMountPoint() override {
-    EXPECT_TRUE(
-        content::BrowserContext::GetMountPoints(profile())->RegisterFileSystem(
-            kLocalMountPointName, storage::kFileSystemTypeLocal,
-            storage::FileSystemMountOption(), mount_point_dir_));
+    EXPECT_TRUE(profile()->GetMountPoints()->RegisterFileSystem(
+        kLocalMountPointName, storage::kFileSystemTypeLocal,
+        storage::FileSystemMountOption(), mount_point_dir_));
     VolumeManager::Get(profile())->AddVolumeForTesting(
         mount_point_dir_, VOLUME_TYPE_TESTING, chromeos::DEVICE_TYPE_UNKNOWN,
         false /* read_only */);
@@ -456,10 +455,9 @@
 
   // FileSystemExtensionApiTestBase override.
   void AddTestMountPoint() override {
-    EXPECT_TRUE(
-        content::BrowserContext::GetMountPoints(profile())->RegisterFileSystem(
-            kRestrictedMountPointName, storage::kFileSystemTypeRestrictedLocal,
-            storage::FileSystemMountOption(), mount_point_dir_));
+    EXPECT_TRUE(profile()->GetMountPoints()->RegisterFileSystem(
+        kRestrictedMountPointName, storage::kFileSystemTypeRestrictedLocal,
+        storage::FileSystemMountOption(), mount_point_dir_));
     VolumeManager::Get(profile())->AddVolumeForTesting(
         mount_point_dir_, VOLUME_TYPE_TESTING, chromeos::DEVICE_TYPE_UNKNOWN,
         true /* read_only */);
@@ -663,10 +661,9 @@
 
   // FileSystemExtensionApiTestBase override.
   void AddTestMountPoint() override {
-    EXPECT_TRUE(
-        content::BrowserContext::GetMountPoints(profile())->RegisterFileSystem(
-            kLocalMountPointName, storage::kFileSystemTypeLocal,
-            storage::FileSystemMountOption(), local_mount_point_dir_));
+    EXPECT_TRUE(profile()->GetMountPoints()->RegisterFileSystem(
+        kLocalMountPointName, storage::kFileSystemTypeLocal,
+        storage::FileSystemMountOption(), local_mount_point_dir_));
     VolumeManager::Get(profile())->AddVolumeForTesting(
         local_mount_point_dir_, VOLUME_TYPE_TESTING,
         chromeos::DEVICE_TYPE_UNKNOWN, false /* read_only */);
diff --git a/chrome/browser/chromeos/full_restore/app_launch_handler.cc b/chrome/browser/chromeos/full_restore/app_launch_handler.cc
index bbe88f77..403b62a 100644
--- a/chrome/browser/chromeos/full_restore/app_launch_handler.cc
+++ b/chrome/browser/chromeos/full_restore/app_launch_handler.cc
@@ -39,6 +39,8 @@
 namespace {
 
 constexpr char kRestoredAppLaunchHistogramPrefix[] = "Apps.RestoredAppLaunch";
+constexpr char kArcGhostWindowLaunchHistogramPrefix[] =
+    "Apps.ArcGhostWindowLaunch";
 
 // Returns apps::AppTypeName used for metrics.
 apps::AppTypeName GetHistogrameAppType(apps::mojom::AppType app_type) {
@@ -198,7 +200,7 @@
     return;
   }
 
-  RecordRestoredAppsCount(apps::AppTypeName::kChromeBrowser);
+  RecordRestoredAppLaunch(apps::AppTypeName::kChromeBrowser);
 
   restore_data_->RemoveApp(extension_misc::kChromeAppId);
 
@@ -270,7 +272,7 @@
     return;
 
   for (const auto& it : launch_list) {
-    RecordRestoredAppsCount(GetHistogrameAppType(app_type));
+    RecordRestoredAppLaunch(GetHistogrameAppType(app_type));
 
     DCHECK(it.second->container.has_value());
     DCHECK(it.second->disposition.has_value());
@@ -298,7 +300,7 @@
   auto* arc_handler = FullRestoreArcTaskHandler::GetForProfile(profile_);
 
   for (const auto& it : launch_list) {
-    RecordRestoredAppsCount(apps::AppTypeName::kArc);
+    RecordRestoredAppLaunch(apps::AppTypeName::kArc);
 
     DCHECK(it.second->event_flag.has_value());
 
@@ -317,8 +319,11 @@
 #if BUILDFLAG(ENABLE_WAYLAND_SERVER)
     if (!window_info->bounds.is_null() && arc_handler &&
         arc_handler->window_handler()) {
+      RecordArcGhostWindowLaunch(/*is_arc_ghost_window=*/true);
       arc_handler->window_handler()->LaunchArcGhostWindow(
           app_id, arc_session_id, it.second.get());
+    } else {
+      RecordArcGhostWindowLaunch(/*is_arc_ghost_window=*/false);
     }
 #endif
 
@@ -335,11 +340,16 @@
   }
 }
 
-void AppLaunchHandler::RecordRestoredAppsCount(
+void AppLaunchHandler::RecordRestoredAppLaunch(
     apps::AppTypeName app_type_name) {
   base::UmaHistogramEnumeration(kRestoredAppLaunchHistogramPrefix,
                                 app_type_name);
 }
 
+void AppLaunchHandler::RecordArcGhostWindowLaunch(bool is_arc_ghost_window) {
+  base::UmaHistogramBoolean(kArcGhostWindowLaunchHistogramPrefix,
+                            is_arc_ghost_window);
+}
+
 }  // namespace full_restore
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/full_restore/app_launch_handler.h b/chrome/browser/chromeos/full_restore/app_launch_handler.h
index 5a5b64f..d0e41d9 100644
--- a/chrome/browser/chromeos/full_restore/app_launch_handler.h
+++ b/chrome/browser/chromeos/full_restore/app_launch_handler.h
@@ -77,7 +77,8 @@
   void LaunchArcApp(const std::string& app_id,
                     const ::full_restore::RestoreData::LaunchList& launch_list);
 
-  void RecordRestoredAppsCount(apps::AppTypeName app_type_name);
+  void RecordRestoredAppLaunch(apps::AppTypeName app_type_name);
+  void RecordArcGhostWindowLaunch(bool is_arc_ghost_window);
 
   Profile* profile_ = nullptr;
 
diff --git a/chrome/browser/chromeos/full_restore/app_launch_handler_browsertest.cc b/chrome/browser/chromeos/full_restore/app_launch_handler_browsertest.cc
index 3164c5d..4695439a 100644
--- a/chrome/browser/chromeos/full_restore/app_launch_handler_browsertest.cc
+++ b/chrome/browser/chromeos/full_restore/app_launch_handler_browsertest.cc
@@ -10,6 +10,7 @@
 
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/autotest_desks_api.h"
+#include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/split_view_test_api.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "ash/shell.h"
@@ -240,6 +241,13 @@
 
   views::Widget* widget = new views::Widget();
   widget->Init(std::move(params));
+
+  // Make the window resizeable.
+  widget->GetNativeWindow()->SetProperty(
+      aura::client::kResizeBehaviorKey,
+      aura::client::kResizeBehaviorCanResize |
+          aura::client::kResizeBehaviorCanMaximize);
+
   exo::SetShellApplicationId(widget->GetNativeWindow(), window_app_id);
   widget->Show();
   widget->Activate();
@@ -1021,12 +1029,24 @@
   int32_t session_id2 =
       ::full_restore::kArcSessionIdOffsetForRestoredLaunching + 1;
 
+  // Create some desks so we can test that the exo window is placed in the
+  // correct desk container after the task is created.
+  ash::AutotestDesksApi().CreateNewDesk();
+  ash::AutotestDesksApi().CreateNewDesk();
+  ash::AutotestDesksApi().CreateNewDesk();
+
   // Create the window to simulate the restoration for the app. The task id
   // needs to match the |window_app_id| arg of CreateExoWindow.
   int32_t kTaskId2 = 200;
   widget = CreateExoWindow("org.chromium.arc.200");
   window = widget->GetNativeWindow();
 
+  // The task is not ready, so the window is currently in a hidden container.
+  EXPECT_EQ(
+      ash::Shell::GetContainer(window->GetRootWindow(),
+                               ash::kShellWindowId_UnparentedControlContainer),
+      window->parent());
+
   VerifyObserver(window, /*launch_count=*/0, /*init_count=*/1);
   VerifyWindowProperty(window, kTaskId2,
                        ::full_restore::kParentToHiddenContainer,
@@ -1035,6 +1055,12 @@
   // Simulate creating the task for the restored window.
   CreateTask(app_id, kTaskId2, session_id2);
 
+  // Tests that after the task is created, the window is placed in the container
+  // associated with `kDeskId` (2), which is desk C.
+  EXPECT_EQ(ash::Shell::GetContainer(window->GetRootWindow(),
+                                     ash::kShellWindowId_DeskContainerC),
+            window->parent());
+
   VerifyObserver(window, /*launch_count=*/1, /*init_count=*/1);
   VerifyWindowProperty(window, kTaskId2, kTaskId1, /*hidden=*/false);
   VerifyWindowInfo(window, kActivationIndex);
@@ -1048,6 +1074,8 @@
   ::full_restore::FullRestoreInfo::GetInstance()->RemoveObserver(
       test_full_restore_info_observer());
   StopInstance();
+
+  RemoveInactiveDesks();
 }
 
 // Test restoration with multiple ARC apps, when the ARC windows are created
diff --git a/chrome/browser/chromeos/launcher_search_provider/launcher_search_provider_service.cc b/chrome/browser/chromeos/launcher_search_provider/launcher_search_provider_service.cc
deleted file mode 100644
index db212089..0000000
--- a/chrome/browser/chromeos/launcher_search_provider/launcher_search_provider_service.cc
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/launcher_search_provider/launcher_search_provider_service.h"
-
-#include <stdint.h>
-
-#include <memory>
-#include <utility>
-
-#include "base/containers/contains.h"
-#include "base/numerics/ranges.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/chromeos/launcher_search_provider/launcher_search_provider_service_factory.h"
-#include "chrome/browser/ui/app_list/search/launcher_search/launcher_search_provider.h"
-#include "chrome/browser/ui/app_list/search/launcher_search/launcher_search_result.h"
-#include "chromeos/components/string_matching/tokenized_string.h"
-#include "chromeos/components/string_matching/tokenized_string_match.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/common/extension_set.h"
-#include "extensions/common/permissions/permissions_data.h"
-
-namespace api_launcher_search_provider =
-    extensions::api::launcher_search_provider;
-using chromeos::string_matching::TokenizedString;
-using chromeos::string_matching::TokenizedStringMatch;
-using extensions::ExtensionId;
-using extensions::ExtensionSet;
-
-namespace chromeos {
-namespace launcher_search_provider {
-
-Service::Service(Profile* profile,
-                 extensions::ExtensionRegistry* extension_registry)
-    : profile_(profile), extension_registry_(extension_registry) {
-  extension_registry_->AddObserver(this);
-}
-
-Service::~Service() {
-  extension_registry_->RemoveObserver(this);
-}
-
-// static
-Service* Service::Get(content::BrowserContext* context) {
-  return ServiceFactory::Get(context);
-}
-
-void Service::OnQueryStarted(app_list::LauncherSearchProvider* provider,
-                             const std::string& query,
-                             const int max_result) {
-  DCHECK(!is_query_running_);
-  query_ = query;
-  is_query_running_ = true;
-  provider_ = provider;
-
-  ++query_id_;
-
-  extensions::EventRouter* event_router =
-      extensions::EventRouter::Get(profile_);
-
-  CacheListenerExtensionIds();
-  for (const ExtensionId& extension_id :
-       *cached_listener_extension_ids_.get()) {
-    // Convert query_id_ to string here since queryId is defined as string in
-    // javascript side API while we use uint32_t internally to generate it.
-    event_router->DispatchEventToExtension(
-        extension_id,
-        std::make_unique<extensions::Event>(
-            extensions::events::LAUNCHER_SEARCH_PROVIDER_ON_QUERY_STARTED,
-            api_launcher_search_provider::OnQueryStarted::kEventName,
-            api_launcher_search_provider::OnQueryStarted::Create(
-                query_id_, query, max_result)));
-  }
-}
-
-void Service::OnQueryEnded() {
-  DCHECK(is_query_running_);
-  provider_ = nullptr;
-
-  extensions::EventRouter* event_router =
-      extensions::EventRouter::Get(profile_);
-
-  CacheListenerExtensionIds();
-  for (const ExtensionId& extension_id :
-       *cached_listener_extension_ids_.get()) {
-    event_router->DispatchEventToExtension(
-        extension_id,
-        std::make_unique<extensions::Event>(
-            extensions::events::LAUNCHER_SEARCH_PROVIDER_ON_QUERY_ENDED,
-            api_launcher_search_provider::OnQueryEnded::kEventName,
-            api_launcher_search_provider::OnQueryEnded::Create(query_id_)));
-  }
-
-  is_query_running_ = false;
-}
-
-void Service::OnOpenResult(const ExtensionId& extension_id,
-                           const std::string& item_id) {
-  CacheListenerExtensionIds();
-  CHECK(base::Contains(*cached_listener_extension_ids_.get(), extension_id));
-
-  extensions::EventRouter* event_router =
-      extensions::EventRouter::Get(profile_);
-  event_router->DispatchEventToExtension(
-      extension_id,
-      std::make_unique<extensions::Event>(
-          extensions::events::LAUNCHER_SEARCH_PROVIDER_ON_OPEN_RESULT,
-          api_launcher_search_provider::OnOpenResult::kEventName,
-          api_launcher_search_provider::OnOpenResult::Create(item_id)));
-}
-
-void Service::SetSearchResults(
-    const extensions::Extension* extension,
-    std::unique_ptr<ErrorReporter> error_reporter,
-    const int query_id,
-    const std::vector<extensions::api::launcher_search_provider::SearchResult>&
-        results) {
-  // If query is not running or query_id is different from current query id,
-  // discard the results.
-  if (!is_query_running_ || query_id != query_id_)
-    return;
-
-  // If |extension| is not in the listener extensions list, ignore it.
-  CacheListenerExtensionIds();
-  if (!base::Contains(*cached_listener_extension_ids_.get(), extension->id())) {
-    return;
-  }
-
-  // Set search results to provider.
-  DCHECK(provider_);
-  std::vector<std::unique_ptr<app_list::LauncherSearchResult>> search_results;
-  for (const auto& result : results) {
-    const int relevance =
-        base::ClampToRange(result.relevance, 0, kMaxSearchResultScore);
-
-    const std::string icon_type =
-        result.icon_type ? *result.icon_type.get() : std::string();
-
-    // Calculate the relevance score by matching the query with the title.
-    // Results with a match score of 0 are discarded. This will also be used to
-    // set the title tags (highlighting which parts of the title matched the
-    // search query).
-    const std::u16string title = base::UTF8ToUTF16(result.title);
-    TokenizedString tokenized_title(title);
-    TokenizedStringMatch match;
-    TokenizedString tokenized_query(base::UTF8ToUTF16(query_));
-    if (!match.Calculate(tokenized_query, tokenized_title))
-      continue;
-
-    auto search_result = std::make_unique<app_list::LauncherSearchResult>(
-        result.item_id, icon_type, relevance, profile_, extension,
-        error_reporter->Duplicate());
-    search_result->UpdateFromMatch(tokenized_title, match);
-    search_results.push_back(std::move(search_result));
-  }
-  provider_->SetSearchResults(extension->id(), std::move(search_results));
-}
-
-bool Service::IsQueryRunning() const {
-  return is_query_running_;
-}
-
-void Service::OnExtensionLoaded(content::BrowserContext* browser_context,
-                                const extensions::Extension* extension) {
-  // Invalidate cache.
-  cached_listener_extension_ids_.reset();
-}
-
-void Service::OnExtensionUnloaded(content::BrowserContext* browser_context,
-                                  const extensions::Extension* extension,
-                                  extensions::UnloadedExtensionReason reason) {
-  // Invalidate cache.
-  cached_listener_extension_ids_.reset();
-}
-
-void Service::CacheListenerExtensionIds() {
-  // If it's already cached, do nothing.
-  if (cached_listener_extension_ids_)
-    return;
-
-  cached_listener_extension_ids_ = std::make_unique<std::set<ExtensionId>>();
-
-  const ExtensionSet& extension_set = extension_registry_->enabled_extensions();
-  for (scoped_refptr<const extensions::Extension> extension : extension_set) {
-    const extensions::PermissionsData* permission_data =
-        extension->permissions_data();
-    const bool has_permission = permission_data->HasAPIPermission(
-        extensions::mojom::APIPermissionID::kLauncherSearchProvider);
-    if (has_permission)
-      cached_listener_extension_ids_->insert(extension->id());
-  }
-}
-
-}  // namespace launcher_search_provider
-}  // namespace chromeos
diff --git a/chrome/browser/continuous_search/BUILD.gn b/chrome/browser/continuous_search/BUILD.gn
index 6c5d2604..177e9ee 100644
--- a/chrome/browser/continuous_search/BUILD.gn
+++ b/chrome/browser/continuous_search/BUILD.gn
@@ -120,8 +120,13 @@
   testonly = true
 
   sources = [
+    "android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerCoordinatorTest.java",
     "android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerMediatorTest.java",
+    "android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerViewBinderTest.java",
     "android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListMediatorTest.java",
+    "android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListViewBinderTest.java",
+    "android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchSceneLayerTest.java",
+    "android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabHelperJUnitTest.java",
   ]
 
   deps = [
@@ -132,12 +137,17 @@
     "//base:base_junit_test_support",
     "//chrome/browser/browser_controls/android:java",
     "//chrome/browser/continuous_search/internal:java",
+    "//chrome/browser/flags:java",
     "//chrome/browser/tab:java",
     "//chrome/browser/ui/android/layouts:java",
     "//chrome/browser/ui/android/theme:java",
+    "//chrome/test/android:chrome_java_test_support",
+    "//components/embedder_support/android:util_java",
     "//content/public/android:content_java",
     "//third_party/android_deps:robolectric_all_java",
+    "//third_party/androidx:androidx_recyclerview_recyclerview_java",
     "//third_party/androidx:androidx_test_core_java",
+    "//third_party/hamcrest:hamcrest_library_java",
     "//third_party/junit",
     "//third_party/mockito:mockito_java",
     "//ui/android:ui_no_recycler_view_java",
diff --git a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousNavigationMetadata.java b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousNavigationMetadata.java
index a983cb89..117e5bf 100644
--- a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousNavigationMetadata.java
+++ b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousNavigationMetadata.java
@@ -63,8 +63,9 @@
         if (!(o instanceof ContinuousNavigationMetadata)) return false;
 
         ContinuousNavigationMetadata other = (ContinuousNavigationMetadata) o;
-        return mRootUrl.equals(other.mRootUrl) && mQuery.equals(other.mQuery)
-                && mCategory == other.mCategory && mGroups.equals(other.mGroups);
+
+        return mRootUrl.equals(other.getRootUrl()) && mQuery.equals(other.getQuery())
+                && mCategory == other.getCategory() && mGroups.equals(other.getGroups());
     }
 
     @Override
diff --git a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerCoordinator.java b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerCoordinator.java
index 3ea35d2..81327ab 100644
--- a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerCoordinator.java
+++ b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerCoordinator.java
@@ -8,6 +8,8 @@
 import android.view.View;
 import android.view.ViewStub;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.Supplier;
@@ -141,4 +143,9 @@
         mContainerMediator.destroy();
         mListCoordinator.destroy();
     }
+
+    @VisibleForTesting
+    ContinuousSearchViewResourceFrameLayout getRootViewForTesting() {
+        return mRootView;
+    }
 }
diff --git a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabHelper.java b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabHelper.java
index 4752ff3..c06f1a8 100644
--- a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabHelper.java
+++ b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabHelper.java
@@ -19,9 +19,11 @@
      * @param tab to enable continuous search support for.
      */
     public static void createForTab(Tab tab) {
-        if (!FeatureList.isNativeInitialized()) return;
+        if (!FeatureList.isInitialized()) return;
 
-        if (!tab.isIncognito()) new BackNavigationTabObserver(tab);
+        if (tab.isIncognito()) return;
+
+        new BackNavigationTabObserver(tab);
 
         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.CONTINUOUS_SEARCH)) return;
 
diff --git a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchViewResourceFrameLayout.java b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchViewResourceFrameLayout.java
index 06264c1..60eadb2 100644
--- a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchViewResourceFrameLayout.java
+++ b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchViewResourceFrameLayout.java
@@ -57,4 +57,4 @@
     public int getShadowHeight() {
         return mShadowHeightPx;
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/PageGroup.java b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/PageGroup.java
index 3eee2264..fb2f779cf 100644
--- a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/PageGroup.java
+++ b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/PageGroup.java
@@ -41,8 +41,8 @@
 
         PageGroup other = (PageGroup) o;
 
-        return mLabel.equals(other.mLabel) && mIsAdGroup == other.mIsAdGroup
-                && mPageItems.equals(other.mPageItems);
+        return mLabel.equals(other.getLabel()) && mIsAdGroup == other.isAdGroup()
+                && mPageItems.equals(other.getPageItems());
     }
 
     @Override
diff --git a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/PageItem.java b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/PageItem.java
index fd4e37f..3fb32f7 100644
--- a/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/PageItem.java
+++ b/chrome/browser/continuous_search/android/java/src/org/chromium/chrome/browser/continuous_search/PageItem.java
@@ -42,7 +42,7 @@
 
         PageItem other = (PageItem) o;
 
-        return mUrl.equals(other.mUrl) && mTitle.equals(other.mTitle);
+        return mUrl.equals(other.getUrl()) && mTitle.equals(other.getTitle());
     }
 
     @Override
diff --git a/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerCoordinatorTest.java b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerCoordinatorTest.java
new file mode 100644
index 0000000..668d4c8
--- /dev/null
+++ b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerCoordinatorTest.java
@@ -0,0 +1,265 @@
+// 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.continuous_search;
+
+import static org.hamcrest.Matchers.lessThan;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.LinearLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.UserDataHost;
+import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.Supplier;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
+import org.chromium.chrome.browser.layouts.LayoutManager;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.theme.ThemeColorProvider;
+import org.chromium.components.embedder_support.util.UrlUtilities;
+import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
+import org.chromium.ui.resources.ResourceManager;
+import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
+import org.chromium.ui.resources.dynamics.ViewResourceAdapter;
+import org.chromium.url.GURL;
+import org.chromium.url.JUnitTestGURLs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test of {@link ContinuousSearchContainerCoordinator}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class ContinuousSearchContainerCoordinatorTest {
+    private static final String TEST_QUERY = "Foo";
+    private static final int TEST_RESULT_TYPE = 1;
+    private static final long FAKE_NATIVE_ADDR = 123456L;
+    private static final int STUB_ID = 123;
+    private static final int INFLATED_ID = 456;
+
+    @Mock
+    private Tab mTabMock;
+    @Mock
+    private LayoutManager mLayoutManagerMock;
+    @Mock
+    private ResourceManager mResourceManagerMock;
+    @Mock
+    private DynamicResourceLoader mResourceLoaderMock;
+    @Mock
+    private BrowserControlsStateProvider mStateProviderMock;
+    @Mock
+    private ThemeColorProvider mThemeColorProviderMock;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private SearchUrlHelper.Natives mSearchUrlHelperJniMock;
+    @Mock
+    private ContinuousSearchSceneLayer.Natives mContinuousSearchSceneLayerJniMock;
+    @Mock
+    private UrlUtilities.Natives mUrlUtilitiesJniMock;
+
+    private ObservableSupplierImpl<Tab> mTabSupplier = new ObservableSupplierImpl<Tab>();
+
+    private Supplier<Boolean> mAnimateNativeControls;
+    private boolean mAnimateNativeControlsValue;
+
+    private Supplier<Integer> mDefaultTopHeight;
+    private int mDefaultTopHeightValue;
+
+    private boolean mAnimateHidingState;
+    private int mAnimateHidingStateCount;
+    private GURL mSrpUrl;
+    private UserDataHost mUserDataHost = new UserDataHost();
+    private ContinuousSearchContainerCoordinator mCoordinator;
+    private ContinuousNavigationUserDataImpl mUserData;
+    private LinearLayout mRoot;
+
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+    @Before
+    public void setUp() {
+        mSrpUrl = JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL);
+        mJniMocker.mock(SearchUrlHelperJni.TEST_HOOKS, mSearchUrlHelperJniMock);
+        mJniMocker.mock(
+                ContinuousSearchSceneLayerJni.TEST_HOOKS, mContinuousSearchSceneLayerJniMock);
+        mJniMocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
+        doReturn(TEST_QUERY).when(mSearchUrlHelperJniMock).getQueryIfValidSrpUrl(eq(mSrpUrl));
+        doReturn(TEST_RESULT_TYPE)
+                .when(mSearchUrlHelperJniMock)
+                .getSrpPageCategoryFromUrl(eq(mSrpUrl));
+        doReturn(FAKE_NATIVE_ADDR).when(mContinuousSearchSceneLayerJniMock).init(any());
+        doReturn(mResourceLoaderMock).when(mResourceManagerMock).getDynamicResourceLoader();
+
+        mRoot = new LinearLayout(ContextUtils.getApplicationContext());
+        mRoot.setLayoutParams(new LinearLayout.LayoutParams(100, 100));
+        mRoot.setOrientation(LinearLayout.VERTICAL);
+        ViewStub viewStub = new ViewStub(ContextUtils.getApplicationContext());
+        viewStub.setId(STUB_ID);
+        viewStub.setInflatedId(INFLATED_ID);
+        viewStub.setLayoutResource(
+                org.chromium.chrome.browser.continuous_search.R.layout.continuous_search_container);
+        mRoot.addView(viewStub);
+        mAnimateNativeControls = () -> {
+            return mAnimateNativeControlsValue;
+        };
+        mDefaultTopHeight = () -> {
+            return mDefaultTopHeightValue;
+        };
+
+        doReturn(mUserDataHost).when(mTabMock).getUserDataHost();
+        mUserData = ContinuousNavigationUserDataImpl.getOrCreateForTab(mTabMock);
+        mUserData.mAllowNativeUrlChecks = false;
+        mCoordinator = new ContinuousSearchContainerCoordinator(viewStub, mLayoutManagerMock,
+                mResourceManagerMock, mTabSupplier, mStateProviderMock, mAnimateNativeControls,
+                mDefaultTopHeight, mThemeColorProviderMock, mResources, (state) -> {
+                    mAnimateHidingState = state;
+                    mAnimateHidingStateCount++;
+                });
+
+        Assert.assertEquals(ContinuousSearchSceneLayer.class,
+                ContinuousSearchContainerCoordinator.getSceneOverlayClass());
+    }
+
+    @After
+    public void tearDown() {
+        mCoordinator.destroy();
+    }
+
+    @Test
+    public void testShowAndDismiss() {
+        Assert.assertNotNull(mRoot.findViewById(STUB_ID));
+        Assert.assertNull(mRoot.findViewById(INFLATED_ID));
+
+        // Create data.
+        GURL resultUrl = JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1);
+        List<PageGroup> groups = new ArrayList<PageGroup>();
+        List<PageItem> results1 = new ArrayList<PageItem>();
+        results1.add(new PageItem(resultUrl, "Red 1"));
+        groups.add(new PageGroup("Red Group", false, results1));
+        ContinuousNavigationMetadata metadata =
+                new ContinuousNavigationMetadata(mSrpUrl, TEST_QUERY, TEST_RESULT_TYPE, groups);
+
+        mTabSupplier.set(mTabMock);
+        mUserData.updateData(metadata, mSrpUrl);
+        mUserData.updateCurrentUrl(resultUrl);
+        ContinuousSearchViewResourceFrameLayout rootView = mCoordinator.getRootViewForTesting();
+        Assert.assertNotNull(rootView);
+        rootView.layout(0, 0, 100, 100);
+        View view = mock(View.class);
+        when(view.getHeight())
+                .thenReturn(40 + mCoordinator.getRootViewForTesting().getShadowHeight());
+        mRoot.findViewById(INFLATED_ID).layout(0, 0, 100, 100);
+        mCoordinator.onLayoutChange(view, 0, 0, 0, 0, 0, 0, 0, 0);
+
+        when(mUrlUtilitiesJniMock.getDomainAndRegistry(any(), anyBoolean()))
+                .thenAnswer((invocation) -> { return (String) invocation.getArguments()[0]; });
+        RecyclerView recyclerView = rootView.findViewById(R.id.recycler_view);
+        Assert.assertNotNull(recyclerView);
+        recyclerView.layout(0, 0, 100, 100);
+
+        // View should now be inflated.
+        Assert.assertNull(mRoot.findViewById(STUB_ID));
+        Assert.assertNotNull(mRoot.findViewById(INFLATED_ID));
+        // UI is still in flux so just assert that the items exist.
+        Assert.assertEquals(2, recyclerView.getAdapter().getItemCount());
+
+        // Invalidate.
+        mUserData.invalidateData();
+        Assert.assertEquals(0, recyclerView.getAdapter().getItemCount());
+    }
+
+    @Test
+    public void testObserveHeightChanges() {
+        ContinuousSearchContainerCoordinator.HeightObserver observer =
+                mock(ContinuousSearchContainerCoordinator.HeightObserver.class);
+        InOrder inOrder = inOrder(observer);
+        mCoordinator.addHeightObserver(observer);
+        mTabSupplier.set(mTabMock);
+
+        GURL resultUrl = JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1);
+        List<PageGroup> groups = new ArrayList<PageGroup>();
+        List<PageItem> results1 = new ArrayList<PageItem>();
+        results1.add(new PageItem(resultUrl, "Red 1"));
+        groups.add(new PageGroup("Red Group", false, results1));
+        ContinuousNavigationMetadata metadata =
+                new ContinuousNavigationMetadata(mSrpUrl, TEST_QUERY, TEST_RESULT_TYPE, groups);
+
+        mUserData.updateData(metadata, mSrpUrl);
+        mUserData.updateCurrentUrl(resultUrl);
+        View view = mock(View.class);
+        when(view.getHeight())
+                .thenReturn(5 + mCoordinator.getRootViewForTesting().getShadowHeight());
+        mCoordinator.onLayoutChange(view, 0, 0, 0, 0, 0, 0, 0, 0);
+        inOrder.verify(observer).onHeightChange(5, false);
+        mCoordinator.removeHeightObserver(observer);
+
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    /**
+     * Verifies that {@link ContinuousSearchViewResourceFrameLayout} captures correctly.
+     */
+    @Test
+    public void testCapture() {
+        GURL resultUrl = JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1);
+        List<PageGroup> groups = new ArrayList<PageGroup>();
+        List<PageItem> results1 = new ArrayList<PageItem>();
+        results1.add(new PageItem(resultUrl, "Red 1"));
+        groups.add(new PageGroup("Red Group", false, results1));
+        ContinuousNavigationMetadata metadata =
+                new ContinuousNavigationMetadata(mSrpUrl, TEST_QUERY, TEST_RESULT_TYPE, groups);
+
+        mTabSupplier.set(mTabMock);
+        mUserData.updateData(metadata, mSrpUrl);
+        mUserData.updateCurrentUrl(resultUrl);
+        View view = mock(View.class);
+        ContinuousSearchViewResourceFrameLayout rootView = mCoordinator.getRootViewForTesting();
+        when(view.getHeight()).thenReturn(5 + rootView.getShadowHeight());
+        mCoordinator.onLayoutChange(view, 0, 0, 0, 0, 0, 0, 0, 0);
+
+        // Force a resize since robolectric view sizes are technically 0x0 by default.
+        rootView.layout(0, 0, 100, 100);
+        ViewResourceAdapter adapter = rootView.createResourceAdapter();
+        adapter.invalidate(new Rect(0, rootView.getHeight() - rootView.getShadowHeight(),
+                rootView.getWidth(), rootView.getHeight()));
+        Bitmap bitmap = adapter.getBitmap();
+
+        // This isn't intended to be a pixel test so just check that the content was actually
+        // captured.
+        Assert.assertNotNull(bitmap);
+        Assert.assertThat(1, lessThan(bitmap.getHeight()));
+        Assert.assertThat(1, lessThan(bitmap.getWidth()));
+    }
+}
diff --git a/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerViewBinderTest.java b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerViewBinderTest.java
new file mode 100644
index 0000000..e9f405f6
--- /dev/null
+++ b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchContainerViewBinderTest.java
@@ -0,0 +1,70 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.continuous_search;
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+
+/**
+ * Test of {@link ContinuousSearchContainerViewBinder}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class ContinuousSearchContainerViewBinderTest {
+    private PropertyModel mModel;
+
+    @Before
+    public void setUp() {
+        mModel = new PropertyModel(ContinuousSearchContainerProperties.ALL_KEYS);
+    }
+
+    /**
+     * Tests that property updates to the Java view work correctly.
+     */
+    @Test
+    public void testBindJavaView() {
+        View view = mock(View.class);
+        InOrder inOrder = inOrder(view);
+        PropertyModelChangeProcessor.create(
+                mModel, view, ContinuousSearchContainerViewBinder::bindJavaView);
+
+        final int yValue = 10;
+        mModel.set(ContinuousSearchContainerProperties.VERTICAL_OFFSET, yValue);
+        inOrder.verify(view).setY((float) yValue);
+
+        final int visibility = View.VISIBLE;
+        mModel.set(ContinuousSearchContainerProperties.ANDROID_VIEW_VISIBILITY, visibility);
+        inOrder.verify(view).setVisibility(visibility);
+    }
+
+    /**
+     * Tests that property updates to the composited view work correctly.
+     */
+    @Test
+    public void testBindCompositedView() {
+        ContinuousSearchSceneLayer sceneLayer = mock(ContinuousSearchSceneLayer.class);
+        InOrder inOrder = inOrder(sceneLayer);
+
+        final int yValue = 10;
+        mModel.set(ContinuousSearchContainerProperties.VERTICAL_OFFSET, yValue);
+        final boolean visibility = true;
+        mModel.set(ContinuousSearchContainerProperties.COMPOSITED_VIEW_VISIBLE, visibility);
+
+        ContinuousSearchContainerViewBinder.bindCompositedView(
+                mModel, sceneLayer, ContinuousSearchContainerProperties.COMPOSITED_VIEW_VISIBLE);
+        inOrder.verify(sceneLayer).setVerticalOffset(yValue);
+        inOrder.verify(sceneLayer).setIsVisible(visibility);
+    }
+}
diff --git a/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListMediatorTest.java b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListMediatorTest.java
index 20f8369..bdb4393 100644
--- a/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListMediatorTest.java
+++ b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListMediatorTest.java
@@ -11,6 +11,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -18,6 +19,7 @@
 import org.chromium.chrome.browser.continuous_search.ContinuousSearchListProperties.ListItemType;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.theme.ThemeColorProvider;
+import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
 import org.chromium.content_public.browser.WebContents;
@@ -58,12 +60,19 @@
                 ApplicationProvider.getApplicationContext().getResources());
         ContinuousNavigationUserDataImpl continuousNavigationUserData =
                 Mockito.mock(ContinuousNavigationUserDataImpl.class);
+        Mockito.doAnswer((invocation) -> {
+                   mMediator.onInvalidate();
+                   return null;
+               })
+                .when(continuousNavigationUserData)
+                .invalidateData();
         ContinuousNavigationUserDataImpl.setInstanceForTesting(continuousNavigationUserData);
     }
 
     @After
     public void tearDown() {
         ContinuousNavigationUserDataImpl.setInstanceForTesting(null);
+        mMediator.destroy();
     }
 
     /**
@@ -84,6 +93,7 @@
                 mLayoutVisibilityFalse.getCallCount());
 
         // UI should hide on invalidate.
+        mMediator.onScrolled();
         mMediator.onInvalidate();
         Assert.assertEquals("mLayoutVisibilityTrue should not have been called.", 0,
                 mLayoutVisibilityTrue.getCallCount());
@@ -179,8 +189,11 @@
      */
     @Test
     public void testModelList() {
+        Tab tab = Mockito.mock(Tab.class);
+        mMediator.onResult(tab);
         // Mock 3 SearchResultGroups, with 1, 2 and 3 SearchResults. The first group is an ad group.
-        PageItem pageItem11 = new PageItem(Mockito.mock(GURL.class), "result 11");
+        GURL url11 = JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1);
+        PageItem pageItem11 = new PageItem(url11, "result 11");
         PageItem pageItem21 = new PageItem(Mockito.mock(GURL.class), "result 21");
         PageItem pageItem22 = new PageItem(Mockito.mock(GURL.class), "result 22");
         PageItem pageItem31 = new PageItem(Mockito.mock(GURL.class), "result 31");
@@ -217,6 +230,72 @@
         assertListItemEqualsSearchResult(mModelList.get(4), pageItem31, false);
         assertListItemEqualsSearchResult(mModelList.get(5), pageItem32, false);
         assertListItemEqualsSearchResult(mModelList.get(6), pageItem33, false);
+
+        mModelList.get(1).model.get(ContinuousSearchListProperties.CLICK_LISTENER).onClick(null);
+        ArgumentCaptor<LoadUrlParams> params = ArgumentCaptor.forClass(LoadUrlParams.class);
+        Mockito.verify(tab, Mockito.times(1)).loadUrl(params.capture());
+        Assert.assertEquals(url11.getSpec(), params.getValue().getUrl());
+    }
+
+    /**
+     * Tests that the data is invalidated on user dismissal.
+     */
+    @Test
+    public void testUserInvalidate() {
+        mMediator.onResult(Mockito.mock(Tab.class));
+        PageItem pageItem = new PageItem(Mockito.mock(GURL.class), "result");
+        PageGroup pageGroup = new PageGroup("group", true, Arrays.asList(pageItem));
+        ContinuousNavigationMetadata continuousNavigationMetadata =
+                new ContinuousNavigationMetadata(
+                        Mockito.mock(GURL.class), "query", 1, Arrays.asList(pageGroup));
+        mMediator.onUpdate(continuousNavigationMetadata);
+        Assert.assertEquals("ModelList length is incorrect.", 2, mModelList.size());
+
+        mRootViewModel.get(ContinuousSearchListProperties.DISMISS_CLICK_CALLBACK).onClick(null);
+        Assert.assertEquals("ModelList length is incorrect.", 0, mModelList.size());
+    }
+
+    /**
+     * Tests that the data is invalidated if a new tab is observed.
+     */
+    @Test
+    public void testObserveNewTab() {
+        PageItem pageItem = new PageItem(Mockito.mock(GURL.class), "result");
+        PageGroup pageGroup = new PageGroup("group", true, Arrays.asList(pageItem));
+        ContinuousNavigationMetadata continuousNavigationMetadata =
+                new ContinuousNavigationMetadata(
+                        Mockito.mock(GURL.class), "query", 1, Arrays.asList(pageGroup));
+        mMediator.onUpdate(continuousNavigationMetadata);
+        Assert.assertEquals("ModelList length is incorrect.", 2, mModelList.size());
+
+        mMediator.onResult(null);
+        Assert.assertEquals("ModelList length is incorrect.", 0, mModelList.size());
+    }
+
+    /**
+     * Tests that theme color is updated correctly.
+     */
+    @Test
+    public void testThemeColorChanged() {
+        PageItem pageItem = new PageItem(Mockito.mock(GURL.class), "result");
+        PageGroup pageGroup = new PageGroup("group", true, Arrays.asList(pageItem));
+        ContinuousNavigationMetadata continuousNavigationMetadata =
+                new ContinuousNavigationMetadata(
+                        Mockito.mock(GURL.class), "query", 1, Arrays.asList(pageGroup));
+        mMediator.onUpdate(continuousNavigationMetadata);
+        Assert.assertEquals("ModelList length is incorrect.", 2, mModelList.size());
+
+        // Use a dark color.
+        int color = 0xFFFFFF;
+        mMediator.onThemeColorChanged(color, false);
+        Assert.assertEquals("Background color incorrect.", color,
+                mRootViewModel.get(ContinuousSearchListProperties.BACKGROUND_COLOR));
+
+        // Use a light color.
+        color = 0x000000;
+        mMediator.onThemeColorChanged(color, false);
+        Assert.assertEquals("Background color incorrect.", color,
+                mRootViewModel.get(ContinuousSearchListProperties.BACKGROUND_COLOR));
     }
 
     private void assertListItemEqualsSearchResult(
diff --git a/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListViewBinderTest.java b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListViewBinderTest.java
new file mode 100644
index 0000000..4f7123e
--- /dev/null
+++ b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchListViewBinderTest.java
@@ -0,0 +1,114 @@
+// 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.continuous_search;
+
+import static org.mockito.AdditionalMatchers.gt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.graphics.drawable.GradientDrawable;
+import android.view.View;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.components.embedder_support.util.UrlUtilities;
+import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.url.GURL;
+import org.chromium.url.JUnitTestGURLs;
+
+/**
+ * Test of {@link ContinuousSearchContainerViewBinder}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class ContinuousSearchListViewBinderTest {
+    @Mock
+    private UrlUtilities.Natives mUrlUtilitiesJniMock;
+
+    private PropertyModel mModel;
+
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+    @Before
+    public void setUp() {
+        mModel = new PropertyModel(ContinuousSearchListProperties.ITEM_KEYS);
+        mJniMocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
+    }
+
+    @Test
+    public void testBindListItem() {
+        View view = mock(View.class);
+        TextView textView = mock(TextView.class);
+        GradientDrawable drawable = mock(GradientDrawable.class);
+        InOrder inOrder = inOrder(view, textView, drawable);
+        PropertyModelChangeProcessor.create(
+                mModel, view, ContinuousSearchListViewBinder::bindListItem);
+
+        doReturn(textView).when(view).findViewById(anyInt());
+
+        final String label = "Label";
+        mModel.set(ContinuousSearchListProperties.LABEL, label);
+        inOrder.verify(textView).setText(eq(label));
+
+        GURL url = JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL);
+        final String domain = "example.com";
+        doReturn(domain)
+                .when(mUrlUtilitiesJniMock)
+                .getDomainAndRegistry(eq(url.getSpec()), anyBoolean());
+        mModel.set(ContinuousSearchListProperties.URL, url);
+        inOrder.verify(textView).setText(eq(domain));
+
+        int color = 0xAABBCC;
+        mModel.set(ContinuousSearchListProperties.IS_SELECTED, false);
+        inOrder.verify(view).getBackground();
+        mModel.set(ContinuousSearchListProperties.BORDER_COLOR, color);
+        inOrder.verify(view).getBackground();
+        when(view.getBackground()).thenReturn(drawable);
+        mModel.set(ContinuousSearchListProperties.IS_SELECTED, true);
+        inOrder.verify(view, times(2)).getBackground();
+        inOrder.verify(drawable).mutate();
+        inOrder.verify(drawable).setStroke(gt(0), eq(color));
+
+        View.OnClickListener listener = (v) -> {};
+        mModel.set(ContinuousSearchListProperties.CLICK_LISTENER, listener);
+        inOrder.verify(view).setOnClickListener(eq(listener));
+
+        color = 0xCCBBAA;
+        mModel.set(ContinuousSearchListProperties.BACKGROUND_COLOR, color);
+        inOrder.verify(view, times(2)).getBackground();
+        inOrder.verify(drawable).mutate();
+        inOrder.verify(drawable).setColor(color);
+
+        int id = 90;
+        mModel.set(ContinuousSearchListProperties.TITLE_TEXT_STYLE, id);
+        inOrder.verify(textView).setTextAppearance(any(), eq(id));
+
+        id = 67;
+        mModel.set(ContinuousSearchListProperties.DESCRIPTION_TEXT_STYLE, id);
+        inOrder.verify(textView).setTextAppearance(any(), eq(id));
+    }
+}
diff --git a/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchSceneLayerTest.java b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchSceneLayerTest.java
new file mode 100644
index 0000000..2e0631b
--- /dev/null
+++ b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchSceneLayerTest.java
@@ -0,0 +1,118 @@
+// 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.continuous_search;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.browser.layouts.components.VirtualView;
+import org.chromium.chrome.browser.layouts.scene_layer.SceneLayer;
+import org.chromium.ui.resources.ResourceManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test of {@link ContinuousSearchSceneLayer}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class ContinuousSearchSceneLayerTest {
+    private static final long FAKE_NATIVE_ADDRESS = 123L;
+    @Mock
+    private ResourceManager mResourceManagerMock;
+    @Mock
+    private SceneLayer mContentTree;
+    @Mock
+    private ContinuousSearchSceneLayer.Natives mContinuousSearchSceneLayerJniMock;
+
+    private ContinuousSearchSceneLayer mSceneLayer;
+
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+    @Before
+    public void setUp() {
+        mJniMocker.mock(
+                ContinuousSearchSceneLayerJni.TEST_HOOKS, mContinuousSearchSceneLayerJniMock);
+        when(mContinuousSearchSceneLayerJniMock.init(any())).thenReturn(FAKE_NATIVE_ADDRESS);
+
+        mSceneLayer = new ContinuousSearchSceneLayer(mResourceManagerMock);
+
+        verify(mContinuousSearchSceneLayerJniMock, times(1)).init(eq(mSceneLayer));
+        mSceneLayer.initializeNative();
+    }
+
+    /**
+     * Tests setting visibility.
+     */
+    @Test
+    public void testVisibility() {
+        mSceneLayer.setIsVisible(true);
+        Assert.assertTrue(mSceneLayer.isSceneOverlayTreeShowing());
+
+        mSceneLayer.setIsVisible(false);
+        Assert.assertFalse(mSceneLayer.isSceneOverlayTreeShowing());
+    }
+
+    /**
+     * Tests setting and updating native.
+     */
+    @Test
+    public void testSetAndUpdate() {
+        InOrder inOrder = inOrder(mContinuousSearchSceneLayerJniMock);
+        mSceneLayer.setContentTree(mContentTree);
+        inOrder.verify(mContinuousSearchSceneLayerJniMock)
+                .setContentTree(eq(FAKE_NATIVE_ADDRESS), eq(mContentTree));
+
+        final int verticalOffset = 5;
+        mSceneLayer.setVerticalOffset(verticalOffset);
+        final int resourceId = 10;
+        mSceneLayer.setResourceId(resourceId);
+
+        mSceneLayer.onSizeChanged(1f, 2f, 3f, 0); // Should have no effect.
+        Assert.assertEquals(
+                mSceneLayer, mSceneLayer.getUpdatedSceneOverlayTree(null, null, null, 0f));
+        inOrder.verify(mContinuousSearchSceneLayerJniMock)
+                .updateContinuousSearchLayer(eq(FAKE_NATIVE_ADDRESS), eq(mResourceManagerMock),
+                        eq(resourceId), eq(verticalOffset));
+    }
+
+    /**
+     * Tests the required overrides that default to no-ops.
+     */
+    @Test
+    public void testDefaultBehaviors() {
+        Assert.assertNull(mSceneLayer.getEventFilter());
+
+        List<VirtualView> views = new ArrayList<>();
+        mSceneLayer.getVirtualViews(views);
+        Assert.assertEquals(0, views.size());
+
+        Assert.assertFalse(mSceneLayer.shouldHideAndroidBrowserControls());
+
+        Assert.assertFalse(mSceneLayer.onBackPressed());
+
+        Assert.assertFalse(mSceneLayer.handlesTabCreating());
+    }
+}
diff --git a/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabHelperJUnitTest.java b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabHelperJUnitTest.java
new file mode 100644
index 0000000..8912b91
--- /dev/null
+++ b/chrome/browser/continuous_search/android/junit/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabHelperJUnitTest.java
@@ -0,0 +1,80 @@
+// 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.continuous_search;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
+import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
+
+/**
+ * Test of {@link ContinuousSearchTabHelper}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class ContinuousSearchTabHelperJUnitTest {
+    @Mock
+    private Tab mTabMock;
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+    @Rule
+    public TestRule mProcessor = new Features.JUnitProcessor();
+
+    /**
+     * Tests initialization success of all tab observers.
+     */
+    @Test
+    @EnableFeatures({ChromeFeatureList.CONTINUOUS_SEARCH})
+    public void testInitializeSuccess() {
+        when(mTabMock.isIncognito()).thenReturn(false);
+
+        ContinuousSearchTabHelper.createForTab(mTabMock);
+
+        verify(mTabMock, times(2)).addObserver(any());
+    }
+
+    /**
+     * Tests initialization is skipped for incognito tabs even if the feature is on.
+     */
+    @Test
+    @EnableFeatures({ChromeFeatureList.CONTINUOUS_SEARCH})
+    public void testNoInitializeIfIncognito() {
+        when(mTabMock.isIncognito()).thenReturn(true);
+
+        ContinuousSearchTabHelper.createForTab(mTabMock);
+
+        verify(mTabMock, never()).addObserver(any());
+    }
+
+    /**
+     * Tests only metrics observer is initialized if the feature flag is off.
+     */
+    @Test
+    @DisableFeatures({ChromeFeatureList.CONTINUOUS_SEARCH})
+    public void testInitializeOnlyNavigationObserver() {
+        when(mTabMock.isIncognito()).thenReturn(false);
+
+        ContinuousSearchTabHelper.createForTab(mTabMock);
+
+        verify(mTabMock, times(1)).addObserver(any());
+    }
+}
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
index a443710..c48ce9d 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest_chromeos.cc
@@ -274,10 +274,9 @@
     ASSERT_TRUE(base::CreateDirectory(mount_point_path));
     ASSERT_TRUE(
         base::CreateDirectory(mount_point_path.Append(kChildDirectory)));
-    ASSERT_TRUE(content::BrowserContext::GetMountPoints(browser()->profile())
-                    ->RegisterFileSystem(
-                        mount_point_name, storage::kFileSystemTypeLocal,
-                        storage::FileSystemMountOption(), mount_point_path));
+    ASSERT_TRUE(browser()->profile()->GetMountPoints()->RegisterFileSystem(
+        mount_point_name, storage::kFileSystemTypeLocal,
+        storage::FileSystemMountOption(), mount_point_path));
     VolumeManager* const volume_manager =
         VolumeManager::Get(browser()->profile());
     ASSERT_TRUE(volume_manager);
diff --git a/chrome/browser/extensions/api/socket/socket_apitest.cc b/chrome/browser/extensions/api/socket/socket_apitest.cc
index fc3495a..ff2083b 100644
--- a/chrome/browser/extensions/api/socket/socket_apitest.cc
+++ b/chrome/browser/extensions/api/socket/socket_apitest.cc
@@ -4,6 +4,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/strings/stringprintf.h"
+#include "build/build_config.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/ui/browser.h"
@@ -108,7 +109,13 @@
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
 
-IN_PROC_BROWSER_TEST_F(SocketApiTest, SocketMulticast) {
+// Fails on MacOS 11, crbug.com/1211141 .
+#if defined(OS_MAC)
+#define MAYBE_SocketMulticast DISABLED_SocketMulticast
+#else
+#define MAYBE_SocketMulticast SocketMulticast
+#endif
+IN_PROC_BROWSER_TEST_F(SocketApiTest, MAYBE_SocketMulticast) {
   ResultCatcher catcher;
   catcher.RestrictToBrowserContext(browser()->profile());
   ExtensionTestMessageListener listener("info_please", true);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7e70f210..8847ea2 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2320,11 +2320,6 @@
     "expiry_milestone": -1
   },
   {
-    "name": "enable-scalable-status-area",
-    "owners": [ "leandre" ],
-    "expiry_milestone": 91
-  },
-  {
     "name": "enable-sharesheet",
     "owners": [ "chromeos-apps-foundation-team@google.com" ],
     "expiry_milestone": 92
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 1634b30..341a15c 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4707,11 +4707,6 @@
     "Enable compatibility mode for Android apps that are not optimized for "
     "large screens, and impose restrictions on resizing the apps";
 
-const char kScalableStatusAreaName[] = "Enable Scalable Status Area";
-const char kScalableStatusAreaDescription[] =
-    "Showing important notification icons in status area when the screen is "
-    "sufficiently large.";
-
 const char kScanAppMediaLinkName[] = "Show Media app link in Scan app";
 const char kScanAppMediaLinkDescription[] =
     "Enables showing a link in the Scan app to open scanned images in the Media"
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 2c1524d5..fcb9d8e 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2747,9 +2747,6 @@
 extern const char kArcResizeLockName[];
 extern const char kArcResizeLockDescription[];
 
-extern const char kScalableStatusAreaName[];
-extern const char kScalableStatusAreaDescription[];
-
 extern const char kScanAppMediaLinkName[];
 extern const char kScanAppMediaLinkDescription[];
 
diff --git a/chrome/browser/lifetime/application_lifetime.cc b/chrome/browser/lifetime/application_lifetime.cc
index f12891c..3824ae3 100644
--- a/chrome/browser/lifetime/application_lifetime.cc
+++ b/chrome/browser/lifetime/application_lifetime.cc
@@ -144,7 +144,7 @@
   // TODO(beng): Can this use ProfileManager::GetLoadedProfiles instead?
   // TODO(crbug.com/1205798): Unset SaveSessionState if the restart fails.
   for (auto* browser : *BrowserList::GetInstance()) {
-    content::BrowserContext::SaveSessionState(browser->profile());
+    browser->profile()->SaveSessionState();
 #if BUILDFLAG(ENABLE_SESSION_SERVICE)
     auto* session_data_service =
         SessionDataServiceFactory::GetForProfile(browser->profile());
diff --git a/chrome/browser/media/media_engagement_autoplay_browsertest.cc b/chrome/browser/media/media_engagement_autoplay_browsertest.cc
index 12149380..96c2f6ccf 100644
--- a/chrome/browser/media/media_engagement_autoplay_browsertest.cc
+++ b/chrome/browser/media/media_engagement_autoplay_browsertest.cc
@@ -28,18 +28,8 @@
 namespace {
 
 base::FilePath GetPythonPath() {
-#if defined(OS_WIN)
-  // Windows bots do not have python installed and available on the PATH.
-  // Please see infra/doc/users/python.md
-  base::FilePath bot_path =
-      base::FilePath(FILE_PATH_LITERAL("c:/infra-system/bin/python.exe"));
-
-  if (base::PathExists(bot_path))
-    return bot_path;
-  return base::FilePath(FILE_PATH_LITERAL("python.exe"));
-#else
-  return base::FilePath(FILE_PATH_LITERAL("python"));
-#endif
+  // Every environment should have vpython3.
+  return base::FilePath(FILE_PATH_LITERAL("vpython3"));
 }
 
 const base::FilePath kTestDataPath = base::FilePath(
diff --git a/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc b/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
index adff52b..1ca502d0 100644
--- a/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
@@ -49,7 +49,8 @@
 // Makes a call and checks that there's encryption or not in the SDP offer.
 // TODO(crbug.com/910216): De-flake this for ChromeOs.
 // TODO(crbug.com/984879): De-flake this for ASAN/MSAN Linux.
-#if defined(OS_CHROMEOS) ||                                        \
+// TODO(crbug.com/1211144): De-flake this for MacOS.
+#if defined(OS_CHROMEOS) || defined(OS_MAC) ||                     \
     (defined(OS_LINUX) &&                                          \
      (defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER))) || \
     (defined(OS_WIN) && defined(ADDRESS_SANITIZER))
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
index d0555514..99455d0c 100644
--- a/chrome/browser/net/profile_network_context_service.cc
+++ b/chrome/browser/net/profile_network_context_service.cc
@@ -840,7 +840,7 @@
 
   // Should be initialized with existing per-profile CORS access lists.
   network_context_params->cors_origin_access_list =
-      content::BrowserContext::GetSharedCorsOriginAccessList(profile_)
+      profile_->GetSharedCorsOriginAccessList()
           ->GetOriginAccessList()
           .CreateCorsOriginAccessPatternsList();
 
diff --git a/chrome/browser/page_load_metrics/observers/back_forward_cache_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/back_forward_cache_page_load_metrics_observer_browsertest.cc
index 2fb9875..08da8ea 100644
--- a/chrome/browser/page_load_metrics/observers/back_forward_cache_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/back_forward_cache_page_load_metrics_observer_browsertest.cc
@@ -536,7 +536,7 @@
             content::RenderFrameHost::LifecycleState::kInBackForwardCache);
 
   // Go back to A.
-  double next_score;
+  float next_score;
   {
     auto waiter = CreatePageLoadMetricsTestWaiter();
     waiter->AddPageExpectation(
diff --git a/chrome/browser/platform_util_unittest.cc b/chrome/browser/platform_util_unittest.cc
index 5212e4f..4456b23 100644
--- a/chrome/browser/platform_util_unittest.cc
+++ b/chrome/browser/platform_util_unittest.cc
@@ -54,7 +54,7 @@
       std::vector<std::unique_ptr<storage::FileSystemBackend>>*
           additional_backends) override {
     storage::ExternalMountPoints* external_mount_points =
-        content::BrowserContext::GetMountPoints(browser_context);
+        browser_context->GetMountPoints();
 
     // New FileSystemBackend that uses our MockSpecialStoragePolicy.
     additional_backends->push_back(
@@ -81,9 +81,9 @@
         content::SetBrowserClientForTesting(content_browser_client_.get());
 
     // The test_directory needs to be mounted for it to be accessible.
-    content::BrowserContext::GetMountPoints(GetProfile())
-        ->RegisterFileSystem("test", storage::kFileSystemTypeLocal,
-                             storage::FileSystemMountOption(), test_directory);
+    GetProfile()->GetMountPoints()->RegisterFileSystem(
+        "test", storage::kFileSystemTypeLocal, storage::FileSystemMountOption(),
+        test_directory);
 
     // To test opening a file, we are going to register a mock extension that
     // handles .txt files. The extension doesn't actually need to exist due to
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 21cf3c2..9ce6fcbb 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -226,7 +226,7 @@
 #include "components/permissions/contexts/geolocation_permission_context_android.h"
 #include "components/query_tiles/tile_service_prefs.h"
 #else  // defined(OS_ANDROID)
-#include "chrome/browser/accessibility/caption_controller.h"
+#include "chrome/browser/accessibility/live_caption_controller.h"
 #include "chrome/browser/cart/cart_service.h"
 #include "chrome/browser/enterprise/reporting/prefs.h"
 #include "chrome/browser/gcm/gcm_product_util.h"
@@ -1079,7 +1079,7 @@
 #else   // defined(OS_ANDROID)
   AppShortcutManager::RegisterProfilePrefs(registry);
   browser_sync::ForeignSessionHandler::RegisterProfilePrefs(registry);
-  captions::CaptionController::RegisterProfilePrefs(registry);
+  captions::LiveCaptionController::RegisterProfilePrefs(registry);
   ChromeAuthenticatorRequestDelegate::RegisterProfilePrefs(registry);
   DevToolsWindow::RegisterProfilePrefs(registry);
   DriveService::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/profiles/profile.cc b/chrome/browser/profiles/profile.cc
index 7f6c64b..7ee045c 100644
--- a/chrome/browser/profiles/profile.cc
+++ b/chrome/browser/profiles/profile.cc
@@ -457,7 +457,7 @@
   if (!sent_destroyed_notification_) {
     sent_destroyed_notification_ = true;
 
-    NotifyWillBeDestroyed(this);
+    NotifyWillBeDestroyed();
 
     for (auto& observer : observers_)
       observer.OnProfileWillBeDestroyed(this);
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc
index f3d8a10d..e5227bb 100644
--- a/chrome/browser/profiles/profile_manager.cc
+++ b/chrome/browser/profiles/profile_manager.cc
@@ -131,8 +131,8 @@
 #endif
 
 #if !defined(OS_ANDROID)
-#include "chrome/browser/accessibility/caption_controller.h"
-#include "chrome/browser/accessibility/caption_controller_factory.h"
+#include "chrome/browser/accessibility/live_caption_controller.h"
+#include "chrome/browser/accessibility/live_caption_controller_factory.h"
 #include "chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager.h"
 #include "chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_factory.h"
 #include "chrome/browser/first_run/first_run.h"
@@ -1628,9 +1628,9 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (!chromeos::ProfileHelper::IsSigninProfile(profile))
-    captions::CaptionControllerFactory::GetForProfile(profile)->Init();
+    captions::LiveCaptionControllerFactory::GetForProfile(profile)->Init();
 #elif !defined(OS_ANDROID)  // !OS_ANDROID && !IS_CHROMEOS_ASH
-  captions::CaptionControllerFactory::GetForProfile(profile)->Init();
+  captions::LiveCaptionControllerFactory::GetForProfile(profile)->Init();
 #endif
 
 #if defined(OS_WIN) && BUILDFLAG(ENABLE_DICE_SUPPORT)
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager.js
index 23b2c2d3..f8429568 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/item_scan_manager.js
@@ -50,6 +50,9 @@
     /** @private {!FocusHistory} */
     this.history_ = new FocusHistory();
 
+    /** @private {boolean} */
+    this.ignoreFocusInKeyboard_ = false;
+
     this.init_();
   }
 
@@ -75,6 +78,7 @@
 
   /** @override */
   enterKeyboard() {
+    this.ignoreFocusInKeyboard_ = true;
     this.node_.automationNode.focus();
     const keyboard = KeyboardRootNode.buildTree();
     this.jumpTo_(keyboard);
@@ -94,6 +98,7 @@
 
   /** @override */
   exitKeyboard() {
+    this.ignoreFocusInKeyboard_ = false;
     const isKeyboard = (data) => data.group instanceof KeyboardRootNode;
     // If we are not in the keyboard, do nothing.
     if (!(this.group_ instanceof KeyboardRootNode) &&
@@ -270,6 +275,14 @@
       return;
     }
 
+    // To be safe, let's ignore focus when we're in the SA menu or over the
+    // keyboard.
+    if (this.ignoreFocusInKeyboard_ ||
+        this.group_ instanceof KeyboardRootNode || MenuManager.isMenuOpen()) {
+      return;
+    }
+
+
     if (this.node_.isEquivalentTo(event.target)) {
       return;
     }
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/keyboard_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/keyboard_node.js
index 72c0439..90b5c1f 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/keyboard_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/keyboard_node.js
@@ -165,10 +165,13 @@
       return;
     }
 
-    KeyboardRootNode.isVisible_ =
-        SwitchAccessPredicate.isVisible(keyboardObject);
+    KeyboardRootNode.isVisible_ = KeyboardRootNode.isKeyboardVisible_();
 
     new EventHandler(
+        keyboardObject, chrome.automation.EventType.LOAD_COMPLETE,
+        KeyboardRootNode.checkVisibilityChanged_)
+        .start();
+    new EventHandler(
         keyboardObject, chrome.automation.EventType.STATE_CHANGED,
         KeyboardRootNode.checkVisibilityChanged_, {exactMatch: true})
         .start();
@@ -177,12 +180,23 @@
   // ================= Private static methods =================
 
   /**
+   * @return {boolean}
+   * @private
+   */
+  static isKeyboardVisible_() {
+    const keyboardObject = KeyboardRootNode.getKeyboardObject();
+    return !!keyboardObject &&
+        SwitchAccessPredicate.isVisible(keyboardObject) &&
+        !!keyboardObject.find({role: chrome.automation.RoleType.ROOT_WEB_AREA});
+  }
+
+  /**
    * @param {chrome.automation.AutomationEvent} event
    * @private
    */
   static checkVisibilityChanged_(event) {
-    const currentlyVisible =
-        SwitchAccessPredicate.isVisible(KeyboardRootNode.getKeyboardObject());
+    const keyboardObject = KeyboardRootNode.getKeyboardObject();
+    const currentlyVisible = KeyboardRootNode.isKeyboardVisible_();
     if (currentlyVisible === KeyboardRootNode.isVisible_) {
       return;
     }
@@ -241,7 +255,6 @@
       return;
     }
 
-    KeyboardRootNode.explicitStateChange_ = true;
     chrome.accessibilityPrivate.setVirtualKeyboardVisible(true);
   }
 }
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
index 865519ca..0ff5a14 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
@@ -143,6 +143,10 @@
     flex-shrink: 0;
     width: var(--emoji-group-button-size);
   }
+
+  .sr-only {
+    user-select: none;
+  }
 </style>
 
 <div class="sr-only" aria-live="polite">
diff --git a/chrome/browser/resources/chromeos/emoji_picker/store.js b/chrome/browser/resources/chromeos/emoji_picker/store.js
index 468d27c..500a2a1 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/store.js
+++ b/chrome/browser/resources/chromeos/emoji_picker/store.js
@@ -57,10 +57,10 @@
    * @param {!StoredEmoji} newEmoji most recently used emoji.
    */
   bumpEmoji(newEmoji) {
-    // find and remove newEmoji from array if it previously existed.
+    // Find and remove newEmoji from array if it previously existed.
     // Note, this explicitly allows for multiple recent emoji entries for the
     // same "base" emoji just with a different variant.
-    const oldIndex = this.data.history.findIndex(x => x === newEmoji);
+    const oldIndex = this.data.history.findIndex(x => x.base === newEmoji.base);
     if (oldIndex !== -1) {
       this.data.history.splice(oldIndex, 1);
     }
diff --git a/chrome/browser/resources/new_tab_page/modules/drive/module.html b/chrome/browser/resources/new_tab_page/modules/drive/module.html
index 6d17f8f..2a061910 100644
--- a/chrome/browser/resources/new_tab_page/modules/drive/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/drive/module.html
@@ -66,6 +66,18 @@
     color: var(--cr-secondary-text-color);
     font-size: 12px;
   }
+
+  ntp-info-dialog a {
+    color: var(--cr-link-color);
+    cursor: pointer;
+    text-decoration: none;
+  }
+
+  ntp-info-dialog a:focus {
+    border-radius: 2px;
+    box-shadow: var(--ntp-focus-shadow);
+    outline: none;
+  }
 </style>
 <ntp-module-header
     dismiss-text="[[i18nRecursive('',
diff --git a/chrome/browser/resources/new_tab_page/modules/info_dialog.html b/chrome/browser/resources/new_tab_page/modules/info_dialog.html
index bea3fa2..01e4a156 100644
--- a/chrome/browser/resources/new_tab_page/modules/info_dialog.html
+++ b/chrome/browser/resources/new_tab_page/modules/info_dialog.html
@@ -7,11 +7,6 @@
   cr-dialog [slot='body'] {
     line-height: 20px;
   }
-
-  cr-dialog [slot='body'] a[href] {
-    color: var(--cr-link-color);
-    text-decoration: none;
-  }
 </style>
 <cr-dialog id="dialog" consume-keydown-event>
   <div slot="title">[[i18n('modulesTasksInfoTitle')]]</div>
diff --git a/chrome/browser/resources/new_tab_page/modules/task_module/module.html b/chrome/browser/resources/new_tab_page/modules/task_module/module.html
index 6d6ad0f..e99f60f 100644
--- a/chrome/browser/resources/new_tab_page/modules/task_module/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/task_module/module.html
@@ -184,6 +184,18 @@
     margin-inline-end: 12px;
     margin-inline-start: 8px;
   }
+
+  ntp-info-dialog a {
+    color: var(--cr-link-color);
+    cursor: pointer;
+    text-decoration: none;
+  }
+
+  ntp-info-dialog a:focus {
+    border-radius: 2px;
+    box-shadow: var(--ntp-focus-shadow);
+    outline: none;
+  }
 </style>
 <ntp-module-header
     dismiss-text="[[i18n('modulesDismissButtonText', dismissName_)]]"
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.html b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.html
index abeda1b..4418b0dd 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/esim_remove_profile_dialog.html
@@ -44,6 +44,10 @@
       #cancel {
         margin-inline-end: 8px;
       }
+
+      #cancel:focus {
+        box-shadow: 0 0 0 2px var(--focus-shadow-color);
+      }
     </style>
     <cr-dialog id="dialog" show-on-attach>
       <div id="title" slot="title">
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.js
index 806eeecb..79caf03 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.js
@@ -88,6 +88,7 @@
       case (AppType.kWeb):
         return 'pwa-detail-view';
       case (AppType.kExtension):
+      case (AppType.kStandaloneBrowser):
         return 'chrome-app-detail-view';
       case (AppType.kArc):
         return 'arc-detail-view';
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_item.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_item.js
index fc0f2a2a..81010a49a 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_item.js
@@ -48,6 +48,7 @@
       case AppType.kArc:
         return AppManagementEntryPoint.MainViewArc;
       case AppType.kExtension:
+      case AppType.kStandaloneBrowser:
         return AppManagementEntryPoint.MainViewChromeApp;
       case AppType.kWeb:
         return AppManagementEntryPoint.MainViewWebApp;
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.js
index 4208433..2ef822e 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.js
@@ -225,6 +225,7 @@
       case AppType.kArc:
         return 'AppManagement.AppDetailViews.ArcApp';
       case AppType.kExtension:
+      case AppType.kStandaloneBrowser:
         return 'AppManagement.AppDetailViews.ChromeApp';
       case AppType.kWeb:
         return 'AppManagement.AppDetailViews.WebApp';
diff --git a/chrome/browser/sessions/better_session_restore_browsertest.cc b/chrome/browser/sessions/better_session_restore_browsertest.cc
index 11d21a2c..18a1f133 100644
--- a/chrome/browser/sessions/better_session_restore_browsertest.cc
+++ b/chrome/browser/sessions/better_session_restore_browsertest.cc
@@ -561,7 +561,7 @@
   void Restart() {
     // Simulate restarting the browser, but let the test exit peacefully.
     for (auto* browser : *BrowserList::GetInstance()) {
-      content::BrowserContext::SaveSessionState(browser->profile());
+      browser->profile()->SaveSessionState();
       SessionDataServiceFactory::GetForProfile(browser->profile())
           ->SetForceKeepSessionState();
     }
@@ -731,7 +731,7 @@
 
   // Disable cookie and storage deletion on shutdown to simulate the
   // process being killed before cleanup is finished.
-  content::BrowserContext::SaveSessionState(browser()->profile());
+  browser()->profile()->SaveSessionState();
 }
 
 IN_PROC_BROWSER_TEST_F(NoSessionRestoreTest, CookiesClearedOnStartup) {
@@ -748,7 +748,7 @@
 
   // Disable cookie and storage deletion on shutdown to simulate the
   // process being killed before cleanup is finished.
-  content::BrowserContext::SaveSessionState(browser()->profile());
+  browser()->profile()->SaveSessionState();
 }
 
 IN_PROC_BROWSER_TEST_F(NoSessionRestoreTest, LocalStorageClearedOnStartup) {
@@ -778,7 +778,7 @@
 
   // Disable cookie and storage deletion handling on shutdown to simulate the
   // process being killed before cleanup is finished.
-  content::BrowserContext::SaveSessionState(browser()->profile());
+  browser()->profile()->SaveSessionState();
 }
 
 IN_PROC_BROWSER_TEST_F(NoSessionRestoreTestWithStartupDeletionDisabled,
@@ -797,7 +797,7 @@
 
   // Disable cookie and storage deletion handling on shutdown to simulate the
   // process being killed before cleanup is finished.
-  content::BrowserContext::SaveSessionState(browser()->profile());
+  browser()->profile()->SaveSessionState();
 }
 
 IN_PROC_BROWSER_TEST_F(NoSessionRestoreTestWithStartupDeletionDisabled,
diff --git a/chrome/browser/signin/signin_util_win.cc b/chrome/browser/signin/signin_util_win.cc
index 790a4412..0ffc61d 100644
--- a/chrome/browser/signin/signin_util_win.cc
+++ b/chrome/browser/signin/signin_util_win.cc
@@ -141,30 +141,13 @@
   profile->GetPrefs()->SetBoolean(prefs::kSignedInWithCredentialProvider, true);
 }
 
-// Extracts preferences to consider while signing in through credential
-// provider. The preferences are set by credential provider after a successful
-// login. They manipulate the behavior of Chrome when importing refresh_token
-// provided by credential provider. When |allow_import_only_on_first_run| is
-// set to true, importing refresh_token is only allowed during Chrome
-// first time run. If it is false, refresh_token is allowed to be imported in
-// subsequent runs. When |allow_import_when_primary_account_exists| is set to
-// false, importing refresh_token is only allowed when profile doesn't have a
-// primary account. If |allow_import_when_primary_account_exists| is set to
-// true, importing refresh_token is allowed even if the profile has primary
-// account for the user authenticated through credential provider.
-void ExtractCredentialImportPreferences(
-    std::wstring* cred_provider_gaia_id,
-    std::wstring* cred_provider_email,
-    bool* allow_import_only_on_first_run,
-    bool* allow_import_when_primary_account_exists) {
+// Extracts the |cred_provider_gaia_id| and |cred_provider_email| for the user
+// signed in throuhg credential provider.
+void ExtractCredentialProviderUser(std::wstring* cred_provider_gaia_id,
+                                   std::wstring* cred_provider_email) {
   DCHECK(cred_provider_gaia_id);
   DCHECK(cred_provider_email);
-  DCHECK(allow_import_only_on_first_run);
-  DCHECK(allow_import_when_primary_account_exists);
 
-  // Initialize to more restricted configuration.
-  *allow_import_only_on_first_run = true;
-  *allow_import_when_primary_account_exists = false;
   cred_provider_gaia_id->clear();
   cred_provider_email->clear();
 
@@ -189,23 +172,8 @@
     return;
   }
 
-  // No need to return immediately if reading following registries fail. They
-  // will set to be stricter by default. cred_provider_gaia_id and
-  // cred_provider_email will be correctly set, though.
-  DWORD reg_import_only_on_first_run = 1;
-  key_account.ReadValueDW(credential_provider::kAllowImportOnlyOnFirstRun,
-                          &reg_import_only_on_first_run);
-
-  DWORD reg_import_when_primary_account_exists = 0;
-  key_account.ReadValueDW(
-      credential_provider::kAllowImportWhenPrimaryAccountExists,
-      &reg_import_when_primary_account_exists);
-
   *cred_provider_gaia_id = it.Name();
   *cred_provider_email = email;
-  *allow_import_only_on_first_run = (reg_import_only_on_first_run == 1);
-  *allow_import_when_primary_account_exists =
-      (reg_import_when_primary_account_exists == 1);
 }
 
 // Attempt to sign in with a credentials from a system installed credential
@@ -306,14 +274,16 @@
 }
 
 void SigninWithCredentialProviderIfPossible(Profile* profile) {
-  bool import_only_on_first_run = true;
-  bool import_when_primary_account_exists = false;
+  // This flow is used for first time signin through credential provider. Any
+  // subsequent signin for the credential provider user needs to go through
+  // reauth flow.
+  if (profile->GetPrefs()->GetBoolean(prefs::kSignedInWithCredentialProvider))
+    return;
+
   std::wstring cred_provider_gaia_id;
   std::wstring cred_provider_email;
 
-  ExtractCredentialImportPreferences(
-      &cred_provider_gaia_id, &cred_provider_email, &import_only_on_first_run,
-      &import_when_primary_account_exists);
+  ExtractCredentialProviderUser(&cred_provider_gaia_id, &cred_provider_email);
   if (cred_provider_gaia_id.empty() || cred_provider_email.empty())
     return;
 
@@ -326,19 +296,12 @@
     return;
   }
 
-  if (import_only_on_first_run && !first_run::IsChromeFirstRun())
-    return;
-
   auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
   std::wstring gaia_id;
   if (identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
     gaia_id = base::UTF8ToWide(
         identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSync)
             .gaia);
-
-    if (!import_when_primary_account_exists) {
-      return;
-    }
   }
 
   TrySigninWithCredentialProvider(profile, gaia_id, gaia_id.empty());
diff --git a/chrome/browser/signin/signin_util_win_browsertest.cc b/chrome/browser/signin/signin_util_win_browsertest.cc
index 7b8e0a2..45ce132 100644
--- a/chrome/browser/signin/signin_util_win_browsertest.cc
+++ b/chrome/browser/signin/signin_util_win_browsertest.cc
@@ -109,14 +109,8 @@
  public:
   BrowserTestHelper(const std::wstring& gaia_id,
                     const std::wstring& email,
-                    const std::string& refresh_token,
-                    int import_only_on_first_run,
-                    int import_on_primary_account)
-      : gaia_id_(gaia_id),
-        email_(email),
-        refresh_token_(refresh_token),
-        import_only_on_first_run_(import_only_on_first_run),
-        import_on_primary_account_(import_on_primary_account) {}
+                    const std::string& refresh_token)
+      : gaia_id_(gaia_id), email_(email), refresh_token_(refresh_token) {}
 
  protected:
   void CreateRegKey(base::win::RegKey* key) {
@@ -168,21 +162,6 @@
     base::win::RegKey key;
     CreateRegKey(&key);
 
-    if (import_only_on_first_run_ != 2) {
-      EXPECT_TRUE(key.Valid());
-      EXPECT_EQ(ERROR_SUCCESS,
-                key.WriteValue(credential_provider::kAllowImportOnlyOnFirstRun,
-                               import_only_on_first_run_));
-    }
-
-    if (import_on_primary_account_ != 2) {
-      EXPECT_TRUE(key.Valid());
-      EXPECT_EQ(ERROR_SUCCESS,
-                key.WriteValue(
-                    credential_provider::kAllowImportWhenPrimaryAccountExists,
-                    import_on_primary_account_));
-    }
-
     if (!email_.empty()) {
       EXPECT_TRUE(key.Valid());
       EXPECT_EQ(ERROR_SUCCESS,
@@ -213,8 +192,6 @@
   std::wstring gaia_id_;
   std::wstring email_;
   std::string refresh_token_;
-  int import_only_on_first_run_;
-  int import_on_primary_account_;
 };
 
 class SigninUtilWinBrowserTest
@@ -225,9 +202,7 @@
   SigninUtilWinBrowserTest()
       : BrowserTestHelper(GetParam().gaia_id,
                           GetParam().email,
-                          GetParam().refresh_token,
-                          2,
-                          2) {}
+                          GetParam().refresh_token) {}
 
  protected:
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -391,15 +366,13 @@
                              /*gaia_id=*/L"gaia-123456",
                              /*email=*/L"foo@gmail.com",
                              /*refresh_token=*/"lst-123456",
-                             /*expect_is_started=*/false)));
+                             /*expect_is_started=*/true)));
 
 struct ExistingWinBrowserSigninUtilTestParams : SigninUtilWinBrowserTestParams {
   ExistingWinBrowserSigninUtilTestParams(
       const std::wstring& gaia_id,
       const std::wstring& email,
       const std::string& refresh_token,
-      const int allow_import_only_on_first_run,
-      const int allow_import_on_primary_account,
       const std::wstring& existing_email,
       bool expect_is_started)
       : SigninUtilWinBrowserTestParams(false,
@@ -407,12 +380,8 @@
                                        email,
                                        refresh_token,
                                        expect_is_started),
-        import_only_on_first_run(allow_import_only_on_first_run),
-        import_on_primary_account(allow_import_on_primary_account),
         existing_email(existing_email) {}
 
-  int import_only_on_first_run;
-  int import_on_primary_account;
   std::wstring existing_email;
 };
 
@@ -425,9 +394,7 @@
   ExistingWinBrowserSigninUtilTest()
       : BrowserTestHelper(GetParam().gaia_id,
                           GetParam().email,
-                          GetParam().refresh_token,
-                          GetParam().import_only_on_first_run,
-                          GetParam().import_on_primary_account) {}
+                          GetParam().refresh_token) {}
 
  protected:
   bool SetUpUserDataDirectory() override {
@@ -482,25 +449,12 @@
     ExpectRefreshTokenExists(false);
 }
 
-INSTANTIATE_TEST_SUITE_P(OnlyAllowFirstRun,
-                         ExistingWinBrowserSigninUtilTest,
-                         testing::Values(ExistingWinBrowserSigninUtilTestParams(
-                             /*gaia_id=*/L"gaia-123456",
-                             /*email=*/L"foo@gmail.com",
-                             /*refresh_token=*/"lst-123456",
-                             /*import_only_on_first_run=*/1,
-                             /*import_on_primary_account=*/0,
-                             /*existing_email=*/std::wstring(),
-                             /*expect_is_started=*/false)));
-
 INSTANTIATE_TEST_SUITE_P(AllowSubsequentRun,
                          ExistingWinBrowserSigninUtilTest,
                          testing::Values(ExistingWinBrowserSigninUtilTestParams(
                              /*gaia_id=*/L"gaia-123456",
                              /*email=*/L"foo@gmail.com",
                              /*refresh_token=*/"lst-123456",
-                             /*import_only_on_first_run=*/0,
-                             /*import_on_primary_account=*/0,
                              /*existing_email=*/std::wstring(),
                              /*expect_is_started=*/true)));
 
@@ -510,8 +464,6 @@
                              /*gaia_id=*/L"gaia_id_for_foo_gmail.com",
                              /*email=*/L"foo@gmail.com",
                              /*refresh_token=*/"lst-123456",
-                             /*import_only_on_first_run=*/0,
-                             /*import_on_primary_account=*/0,
                              /*existing_email=*/L"bar@gmail.com",
                              /*expect_is_started=*/false)));
 
@@ -521,21 +473,9 @@
                              /*gaia_id=*/L"gaia_id_for_foo_gmail.com",
                              /*email=*/L"foo@gmail.com",
                              /*refresh_token=*/"lst-123456",
-                             /*import_only_on_first_run=*/0,
-                             /*import_on_primary_account=*/1,
                              /*existing_email=*/L"bar@gmail.com",
                              /*expect_is_started=*/false)));
 
-INSTANTIATE_TEST_SUITE_P(AllowProfileWithPrimaryAccount_DisabledImport,
-                         ExistingWinBrowserSigninUtilTest,
-                         testing::Values(ExistingWinBrowserSigninUtilTestParams(
-                             /*gaia_id=*/L"gaia_id_for_foo_gmail.com",
-                             /*email=*/L"foo@gmail.com",
-                             /*refresh_token=*/"lst-123456",
-                             /*import_only_on_first_run=*/0,
-                             /*import_on_primary_account=*/0,
-                             /*existing_email=*/L"foo@gmail.com",
-                             /*expect_is_started=*/false)));
 
 INSTANTIATE_TEST_SUITE_P(AllowProfileWithPrimaryAccount_SameUser,
                          ExistingWinBrowserSigninUtilTest,
@@ -543,8 +483,6 @@
                              /*gaia_id=*/L"gaia_id_for_foo_gmail.com",
                              /*email=*/L"foo@gmail.com",
                              /*refresh_token=*/"lst-123456",
-                             /*import_only_on_first_run=*/0,
-                             /*import_on_primary_account=*/1,
                              /*existing_email=*/L"foo@gmail.com",
                              /*expect_is_started=*/true)));
 
@@ -605,9 +543,7 @@
   ExistingWinBrowserProfilesSigninUtilTest()
       : BrowserTestHelper(L"gaia_id_for_foo_gmail.com",
                           L"foo@gmail.com",
-                          "lst-123456",
-                          0,
-                          1) {
+                          "lst-123456") {
     // TODO(droger): Disable the profile picker using the local state preference
     // instead.
     feature_list_.InitAndDisableFeature(features::kNewProfilePicker);
@@ -624,7 +560,7 @@
       SetSigninUtilRegistry();
     } else if (IsPrePreTest() && GetParam().cred_provider_used_other_profile) {
       BrowserTestHelper(L"gaia_id_for_bar_gmail.com", L"bar@gmail.com",
-                        "lst-123456", 0, 1)
+                        "lst-123456")
           .SetSigninUtilRegistry();
     }
 
diff --git a/chrome/browser/speech/speech_recognition_service_browsertest.cc b/chrome/browser/speech/speech_recognition_service_browsertest.cc
index ddfb8d520..5f59117 100644
--- a/chrome/browser/speech/speech_recognition_service_browsertest.cc
+++ b/chrome/browser/speech/speech_recognition_service_browsertest.cc
@@ -25,6 +25,7 @@
 #include "content/public/browser/audio_service.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
+#include "media/audio/audio_device_description.h"
 #include "media/audio/wav_audio_handler.h"
 #include "media/base/media_switches.h"
 #include "media/mojo/mojom/audio_input_stream.mojom.h"
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
index 7d9fae4..0bcaecc 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.cc
@@ -228,6 +228,11 @@
   return pinned_files;
 }
 
+void HoldingSpaceKeyedService::AddDiagnosticsLog(
+    const base::FilePath& diagnostics_log_path) {
+  AddItemOfType(HoldingSpaceItem::Type::kDiagnosticsLog, diagnostics_log_path);
+}
+
 void HoldingSpaceKeyedService::AddDownload(
     HoldingSpaceItem::Type type,
     const base::FilePath& download_file,
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
index 0f3173c..358e5e5 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
@@ -83,6 +83,9 @@
   // files system URLs as GURLs.
   std::vector<GURL> GetPinnedFiles() const;
 
+  // Adds a diagnostics log item backed by the provided absolute file path.
+  void AddDiagnosticsLog(const base::FilePath& diagnostics_log_path);
+
   // Adds a download item of the specified `type` backed by the provided
   // absolute file path.
   // NOTE: `type` must refer to a download type.
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
index e494ad74..fa02773f 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
@@ -1731,6 +1731,9 @@
     ASSERT_TRUE(holding_space_service);
 
     switch (type) {
+      case HoldingSpaceItem::Type::kDiagnosticsLog:
+        holding_space_service->AddDiagnosticsLog(file_path);
+        break;
       case HoldingSpaceItem::Type::kArcDownload:
       case HoldingSpaceItem::Type::kDownload:
         holding_space_service->AddDownload(type, file_path);
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc b/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
index 79d7386..54dd429 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_ui_browsertest.cc
@@ -980,6 +980,7 @@
 
   // Right clicking a pinned item should cause a context menu to show.
   ASSERT_FALSE(views::MenuController::GetActiveInstance());
+  ViewDrawnWaiter().Wait(pinned_file_chips.front());
   RightClick(pinned_file_chips.front());
   ASSERT_TRUE(views::MenuController::GetActiveInstance());
 
@@ -994,6 +995,7 @@
   ASSERT_GT(download_chips.size(), 1u);
 
   // Add a download item to the selection and show the context menu.
+  ViewDrawnWaiter().Wait(download_chips.front());
   Click(download_chips.front(), ui::EF_CONTROL_DOWN);
   RightClick(download_chips.front());
   ASSERT_TRUE(views::MenuController::GetActiveInstance());
@@ -1048,6 +1050,7 @@
   ASSERT_GT(screen_capture_views.size(), 1u);
 
   // Select a screen capture item and show the context menu.
+  ViewDrawnWaiter().Wait(screen_capture_views.front());
   Click(screen_capture_views.front());
   RightClick(screen_capture_views.front());
   ASSERT_TRUE(views::MenuController::GetActiveInstance());
@@ -1085,12 +1088,16 @@
   // due to max visibility count restrictions.
   while (!download_chips.empty() || !screen_capture_views.empty()) {
     // Select all visible download items.
-    for (views::View* download_chip : download_chips)
+    for (views::View* download_chip : download_chips) {
+      ViewDrawnWaiter().Wait(download_chip);
       Click(download_chip, ui::EF_CONTROL_DOWN);
+    }
 
     // Select all visible screen capture items.
-    for (views::View* screen_capture_view : screen_capture_views)
+    for (views::View* screen_capture_view : screen_capture_views) {
+      ViewDrawnWaiter().Wait(screen_capture_view);
       Click(screen_capture_view, ui::EF_CONTROL_DOWN);
+    }
 
     // Show the context menu. There should be a `kRemoveItem` command.
     RightClick(download_chips.size() ? download_chips.front()
@@ -1230,6 +1237,7 @@
 
   // Right click the tray icon, and expect a context menu to be shown which will
   // allow the user to hide previews.
+  ViewDrawnWaiter().Wait(previews_tray_icon);
   RightClick(previews_tray_icon);
   ASSERT_TRUE(views::MenuController::GetActiveInstance());
 
@@ -1249,6 +1257,7 @@
 
   // Right click the tray icon, and expect a context menu to be shown which will
   // allow the user to show previews.
+  ViewDrawnWaiter().Wait(default_tray_icon);
   RightClick(default_tray_icon);
   ASSERT_TRUE(views::MenuController::GetActiveInstance());
 
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 0c2e0d3..edd45b7 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -31,6 +31,7 @@
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
+#include "chrome/browser/ui/autofill/payments/autofill_snackbar_controller_impl.h"
 #include "chrome/browser/ui/autofill/payments/create_card_unmask_prompt_view.h"
 #include "chrome/browser/ui/autofill/payments/credit_card_scanner_controller.h"
 #include "chrome/browser/ui/autofill/save_update_address_profile_bubble_controller_impl.h"
@@ -684,10 +685,11 @@
 #endif
 }
 
-void ChromeAutofillClient::ShowVirtualCardManualFallbackBubble(
-    const CreditCard* credit_card,
-    const std::u16string& cvc) {
-#if !defined(OS_ANDROID)  // Desktop only
+void ChromeAutofillClient::OnVirtualCardFetched(const CreditCard* credit_card,
+                                                const std::u16string& cvc) {
+#if defined(OS_ANDROID)
+  (new AutofillSnackbarControllerImpl(web_contents()))->Show();
+#else
   VirtualCardManualFallbackBubbleControllerImpl::CreateForWebContents(
       web_contents());
   VirtualCardManualFallbackBubbleControllerImpl* controller =
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index 66d8d708..013a18e3 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -150,8 +150,8 @@
   void HideAutofillPopup(PopupHidingReason reason) override;
   void ShowOfferNotificationIfApplicable(
       const AutofillOfferData* offer) override;
-  void ShowVirtualCardManualFallbackBubble(const CreditCard* card,
-                                           const std::u16string& cvc) override;
+  void OnVirtualCardFetched(const CreditCard* card,
+                            const std::u16string& cvc) override;
   bool IsAutofillAssistantShowing() override;
   bool IsAutocompleteEnabled() override;
   void PropagateAutofillPredictions(
diff --git a/chrome/browser/ui/caption_bubble_controller.h b/chrome/browser/ui/caption_bubble_controller.h
index 645e754..026ea73 100644
--- a/chrome/browser/ui/caption_bubble_controller.h
+++ b/chrome/browser/ui/caption_bubble_controller.h
@@ -53,7 +53,7 @@
       absl::optional<ui::CaptionStyle> caption_style) = 0;
 
  private:
-  friend class CaptionControllerTest;
+  friend class LiveCaptionControllerTest;
 
   virtual bool IsWidgetVisibleForTesting() = 0;
   virtual std::string GetBubbleLabelTextForTesting() = 0;
diff --git a/chrome/browser/ui/tabs/tab_renderer_data.cc b/chrome/browser/ui/tabs/tab_renderer_data.cc
index 01df75e..fdf7acb 100644
--- a/chrome/browser/ui/tabs/tab_renderer_data.cc
+++ b/chrome/browser/ui/tabs/tab_renderer_data.cc
@@ -13,9 +13,31 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
 #include "chrome/browser/ui/thumbnails/thumbnail_tab_helper.h"
+#include "chrome/common/webui_url_constants.h"
+#include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/url_constants.h"
 
+namespace {
+
+bool ShouldThemifyFaviconForEntryUrl(const GURL& url) {
+  // Themify favicon for the default NTP and incognito NTP.
+  return url.SchemeIs(content::kChromeUIScheme) &&
+         (url.host_piece() == chrome::kChromeUINewTabPageHost ||
+          url.host_piece() == chrome::kChromeUINewTabHost);
+}
+
+bool ShouldThemifyFaviconForVisibleUrl(const GURL& visible_url) {
+  return visible_url.SchemeIs(content::kChromeUIScheme) &&
+         visible_url.host_piece() != chrome::kChromeUIAppLauncherPageHost &&
+         visible_url.host_piece() != chrome::kChromeUIHelpHost &&
+         visible_url.host_piece() != chrome::kChromeUIVersionHost &&
+         visible_url.host_piece() != chrome::kChromeUINetExportHost &&
+         visible_url.host_piece() != chrome::kChromeUINewTabHost;
+}
+
+}  // namespace
+
 // static
 TabRendererData TabRendererData::FromTabInModel(TabStripModel* model,
                                                 int index) {
@@ -45,6 +67,13 @@
   data.blocked = model->IsTabBlocked(index);
   data.should_hide_throbber = tab_ui_helper->ShouldHideThrobber();
   data.alert_state = chrome::GetTabAlertStatesForContents(contents);
+
+  content::NavigationEntry* entry =
+      contents->GetController().GetLastCommittedEntry();
+  data.should_themify_favicon =
+      (entry && ShouldThemifyFaviconForEntryUrl(entry->GetURL())) ||
+      ShouldThemifyFaviconForVisibleUrl(contents->GetVisibleURL());
+
   return data;
 }
 
diff --git a/chrome/browser/ui/tabs/tab_renderer_data.h b/chrome/browser/ui/tabs/tab_renderer_data.h
index 9f4f9ecd..260e988 100644
--- a/chrome/browser/ui/tabs/tab_renderer_data.h
+++ b/chrome/browser/ui/tabs/tab_renderer_data.h
@@ -52,6 +52,7 @@
   std::vector<TabAlertState> alert_state;
   bool should_hide_throbber = false;
   bool should_render_empty_title = false;
+  bool should_themify_favicon = false;
 };
 
 #endif  // CHROME_BROWSER_UI_TABS_TAB_RENDERER_DATA_H_
diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
index 247a351e..fc00e3a 100644
--- a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
+++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include <algorithm>
+#include <memory>
 
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
@@ -42,6 +43,7 @@
 #include "components/sync_sessions/session_sync_service.h"
 #include "components/sync_sessions/synced_session.h"
 #include "components/tab_groups/tab_group_id.h"
+#include "components/vector_icons/vector_icons.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/models/image_model.h"
@@ -63,6 +65,7 @@
 // only have navigatabale/executable tab items.
 // - |local_window_items_| only has executable open window items.
 // - |local_group_items_| only has executable open group items.
+// - |local_sub_menu_items_| only has non-executable sub menu items.
 // Using initial command IDs for local tab, local window, local group, and other
 // devices' tab items makes it easier and less error-prone to manipulate the
 // menumodel and storage structures.
@@ -75,11 +78,15 @@
 // |AppMenuModel::kMinRecentTabsCommandId| i.e. 1001 and 1200
 // (|AppMenuModel::kMaxRecentTabsCommandId|) inclusively.
 const int kFirstLocalTabCommandId = AppMenuModel::kMinRecentTabsCommandId;
-const int kFirstLocalWindowCommandId = 1031;
-const int kFirstLocalGroupCommandId = 1051;
-const int kFirstOtherDevicesTabCommandId = 1071;
+const int kFirstLocalWindowCommandId = kFirstLocalTabCommandId + 30;
+const int kFirstLocalGroupCommandId = kFirstLocalWindowCommandId + 20;
+const int kFirstLocalSubMenuCommandId = kFirstLocalGroupCommandId + 20;
+const int kFirstOtherDevicesTabCommandId = kFirstLocalSubMenuCommandId + 20;
 const int kMinDeviceNameCommandId = 1120;
 const int kMaxDeviceNameCommandId = 1130;
+static_assert(kMaxDeviceNameCommandId <= AppMenuModel::kMaxRecentTabsCommandId,
+              "Final command id within RecentTabsSubMenuModel must not exceed "
+              "those allocated to it within AppMenuModel");
 
 // The maximum number of local recently closed entries (tab or window) to be
 // shown in the menu.
@@ -109,6 +116,12 @@
 // Returns true if the command id identifies a group menu item.
 bool IsGroupModelCommandId(int command_id) {
   return command_id >= kFirstLocalGroupCommandId &&
+         command_id < kFirstLocalSubMenuCommandId;
+}
+
+// Returns true if the command id identifies a sub menu item.
+bool IsSubMenuModelCommandId(int command_id) {
+  return command_id >= kFirstLocalSubMenuCommandId &&
          command_id < kFirstOtherDevicesTabCommandId;
 }
 
@@ -145,6 +158,13 @@
   return command_id;
 }
 
+// Convert |sub_menu_vector_index| to command id of menu item.
+int SubMenuVectorIndexToCommandId(int sub_menu_vector_index) {
+  int command_id = sub_menu_vector_index + kFirstLocalSubMenuCommandId;
+  DCHECK(IsSubMenuModelCommandId(command_id));
+  return command_id;
+}
+
 // Convert |command_id| of menu item to index in |local_group_items_|.
 int CommandIdToGroupVectorIndex(int command_id) {
   DCHECK(IsGroupModelCommandId(command_id));
@@ -192,6 +212,29 @@
   GURL url;
 };
 
+// An element in |RecentTabsSubMenuModel::sub_menu_items_| that records a sub
+// menu item's model and its own command id.
+// TODO(emshack): This solution, where sub menus are represented by a
+// SimpleMenuModel and managed by the parent RecentTabsSubMenuModel, is not
+// ideal. However, AppMenuModel requires unique ids across its descendant sub
+// menus, and queries a single model for these ids. This doesn't work well with
+// our preferred approach, which would decouple sub menus from their parent and
+// allow them to manage their own items and command ids.
+struct RecentTabsSubMenuModel::SubMenuItem {
+  SubMenuItem(int command_id,
+              std::unique_ptr<ui::SimpleMenuModel> sub_menu_model)
+      : parent_id(command_id), menu_model(std::move(sub_menu_model)) {
+    const int child_id_count = menu_model->GetItemCount();
+    for (int i = 0; i < child_id_count; i++) {
+      child_ids.insert(menu_model->GetCommandIdAt(i));
+    }
+  }
+
+  const int parent_id;
+  std::unordered_set<int> child_ids;
+  std::unique_ptr<ui::SimpleMenuModel> menu_model;
+};
+
 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
     ui::AcceleratorProvider* accelerator_provider,
     Browser* browser)
@@ -230,7 +273,7 @@
   }
 }
 
-RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {}
+RecentTabsSubMenuModel::~RecentTabsSubMenuModel() = default;
 
 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
   return false;
@@ -248,10 +291,19 @@
     ui::Accelerator* accelerator) const {
   // If there are no recently closed items, we show the accelerator beside
   // the header, otherwise, we show it beside the first item underneath it.
-  int index_in_menu = GetIndexOfCommandId(command_id);
-  int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId);
+  // If the first item underneath it is a submenu, we instead show it beside
+  // the first item in that submenu.
+  const int index_in_menu = GetIndexOfCommandId(command_id);
+  const int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId);
+  const int parent_id = GetParentCommandId(command_id);
+  const int parent_index =
+      parent_id == -1 ? -1 : GetIndexOfCommandId(parent_id);
   if ((command_id == kDisabledRecentlyClosedHeaderCommandId ||
-       (header_index != -1 && index_in_menu == header_index + 1)) &&
+       ((header_index != -1 && (!IsSubMenuModelCommandId(command_id) &&
+                                index_in_menu == header_index + 1)) ||
+        ((IsWindowModelCommandId(command_id) ||
+          IsGroupModelCommandId(command_id)) &&
+         parent_index == header_index + 1))) &&
       reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
     *accelerator = reopen_closed_tab_accelerator_;
     return true;
@@ -292,8 +344,8 @@
       BrowserLiveTabContext::FindContextForWebContents(
           browser_->tab_strip_model()->GetActiveWebContents());
   if (IsTabModelCommandId(command_id)) {
-    TabNavigationItems* tab_items = NULL;
-    int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
+    TabNavigationItems* tab_items = nullptr;
+    const int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
     const TabNavigationItem& item = (*tab_items)[tab_items_idx];
     DCHECK(item.tab_id.is_valid() && item.url.is_valid());
 
@@ -324,7 +376,7 @@
     }
   } else if (IsWindowModelCommandId(command_id)) {
     if (service && context) {
-      int window_items_idx = CommandIdToWindowVectorIndex(command_id);
+      const int window_items_idx = CommandIdToWindowVectorIndex(command_id);
       DCHECK(window_items_idx >= 0 &&
              window_items_idx < static_cast<int>(local_window_items_.size()));
       base::RecordAction(
@@ -335,7 +387,7 @@
                                 disposition);
     }
   } else if (IsGroupModelCommandId(command_id)) {
-    int group_items_idx = CommandIdToGroupVectorIndex(command_id);
+    const int group_items_idx = CommandIdToGroupVectorIndex(command_id);
     DCHECK(group_items_idx >= 0 &&
            group_items_idx < static_cast<int>(local_group_items_.size()));
     base::RecordAction(base::UserMetricsAction("WrenchMenu_OpenRecentGroup"));
@@ -343,6 +395,8 @@
                               LIMIT_RECENT_TAB_ACTION);
     service->RestoreEntryById(context, local_group_items_[group_items_idx],
                               disposition);
+  } else if (IsSubMenuModelCommandId(command_id)) {
+    return;
   } else {
     NOTREACHED();
   }
@@ -361,17 +415,17 @@
 
 const gfx::FontList* RecentTabsSubMenuModel::GetLabelFontListAt(
     int index) const {
-  int command_id = GetCommandIdAt(index);
+  const int command_id = GetCommandIdAt(index);
   if (command_id == kRecentlyClosedHeaderCommandId ||
       IsDeviceNameCommandId(command_id)) {
     return &ui::ResourceBundle::GetSharedInstance().GetFontList(
         ui::ResourceBundle::BoldFont);
   }
-  return NULL;
+  return nullptr;
 }
 
 int RecentTabsSubMenuModel::GetMaxWidthForItemAtIndex(int item_index) const {
-  int command_id = GetCommandIdAt(item_index);
+  const int command_id = GetCommandIdAt(item_index);
   if (command_id == IDC_RECENT_TABS_NO_DEVICE_TABS ||
       command_id == kRecentlyClosedHeaderCommandId ||
       command_id == kDisabledRecentlyClosedHeaderCommandId) {
@@ -384,10 +438,10 @@
     int index,
     std::string* url,
     std::u16string* title) {
-  int command_id = GetCommandIdAt(index);
+  const int command_id = GetCommandIdAt(index);
   if (IsTabModelCommandId(command_id)) {
-    TabNavigationItems* tab_items = NULL;
-    int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
+    TabNavigationItems* tab_items = nullptr;
+    const int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
     const TabNavigationItem& item = (*tab_items)[tab_items_idx];
     *url = item.url.possibly_invalid_spec();
     *title = item.title;
@@ -453,21 +507,17 @@
           break;
         }
         case sessions::TabRestoreService::WINDOW: {
-          // TODO(chrisha): Make this menu entry better. When windows contain a
-          // single tab, display that tab directly in the menu. Otherwise, offer
-          // a hover over or alternative mechanism for seeing which tabs were in
-          // the window.
-          BuildLocalWindowItem(
-              entry->id,
-              static_cast<const sessions::TabRestoreService::Window&>(*entry)
-                  .tabs.size(),
-              ++last_local_model_index_);
+          auto& window =
+              static_cast<sessions::TabRestoreService::Window&>(*entry);
+          BuildLocalWindowItem(entry->id, CreateWindowSubMenuModel(window),
+                               window.tabs.size(), ++last_local_model_index_);
           break;
         }
         case sessions::TabRestoreService::GROUP: {
           auto& group =
               static_cast<const sessions::TabRestoreService::Group&>(*entry);
-          BuildLocalGroupItem(group.id, group.visual_data, group.tabs.size(),
+          BuildLocalGroupItem(group.id, group.visual_data,
+                              CreateGroupSubMenuModel(group), group.tabs.size(),
                               ++last_local_model_index_);
           break;
         }
@@ -510,7 +560,7 @@
     // Add the header for the device session.
     DCHECK(!session->session_name.empty());
     AddSeparator(ui::NORMAL_SEPARATOR);
-    int command_id = kMinDeviceNameCommandId + i;
+    const int command_id = kMinDeviceNameCommandId + i;
     DCHECK_LE(command_id, kMaxDeviceNameCommandId);
     AddItem(command_id, base::UTF8ToUTF16(session->session_name));
     AddDeviceFavicon(GetItemCount() - 1, session->device_type);
@@ -537,7 +587,7 @@
     const GURL& url,
     int curr_model_index) {
   TabNavigationItem item(std::string(), session_id, title, url);
-  int command_id = TabVectorIndexToCommandId(
+  const int command_id = TabVectorIndexToCommandId(
       local_tab_navigation_items_.size(), kFirstLocalTabCommandId);
   // See comments in BuildLocalEntries() about usage of InsertItem*At().
   // There may be no tab title, in which case, use the url as tab title.
@@ -562,24 +612,29 @@
   local_tab_navigation_items_.push_back(item);
 }
 
-void RecentTabsSubMenuModel::BuildLocalWindowItem(SessionID window_id,
-                                                  int num_tabs,
-                                                  int curr_model_index) {
-  int command_id = WindowVectorIndexToCommandId(local_window_items_.size());
-  // See comments in BuildLocalEntries() about usage of InsertItem*At().
-  InsertItemAt(curr_model_index, command_id, l10n_util::GetPluralStringFUTF16(
-      IDS_RECENTLY_CLOSED_WINDOW, num_tabs));
+void RecentTabsSubMenuModel::BuildLocalWindowItem(
+    SessionID window_id,
+    std::unique_ptr<ui::SimpleMenuModel> window_model,
+    int num_tabs,
+    int curr_model_index) {
+  const int command_id =
+      SubMenuVectorIndexToCommandId(local_sub_menu_items_.size());
+  InsertSubMenuAt(
+      curr_model_index, command_id,
+      l10n_util::GetPluralStringFUTF16(IDS_RECENTLY_CLOSED_WINDOW, num_tabs),
+      window_model.get());
   SetIcon(curr_model_index, CreateFavicon(kTabIcon));
-  local_window_items_.push_back(window_id);
+  SubMenuItem sub_menu_item = SubMenuItem(command_id, std::move(window_model));
+  local_sub_menu_items_.push_back(std::move(sub_menu_item));
 }
 
 void RecentTabsSubMenuModel::BuildLocalGroupItem(
     SessionID session_id,
     tab_groups::TabGroupVisualData visual_data,
+    std::unique_ptr<ui::SimpleMenuModel> group_model,
     int num_tabs,
     int curr_model_index) {
-  int command_id = GroupVectorIndexToCommandId(local_group_items_.size());
-
+  int command_id = SubMenuVectorIndexToCommandId(local_sub_menu_items_.size());
   // Set the item label to the name of the group and the number of tabs.
   std::u16string item_label;
   if (visual_data.title().empty()) {
@@ -591,9 +646,7 @@
     item_label = base::ReplaceStringPlaceholders(
         item_label, {visual_data.title()}, nullptr);
   }
-
-  // See comments in BuildLocalEntries() about usage of InsertItem*At().
-  InsertItemAt(curr_model_index, command_id, item_label);
+  InsertSubMenuAt(curr_model_index, command_id, item_label, group_model.get());
 
   // Set the item icon to the group color.
   const auto& theme =
@@ -602,8 +655,8 @@
   ui::ImageModel group_icon = ui::ImageModel::FromVectorIcon(
       kTabGroupIcon, theme.GetColor(color_id), gfx::kFaviconSize);
   SetIcon(curr_model_index, group_icon);
-
-  local_group_items_.push_back(session_id);
+  SubMenuItem sub_menu_item = SubMenuItem(command_id, std::move(group_model));
+  local_sub_menu_items_.push_back(std::move(sub_menu_item));
 }
 
 void RecentTabsSubMenuModel::BuildOtherDevicesTabItem(
@@ -613,9 +666,9 @@
       tab.navigations.at(tab.normalized_navigation_index());
   TabNavigationItem item(session_tag, tab.tab_id, current_navigation.title(),
                          current_navigation.virtual_url());
-  int command_id = TabVectorIndexToCommandId(
-      other_devices_tab_navigation_items_.size(),
-      kFirstOtherDevicesTabCommandId);
+  const int command_id =
+      TabVectorIndexToCommandId(other_devices_tab_navigation_items_.size(),
+                                kFirstOtherDevicesTabCommandId);
   // See comments in BuildTabsFromOtherDevices() about usage of AddItem*().
   // There may be no tab title, in which case, use the url as tab title.
   AddItem(command_id,
@@ -625,6 +678,43 @@
   other_devices_tab_navigation_items_.push_back(item);
 }
 
+std::unique_ptr<ui::SimpleMenuModel>
+RecentTabsSubMenuModel::CreateWindowSubMenuModel(
+    const sessions::TabRestoreService::Window& window) {
+  std::unique_ptr<ui::SimpleMenuModel> window_model =
+      std::make_unique<ui::SimpleMenuModel>(this);
+  const int command_id =
+      WindowVectorIndexToCommandId(local_window_items_.size());
+  window_model->AddItemWithStringIdAndIcon(
+      command_id, IDS_RESTORE_ALL_TABS,
+      ui::ImageModel::FromVectorIcon(vector_icons::kLaunchIcon));
+  local_window_items_.push_back(window.id);
+  return window_model;
+}
+
+std::unique_ptr<ui::SimpleMenuModel>
+RecentTabsSubMenuModel::CreateGroupSubMenuModel(
+    const sessions::TabRestoreService::Group& group) {
+  std::unique_ptr<ui::SimpleMenuModel> group_model =
+      std::make_unique<ui::SimpleMenuModel>(this);
+  const int command_id = GroupVectorIndexToCommandId(local_group_items_.size());
+  group_model->AddItemWithStringIdAndIcon(
+      command_id, IDS_RESTORE_ALL_TABS,
+      ui::ImageModel::FromVectorIcon(vector_icons::kLaunchIcon));
+  local_group_items_.push_back(group.id);
+  return group_model;
+}
+
+int RecentTabsSubMenuModel::GetParentCommandId(int command_id) const {
+  for (const SubMenuItem& sub_menu_item : local_sub_menu_items_) {
+    if (sub_menu_item.child_ids.find(command_id) !=
+        sub_menu_item.child_ids.end()) {
+      return sub_menu_item.parent_id;
+    }
+  }
+  return -1;
+}
+
 void RecentTabsSubMenuModel::AddDeviceFavicon(
     int index_in_menu,
     sync_pb::SyncEnums::DeviceType device_type) {
@@ -652,13 +742,13 @@
 }
 
 void RecentTabsSubMenuModel::AddTabFavicon(int command_id, const GURL& url) {
-  int index_in_menu = GetIndexOfCommandId(command_id);
+  const int index_in_menu = GetIndexOfCommandId(command_id);
 
   // Set default icon first.
   SetIcon(index_in_menu,
           ui::ImageModel::FromImage(favicon::GetDefaultFavicon()));
 
-  bool is_local_tab = command_id < kFirstOtherDevicesTabCommandId;
+  const bool is_local_tab = command_id < kFirstOtherDevicesTabCommandId;
   if (is_local_tab) {
     // Request only from local storage to avoid leaking user data.
     favicon::FaviconService* favicon_service =
@@ -697,7 +787,7 @@
     // Default icon has already been set.
     return;
   }
-  int index_in_menu = GetIndexOfCommandId(command_id);
+  const int index_in_menu = GetIndexOfCommandId(command_id);
   DCHECK_GT(index_in_menu, -1);
   SetIcon(index_in_menu, ui::ImageModel::FromImage(image_result.image));
   ui::MenuModelDelegate* delegate = menu_model_delegate();
@@ -732,11 +822,14 @@
 
   // Remove all local window items.
   local_window_items_.clear();
+
+  // Remove all local sub menu items.
+  local_sub_menu_items_.clear();
 }
 
 void RecentTabsSubMenuModel::ClearTabsFromOtherDevices() {
   DCHECK_GE(last_local_model_index_, 0);
-  int count = GetItemCount();
+  const int count = GetItemCount();
   for (int index = count - 1; index > last_local_model_index_; --index)
     RemoveItemAt(index);
 
diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h
index 064e38f..041c858 100644
--- a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h
+++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h
@@ -81,6 +81,8 @@
   using TabNavigationItems = std::vector<TabNavigationItem>;
   using WindowItems = std::vector<SessionID>;
   using GroupItems = std::vector<SessionID>;
+  struct SubMenuItem;
+  using SubMenuItems = std::vector<SubMenuItem>;
 
   // Index of the separator that follows the history menu item. Used as a
   // reference position for inserting local entries.
@@ -107,6 +109,7 @@
   // Build the recently closed window item with parameters needed to restore it,
   // and add it to the menumodel at |curr_model_index|.
   void BuildLocalWindowItem(SessionID window_id,
+                            std::unique_ptr<ui::SimpleMenuModel> window_model,
                             int num_tabs,
                             int curr_model_index);
 
@@ -114,6 +117,7 @@
   // and add it to the menumodel at |curr_model_index|.
   void BuildLocalGroupItem(SessionID session_id,
                            tab_groups::TabGroupVisualData visual_data,
+                           std::unique_ptr<ui::SimpleMenuModel> group_model,
                            int num_tabs,
                            int curr_model_index);
 
@@ -121,6 +125,19 @@
   void BuildOtherDevicesTabItem(const std::string& session_tag,
                                 const sessions::SessionTab& tab);
 
+  // Create a submenu model representing the tabs within a window.
+  std::unique_ptr<ui::SimpleMenuModel> CreateWindowSubMenuModel(
+      const sessions::TabRestoreService::Window& window);
+
+  // Create a submenu model representing the tabs within a tab group.
+  std::unique_ptr<ui::SimpleMenuModel> CreateGroupSubMenuModel(
+      const sessions::TabRestoreService::Group& group);
+
+  // Return the command id of the given id's parent submenu, if it has one that
+  // is created by this menu model. Otherwise, return -1. This will be the case
+  // for all items whose parent is the RecentTabsSubMenuModel itself.
+  int GetParentCommandId(int command_id) const;
+
   // Add the favicon for the device section header.
   void AddDeviceFavicon(int index_in_menu,
                         sync_pb::SyncEnums::DeviceType device_type);
@@ -187,6 +204,12 @@
   // |local_group_items_| and used to create the specified group.
   GroupItems local_group_items_;
 
+  // SubMenu items for submenu entry points representing local recently
+  // closed groups and windows.  The |command_id| for these is set to
+  // |kFirstLocalSubMenuCommandId| plus the index into the vector. These are
+  // not executable.
+  SubMenuItems local_sub_menu_items_;
+
   // Index of the last local entry (recently closed tab or window or group) in
   // the menumodel.
   int last_local_model_index_ = kHistorySeparatorIndex;
diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
index 1a306e3..55816dfb4 100644
--- a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
@@ -303,7 +303,9 @@
   EXPECT_TRUE(model.IsEnabledAt(1));
   EXPECT_FALSE(model.IsEnabledAt(2));
   EXPECT_TRUE(model.IsEnabledAt(3));
+  EXPECT_EQ(ui::MenuModel::TYPE_SUBMENU, model.GetTypeAt(3));
   EXPECT_TRUE(model.IsEnabledAt(4));
+  EXPECT_EQ(ui::MenuModel::TYPE_SUBMENU, model.GetTypeAt(4));
   model.ActivatedAt(3);
   model.ActivatedAt(4);
   EXPECT_FALSE(model.IsEnabledAt(6));
@@ -420,6 +422,7 @@
   EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model.GetTypeAt(1));
   EXPECT_FALSE(model.IsEnabledAt(2));
   EXPECT_TRUE(model.IsEnabledAt(3));
+  EXPECT_EQ(ui::MenuModel::TYPE_SUBMENU, model.GetTypeAt(3));
   EXPECT_TRUE(model.IsEnabledAt(4));
   EXPECT_TRUE(model.IsEnabledAt(5));
   model.ActivatedAt(3);
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.cc b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.cc
index 5dabb0a..54d97ad 100644
--- a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.cc
+++ b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.cc
@@ -8,8 +8,8 @@
 #include <string>
 
 #include "base/bind.h"
-#include "chrome/browser/accessibility/caption_controller.h"
-#include "chrome/browser/accessibility/caption_controller_factory.h"
+#include "chrome/browser/accessibility/live_caption_controller.h"
+#include "chrome/browser/accessibility/live_caption_controller_factory.h"
 #include "chrome/browser/accessibility/live_caption_speech_recognition_host.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
diff --git a/chrome/browser/ui/views/autofill/address_editor_view.cc b/chrome/browser/ui/views/autofill/address_editor_view.cc
index 599757c..e421535 100644
--- a/chrome/browser/ui/views/autofill/address_editor_view.cc
+++ b/chrome/browser/ui/views/autofill/address_editor_view.cc
@@ -50,63 +50,44 @@
 
 void AddressEditorView::CreateEditorView() {
   text_fields_.clear();
-  constexpr int kRowHorizontalInsets = 16;
 
-  // The editor view is padded horizontally.
-  SetBorder(views::CreateEmptyBorder(0, kRowHorizontalInsets, 0,
-                                     kRowHorizontalInsets));
-
-  // All views have fixed size except the Field which stretches. The fixed
-  // padding at the end is computed so that Field views have a minimum of
-  // 176/272dp (short/long fields) as per spec.
-  // ______________________________________________________
-  // |Label | 16dp pad | Field (flex) |  Computed Padding |
-  // |______|__________|______________|___________________|
+  // Field views have a width of 196/260dp (short/long fields) as per spec.
+  // __________________________________
+  // |Label | 16dp pad | Field (flex) |
+  // |______|__________|______________|
   constexpr int kLabelWidth = 140;
-  constexpr int kDialogMinWidth = 512;
   // This is the horizontal padding between the label and the field.
   constexpr int kLabelInputFieldHorizontalPadding = 16;
-  constexpr int kShortFieldMinimumWidth = 176;
-  constexpr int kLongFieldMinimumWidth = 272;
+  constexpr int kShortFieldWidth = 196;
+  constexpr int kLongFieldWidth = 260;
 
   using ColumnSize = views::GridLayout::ColumnSize;
   views::GridLayout* editor_layout =
       SetLayoutManager(std::make_unique<views::GridLayout>());
   // Column set for short fields.
-  views::ColumnSet* columns_short = editor_layout->AddColumnSet(0);
+  views::ColumnSet* columns_short = editor_layout->AddColumnSet(
+      /*id=*/static_cast<int>(EditorField::LengthHint::HINT_SHORT));
   columns_short->AddColumn(
       views::GridLayout::LEADING, views::GridLayout::CENTER,
       views::GridLayout::kFixedSize, ColumnSize::kFixed, kLabelWidth, 0);
   columns_short->AddPaddingColumn(views::GridLayout::kFixedSize,
                                   kLabelInputFieldHorizontalPadding);
-  // The field view column stretches.
   columns_short->AddColumn(views::GridLayout::LEADING,
-                           views::GridLayout::CENTER, 1.0,
-                           ColumnSize::kUsePreferred, 0, 0);
-  // The padding at the end is fixed, computed to make sure the short field
-  // maintains its minimum width.
-  int short_padding = kDialogMinWidth - kShortFieldMinimumWidth - kLabelWidth -
-                      (2 * kRowHorizontalInsets) -
-                      kLabelInputFieldHorizontalPadding;
-  columns_short->AddPaddingColumn(views::GridLayout::kFixedSize, short_padding);
+                           views::GridLayout::CENTER,
+                           views::GridLayout::kFixedSize, ColumnSize::kFixed,
+                           kShortFieldWidth, /*min_width=*/0);
 
   // Column set for long fields.
-  views::ColumnSet* columns_long = editor_layout->AddColumnSet(1);
+  views::ColumnSet* columns_long = editor_layout->AddColumnSet(
+      /*id=*/static_cast<int>(EditorField::LengthHint::HINT_LONG));
   columns_long->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
                           views::GridLayout::kFixedSize, ColumnSize::kFixed,
                           kLabelWidth, 0);
   columns_long->AddPaddingColumn(views::GridLayout::kFixedSize,
                                  kLabelInputFieldHorizontalPadding);
-  // The field view column stretches.
   columns_long->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
-                          1.0, ColumnSize::kUsePreferred, 0, 0);
-
-  // The padding at the end is fixed, computed to make sure the long field
-  // maintains its minimum width.
-  int long_padding = kDialogMinWidth - kLongFieldMinimumWidth - kLabelWidth -
-                     (2 * kRowHorizontalInsets) -
-                     kLabelInputFieldHorizontalPadding;
-  columns_long->AddPaddingColumn(views::GridLayout::kFixedSize, long_padding);
+                          views::GridLayout::kFixedSize, ColumnSize::kFixed,
+                          kLongFieldWidth, /*min_width=*/0);
 
   for (const auto& field : controller_->editor_fields()) {
     CreateInputField(editor_layout, field);
@@ -119,13 +100,12 @@
 // +----------------------------------------------------------+
 views::View* AddressEditorView::CreateInputField(views::GridLayout* layout,
                                                  const EditorField& field) {
-  int column_set =
-      field.length_hint == EditorField::LengthHint::HINT_SHORT ? 0 : 1;
-
   // This is the top padding for every row.
   constexpr int kInputRowSpacing = 6;
-  layout->StartRowWithPadding(views::GridLayout::kFixedSize, column_set,
-                              views::GridLayout::kFixedSize, kInputRowSpacing);
+  layout->StartRowWithPadding(
+      views::GridLayout::kFixedSize,
+      /*column_set_id=*/static_cast<int>(field.length_hint),
+      views::GridLayout::kFixedSize, kInputRowSpacing);
 
   std::unique_ptr<views::Label> label =
       std::make_unique<views::Label>(field.label);
diff --git a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
index cb45c14..07afb24e 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
+++ b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
@@ -241,7 +241,7 @@
         IDS_NOTIFICATIONS_QUIET_PERMISSION_NEW_REQUEST_PROMO;
     bubble_params.anchor_view = this;
     bubble_params.arrow = views::BubbleBorder::TOP_RIGHT;
-    bubble_params.allow_focus = true;
+    bubble_params.focus_on_create = true;
     bubble_params.persist_on_blur = false;
     bubble_params.preferred_width = promo_width;
 
diff --git a/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.cc b/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.cc
index 805f53b..a06b1a58 100644
--- a/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.cc
+++ b/chrome/browser/ui/views/passwords/password_save_update_with_account_store_view.cc
@@ -673,9 +673,9 @@
   FeaturePromoBubbleParams bubble_params;
   bubble_params.anchor_view = destination_dropdown_;
   bubble_params.arrow = views::BubbleBorder::RIGHT_CENTER;
-  bubble_params.preferred_width = kAccountStoragePromoWidth;
-  bubble_params.allow_focus = true;
+  bubble_params.focus_on_create = true;
   bubble_params.persist_on_blur = false;
+  bubble_params.preferred_width = kAccountStoragePromoWidth;
   bubble_params.timeout_default = GetRegularIPHTimeout();
   bubble_params.timeout_short = GetShortIPHTimeout();
 
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index cf21a73..1ae42fa 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -2539,6 +2539,9 @@
   ASSERT_TRUE(PressInput(tab_1_center));
   ASSERT_TRUE(DragInputTo(tab_1_center +
                           gfx::Vector2d(TabStyle::GetStandardWidth(), 0)));
+  BrowserView::GetBrowserViewForBrowser(browser())
+      ->GetWidget()
+      ->LayoutRootViewIfNecessary();
   EXPECT_EQ(tab_strip_width + TabStyle::GetStandardWidth(), tab_strip->width());
   ASSERT_TRUE(ReleaseInput());
 }
diff --git a/chrome/browser/ui/views/tabs/tab_icon.cc b/chrome/browser/ui/views/tabs/tab_icon.cc
index 7317e662..55545e1 100644
--- a/chrome/browser/ui/views/tabs/tab_icon.cc
+++ b/chrome/browser/ui/views/tabs/tab_icon.cc
@@ -41,16 +41,6 @@
 constexpr int kAttentionIndicatorRadius = 3;
 constexpr int kLoadingAnimationStrokeWidthDp = 2;
 
-// Returns whether the favicon for the given URL should be colored according to
-// the browser theme.
-bool ShouldThemifyFaviconForUrl(const GURL& url) {
-  return url.SchemeIs(content::kChromeUIScheme) &&
-         url.host_piece() != chrome::kChromeUIAppLauncherPageHost &&
-         url.host_piece() != chrome::kChromeUIHelpHost &&
-         url.host_piece() != chrome::kChromeUIVersionHost &&
-         url.host_piece() != chrome::kChromeUINetExportHost;
-}
-
 bool NetworkStateIsAnimated(TabNetworkState network_state) {
   return network_state != TabNetworkState::kNone &&
          network_state != TabNetworkState::kError;
@@ -109,7 +99,7 @@
   const bool was_showing_load = GetShowingLoadingAnimation();
 
   inhibit_loading_animation_ = data.should_hide_throbber;
-  SetIcon(data.visible_url, data.favicon);
+  SetIcon(data.favicon, data.should_themify_favicon);
   SetNetworkState(data.network_state);
   SetCrashed(data.IsCrashed());
   has_tab_renderer_data_ = true;
@@ -361,7 +351,7 @@
                                    favicon::GetDefaultFavicon().AsImageSkia());
 }
 
-void TabIcon::SetIcon(const GURL& url, const gfx::ImageSkia& icon) {
+void TabIcon::SetIcon(const gfx::ImageSkia& icon, bool should_themify_favicon) {
   // Detect when updating to the same icon. This avoids re-theming and
   // re-painting.
   if (favicon_.BackedBySameObjectAs(icon))
@@ -369,7 +359,7 @@
 
   favicon_ = icon;
 
-  if (!GetNonDefaultFavicon() || ShouldThemifyFaviconForUrl(url)) {
+  if (!GetNonDefaultFavicon() || should_themify_favicon) {
     themed_favicon_ = ThemeImage(icon);
   } else {
     themed_favicon_ = gfx::ImageSkia();
diff --git a/chrome/browser/ui/views/tabs/tab_icon.h b/chrome/browser/ui/views/tabs/tab_icon.h
index 76a6c5fb..f8f4a3e 100644
--- a/chrome/browser/ui/views/tabs/tab_icon.h
+++ b/chrome/browser/ui/views/tabs/tab_icon.h
@@ -18,7 +18,6 @@
 class TickClock;
 }
 
-class GURL;
 struct TabRendererData;
 
 // View that displays the favicon, sad tab, throbber, and attention indicator
@@ -99,8 +98,8 @@
                          const gfx::Rect& bounds);
   bool GetNonDefaultFavicon() const;
 
-  // Sets the icon. Depending on the URL the icon may be automatically themed.
-  void SetIcon(const GURL& url, const gfx::ImageSkia& favicon);
+  // Sets the icon.
+  void SetIcon(const gfx::ImageSkia& icon, bool should_themify_favicon);
 
   // For certain types of tabs the loading animation is not desired so the
   // caller can set inhibit_loading_animation to true. When false, the loading
diff --git a/chrome/browser/ui/views/user_education/feature_promo_bubble_params.h b/chrome/browser/ui/views/user_education/feature_promo_bubble_params.h
index b4dda25..7841a1ca 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_bubble_params.h
+++ b/chrome/browser/ui/views/user_education/feature_promo_bubble_params.h
@@ -66,15 +66,13 @@
   // possible.
   absl::optional<int> preferred_width;
 
-  // Determines if this bubble can be focused. If true, it will get
-  // focused on creation.
-  bool allow_focus = false;
+  // Determines if the bubble will get focused on creation.
+  bool focus_on_create = false;
 
   // Determines if this bubble will be dismissed when it loses focus.
-  // Only meaningful when |allow_focus| is true. When |allow_focus|
-  // is false, the bubble will always persist because it will never
-  // get blurred.
-  bool persist_on_blur = false;
+  // Only meaningful when |focus_on_create| is true. If it's false then it
+  // starts out blurred.
+  bool persist_on_blur = true;
 
   // Determines if this IPH can be snoozed and reactivated later.
   // If true, |allow_focus| must be true for keyboard accessibility.
diff --git a/chrome/browser/ui/views/user_education/feature_promo_bubble_view.cc b/chrome/browser/ui/views/user_education/feature_promo_bubble_view.cc
index 489f0d9..a48a2893 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_bubble_view.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_bubble_view.cc
@@ -182,15 +182,10 @@
     : BubbleDialogDelegateView(params.anchor_view,
                                params.arrow,
                                views::BubbleBorder::STANDARD_SHADOW),
-      focusable_(params.focusable),
-      persist_on_blur_(params.persist_on_blur),
       preferred_width_(params.preferred_width) {
   DCHECK(params.anchor_view);
-  DCHECK(params.buttons.empty() || params.focusable)
-      << "A snoozable bubble must be focusable to allow keyboard "
-         "accessibility.";
-  DCHECK(!params.persist_on_blur || params.focusable)
-      << "A bubble that persists on blur must be focusable.";
+  DCHECK(params.persist_on_blur || params.focus_on_create)
+      << "A bubble that closes on blur must be initially focused.";
   UseCompactMargins();
 
   // Bubble will not auto-dismiss if there's buttons.
@@ -321,10 +316,7 @@
     }
   }
 
-  if (!focusable_)
-    SetCanActivate(false);
-
-  set_close_on_deactivate(!persist_on_blur_);
+  set_close_on_deactivate(!params.persist_on_blur);
 
   set_margins(gfx::Insets());
   set_title_margins(gfx::Insets());
@@ -338,7 +330,11 @@
       ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
           views::Emphasis::kHigh));
 
-  widget->Show();
+  if (params.focus_on_create)
+    widget->Show();
+  else
+    widget->ShowInactive();
+
   if (feature_promo_bubble_timeout_)
     feature_promo_bubble_timeout_->OnBubbleShown(this);
 }
diff --git a/chrome/browser/ui/views/user_education/feature_promo_bubble_view.h b/chrome/browser/ui/views/user_education/feature_promo_bubble_view.h
index e5234c9..f2d5f7e4 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_bubble_view.h
+++ b/chrome/browser/ui/views/user_education/feature_promo_bubble_view.h
@@ -66,8 +66,8 @@
 
     absl::optional<int> preferred_width;
 
-    bool focusable = false;
-    bool persist_on_blur = false;
+    bool focus_on_create = false;
+    bool persist_on_blur = true;
 
     // Determines how progress indicators for tutorials will be rendered. If not
     // provided, no progress indicator will be visible.
@@ -105,16 +105,6 @@
   }
   gfx::Size CalculatePreferredSize() const override;
 
-  // Determines if this bubble can be focused. If true, it will get
-  // focus on creation.
-  bool focusable_ = false;
-
-  // Determines if this bubble will be dismissed when it loses focus.
-  // Only meaningful when |focusable_| is true. When |allow_focus|
-  // is false, the bubble will always persist because it will never
-  // get blurred.
-  bool persist_on_blur_ = false;
-
   // If the bubble has buttons, it must be focusable.
   std::vector<views::MdTextButton*> buttons_;
 
diff --git a/chrome/browser/ui/views/user_education/feature_promo_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/user_education/feature_promo_bubble_view_interactive_uitest.cc
new file mode 100644
index 0000000..44add555
--- /dev/null
+++ b/chrome/browser/ui/views/user_education/feature_promo_bubble_view_interactive_uitest.cc
@@ -0,0 +1,68 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/user_education/feature_promo_bubble_view.h"
+
+#include "chrome/browser/ui/view_ids.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/toolbar/browser_app_menu_button.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "content/public/test/browser_test.h"
+#include "ui/views/focus/focus_manager.h"
+#include "ui/views/test/widget_test.h"
+
+class FeaturePromoBubbleViewInteractiveTest : public InProcessBrowserTest {
+ public:
+  FeaturePromoBubbleViewInteractiveTest() = default;
+  ~FeaturePromoBubbleViewInteractiveTest() override = default;
+
+ protected:
+  FeaturePromoBubbleView::CreateParams GetBubbleParams() {
+    FeaturePromoBubbleView::CreateParams params;
+    params.body_text = u"To X, do Y";
+    params.anchor_view = BrowserView::GetBrowserViewForBrowser(browser())
+                             ->toolbar()
+                             ->app_menu_button();
+    params.arrow = views::BubbleBorder::TOP_RIGHT;
+    params.timeout_default = absl::nullopt;
+    params.timeout_short = absl::nullopt;
+    return params;
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoBubbleViewInteractiveTest,
+                       WidgetNotActivatedByDefault) {
+  auto params = GetBubbleParams();
+  params.focus_on_create = false;
+
+  auto* const browser_view = BrowserView::GetBrowserViewForBrowser(browser());
+  auto* const focus_manager = browser_view->GetWidget()->GetFocusManager();
+  EXPECT_TRUE(browser_view->GetWidget()->IsActive());
+
+  browser_view->FocusToolbar();
+  views::View* const initial_focused_view = focus_manager->GetFocusedView();
+  EXPECT_NE(nullptr, initial_focused_view);
+
+  auto* const bubble = FeaturePromoBubbleView::Create(std::move(params));
+  views::test::WidgetVisibleWaiter(bubble->GetWidget()).Wait();
+
+  EXPECT_TRUE(browser_view->GetWidget()->IsActive());
+  EXPECT_FALSE(bubble->GetWidget()->IsActive());
+}
+
+IN_PROC_BROWSER_TEST_F(FeaturePromoBubbleViewInteractiveTest,
+                       WidgetActivatedWhenRequested) {
+  auto params = GetBubbleParams();
+  params.focus_on_create = true;
+
+  auto* const browser_view = BrowserView::GetBrowserViewForBrowser(browser());
+  EXPECT_TRUE(browser_view->GetWidget()->IsActive());
+
+  auto* const bubble = FeaturePromoBubbleView::Create(std::move(params));
+  views::test::WidgetVisibleWaiter(bubble->GetWidget()).Wait();
+
+  EXPECT_TRUE(bubble->GetWidget()->IsActive());
+}
diff --git a/chrome/browser/ui/views/user_education/feature_promo_bubble_view_unittest.cc b/chrome/browser/ui/views/user_education/feature_promo_bubble_view_unittest.cc
index 5e73fd8..ae61e43 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_bubble_view_unittest.cc
@@ -39,7 +39,7 @@
     params.arrow = views::BubbleBorder::TOP_RIGHT;
 
     if (button_callback) {
-      params.focusable = true;
+      params.focus_on_create = true;
       params.persist_on_blur = true;
 
       FeaturePromoBubbleView::ButtonParams button_params;
diff --git a/chrome/browser/ui/views/user_education/feature_promo_controller_views.cc b/chrome/browser/ui/views/user_education/feature_promo_controller_views.cc
index 3f9edac..6eddedab 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_controller_views.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_controller_views.cc
@@ -275,7 +275,7 @@
         l10n_util::GetStringUTF16(*params.screenreader_string_specifier);
   }
 
-  create_params.focusable = params.allow_focus;
+  create_params.focus_on_create = params.focus_on_create;
   create_params.persist_on_blur = params.persist_on_blur;
 
   create_params.arrow = params.arrow;
diff --git a/chrome/browser/ui/views/user_education/feature_promo_registry.cc b/chrome/browser/ui/views/user_education/feature_promo_registry.cc
index 6c315d2..299f3c7a 100644
--- a/chrome/browser/ui/views/user_education/feature_promo_registry.cc
+++ b/chrome/browser/ui/views/user_education/feature_promo_registry.cc
@@ -198,8 +198,7 @@
     // Turn on IPH Snooze for Tab Group.
     if (base::FeatureList::IsEnabled(
             feature_engagement::kIPHDesktopSnoozeFeature)) {
-      params.allow_focus = true;
-      params.persist_on_blur = true;
+      params.focus_on_create = true;
       params.allow_snooze = true;
     }
 
@@ -227,9 +226,6 @@
         IDS_PROFILE_SWITCH_PROMO_SCREENREADER;
     params.feature_command_id = IDC_SHOW_AVATAR_MENU;
     params.arrow = views::BubbleBorder::Arrow::TOP_RIGHT;
-    // Focusable for accessibility (https://crbug.com/1198049).
-    params.allow_focus = true;
-    params.persist_on_blur = true;
 
     RegisterFeature(feature_engagement::kIPHProfileSwitchFeature, params,
                     base::BindRepeating(GetAvatarToolbarButton));
@@ -255,8 +251,7 @@
     // Turn on IPH Snooze for Read Later entry point.
     if (base::FeatureList::IsEnabled(
             feature_engagement::kIPHDesktopSnoozeFeature)) {
-      params.allow_focus = true;
-      params.persist_on_blur = true;
+      params.focus_on_create = true;
       params.allow_snooze = true;
     }
 
diff --git a/chrome/browser/ui/views/user_education/tutorial_dialog_browsertest.cc b/chrome/browser/ui/views/user_education/tutorial_dialog_browsertest.cc
index e74230b..0de3f41 100644
--- a/chrome/browser/ui/views/user_education/tutorial_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/user_education/tutorial_dialog_browsertest.cc
@@ -22,7 +22,6 @@
     params.anchor_view = browser_view->toolbar()->app_menu_button();
     params.arrow = views::BubbleBorder::TOP_RIGHT;
     params.body_text = u"Hello world, I am a tutorial";
-    params.focusable = true;
     params.persist_on_blur = true;
     params.tutorial_progress_current = 3;
     params.tutorial_progress_max = 5;
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc
index 8505595..8c275f2 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc
@@ -411,10 +411,20 @@
 
   if (action_base == "add_policy_app_internal_tabbed") {
     AddPolicyAppInternal(action_param,
-                         base::Value(kDefaultLaunchContainerTabValue));
+                         base::Value(kDefaultLaunchContainerTabValue),
+                         /*create_shortcut=*/true);
+  } else if (action_base == "add_policy_app_internal_tabbed_no_shortcut") {
+    AddPolicyAppInternal(action_param,
+                         base::Value(kDefaultLaunchContainerTabValue),
+                         /*create_shortcut=*/false);
   } else if (action_base == "add_policy_app_internal_windowed") {
     AddPolicyAppInternal(action_param,
-                         base::Value(kDefaultLaunchContainerWindowValue));
+                         base::Value(kDefaultLaunchContainerWindowValue),
+                         /*create_shortcut=*/true);
+  } else if (action_base == "add_policy_app_internal_windowed_no_shortcut") {
+    AddPolicyAppInternal(action_param,
+                         base::Value(kDefaultLaunchContainerWindowValue),
+                         /*create_shortcut=*/false);
   } else if (action_base == "close_pwa") {
     ClosePWA();
   } else if (action_base == "install_create_shortcut_tabbed") {
@@ -510,7 +520,8 @@
 // Automated Testing Actions
 void WebAppIntegrationBrowserTestBase::AddPolicyAppInternal(
     const std::string& action_param,
-    base::Value default_launch_container) {
+    base::Value default_launch_container,
+    const bool create_shortcut) {
   GURL url = GetInstallableAppURL(action_param);
   auto* web_app_registrar =
       WebAppProvider::Get(profile())->registrar().AsWebAppRegistrar();
@@ -531,6 +542,7 @@
     item.SetKey(kUrlKey, base::Value(url.spec()));
     item.SetKey(kDefaultLaunchContainerKey,
                 std::move(default_launch_container));
+    item.SetKey(kCreateDesktopShortcutKey, base::Value(create_shortcut));
     ListPrefUpdate update(profile()->GetPrefs(),
                           prefs::kWebAppInstallForceList);
     update->Append(item.Clone());
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h
index c938a4b..0b9e383 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.h
@@ -150,7 +150,8 @@
 
   // Automated Testing Actions
   void AddPolicyAppInternal(const std::string& action_param,
-                            base::Value default_launch_container);
+                            base::Value default_launch_container,
+                            const bool create_shortcut);
   void ClosePWA();
   void InstallCreateShortcut(bool open_in_window);
   void InstallLocally();
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler.cc b/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
index 9c90b090..99f5ec36 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
@@ -42,7 +42,8 @@
 };
 
 constexpr char const* kAppIdsWithHiddenPinToShelf[] = {
-  extension_misc::kChromeAppId,
+    extension_misc::kChromeAppId,
+    extension_misc::kLacrosAppId,
 };
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
index 2073bbf..d618c6d 100644
--- a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.cc
@@ -57,7 +57,8 @@
      IDS_CELLULAR_SETUP_ESIM_FINAL_PAGE_ERROR_MESSAGE},
     {"eSimProfileDetectMessage",
      IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_MESSAGE},
-    {"eSimConnectionWarning", IDS_CELLULAR_SETUP_ESIM_CONNECTION_WARNING},
+    {"eSimProfileDetectDuringActiveCellularConnectionMessage",
+     IDS_CELLULAR_SETUP_ESIM_PROFILE_DETECT_DURING_ACTIVE_CELLULAR_CONNECTION_MESSAGE},
     {"scanQRCode", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE},
     {"scanQRCodeEnterActivationCode",
      IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_ENTER_ACTIVATION_CODE},
diff --git a/chrome/browser/web_applications/components/web_app_helpers.cc b/chrome/browser/web_applications/components/web_app_helpers.cc
index a39db1b..fcbfc9f 100644
--- a/chrome/browser/web_applications/components/web_app_helpers.cc
+++ b/chrome/browser/web_applications/components/web_app_helpers.cc
@@ -69,8 +69,10 @@
   // <start_url_origin>/<manifest_id>.
   // Note: start_url.GetOrigin().spec() returns the origin ending with slash.
   if (manifest_id.has_value()) {
-    return crx_file::id_util::GenerateId(crypto::SHA256HashString(
-        start_url.GetOrigin().spec() + manifest_id.value()));
+    GURL app_id(start_url.GetOrigin().spec() + manifest_id.value());
+    DCHECK(app_id.is_valid());
+    return crx_file::id_util::GenerateId(
+        crypto::SHA256HashString(app_id.spec()));
   }
   return GenerateAppIdFromURL(start_url);
 }
diff --git a/chrome/browser/web_applications/components/web_app_helpers_unittest.cc b/chrome/browser/web_applications/components/web_app_helpers_unittest.cc
index 1b4194f..f6a871dd7 100644
--- a/chrome/browser/web_applications/components/web_app_helpers_unittest.cc
+++ b/chrome/browser/web_applications/components/web_app_helpers_unittest.cc
@@ -72,4 +72,18 @@
       GURL("filesystem:http://example.com/path/file.html")));
 }
 
+TEST(WebAppHelpers, ManifestIdEncoding) {
+  GURL start_url("https://example.com/abc");
+  // ASCII character.
+  EXPECT_EQ(GenerateAppId("j", start_url), GenerateAppId("%6a", start_url));
+  EXPECT_EQ(GenerateAppId("%6Ax", start_url), GenerateAppId("%6ax", start_url));
+
+  // Special characters.
+  EXPECT_EQ(GenerateAppId("a😀b", start_url),
+            GenerateAppId("a%F0%9F%98%80b", start_url));
+  EXPECT_EQ(GenerateAppId("a b", start_url), GenerateAppId("a%20b", start_url));
+
+  // "/"" is excluded from encoding according to url spec.
+  EXPECT_NE(GenerateAppId("a/b", start_url), GenerateAppId("a%2Fb", start_url));
+}
 }  // namespace web_app
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index b3424ee..228bb12 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-master-1621447189-bda12fafea78e5ddeafef2f6099b794eeb4ffb44.profdata
+chrome-win32-master-1621457572-e60d595e49521ac06da03b86bcaef384d8be53cc.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 69715c0..a4d1a92d 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-master-1621447189-0acc1aeb3b834bdede092c7b4e776bb25a5fa9c0.profdata
+chrome-win64-master-1621457572-764acf1b3fb28a91d1007d86e7f3479c23440a11.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index bbdfc370..a10f7f9 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -20,7 +20,7 @@
 // If enabled device status collector will add the type of session (Affiliated
 // User, Kiosks, Managed Guest Sessions) to the device status report.
 const base::Feature kActivityReportingSessionType{
-    "ActivityReportingSessionType", base::FEATURE_DISABLED_BY_DEFAULT};
+    "ActivityReportingSessionType", base::FEATURE_ENABLED_BY_DEFAULT};
 #endif  // defined(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -748,14 +748,8 @@
     "PredictivePrefetchingAllowedOnAllConnectionTypes",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
-const base::Feature kPrefixWebAppWindowsWithAppName {
-  "PrefixWebAppWindowsWithAppName",
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-      base::FEATURE_ENABLED_BY_DEFAULT
-#else
-      base::FEATURE_DISABLED_BY_DEFAULT
-#endif
-};
+const base::Feature kPrefixWebAppWindowsWithAppName{
+    "PrefixWebAppWindowsWithAppName", base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Allows Chrome to do preconnect when prerender fails.
 const base::Feature kPrerenderFallbackToPreconnect{
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index 6f36f6a..e503946 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -591,10 +591,6 @@
       "chrome://settings/*"
     ]
   }],
-  "launcherSearchProvider": {
-    "dependencies": ["permission:launcherSearchProvider"],
-    "contexts": ["blessed_extension"]
-  },
   "login": {
     "dependencies": ["permission:login"],
     "contexts": ["blessed_extension"],
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 0853789..5701a97 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -430,14 +430,6 @@
     "extension_types": ["extension", "platform_app"],
     "location": "component"
   },
-  "launcherSearchProvider": {
-    "channel": "stable",
-    "extension_types": ["extension", "platform_app"],
-    "platforms": ["chromeos"],
-    "allowlist": [
-      "A948368FC53BE437A55FEB414106E207925482F5"  // File Manager
-    ]
-  },
   "lockWindowFullscreenPrivate": {
     "channel": "stable",
     "component_extensions_auto_granted": false,
diff --git a/chrome/common/extensions/api/api_sources.gni b/chrome/common/extensions/api/api_sources.gni
index ec83475..27fc40b 100644
--- a/chrome/common/extensions/api/api_sources.gni
+++ b/chrome/common/extensions/api/api_sources.gni
@@ -100,7 +100,6 @@
     "file_system_provider_internal.idl",
     "input_ime.json",
     "input_method_private.json",
-    "launcher_search_provider.idl",
     "login.idl",
     "login_screen_storage.idl",
     "login_screen_ui.idl",
diff --git a/chrome/common/extensions/api/launcher_search_provider.idl b/chrome/common/extensions/api/launcher_search_provider.idl
deleted file mode 100644
index 2521a83..0000000
--- a/chrome/common/extensions/api/launcher_search_provider.idl
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// An API to listen queries of the Chrome Launcher and provide search results
-// to it.
-[platforms=("chromeos"),
- implemented_in="chrome/browser/chromeos/extensions/launcher_search_provider.h",
- nodoc]
-namespace launcherSearchProvider {
-  dictionary SearchResult {
-    DOMString itemId;
-    DOMString title;
-    // If iconType is not provided, a generic icon is used automatically.
-    DOMString? iconType;
-    // Relevance ranges from 0 to 4. 0 is the lowest relevance, 4 is highest.
-    long relevance;
-  };
-
-  interface Functions {
-    // Sets search result of this extension for the query. Setting a new search
-    // results overwrites any previous results of this extension. If queryId is
-    // invalid, the results are discarded. Since the space is limited, it is not
-    // guranteed that all provided results are shown to a user. The search
-    // results will be sorted by relevance, with ties broken by the order of the
-    // results in this list (highest priority first).
-    static void setSearchResults(long queryId, SearchResult[] results);
-  };
-
-  interface Events {
-    // Called when a user typed a query. maxResult is the maximum number of
-    // results the extension should provide.
-    static void onQueryStarted(long queryId,
-                               DOMString query,
-                               long maxResult);
-
-    // Called when query of |queryId| is ended. After this call,
-    // setSearchResults no longer accept the results for queryId.
-    static void onQueryEnded(long queryId);
-
-    // Called when a user clicks a search result which is provided by
-    // setSearchResults.
-    static void onOpenResult(DOMString itemId);
-  };
-};
diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc
index 2df23190..815285a1 100644
--- a/chrome/common/extensions/permissions/chrome_api_permissions.cc
+++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc
@@ -225,7 +225,6 @@
     // Platform-app permissions.
     {APIPermissionID::kFileSystemProvider, "fileSystemProvider",
      APIPermissionInfo::kFlagDoesNotRequireManagedSessionFullLoginWarning},
-    {APIPermissionID::kLauncherSearchProvider, "launcherSearchProvider"},
 
     // Settings override permissions.
     {APIPermissionID::kHomepage, "homepage",
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index 99e347da..46f95f0 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -869,7 +869,6 @@
   skip.insert(APIPermissionID::kSocket);
   skip.insert(APIPermissionID::kUsb);
   skip.insert(APIPermissionID::kVirtualKeyboard);
-  skip.insert(APIPermissionID::kLauncherSearchProvider);
 
   // The lock screen apps are set by user through settings, no need to warn at
   // installation time.
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
index 68d5f4c..d38db44 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
@@ -104,7 +104,7 @@
     // Add an empty visual target to ensure visual detection runs.
     model.mutable_vision_model()->add_targets();
 
-    scorer_.reset(Scorer::Create(model.SerializeAsString()));
+    scorer_.reset(Scorer::Create(model.SerializeAsString(), base::File()));
     ASSERT_TRUE(scorer_.get());
   }
 
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
index 12ef8e1..24b5d75 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "chrome/test/base/chrome_unit_test_suite.h"
@@ -248,6 +249,26 @@
   EXPECT_CALL(*classifier_, CancelPendingClassification());
 }
 
+TEST_F(PhishingClassifierDelegateTest, HasVisualTfLiteModel) {
+  ASSERT_FALSE(classifier_->is_ready());
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  base::FilePath file_path =
+      temp_dir.GetPath().AppendASCII("visual_model.tflite");
+  base::File file(file_path, base::File::FLAG_OPEN_ALWAYS |
+                                 base::File::FLAG_READ |
+                                 base::File::FLAG_WRITE);
+  std::string file_contents = "visual model file";
+  file.WriteAtCurrentPos(file_contents.data(), file_contents.size());
+
+  delegate_->SetPhishingModel("", std::move(file));
+  ASSERT_TRUE(classifier_->is_ready());
+
+  // The delegate will cancel pending classification on destruction.
+  EXPECT_CALL(*classifier_, CancelPendingClassification());
+}
+
 TEST_F(PhishingClassifierDelegateTest, NoScorer) {
   // For this test, we'll create the delegate with no scorer available yet.
   ASSERT_FALSE(classifier_->is_ready());
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 72c9ce5..a582e12 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1083,9 +1083,9 @@
       "../../apps/load_and_launch_browsertest.cc",
       "../browser/accessibility/accessibility_labels_service_browsertest.cc",
       "../browser/accessibility/browser_accessibility_state_browsertest.cc",
-      "../browser/accessibility/caption_controller_browsertest.cc",
       "../browser/accessibility/image_annotation_browsertest.cc",
       "../browser/accessibility/interstitial_accessibility_browsertest.cc",
+      "../browser/accessibility/live_caption_controller_browsertest.cc",
       "../browser/accessibility/live_caption_speech_recognition_host_browsertest.cc",
       "../browser/apps/guest_view/app_view_browsertest.cc",
       "../browser/apps/guest_view/web_view_browsertest.cc",
@@ -7032,6 +7032,7 @@
         "../browser/ui/views/test/view_event_test_base.h",
         "../browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc",
         "../browser/ui/views/translate/translate_bubble_test_utils_views.cc",
+        "../browser/ui/views/user_education/feature_promo_bubble_view_interactive_uitest.cc",
         "../browser/ui/views/user_education/feature_promo_snooze_interactive_uitest.cc",
       ]
       deps += [
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
index fa2fd8c..4e6e03f 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.test.util;
 
 import android.content.Context;
-import android.util.Pair;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 
@@ -25,14 +24,10 @@
 import org.chromium.chrome.browser.omnibox.suggestions.header.HeaderView;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.omnibox.AutocompleteResult;
-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.ui.modelutil.MVCListAdapter.ModelList;
 
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
 import java.util.concurrent.Callable;
 
 /**
@@ -42,76 +37,12 @@
     private OmniboxTestUtils() {}
 
     /**
-     * AutocompleteController instance that allows for easy testing.
-     */
-    public static class TestAutocompleteController extends AutocompleteController {
-        private final Map<String, Pair<String, AutocompleteResult>> mAutocompleteResults;
-
-        /**
-         * Create new Autocomplete controller.
-         * @param listener
-         */
-        public TestAutocompleteController(OnSuggestionsReceivedListener listener) {
-            super(profile -> {});
-            mAutocompleteResults = new HashMap<>();
-            setOnSuggestionsReceivedListener(listener);
-        }
-
-        /**
-         * Register new AutocompleteResult offered when the test user input matches the
-         * forInputText.
-         *
-         * @param forInputText String to match against: user query.
-         * @param autocompleteText Recommended default autocompletion.
-         * @param autocompleteResult List of suggestions associated with the query.
-         */
-        public void addAutocompleteResult(String forInputText, String autocompleteText,
-                AutocompleteResult autocompleteResult) {
-            mAutocompleteResults.put(forInputText, new Pair(autocompleteText, autocompleteResult));
-        }
-
-        /**
-         * Suppress any suggestion logging mechanisms so that artificially created suggestions
-         * do not attempt to log selection.
-         */
-        @Override
-        public void onSuggestionSelected(int selectedIndex, int disposition, int type,
-                String currentPageUrl, int pageClassification, long elapsedTimeSinceModified,
-                int completedLength, WebContents webContents) {}
-
-        @Override
-        public void start(Profile profile, String url, int pageClassification, final String text,
-                int cursorPosition, boolean preventInlineAutocomplete, String queryTileId,
-                boolean isQueryStartedFromTiles) {
-            if (sendSuggestions(text)) return;
-            super.start(profile, url, pageClassification, text, cursorPosition,
-                    preventInlineAutocomplete, queryTileId, isQueryStartedFromTiles);
-        }
-
-        @Override
-        public void startZeroSuggest(Profile profile, String omniboxText, String url,
-                int pageClassification, String title) {
-            if (sendSuggestions(omniboxText)) return;
-            super.startZeroSuggest(profile, omniboxText, url, pageClassification, title);
-        }
-
-        private boolean sendSuggestions(String forText) {
-            String autocompleteText = forText.toLowerCase(Locale.US);
-            Pair<String, AutocompleteResult> autocompleteSet =
-                    mAutocompleteResults.get(autocompleteText);
-            if (autocompleteSet == null) return false;
-            onSuggestionsReceived(autocompleteSet.second, autocompleteSet.first);
-            return true;
-        }
-    }
-
-    /**
      * AutocompleteController instance that will trigger no suggestions.
      */
     public static class StubAutocompleteController extends AutocompleteController {
         public StubAutocompleteController() {
-            super(profile -> {});
-            setOnSuggestionsReceivedListener(new OnSuggestionsReceivedListener() {
+            super(null, profile -> {}, () -> {});
+            addOnSuggestionsReceivedListener(new OnSuggestionsReceivedListener() {
                 @Override
                 public void onSuggestionsReceived(
                         AutocompleteResult autocompleteResult, String inlineAutocompleteText) {
@@ -131,9 +62,6 @@
 
         @Override
         public void stop(boolean clear) {}
-
-        @Override
-        public void setProfile(Profile profile) {}
     }
 
     /**
diff --git a/chrome/test/data/media/engagement/preload/BUILD.gn b/chrome/test/data/media/engagement/preload/BUILD.gn
index 597329be..f3f9634 100644
--- a/chrome/test/data/media/engagement/preload/BUILD.gn
+++ b/chrome/test/data/media/engagement/preload/BUILD.gn
@@ -3,11 +3,9 @@
 # found in the LICENSE file.
 
 import("//build/compiled_action.gni")
-import("//build/config/python.gni")
 
 # Generates a proto file based on the real list.
-# TODO(crbug.com/1112471): Get this to run cleanly under Python 3.
-python2_action_foreach("generate_preload_list") {
+action_foreach("generate_preload_list") {
   script = "//tools/media_engagement_preload/make_dafsa.py"
 
   sources = [ "test.json" ]
diff --git a/chrome/test/data/webui/chromeos/ash_common/navigation_selector_test.js b/chrome/test/data/webui/chromeos/ash_common/navigation_selector_test.js
index 56ae6669..1430487 100644
--- a/chrome/test/data/webui/chromeos/ash_common/navigation_selector_test.js
+++ b/chrome/test/data/webui/chromeos/ash_common/navigation_selector_test.js
@@ -26,10 +26,12 @@
   /**
    * @param {string} name
    * @param {string} pageIs
+   * @param {string} icon
    * @return {!SelectorItem}
    */
-  function createSelectorItem(name, pageIs) {
-    let item = /** @type{SelectorItem} */ ({'name': name, 'pageIs': pageIs});
+  function createSelectorItem(name, pageIs, icon) {
+    let item = /** @type{SelectorItem} */ (
+        {'name': name, 'pageIs': pageIs, 'icon': icon});
     return item;
   }
 
@@ -62,8 +64,8 @@
   }
 
   test('navigationSelectorLoadEntries', async () => {
-    const item1 = createSelectorItem('test1', 'test-page1');
-    const item2 = createSelectorItem('test2', 'test-page2');
+    const item1 = createSelectorItem('test1', 'test-page1', '');
+    const item2 = createSelectorItem('test2', 'test-page2', '');
 
     const property1 = createProperty(false, false, []);
     const property2 = createProperty(false, false, []);
@@ -85,8 +87,12 @@
   });
 
   test('navigationSelectorLoadsCollapsibleEntries', async () => {
-    const item1 = createSelectorItem('test1', 'test-page1');
-    const item2 = createSelectorItem('Advanced', '');
+    const item1 = createSelectorItem(
+        'test1',
+        'test-page1',
+        '',
+    );
+    const item2 = createSelectorItem('Advanced', '', '');
 
     const property = createProperty(true, false, [item1]);
 
diff --git a/chrome/test/data/webui/chromeos/ash_common/navigation_view_panel_test.js b/chrome/test/data/webui/chromeos/ash_common/navigation_view_panel_test.js
index a3bead1b..5d4bd7d 100644
--- a/chrome/test/data/webui/chromeos/ash_common/navigation_view_panel_test.js
+++ b/chrome/test/data/webui/chromeos/ash_common/navigation_view_panel_test.js
@@ -151,7 +151,7 @@
     let subItem =
         /** @type {SelectorItem} */ ({'name': 'subItem', 'pageIs': subPage});
 
-    viewElement.addSelector('dummyPage1', dummyPage1, [subItem]);
+    viewElement.addSelector('dummyPage1', dummyPage1, '', [subItem]);
     viewElement.addSelector('dummyPage2', dummyPage2);
 
     assertFalse(viewElement.shadowRoot.querySelector(`#${subPage}`).hidden);
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.js b/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.js
index c2a4e76..8b9b3a0 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.js
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js';
-import {ComponentRepairState, ComponentType, RmadErrorCode, RmaState, ShimlessRmaServiceInterface} from 'chrome://shimless-rma/shimless_rma_types.js';
+import {CalibrationComponent, CalibrationObserver, ComponentRepairState, ComponentType, ErrorObserver, HardwareWriteProtectionStateObserver, PowerCableStateObserver, ProvisioningObserver, ProvisioningStep, RmadErrorCode, RmaState, ShimlessRmaServiceInterface} from 'chrome://shimless-rma/shimless_rma_types.js';
 
-import {assertDeepEquals, assertEquals} from '../../chai_assert.js';
+import {assertDeepEquals, assertEquals, assertGE, assertLE} from '../../chai_assert.js';
 
 export function fakeShimlessRmaServiceTestSuite() {
   /** @type {?FakeShimlessRmaService} */
@@ -19,6 +19,7 @@
     service = null;
   });
 
+
   test('GetCurrentStateDefaultRmaNotRequired', () => {
     return service.getCurrentState().then((state) => {
       assertEquals(state.currentState, RmaState.kUnknown);
@@ -674,4 +675,89 @@
       assertEquals(error.error, RmadErrorCode.kRequestInvalid);
     });
   });
+
+  test('ObserveError', () => {
+    /** @type {!ErrorObserver} */
+    const errorObserver = /** @type {!ErrorObserver} */ ({
+      /**
+       * Implements ErrorObserver.onError()
+       * @param {!RmadErrorCode} error
+       */
+      onError(error) {
+        assertEquals(error, RmadErrorCode.kRequestInvalid);
+      }
+    });
+    service.observeError(errorObserver);
+    return service.triggerErrorObserver(RmadErrorCode.kRequestInvalid, 0);
+  });
+
+  test('ObserveCalibrationUpdate', () => {
+    /** @type {!CalibrationObserver} */
+    const calibrationObserver = /** @type {!CalibrationObserver} */ ({
+      /**
+       * Implements CalibrationObserver.onCalibrationUpdated()
+       * @param {!CalibrationComponent} component
+       * @param {number} progress
+       */
+      onCalibrationUpdated(component, progress) {
+        assertEquals(component, CalibrationComponent.kAccelerometer);
+        assertEquals(progress, 0.5);
+      }
+    });
+    service.observeCalibration(calibrationObserver);
+    return service.triggerCalibrationObserver(
+        CalibrationComponent.kAccelerometer, 0.5, 0);
+  });
+
+  test('ObserveProvisioningUpdate', () => {
+    /** @type {!ProvisioningObserver} */
+    const provisioningObserver = /** @type {!ProvisioningObserver} */ ({
+      /**
+       * Implements ProvisioningObserver.onProvisioningUpdated()
+       * @param {!ProvisioningStep} step
+       * @param {number} progress
+       */
+      onProvisioningUpdated(step, progress) {
+        assertEquals(step, ProvisioningStep.kTwiddleSettings);
+        assertEquals(progress, 0.25);
+      }
+    });
+    service.observeProvisioning(provisioningObserver);
+    return service.triggerProvisioningObserver(
+        ProvisioningStep.kTwiddleSettings, 0.25, 0);
+  });
+
+  test('ObserveHardwareWriteProtectionStateChange', () => {
+    /** @type {!HardwareWriteProtectionStateObserver} */
+    const hardwareWriteProtectionStateObserver =
+        /** @type {!HardwareWriteProtectionStateObserver} */ ({
+          /**
+           * Implements
+           * HardwareWriteProtectionStateObserver.
+           *     onHardwareWriteProtectionStateChanged()
+           * @param {boolean} enable
+           */
+          onHardwareWriteProtectionStateChanged(enable) {
+            assertEquals(enable, true);
+          }
+        });
+    service.observeHardwareWriteProtectionState(
+        hardwareWriteProtectionStateObserver);
+    return service.triggerHardwareWriteProtectionObserver(true, 0);
+  });
+
+  test('ObservePowerCableStateChange', () => {
+    /** @type {!PowerCableStateObserver} */
+    const powerCableStateObserver = /** @type {!PowerCableStateObserver} */ ({
+      /**
+       * Implements PowerCableStateObserver.onPowerCableStateChanged()
+       * @param {boolean} enable
+       */
+      onPowerCableStateChanged(enable) {
+        assertEquals(enable, true);
+      }
+    });
+    service.observePowerCableState(powerCableStateObserver);
+    return service.triggerPowerCableObserver(true, 0);
+  });
 }
diff --git a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.js b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.js
index 9badf3c..18f36e2 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.js
@@ -17,7 +17,6 @@
 // #import {FakeNetworkConfig} from 'chrome://test/chromeos/fake_network_config_mojom.m.js';
 // #import {OncMojo} from 'chrome://resources/cr_components/chromeos/network/onc_mojo.m.js';
 // #import {MojoInterfaceProviderImpl} from 'chrome://resources/cr_components/chromeos/network/mojo_interface_provider.m.js';
-// #import {LoadingPageState} from 'chrome://resources/cr_components/chromeos/cellular_setup/setup_loading_page.m.js';
 // #import {MockMetricsPrivate} from './mock_metrics_private.m.js';
 // clang-format on
 
@@ -752,6 +751,10 @@
   test(
       'Show cellular disconnect warning if connected to pSIM network',
       async function() {
+        assertEquals(
+            profileLoadingPage.loadingMessage,
+            eSimPage.i18n('eSimProfileDetectMessage'));
+
         const pSimNetwork = OncMojo.getDefaultNetworkState(
             chromeos.networkConfig.mojom.NetworkType.kCellular, 'cellular');
         pSimNetwork.connectionState =
@@ -762,8 +765,9 @@
         await flushAsync();
 
         assertEquals(
-            profileLoadingPage.state,
-            LoadingPageState.CELLULAR_DISCONNECT_WARNING);
+            profileLoadingPage.loadingMessage,
+            eSimPage.i18n(
+                'eSimProfileDetectDuringActiveCellularConnectionMessage'));
 
         // Disconnect from the network.
         networkConfigRemote.removeNetworkForTest(pSimNetwork);
@@ -771,8 +775,9 @@
 
         // The warning should still be showing.
         assertEquals(
-            profileLoadingPage.state,
-            LoadingPageState.CELLULAR_DISCONNECT_WARNING);
+            profileLoadingPage.loadingMessage,
+            eSimPage.i18n(
+                'eSimProfileDetectDuringActiveCellularConnectionMessage'));
       });
 
   test('Show final page with error if no EUICC', async function() {
diff --git a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/setup_loading_page_test.js b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/setup_loading_page_test.js
index b0dd77d..06036b4 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/setup_loading_page_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/setup_loading_page_test.js
@@ -8,50 +8,30 @@
 
 // #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 // #import {assertFalse, assertTrue} from '../../../chai_assert.js';
-// #import {LoadingPageState} from 'chrome://resources/cr_components/chromeos/cellular_setup/setup_loading_page.m.js';
-// #import {FakeCellularSetupDelegate} from './fake_cellular_setup_delegate.m.js';
 // clang-format on
 
 suite('CrComponentsSetupLoadingPageTest', function() {
-  let simDetectPage;
+  let setupLoadingPage;
   let basePage;
-  let messageIcon;
 
   setup(function() {
-    simDetectPage = document.createElement('setup-loading-page');
-    simDetectPage.delegate = new cellular_setup.FakeCellularSetupDelegate();
-    document.body.appendChild(simDetectPage);
+    setupLoadingPage = document.createElement('setup-loading-page');
+    document.body.appendChild(setupLoadingPage);
     Polymer.dom.flush();
 
-    basePage = simDetectPage.$$('base-page');
+    basePage = setupLoadingPage.$$('base-page');
     assertTrue(!!basePage);
-    messageIcon = basePage.$$('iron-icon');
-    assertTrue(!!messageIcon);
   });
 
-  test('No message is shown', function() {
-    simDetectPage.state = LoadingPageState.LOADING;
-    assertFalse(!!basePage.message);
-    assertTrue(messageIcon.hidden);
-  });
+  test('Loading animation and error graphic shown correctly', function() {
+    setupLoadingPage.isSimDetectError = false;
+    Polymer.dom.flush();
+    assertTrue(!!setupLoadingPage.$$('#animationContainer'));
+    assertTrue(setupLoadingPage.$$('#simDetectError').hidden);
 
-  test('Warning message is shown', function() {
-    simDetectPage.state = LoadingPageState.CELLULAR_DISCONNECT_WARNING;
-    assertEquals(basePage.message, simDetectPage.i18n('eSimConnectionWarning'));
-    assertFalse(messageIcon.hidden);
-  });
-
-  test('Retry error message is shown', function() {
-    simDetectPage.state = LoadingPageState.SIM_DETECT_ERROR;
-    assertEquals(
-        basePage.message, simDetectPage.i18n('simDetectPageErrorMessage'));
-    assertTrue(messageIcon.hidden);
-  });
-
-  test('Final error message is shown', function() {
-    simDetectPage.state = LoadingPageState.FINAL_SIM_DETECT_ERROR;
-    assertEquals(
-        basePage.message, simDetectPage.i18n('simDetectPageFinalErrorMessage'));
-    assertTrue(messageIcon.hidden);
+    setupLoadingPage.isSimDetectError = true;
+    Polymer.dom.flush();
+    assertFalse(!!setupLoadingPage.$$('#animationContainer'));
+    assertFalse(setupLoadingPage.$$('#simDetectError').hidden);
   });
 });
diff --git a/chrome/test/data/webui/cr_components/chromeos/network/network_list_item_test.js b/chrome/test/data/webui/cr_components/chromeos/network/network_list_item_test.js
index e278131..d8a62fe4 100644
--- a/chrome/test/data/webui/cr_components/chromeos/network/network_list_item_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/network/network_list_item_test.js
@@ -271,6 +271,12 @@
     listItem.$.divOuter.click();
     const showDetailEvent = await showDetailPromise;
     assertEquals(showDetailEvent.detail, networkState);
+
+    // Setting showButtons to false should hide activate and arrow button.
+    listItem.showButtons = false;
+    await flushAsync();
+    assertFalse(!!listItem.$$('#activateButton'));
+    assertFalse(!!listItem.$$('#subpageButton'));
   });
 
   test('Unavailable pSIM UI visibility', async () => {
@@ -428,6 +434,11 @@
 
         await flushAsync();
         assertEquals(installProfileEventIccid, 'iccid');
+
+        // Setting showButtons to false should hide install button.
+        listItem.showButtons = false;
+        await flushAsync();
+        assertFalse(!!listItem.$$('#installButton'));
       });
 
   test(
@@ -538,6 +549,11 @@
     networkStateText = listItem.$$('#networkStateText');
     assertTrue(!!networkStateText);
     assertEquals(networkStateLockedText, networkStateText.textContent.trim());
+
+    // Setting showButtons to false should hide unlock button.
+    listItem.showButtons = false;
+    await flushAsync();
+    assertFalse(!!listItem.$$('#unlockButton'));
   });
 
   test('Disable sim lock button when device is inhibited', async () => {
diff --git a/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.html b/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.html
index 9b55781..92b432583 100644
--- a/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.html
+++ b/chrome/test/data/webui/js/cr/ui/context_menu_handler_test.html
@@ -2,8 +2,8 @@
 <html>
 <body>
 <script src="chrome://resources/js/assert.js"></script>
-<script src="chrome://resources/js/event_tracker.js"></script>
 <script src="chrome://resources/js/cr.js"></script>
+<script src="chrome://resources/js/event_tracker.js"></script>
 <script src="chrome://resources/js/cr/event_target.js"></script>
 <script src="chrome://resources/js/cr/ui.js"></script>
 <script src="chrome://resources/js/cr/ui/position_util.js"></script>
diff --git a/chrome/test/data/webui/js/cr/ui/menu_button_test.html b/chrome/test/data/webui/js/cr/ui/menu_button_test.html
index 8ee6e854..1a6d8820 100644
--- a/chrome/test/data/webui/js/cr/ui/menu_button_test.html
+++ b/chrome/test/data/webui/js/cr/ui/menu_button_test.html
@@ -2,8 +2,8 @@
 <html>
 <body>
 <script src="chrome://resources/js/assert.js"></script>
-<script src="chrome://resources/js/event_tracker.js"></script>
 <script src="chrome://resources/js/cr.js"></script>
+<script src="chrome://resources/js/event_tracker.js"></script>
 <script src="chrome://resources/js/cr/ui.js"></script>
 <script src="chrome://resources/js/cr/ui/position_util.js"></script>
 <script src="chrome://resources/js/cr/ui/menu_button.js"></script>
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc
index 9053f35..8c81f45 100644
--- a/chrome/test/ppapi/ppapi_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_browsertest.cc
@@ -987,7 +987,12 @@
 PPAPI_SOCKET_TEST(UDPSocket_SetOption)
 PPAPI_SOCKET_TEST(UDPSocket_SetOption_1_0)
 PPAPI_SOCKET_TEST(UDPSocket_SetOption_1_1)
+
+// Fails on MacOS 11, crbug.com/1211138 .
+#if !defined(OS_MAC)
 PPAPI_SOCKET_TEST(UDPSocket_Broadcast)
+#endif
+
 PPAPI_SOCKET_TEST(UDPSocket_ParallelSend)
 PPAPI_SOCKET_TEST(UDPSocket_Multicast)
 
@@ -998,7 +1003,12 @@
 TEST_PPAPI_OUT_OF_PROCESS_VIA_HTTP(UDPSocketPrivate_SetSocketFeatureErrors)
 TEST_PPAPI_NACL(UDPSocketPrivate_Connect)
 TEST_PPAPI_NACL(UDPSocketPrivate_ConnectFailure)
+
+// Fails on MacOS 11, crbug.com/1211138 .
+#if !defined(OS_MAC)
 TEST_PPAPI_NACL(UDPSocketPrivate_Broadcast)
+#endif
+
 TEST_PPAPI_NACL(UDPSocketPrivate_SetSocketFeatureErrors)
 
 namespace {
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 13793d6..dc8ad07 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -59,8 +59,6 @@
       "enum_traits.h",
       "persisted_data.cc",
       "persisted_data.h",
-      "policy_manager.cc",
-      "policy_manager.h",
       "registration_data.cc",
       "registration_data.h",
       "splash_screen.h",
@@ -128,8 +126,6 @@
       "installer.cc",
       "installer.h",
       "lib_util.h",
-      "policy_service.cc",
-      "policy_service.h",
       "prefs.cc",
       "prefs.h",
       "prefs_impl.h",
@@ -200,6 +196,7 @@
       ":version_header",
       "//base",
       "//base:i18n",
+      "//chrome/updater/policy",
       "//components/crash/core/common:crash_key",
       "//components/crx_file:crx_file",
       "//components/prefs",
@@ -229,7 +226,6 @@
     if (is_mac) {
       deps += [
         "//chrome/updater/app/server/mac:protocol",
-        "//chrome/updater/mac:enterprise",
         "//chrome/updater/mac:installer_sources",
         "//chrome/updater/mac:network_fetcher_sources",
         "//chrome/updater/mac:updater_setup_sources",
@@ -354,8 +350,6 @@
       "external_constants_override_unittest.cc",
       "lib_util_unittest.cc",
       "persisted_data_unittest.cc",
-      "policy_manager_unittest.cc",
-      "policy_service_unittest.cc",
       "prefs_unittest.cc",
       "tag_unittest.cc",
       "test/integration_test_commands.h",
@@ -384,6 +378,7 @@
       "//base/test:test_support",
       "//chrome/common:constants",
       "//chrome/updater/device_management:unittest",
+      "//chrome/updater/policy:unittest",
       "//chrome/updater/test/test_app:constants",
       "//chrome/updater/test/test_app:version_header",
       "//chrome/updater/tools:unittest",
@@ -423,7 +418,6 @@
       deps += [
         "//chrome/common/mac:launchd",
         "//chrome/updater/app/server/mac:protocol",
-        "//chrome/updater/mac:enterprise_tests",
         "//chrome/updater/mac:network_fetcher_sources",
         "//chrome/updater/mac:updater_bundle",
         "//chrome/updater/mac:updater_setup_tests",
diff --git a/chrome/updater/device_management/BUILD.gn b/chrome/updater/device_management/BUILD.gn
index adf109a..89f9c6e5 100644
--- a/chrome/updater/device_management/BUILD.gn
+++ b/chrome/updater/device_management/BUILD.gn
@@ -16,8 +16,6 @@
     "dm_client.h",
     "dm_message.cc",
     "dm_message.h",
-    "dm_policy_manager.cc",
-    "dm_policy_manager.h",
     "dm_response_validator.cc",
     "dm_response_validator.h",
     "dm_storage.cc",
@@ -61,7 +59,6 @@
     "dm_message_unittest.cc",
     "dm_policy_builder_for_testing.cc",
     "dm_policy_builder_for_testing.h",
-    "dm_policy_manager_unittest.cc",
     "dm_response_validator_unittest.cc",
     "dm_storage_unittest.cc",
   ]
diff --git a/chrome/updater/device_management/dm_client_unittest.cc b/chrome/updater/device_management/dm_client_unittest.cc
index 8c8ea1a1..31b36ba2 100644
--- a/chrome/updater/device_management/dm_client_unittest.cc
+++ b/chrome/updater/device_management/dm_client_unittest.cc
@@ -25,7 +25,7 @@
 #include "chrome/updater/device_management/dm_policy_builder_for_testing.h"
 #include "chrome/updater/device_management/dm_response_validator.h"
 #include "chrome/updater/device_management/dm_storage.h"
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/protos/omaha_settings.pb.h"
 #include "chrome/updater/unittest_util.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "components/update_client/network.h"
@@ -220,23 +220,20 @@
       EXPECT_EQ(info->public_key(), GetTestKey1()->GetPublicKeyString());
 
     if (result == DMClient::RequestResult::kSuccess) {
-      std::unique_ptr<PolicyManagerInterface> policy_manager =
-          storage_->GetOmahaPolicyManager();
-      EXPECT_NE(policy_manager, nullptr);
+      std::unique_ptr<::wireless_android_enterprise_devicemanagement::
+                          OmahaSettingsClientProto>
+          omaha_settings = storage_->GetOmahaPolicySettings();
+      EXPECT_NE(omaha_settings, nullptr);
 
       // Sample some of the policy values and check they are expected.
-      EXPECT_TRUE(policy_manager->IsManaged());
-      std::string proxy_mode;
-      EXPECT_TRUE(policy_manager->GetProxyMode(&proxy_mode));
-      EXPECT_EQ(proxy_mode, "pac_script");
-      int update_policy = 0;
-      EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppUpdates(
-          kChromeAppId, &update_policy));
-      EXPECT_EQ(update_policy, kPolicyAutomaticUpdatesOnly);
-      std::string target_version_prefix;
-      EXPECT_TRUE(policy_manager->GetTargetVersionPrefix(
-          kChromeAppId, &target_version_prefix));
-      EXPECT_EQ(target_version_prefix, "81.");
+      EXPECT_EQ(omaha_settings->proxy_mode(), "pac_script");
+      const ::wireless_android_enterprise_devicemanagement::ApplicationSettings&
+          chrome_settings = omaha_settings->application_settings()[0];
+      EXPECT_EQ(chrome_settings.app_guid(), kChromeAppId);
+      EXPECT_EQ(chrome_settings.update(),
+                ::wireless_android_enterprise_devicemanagement::
+                    AUTOMATIC_UPDATES_ONLY);
+      EXPECT_EQ(chrome_settings.target_version_prefix(), "81.");
     }
 
     EXPECT_EQ(expected_validation_results_, validation_results);
diff --git a/chrome/updater/device_management/dm_storage.cc b/chrome/updater/device_management/dm_storage.cc
index 622b0b04..9023c71 100644
--- a/chrome/updater/device_management/dm_storage.cc
+++ b/chrome/updater/device_management/dm_storage.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/updater/device_management/dm_storage.h"
 
+#include <memory>
 #include <set>
 #include <string>
 #include <utility>
@@ -15,7 +16,7 @@
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/updater/device_management/dm_cached_policy_info.h"
 #include "chrome/updater/device_management/dm_message.h"
-#include "chrome/updater/device_management/dm_policy_manager.h"
+#include "chrome/updater/protos/omaha_settings.pb.h"
 #include "chrome/updater/util.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -140,7 +141,7 @@
   return DeleteObsoletePolicies(policy_cache_root_, policy_types_base64);
 }
 
-std::unique_ptr<CachedPolicyInfo> DMStorage::GetCachedPolicyInfo() {
+std::unique_ptr<CachedPolicyInfo> DMStorage::GetCachedPolicyInfo() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto cached_info = std::make_unique<CachedPolicyInfo>();
 
@@ -159,7 +160,9 @@
   return cached_info;
 }
 
-std::unique_ptr<PolicyManagerInterface> DMStorage::GetOmahaPolicyManager() {
+std::unique_ptr<
+    ::wireless_android_enterprise_devicemanagement::OmahaSettingsClientProto>
+DMStorage::GetOmahaPolicySettings() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!IsValidDMToken())
@@ -174,18 +177,19 @@
   std::string response_data;
   ::enterprise_management::PolicyFetchResponse response;
   ::enterprise_management::PolicyData policy_data;
-  ::wireless_android_enterprise_devicemanagement::OmahaSettingsClientProto
-      omaha_settings;
+  auto omaha_settings =
+      std::make_unique<::wireless_android_enterprise_devicemanagement::
+                           OmahaSettingsClientProto>();
   if (!base::PathExists(omaha_policy_file) ||
       !base::ReadFileToString(omaha_policy_file, &response_data) ||
       response_data.empty() || !response.ParseFromString(response_data) ||
       !policy_data.ParseFromString(response.policy_data()) ||
       !policy_data.has_policy_value() ||
-      !omaha_settings.ParseFromString(policy_data.policy_value())) {
+      !omaha_settings->ParseFromString(policy_data.policy_value())) {
     return nullptr;
   }
 
-  return std::make_unique<DMPolicyManager>(omaha_settings);
+  return omaha_settings;
 }
 
 scoped_refptr<DMStorage> GetDefaultDMStorage() {
diff --git a/chrome/updater/device_management/dm_storage.h b/chrome/updater/device_management/dm_storage.h
index ca04ab0..31ff82a 100644
--- a/chrome/updater/device_management/dm_storage.h
+++ b/chrome/updater/device_management/dm_storage.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+
 #include "base/containers/flat_map.h"
 #include "base/files/file_path.h"
 #include "base/memory/ref_counted.h"
@@ -14,10 +15,13 @@
 #include "build/build_config.h"
 #include "chrome/updater/device_management/dm_message.h"
 
+namespace wireless_android_enterprise_devicemanagement {
+class OmahaSettingsClientProto;
+}
+
 namespace updater {
 
 class CachedPolicyInfo;
-class PolicyManagerInterface;
 
 // The token service interface defines how to serialize tokens.
 class TokenServiceInterface {
@@ -117,12 +121,13 @@
 
   // Creates a CachedPolicyInfo object and populates it with the public key
   // information loaded from file |policy_cache_root_|\CachedPolicyInfo.
-  std::unique_ptr<CachedPolicyInfo> GetCachedPolicyInfo();
+  std::unique_ptr<CachedPolicyInfo> GetCachedPolicyInfo() const;
 
-  // Creates a policy manager and populates it with the Omaha policies loaded
-  // from PolicyFetchResponse file within
+  // Returns the Omaha policy settings loaded from PolicyFetchResponse file in
   // |policy_cache_root_|\{Base64Encoded{kGoogleUpdatePolicyType}} directory.
-  std::unique_ptr<PolicyManagerInterface> GetOmahaPolicyManager();
+  std::unique_ptr<
+      ::wireless_android_enterprise_devicemanagement::OmahaSettingsClientProto>
+  GetOmahaPolicySettings() const;
 
  private:
   friend class base::RefCountedThreadSafe<DMStorage>;
diff --git a/chrome/updater/device_management/dm_storage_unittest.cc b/chrome/updater/device_management/dm_storage_unittest.cc
index ee9c39b..fc83d08a 100644
--- a/chrome/updater/device_management/dm_storage_unittest.cc
+++ b/chrome/updater/device_management/dm_storage_unittest.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 <memory>
 #include <utility>
 
 #include "base/files/file_util.h"
@@ -10,7 +11,6 @@
 #include "chrome/updater/constants.h"
 #include "chrome/updater/device_management/dm_cached_policy_info.h"
 #include "chrome/updater/device_management/dm_storage.h"
-#include "chrome/updater/policy_manager.h"
 #include "chrome/updater/protos/omaha_settings.pb.h"
 #include "chrome/updater/unittest_util.h"
 #include "components/policy/proto/device_management_backend.pb.h"
@@ -184,80 +184,38 @@
       cache_root.GetPath(), std::make_unique<TestTokenService>());
   EXPECT_TRUE(storage->PersistPolicies(policies));
 
-  auto policy_manager = storage->GetOmahaPolicyManager();
-  ASSERT_NE(policy_manager, nullptr);
+  std::unique_ptr<
+      ::wireless_android_enterprise_devicemanagement::OmahaSettingsClientProto>
+      omaha_settings = storage->GetOmahaPolicySettings();
+  ASSERT_NE(omaha_settings, nullptr);
+  EXPECT_EQ(omaha_settings->auto_update_check_period_minutes(), 111);
 
-  int check_interval = 0;
-  EXPECT_TRUE(policy_manager->GetLastCheckPeriodMinutes(&check_interval));
-  EXPECT_EQ(check_interval, 111);
+  EXPECT_EQ(omaha_settings->updates_suppressed().start_hour(), 8);
+  EXPECT_EQ(omaha_settings->updates_suppressed().start_minute(), 8);
+  EXPECT_EQ(omaha_settings->updates_suppressed().duration_min(), 47);
 
-  UpdatesSuppressedTimes suppressed_times;
-  EXPECT_TRUE(policy_manager->GetUpdatesSuppressedTimes(&suppressed_times));
-  EXPECT_EQ(suppressed_times.start_hour, 8);
-  EXPECT_EQ(suppressed_times.start_minute, 8);
-  EXPECT_EQ(suppressed_times.duration_minute, 47);
+  EXPECT_EQ(omaha_settings->proxy_mode(), "proxy_pac_script");
+  EXPECT_EQ(omaha_settings->proxy_pac_url(), "foo.c/proxy.pa");
+  EXPECT_FALSE(omaha_settings->has_proxy_server());
 
-  // Proxy policies.
-  std::string proxy_mode;
-  EXPECT_TRUE(policy_manager->GetProxyMode(&proxy_mode));
-  EXPECT_EQ(proxy_mode, "proxy_pac_script");
-  std::string proxy_pac_url;
-  EXPECT_TRUE(policy_manager->GetProxyPacUrl(&proxy_pac_url));
-  EXPECT_EQ(proxy_pac_url, "foo.c/proxy.pa");
-  std::string proxy_server;
-  EXPECT_FALSE(policy_manager->GetProxyServer(&proxy_server));
-
-  // Download preference.
-  std::string download_preference;
-  EXPECT_TRUE(
-      policy_manager->GetDownloadPreferenceGroupPolicy(&download_preference));
-  EXPECT_EQ(download_preference, "cacheable");
-
-  // Cache policies.
-  int cache_size = 0;
-  EXPECT_FALSE(policy_manager->GetPackageCacheSizeLimitMBytes(&cache_size));
-  int cache_life = 0;
-  EXPECT_FALSE(policy_manager->GetPackageCacheExpirationTimeDays(&cache_life));
+  EXPECT_EQ(omaha_settings->download_preference(), "cacheable");
 
   // Chrome policies.
-  int chrome_install_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppInstalls(
-      kChromeAppId, &chrome_install_policy));
-  EXPECT_EQ(chrome_install_policy, kPolicyDisabled);
-  int chrome_update_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppUpdates(
-      kChromeAppId, &chrome_update_policy));
-  EXPECT_EQ(chrome_update_policy, kPolicyAutomaticUpdatesOnly);
-  std::string target_version_prefix;
-  EXPECT_TRUE(policy_manager->GetTargetVersionPrefix(kChromeAppId,
-                                                     &target_version_prefix));
-  EXPECT_EQ(target_version_prefix, "3.6.55");
-  bool rollback_allowed = false;
-  EXPECT_TRUE(policy_manager->IsRollbackToTargetVersionAllowed(
-      kChromeAppId, &rollback_allowed));
-  EXPECT_TRUE(rollback_allowed);
+  const auto& chrome_settings = omaha_settings->application_settings()[0];
+  EXPECT_EQ(chrome_settings.install(),
+            ::wireless_android_enterprise_devicemanagement::INSTALL_DISABLED);
+  EXPECT_EQ(
+      chrome_settings.update(),
+      ::wireless_android_enterprise_devicemanagement::AUTOMATIC_UPDATES_ONLY);
+  EXPECT_EQ(chrome_settings.target_version_prefix(), "3.6.55");
+  EXPECT_EQ(chrome_settings.rollback_to_target_version(),
+            ::wireless_android_enterprise_devicemanagement::
+                ROLLBACK_TO_TARGET_VERSION_ENABLED);
 
-  // No app-specific policy should fallback to global.
-  const std::string non_exist_appid = "{00000000-1111-2222-3333-444444444444}";
-  int app_install_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppInstalls(
-      non_exist_appid, &app_install_policy));
-  EXPECT_EQ(app_install_policy, kPolicyDisabled);
-  int app_update_policy = -1;
-  EXPECT_TRUE(policy_manager->GetEffectivePolicyForAppUpdates(
-      non_exist_appid, &app_update_policy));
-  EXPECT_EQ(app_update_policy, kPolicyManualUpdatesOnly);
-  std::string app_target_version_prefix;
-  EXPECT_FALSE(policy_manager->GetTargetVersionPrefix(
-      non_exist_appid, &app_target_version_prefix));
-  bool app_rollback_allowed = false;
-  EXPECT_FALSE(policy_manager->IsRollbackToTargetVersionAllowed(
-      non_exist_appid, &app_rollback_allowed));
-
-  // Verify no policy manager once device is deregistered.
+  // Verify no policy settings once device is de-registered.
   EXPECT_TRUE(storage->DeregisterDevice());
   EXPECT_FALSE(storage->IsValidDMToken());
-  ASSERT_EQ(storage->GetOmahaPolicyManager(), nullptr);
+  ASSERT_EQ(storage->GetOmahaPolicySettings(), nullptr);
 }
 
 }  // namespace updater
diff --git a/chrome/updater/installer.cc b/chrome/updater/installer.cc
index 01d982a..9a6f7c5 100644
--- a/chrome/updater/installer.cc
+++ b/chrome/updater/installer.cc
@@ -18,7 +18,7 @@
 #include "build/build_config.h"
 #include "chrome/updater/action_handler.h"
 #include "chrome/updater/constants.h"
-#include "chrome/updater/policy_service.h"
+#include "chrome/updater/policy/service.h"
 #include "chrome/updater/util.h"
 #include "components/crx_file/crx_verifier.h"
 #include "components/update_client/update_client_errors.h"
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn
index b1d0aad..6192d50 100644
--- a/chrome/updater/mac/BUILD.gn
+++ b/chrome/updater/mac/BUILD.gn
@@ -190,34 +190,3 @@
     "//testing/gtest",
   ]
 }
-
-source_set("enterprise") {
-  sources = [
-    "managed_preference_policy_manager.h",
-    "managed_preference_policy_manager.mm",
-    "managed_preference_policy_manager_impl.h",
-    "managed_preference_policy_manager_impl.mm",
-  ]
-
-  deps = [
-    "//base",
-    "//chrome/updater:base",
-  ]
-}
-
-source_set("enterprise_tests") {
-  testonly = true
-
-  sources = [
-    "managed_preference_policy_manager_impl_unittest.mm",
-    "managed_preference_policy_manager_unittest.cc",
-  ]
-
-  deps = [
-    ":enterprise",
-    "//base",
-    "//base/test:test_support",
-    "//chrome/updater:base",
-    "//testing/gtest",
-  ]
-}
diff --git a/chrome/updater/mac/managed_preference_policy_manager.h b/chrome/updater/mac/managed_preference_policy_manager.h
deleted file mode 100644
index 43b0be5..0000000
--- a/chrome/updater/mac/managed_preference_policy_manager.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_UPDATER_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_H_
-#define CHROME_UPDATER_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_H_
-
-#include <memory>
-
-#include "chrome/updater/policy_manager.h"
-
-namespace updater {
-
-// A factory method to create a managed preference policy manager.
-std::unique_ptr<PolicyManagerInterface> CreateManagedPreferencePolicyManager();
-
-}  // namespace updater
-
-#endif  // CHROME_UPDATER_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_H_
diff --git a/chrome/updater/policy/BUILD.gn b/chrome/updater/policy/BUILD.gn
new file mode 100644
index 0000000..0efcee3e
--- /dev/null
+++ b/chrome/updater/policy/BUILD.gn
@@ -0,0 +1,78 @@
+# Copyright 2021 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("//build/config/chrome_build.gni")
+import("//build/config/sanitizers/sanitizers.gni")
+import("//build/util/process_version.gni")
+import("//chrome/updater/branding.gni")
+import("//testing/test.gni")
+
+source_set("policy") {
+  sources = [
+    "dm_policy_manager.cc",
+    "dm_policy_manager.h",
+    "manager.cc",
+    "manager.h",
+    "service.cc",
+    "service.h",
+  ]
+
+  deps = [
+    "//base",
+    "//chrome/updater:base",
+    "//chrome/updater:branding_header",
+    "//chrome/updater:version_header",
+    "//chrome/updater/protos:omaha_proto",
+  ]
+  public_deps = [ "//components/policy/proto" ]
+
+  if (is_mac) {
+    sources += [
+      "mac/managed_preference_policy_manager.h",
+      "mac/managed_preference_policy_manager.mm",
+      "mac/managed_preference_policy_manager_impl.h",
+      "mac/managed_preference_policy_manager_impl.mm",
+    ]
+  }
+  if (is_win) {
+    sources += [
+      "win/group_policy_manager.cc",
+      "win/group_policy_manager.h",
+    ]
+    deps += [ "//chrome/updater/win:constants" ]
+  }
+}
+
+source_set("unittest") {
+  testonly = true
+
+  sources = [
+    "dm_policy_manager_unittest.cc",
+    "manager_unittest.cc",
+    "service_unittest.cc",
+  ]
+
+  deps = [
+    ":policy",
+    "//base",
+    "//base/test:test_support",
+    "//chrome/updater:base",
+    "//chrome/updater:updater_tests_support",
+    "//chrome/updater/protos:omaha_proto",
+    "//net:test_support",
+    "//testing/gtest",
+  ]
+
+  if (is_mac) {
+    sources += [
+      "mac/managed_preference_policy_manager_impl_unittest.mm",
+      "mac/managed_preference_policy_manager_unittest.cc",
+    ]
+  }
+
+  if (is_win) {
+    sources += [ "win/group_policy_manager_unittest.cc" ]
+    deps += [ "//chrome/updater/win:constants" ]
+  }
+}
diff --git a/chrome/updater/device_management/dm_policy_manager.cc b/chrome/updater/policy/dm_policy_manager.cc
similarity index 98%
rename from chrome/updater/device_management/dm_policy_manager.cc
rename to chrome/updater/policy/dm_policy_manager.cc
index d3fd9ab..5e85ad8 100644
--- a/chrome/updater/device_management/dm_policy_manager.cc
+++ b/chrome/updater/policy/dm_policy_manager.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/device_management/dm_policy_manager.h"
+#include "chrome/updater/policy/dm_policy_manager.h"
 
 #include "base/enterprise_util.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
 #include "chrome/updater/constants.h"
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 
 namespace updater {
 
diff --git a/chrome/updater/device_management/dm_policy_manager.h b/chrome/updater/policy/dm_policy_manager.h
similarity index 90%
rename from chrome/updater/device_management/dm_policy_manager.h
rename to chrome/updater/policy/dm_policy_manager.h
index 0d94fc9..96cc54b 100644
--- a/chrome/updater/device_management/dm_policy_manager.h
+++ b/chrome/updater/policy/dm_policy_manager.h
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_UPDATER_DEVICE_MANAGEMENT_DM_POLICY_MANAGER_H_
-#define CHROME_UPDATER_DEVICE_MANAGEMENT_DM_POLICY_MANAGER_H_
+#ifndef CHROME_UPDATER_POLICY_DM_POLICY_MANAGER_H_
+#define CHROME_UPDATER_POLICY_DM_POLICY_MANAGER_H_
 
 #include <memory>
 #include <string>
 
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 #include "chrome/updater/protos/omaha_settings.pb.h"
 
 namespace updater {
@@ -61,4 +61,4 @@
 
 }  // namespace updater
 
-#endif  // CHROME_UPDATER_DEVICE_MANAGEMENT_DM_POLICY_MANAGER_H_
+#endif  // CHROME_UPDATER_POLICY_DM_POLICY_MANAGER_H_
diff --git a/chrome/updater/device_management/dm_policy_manager_unittest.cc b/chrome/updater/policy/dm_policy_manager_unittest.cc
similarity index 99%
rename from chrome/updater/device_management/dm_policy_manager_unittest.cc
rename to chrome/updater/policy/dm_policy_manager_unittest.cc
index a553c9e..1999b0d 100644
--- a/chrome/updater/device_management/dm_policy_manager_unittest.cc
+++ b/chrome/updater/policy/dm_policy_manager_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/device_management/dm_policy_manager.h"
+#include "chrome/updater/policy/dm_policy_manager.h"
 
 #include "build/build_config.h"
 #include "chrome/updater/constants.h"
diff --git a/chrome/updater/policy/mac/managed_preference_policy_manager.h b/chrome/updater/policy/mac/managed_preference_policy_manager.h
new file mode 100644
index 0000000..28304e3
--- /dev/null
+++ b/chrome/updater/policy/mac/managed_preference_policy_manager.h
@@ -0,0 +1,19 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_UPDATER_POLICY_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_H_
+#define CHROME_UPDATER_POLICY_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_H_
+
+#include <memory>
+
+#include "chrome/updater/policy/manager.h"
+
+namespace updater {
+
+// A factory method to create a managed preference policy manager.
+std::unique_ptr<PolicyManagerInterface> CreateManagedPreferencePolicyManager();
+
+}  // namespace updater
+
+#endif  // CHROME_UPDATER_POLICY_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_H_
diff --git a/chrome/updater/mac/managed_preference_policy_manager.mm b/chrome/updater/policy/mac/managed_preference_policy_manager.mm
similarity index 97%
rename from chrome/updater/mac/managed_preference_policy_manager.mm
rename to chrome/updater/policy/mac/managed_preference_policy_manager.mm
index 0e3dcfb..1e71181b 100644
--- a/chrome/updater/mac/managed_preference_policy_manager.mm
+++ b/chrome/updater/policy/mac/managed_preference_policy_manager.mm
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/mac/managed_preference_policy_manager.h"
+#include "chrome/updater/policy/mac/managed_preference_policy_manager.h"
 
 #include <string>
 
 #include "base/mac/scoped_cftyperef.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
-#include "chrome/updater/mac/managed_preference_policy_manager_impl.h"
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/mac/managed_preference_policy_manager_impl.h"
+#include "chrome/updater/policy/manager.h"
 
 namespace updater {
 
diff --git a/chrome/updater/mac/managed_preference_policy_manager_impl.h b/chrome/updater/policy/mac/managed_preference_policy_manager_impl.h
similarity index 89%
rename from chrome/updater/mac/managed_preference_policy_manager_impl.h
rename to chrome/updater/policy/mac/managed_preference_policy_manager_impl.h
index 925ccc0..7adcfad 100644
--- a/chrome/updater/mac/managed_preference_policy_manager_impl.h
+++ b/chrome/updater/policy/mac/managed_preference_policy_manager_impl.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_UPDATER_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_IMPL_H_
-#define CHROME_UPDATER_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_IMPL_H_
+#ifndef CHROME_UPDATER_POLICY_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_IMPL_H_
+#define CHROME_UPDATER_POLICY_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_IMPL_H_
 
 #import <Foundation/Foundation.h>
 
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 
 // TODO: crbug/1073980
 //     Add a doc link for the managed preferences dictionary format.
@@ -72,4 +72,4 @@
 
 @end
 
-#endif  // CHROME_UPDATER_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_IMPL_H_
+#endif  // CHROME_UPDATER_POLICY_MAC_MANAGED_PREFERENCE_POLICY_MANAGER_IMPL_H_
diff --git a/chrome/updater/mac/managed_preference_policy_manager_impl.mm b/chrome/updater/policy/mac/managed_preference_policy_manager_impl.mm
similarity index 98%
rename from chrome/updater/mac/managed_preference_policy_manager_impl.mm
rename to chrome/updater/policy/mac/managed_preference_policy_manager_impl.mm
index fb15bec1..20803cbf 100644
--- a/chrome/updater/mac/managed_preference_policy_manager_impl.mm
+++ b/chrome/updater/policy/mac/managed_preference_policy_manager_impl.mm
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "chrome/updater/mac/managed_preference_policy_manager_impl.h"
+#import "chrome/updater/policy/mac/managed_preference_policy_manager_impl.h"
 
 #include "base/mac/scoped_nsobject.h"
 #include "chrome/updater/constants.h"
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 
 // Constants for managed preference policy keys.
 static NSString* kGlobalPolicyKey = @"global";
diff --git a/chrome/updater/mac/managed_preference_policy_manager_impl_unittest.mm b/chrome/updater/policy/mac/managed_preference_policy_manager_impl_unittest.mm
similarity index 98%
rename from chrome/updater/mac/managed_preference_policy_manager_impl_unittest.mm
rename to chrome/updater/policy/mac/managed_preference_policy_manager_impl_unittest.mm
index dbd5be68e..34639989 100644
--- a/chrome/updater/mac/managed_preference_policy_manager_impl_unittest.mm
+++ b/chrome/updater/policy/mac/managed_preference_policy_manager_impl_unittest.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/mac/managed_preference_policy_manager_impl.h"
+#include "chrome/updater/policy/mac/managed_preference_policy_manager_impl.h"
 
 #include "base/mac/scoped_nsobject.h"
 #include "chrome/updater/constants.h"
diff --git a/chrome/updater/mac/managed_preference_policy_manager_unittest.cc b/chrome/updater/policy/mac/managed_preference_policy_manager_unittest.cc
similarity index 84%
rename from chrome/updater/mac/managed_preference_policy_manager_unittest.cc
rename to chrome/updater/policy/mac/managed_preference_policy_manager_unittest.cc
index d7e74268..33ceec96 100644
--- a/chrome/updater/mac/managed_preference_policy_manager_unittest.cc
+++ b/chrome/updater/policy/mac/managed_preference_policy_manager_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/mac/managed_preference_policy_manager.h"
+#include "chrome/updater/policy/mac/managed_preference_policy_manager.h"
 
 #include <memory>
 
diff --git a/chrome/updater/policy_manager.cc b/chrome/updater/policy/manager.cc
similarity index 98%
rename from chrome/updater/policy_manager.cc
rename to chrome/updater/policy/manager.cc
index fed631d..34cc1a8b 100644
--- a/chrome/updater/policy_manager.cc
+++ b/chrome/updater/policy/manager.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 
 namespace updater {
 
diff --git a/chrome/updater/policy_manager.h b/chrome/updater/policy/manager.h
similarity index 100%
rename from chrome/updater/policy_manager.h
rename to chrome/updater/policy/manager.h
diff --git a/chrome/updater/policy_manager_unittest.cc b/chrome/updater/policy/manager_unittest.cc
similarity index 90%
rename from chrome/updater/policy_manager_unittest.cc
rename to chrome/updater/policy/manager_unittest.cc
index 298f014..3d4b24d 100644
--- a/chrome/updater/policy_manager_unittest.cc
+++ b/chrome/updater/policy/manager_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace updater {
diff --git a/chrome/updater/policy_service.cc b/chrome/updater/policy/service.cc
similarity index 97%
rename from chrome/updater/policy_service.cc
rename to chrome/updater/policy/service.cc
index 5a190dc..10dfc8a 100644
--- a/chrome/updater/policy_service.cc
+++ b/chrome/updater/policy/service.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/policy_service.h"
+#include "chrome/updater/policy/service.h"
 
 #include <algorithm>
 
@@ -13,9 +13,9 @@
 #include "build/build_config.h"
 
 #if defined(OS_WIN)
-#include "chrome/updater/win/group_policy_manager.h"
+#include "chrome/updater/policy/win/group_policy_manager.h"
 #elif defined(OS_MAC)
-#include "chrome/updater/mac/managed_preference_policy_manager.h"
+#include "chrome/updater/policy/mac/managed_preference_policy_manager.h"
 #endif
 
 namespace updater {
diff --git a/chrome/updater/policy_service.h b/chrome/updater/policy/service.h
similarity index 98%
rename from chrome/updater/policy_service.h
rename to chrome/updater/policy/service.h
index 0ec97b6..15daf60b 100644
--- a/chrome/updater/policy_service.h
+++ b/chrome/updater/policy/service.h
@@ -10,7 +10,7 @@
 #include <vector>
 
 #include "base/callback_forward.h"
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace updater {
diff --git a/chrome/updater/policy_service_unittest.cc b/chrome/updater/policy/service_unittest.cc
similarity index 98%
rename from chrome/updater/policy_service_unittest.cc
rename to chrome/updater/policy/service_unittest.cc
index 0eaebb5..4ff3513 100644
--- a/chrome/updater/policy_service_unittest.cc
+++ b/chrome/updater/policy/service_unittest.cc
@@ -7,8 +7,8 @@
 #include <utility>
 #include <vector>
 
-#include "chrome/updater/policy_manager.h"
-#include "chrome/updater/policy_service.h"
+#include "chrome/updater/policy/manager.h"
+#include "chrome/updater/policy/service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace updater {
diff --git a/chrome/updater/win/group_policy_manager.cc b/chrome/updater/policy/win/group_policy_manager.cc
similarity index 98%
rename from chrome/updater/win/group_policy_manager.cc
rename to chrome/updater/policy/win/group_policy_manager.cc
index cee913b..11ddbba 100644
--- a/chrome/updater/win/group_policy_manager.cc
+++ b/chrome/updater/policy/win/group_policy_manager.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/win/group_policy_manager.h"
+#include "chrome/updater/policy/win/group_policy_manager.h"
 
 #include <string>
 
 #include "base/strings/sys_string_conversions.h"
 #include "base/win/win_util.h"
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 #include "chrome/updater/win/constants.h"
 
 namespace updater {
diff --git a/chrome/updater/win/group_policy_manager.h b/chrome/updater/policy/win/group_policy_manager.h
similarity index 90%
rename from chrome/updater/win/group_policy_manager.h
rename to chrome/updater/policy/win/group_policy_manager.h
index d96839db..7245a67 100644
--- a/chrome/updater/win/group_policy_manager.h
+++ b/chrome/updater/policy/win/group_policy_manager.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_UPDATER_WIN_GROUP_POLICY_MANAGER_H_
-#define CHROME_UPDATER_WIN_GROUP_POLICY_MANAGER_H_
+#ifndef CHROME_UPDATER_POLICY_WIN_GROUP_POLICY_MANAGER_H_
+#define CHROME_UPDATER_POLICY_WIN_GROUP_POLICY_MANAGER_H_
 
 #include <memory>
 #include <string>
 
 #include "base/win/registry.h"
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/manager.h"
 
 namespace updater {
 
@@ -58,4 +58,4 @@
 
 }  // namespace updater
 
-#endif  // CHROME_UPDATER_WIN_GROUP_POLICY_MANAGER_H_
+#endif  // CHROME_UPDATER_POLICY_WIN_GROUP_POLICY_MANAGER_H_
diff --git a/chrome/updater/win/group_policy_manager_unittest.cc b/chrome/updater/policy/win/group_policy_manager_unittest.cc
similarity index 92%
rename from chrome/updater/win/group_policy_manager_unittest.cc
rename to chrome/updater/policy/win/group_policy_manager_unittest.cc
index 70ab7e4..6e1be2e4 100644
--- a/chrome/updater/win/group_policy_manager_unittest.cc
+++ b/chrome/updater/policy/win/group_policy_manager_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/updater/win/group_policy_manager.h"
+#include "chrome/updater/policy/win/group_policy_manager.h"
 
 #include <memory>
 
diff --git a/chrome/updater/win/BUILD.gn b/chrome/updater/win/BUILD.gn
index 94849fa..e731ac1 100644
--- a/chrome/updater/win/BUILD.gn
+++ b/chrome/updater/win/BUILD.gn
@@ -102,8 +102,6 @@
 
   sources = [
     "action_handler.cc",
-    "group_policy_manager.cc",
-    "group_policy_manager.h",
     "installer.cc",
     "installer.h",
     "net/net_util.cc",
@@ -161,6 +159,7 @@
     "//chrome/updater/app/server/win:updater_idl_idl",
     "//chrome/updater/app/server/win:updater_internal_idl_idl",
     "//chrome/updater/app/server/win:updater_legacy_idl_idl",
+    "//chrome/updater/policy",
     "//components/update_client",
     "//url:url",
   ]
@@ -225,7 +224,6 @@
   testonly = true
 
   sources = [
-    "group_policy_manager_unittest.cc",
     "installer_unittest.cc",
     "net/network_unittest.cc",
     "net/proxy_configuration_unittest.cc",
diff --git a/chrome/updater/win/net/proxy_configuration.cc b/chrome/updater/win/net/proxy_configuration.cc
index 8e9441b..7fb9686 100644
--- a/chrome/updater/win/net/proxy_configuration.cc
+++ b/chrome/updater/win/net/proxy_configuration.cc
@@ -9,7 +9,7 @@
 #include "base/win/scoped_handle.h"
 #include "base/win/windows_version.h"
 #include "chrome/updater/constants.h"
-#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy/service.h"
 #include "chrome/updater/win/net/net_util.h"
 #include "chrome/updater/win/net/proxy_info.h"
 #include "chrome/updater/win/net/scoped_winttp_proxy_info.h"
@@ -164,9 +164,10 @@
 }
 
 scoped_refptr<ProxyConfiguration> GetProxyConfiguration() {
-  std::unique_ptr<PolicyManagerInterface> policy_manager = GetPolicyManager();
+  std::unique_ptr<PolicyService> policy_service = GetUpdaterPolicyService();
+
   std::string policy_proxy_mode;
-  if (policy_manager->GetProxyMode(&policy_proxy_mode) &&
+  if (policy_service->GetProxyMode(nullptr, &policy_proxy_mode) &&
       policy_proxy_mode.compare(kProxyModeSystem) != 0) {
     DVLOG(3) << "Using policy proxy " << policy_proxy_mode;
     bool auto_detect = false;
@@ -176,7 +177,7 @@
 
     if (policy_proxy_mode.compare(kProxyModeFixedServers) == 0) {
       std::string policy_proxy_url;
-      if (!policy_manager->GetProxyServer(&policy_proxy_url)) {
+      if (!policy_service->GetProxyServer(nullptr, &policy_proxy_url)) {
         VLOG(1) << "Fixed server mode proxy has no URL specified.";
         is_policy_config_valid = false;
       } else {
@@ -184,7 +185,7 @@
       }
     } else if (policy_proxy_mode.compare(kProxyModePacScript) == 0) {
       std::string policy_pac_url;
-      if (!policy_manager->GetProxyServer(&policy_pac_url)) {
+      if (!policy_service->GetProxyServer(nullptr, &policy_pac_url)) {
         VLOG(1) << "PAC proxy policy has no PAC URL specified.";
         is_policy_config_valid = false;
       } else {
diff --git a/chromecast/browser/cast_browser_context.cc b/chromecast/browser/cast_browser_context.cc
index 205a340..f7a73bc 100644
--- a/chromecast/browser/cast_browser_context.cc
+++ b/chromecast/browser/cast_browser_context.cc
@@ -52,7 +52,7 @@
 
 CastBrowserContext::~CastBrowserContext() {
   SimpleKeyMap::GetInstance()->Dissociate(this);
-  BrowserContext::NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
   ShutdownStoragePartitions();
   content::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
                                                  resource_context_.release());
diff --git a/chromecast/browser/webview/webview_browser_context.cc b/chromecast/browser/webview/webview_browser_context.cc
index 616e1774..51414ee 100644
--- a/chromecast/browser/webview/webview_browser_context.cc
+++ b/chromecast/browser/webview/webview_browser_context.cc
@@ -30,7 +30,7 @@
 }
 
 WebviewBrowserContext::~WebviewBrowserContext() {
-  BrowserContext::NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
   ShutdownStoragePartitions();
   BrowserContextDependencyManager::GetInstance()->DestroyBrowserContextServices(
       this);
diff --git a/chromeos/ime/BUILD.gn b/chromeos/ime/BUILD.gn
index a8086cd..7f07c98 100644
--- a/chromeos/ime/BUILD.gn
+++ b/chromeos/ime/BUILD.gn
@@ -2,10 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/python.gni")
-
-# TODO(crbug.com/1112471): Get this to run cleanly under Python 3.
-python2_action("gencode") {
+action("gencode") {
   script = "gen_input_methods.py"
   sources = [ "//chromeos/ime/input_methods.txt" ]
   outputs = [ "$target_gen_dir/input_methods.h" ]
diff --git a/chromeos/ime/gen_input_methods.py b/chromeos/ime/gen_input_methods.py
index 93a14b8..04eaf349 100755
--- a/chromeos/ime/gen_input_methods.py
+++ b/chromeos/ime/gen_input_methods.py
@@ -36,6 +36,8 @@
 
 """
 
+from __future__ import print_function
+
 import fileinput
 import re
 import sys
@@ -78,7 +80,7 @@
 
 def main(argv):
   if len(argv) != 3:
-    print 'Usage: gen_input_methods.py [input_methods.txt] [output]'
+    print('Usage: gen_input_methods.py [input_methods.txt] [output]')
     sys.exit(1)
   login_xkb_layout_ids = []
   for line in fileinput.input(sys.argv[1]):
diff --git a/chromeos/network/network_state.cc b/chromeos/network/network_state.cc
index 76ce1585..5269887 100644
--- a/chromeos/network/network_state.cc
+++ b/chromeos/network/network_state.cc
@@ -533,8 +533,6 @@
 network_config::mojom::ActivationStateType
 NetworkState::GetMojoActivationState() const {
   using network_config::mojom::ActivationStateType;
-  if (IsNonShillCellularNetwork())
-    return ActivationStateType::kNoService;
   if (activation_state_.empty())
     return ActivationStateType::kUnknown;
   if (activation_state_ == shill::kActivationStateActivated)
@@ -626,6 +624,7 @@
   new_state->iccid_ = iccid;
   new_state->eid_ = eid;
   new_state->guid_ = guid;
+  new_state->activation_state_ = shill::kActivationStateActivated;
   return new_state;
 }
 
diff --git a/chromeos/services/nearby/public/cpp/nearby_process_manager.h b/chromeos/services/nearby/public/cpp/nearby_process_manager.h
index 42734f2c..684c09a 100644
--- a/chromeos/services/nearby/public/cpp/nearby_process_manager.h
+++ b/chromeos/services/nearby/public/cpp/nearby_process_manager.h
@@ -62,6 +62,9 @@
   // shutting down.
   virtual std::unique_ptr<NearbyProcessReference> GetNearbyProcessReference(
       NearbyProcessStoppedCallback on_process_stopped_callback) = 0;
+
+ private:
+  using KeyedService::Shutdown;
 };
 
 std::ostream& operator<<(
diff --git a/codelabs/DIR_METADATA b/codelabs/DIR_METADATA
index 2217bb55..fba861d7 100644
--- a/codelabs/DIR_METADATA
+++ b/codelabs/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Infra>Documentation"
diff --git a/codelabs/cpp101/codelab.md b/codelabs/cpp101/codelab.md
index 13cd52b7..4c30533 100644
--- a/codelabs/cpp101/codelab.md
+++ b/codelabs/cpp101/codelab.md
@@ -9,10 +9,10 @@
 
 As always, consider the following resources as of primary importance:
 
--   [Coding Style](https://chromium.googlesource.com/chromium/src/+/master/styleguide/styleguide.md)
+-   [Coding Style](https://chromium.googlesource.com/chromium/src/+/main/styleguide/styleguide.md)
 -   [Callback<> and Bind()](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/callback.md)
--   [Threading and Tasks in Chrome](https://chromium.googlesource.com/chromium/src/+/master/docs/threading_and_tasks.md)
--   [Intro to Mojo & Services](https://chromium.googlesource.com/chromium/src.git/+/master/docs/mojo_and_services.md)
+-   [Threading and Tasks in Chrome](https://chromium.googlesource.com/chromium/src/+/main/docs/threading_and_tasks.md)
+-   [Intro to Mojo & Services](https://chromium.googlesource.com/chromium/src.git/+/main/docs/mojo_and_services.md)
 -   [Important Abstractions and Data Structures](https://sites.google.com/a/chromium.org/dev/developers/coding-style/important-abstractions-and-data-structures) (badly needs updating)
 
 This tutorial does not assume you have read any of the above,
@@ -49,8 +49,8 @@
 
 ### More information
 
-[Git Tips](https://chromium.googlesource.com/chromium/src.git/+/master/docs/git_tips.md)
-and [Git Cookbook](https://chromium.googlesource.com/chromium/src.git/+/master/docs/git_cookbook.md)
+[Git Tips](https://chromium.googlesource.com/chromium/src.git/+/main/docs/git_tips.md)
+and [Git Cookbook](https://chromium.googlesource.com/chromium/src.git/+/main/docs/git_cookbook.md)
 
 [Life of a Chromium Developer](https://docs.google.com/a/google.com/presentation/d/1abnqM9j6zFodPHA38JG1061rG2iGj_GABxEDgZsdbJg/)
 
@@ -193,7 +193,7 @@
 ## Part 3: Threads and task runners
 
 Chromium has a number of abstractions for sequencing and threading.
-[Threading and Tasks in Chrome](https://chromium.googlesource.com/chromium/src/+/master/docs/threading_and_tasks.md)
+[Threading and Tasks in Chrome](https://chromium.googlesource.com/chromium/src/+/main/docs/threading_and_tasks.md)
 is a must-read and go-to reference for anything related to tasks, thread pools,
 task runners, and more.
 
@@ -261,22 +261,22 @@
 
 ### More information
 
-[Threading and Tasks in Chrome](https://chromium.googlesource.com/chromium/src/+/master/docs/threading_and_tasks.md)
+[Threading and Tasks in Chrome](https://chromium.googlesource.com/chromium/src/+/main/docs/threading_and_tasks.md)
 
 ## Part 4: Mojo
 
 Mojo is Chromium's abstraction of IPC. Mojo allows for developers to easily
 connect interface clients and implementations across arbitrary intra- and
 inter-process boundaries. See the
-[Intro to Mojo and Services](https://chromium.googlesource.com/chromium/src.git/+/master/docs/mojo_and_services.md)
+[Intro to Mojo and Services](https://chromium.googlesource.com/chromium/src.git/+/main/docs/mojo_and_services.md)
 guide to get started.
 
  ### Exercise 4: Building a simple out-of-process service
 
-See the [building a simple out-of-process service](https://chromium.googlesource.com/chromium/src.git/+/master/docs/mojo_and_services.md#example_building-a-simple-out_of_process-service)
+See the [building a simple out-of-process service](https://chromium.googlesource.com/chromium/src.git/+/main/docs/mojo_and_services.md#example_building-a-simple-out_of_process-service)
 tutorial on using Mojo to define, hook up, and launch an out-of-process service.
 
 ### More Information
 
-[Mojo C++ Bindings API Docs](https://chromium.googlesource.com/chromium/src.git/+/master/mojo/public/cpp/bindings/README.md)
-[Mojo Docs](https://chromium.googlesource.com/chromium/src.git/+/master/mojo/README.md)
+[Mojo C++ Bindings API Docs](https://chromium.googlesource.com/chromium/src.git/+/main/mojo/public/cpp/bindings/README.md)
+[Mojo Docs](https://chromium.googlesource.com/chromium/src.git/+/main/mojo/README.md)
diff --git a/components/arc/mojom/payment_app.mojom b/components/arc/mojom/payment_app.mojom
index a52ba82..d206f0d4 100644
--- a/components/arc/mojom/payment_app.mojom
+++ b/components/arc/mojom/payment_app.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Next MinVersion: 3
+// Next MinVersion: 4
 
 module arc.mojom;
 
@@ -85,6 +85,11 @@
   // not currently need this parameter.
   [MinVersion=2]
   string? payment_request_id;
+
+  // Opaque, browser-generated identifier for this payment request. Used to
+  // identify a particular request across calls.
+  [MinVersion=3]
+  string? request_token;
 };
 
 // After the browser calls IsReadyToPay(), ARC sends back this result.
@@ -165,4 +170,14 @@
   [MinVersion=2]
   InvokePaymentApp@2(PaymentParameters parameters)
       => (InvokePaymentAppResult response);
+
+  // Requests to abort a previous payment flow (identified by |request_token|)
+  // which was opened with InvokePaymentApp().
+  //
+  // This may be called by the website if a payment should no longer be made
+  // (e.g., when an item goes out of stock), or by the browser if the payment is
+  // no longer available (e.g., the page was refreshed).
+  [MinVersion=3]
+  AbortPaymentApp@3(string request_token)
+      => (bool aborted);
 };
diff --git a/components/arc/pay/arc_payment_app_bridge.cc b/components/arc/pay/arc_payment_app_bridge.cc
index 33c435d0..c869700 100644
--- a/components/arc/pay/arc_payment_app_bridge.cc
+++ b/components/arc/pay/arc_payment_app_bridge.cc
@@ -100,4 +100,16 @@
   payment_app->InvokePaymentApp(std::move(parameters), std::move(callback));
 }
 
+void ArcPaymentAppBridge::AbortPaymentApp(const std::string& request_token,
+                                          AbortPaymentAppCallback callback) {
+  mojom::PaymentAppInstance* payment_app = ARC_GET_INSTANCE_FOR_METHOD(
+      arc_bridge_service_->payment_app(), AbortPaymentApp);
+  if (!payment_app) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  payment_app->AbortPaymentApp(request_token, std::move(callback));
+}
+
 }  // namespace arc
diff --git a/components/arc/pay/arc_payment_app_bridge.h b/components/arc/pay/arc_payment_app_bridge.h
index 2d8583bd..d0b5c55 100644
--- a/components/arc/pay/arc_payment_app_bridge.h
+++ b/components/arc/pay/arc_payment_app_bridge.h
@@ -28,6 +28,7 @@
       base::OnceCallback<void(mojom::IsReadyToPayResultPtr)>;
   using InvokePaymentAppCallback =
       base::OnceCallback<void(mojom::InvokePaymentAppResultPtr)>;
+  using AbortPaymentAppCallback = base::OnceCallback<void(bool)>;
 
   // Returns the instance owned by the given BrowserContext, or nullptr if the
   // browser |context| is not allowed to use ARC.
@@ -60,6 +61,10 @@
   void InvokePaymentApp(mojom::PaymentParametersPtr parameters,
                         InvokePaymentAppCallback callback);
 
+  // Aborts an existing TWA payment app flow.
+  void AbortPaymentApp(const std::string& request_token,
+                       AbortPaymentAppCallback callback);
+
  private:
   ArcBridgeService* const arc_bridge_service_;  // Owned by ArcServiceManager.
 };
diff --git a/components/arc/pay/arc_payment_app_bridge_unittest.cc b/components/arc/pay/arc_payment_app_bridge_unittest.cc
index 57876c4e..2017520e 100644
--- a/components/arc/pay/arc_payment_app_bridge_unittest.cc
+++ b/components/arc/pay/arc_payment_app_bridge_unittest.cc
@@ -13,6 +13,7 @@
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace arc {
 namespace {
@@ -39,10 +40,13 @@
     invoke_app_ = std::move(response);
   }
 
+  void OnAbortPaymentAppResponse(bool response) { abort_app_ = response; }
+
   ArcPaymentAppBridgeTestSupport support_;
   mojom::IsPaymentImplementedResultPtr is_implemented_;
   mojom::IsReadyToPayResultPtr is_ready_to_pay_;
   mojom::InvokePaymentAppResultPtr invoke_app_;
+  absl::optional<bool> abort_app_;
 };
 
 TEST_F(ArcPaymentAppBridgeTest, UnableToConnectInIsImplemented) {
@@ -305,5 +309,41 @@
   EXPECT_EQ("Error message.", invoke_app_->get_error());
 }
 
+TEST_F(ArcPaymentAppBridgeTest, UnableToConnectAbortPaymentApp) {
+  // Intentionally do not set an instance.
+
+  EXPECT_CALL(*support_.instance(), AbortPaymentApp(testing::_, testing::_))
+      .Times(0);
+
+  ArcPaymentAppBridge::GetForBrowserContextForTesting(support_.context())
+      ->AbortPaymentApp(
+          "some token",
+          base::BindOnce(&ArcPaymentAppBridgeTest::OnAbortPaymentAppResponse,
+                         base::Unretained(this)));
+
+  ASSERT_TRUE(abort_app_.has_value());
+  ASSERT_FALSE(abort_app_.value());
+}
+
+TEST_F(ArcPaymentAppBridgeTest, AbortPaymentAppOK) {
+  auto scoped_set_instance = support_.CreateScopedSetInstance();
+
+  EXPECT_CALL(*support_.instance(), AbortPaymentApp(testing::_, testing::_))
+      .WillOnce(testing::Invoke(
+          [](const std::string& request_token,
+             ArcPaymentAppBridge::AbortPaymentAppCallback callback) {
+            std::move(callback).Run(true);
+          }));
+
+  ArcPaymentAppBridge::GetForBrowserContextForTesting(support_.context())
+      ->AbortPaymentApp(
+          "some token",
+          base::BindOnce(&ArcPaymentAppBridgeTest::OnAbortPaymentAppResponse,
+                         base::Unretained(this)));
+
+  ASSERT_TRUE(abort_app_.has_value());
+  ASSERT_TRUE(abort_app_.value());
+}
+
 }  // namespace
 }  // namespace arc
diff --git a/components/arc/test/arc_payment_app_bridge_test_support.h b/components/arc/test/arc_payment_app_bridge_test_support.h
index 7c14fa2..5315566 100644
--- a/components/arc/test/arc_payment_app_bridge_test_support.h
+++ b/components/arc/test/arc_payment_app_bridge_test_support.h
@@ -44,6 +44,9 @@
     MOCK_METHOD2(InvokePaymentApp,
                  void(mojom::PaymentParametersPtr,
                       ArcPaymentAppBridge::InvokePaymentAppCallback));
+    MOCK_METHOD2(AbortPaymentApp,
+                 void(const std::string&,
+                      ArcPaymentAppBridge::AbortPaymentAppCallback));
   };
 
   // Sets up the payment_app.mojom connection in the constructor and disconnects
diff --git a/components/autofill/core/browser/autofill_client.cc b/components/autofill/core/browser/autofill_client.cc
index f8ffc93..a13ad261 100644
--- a/components/autofill/core/browser/autofill_client.cc
+++ b/components/autofill/core/browser/autofill_client.cc
@@ -66,11 +66,10 @@
   // ChromeAutofillClient (Chrome Desktop and Clank) implements this.
 }
 
-void AutofillClient::ShowVirtualCardManualFallbackBubble(
-    const CreditCard* credit_card,
-    const std::u16string& cvc) {
+void AutofillClient::OnVirtualCardFetched(const CreditCard* credit_card,
+                                          const std::u16string& cvc) {
   // This is overridden by platform subclasses. Currently only
-  // ChromeAutofillClient (Chrome Desktop) implements this.
+  // ChromeAutofillClient (Chrome Desktop & Android) implements this.
 }
 
 bool AutofillClient::IsAutofillAssistantShowing() {
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 6eedae2..98c2b6c 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -545,11 +545,10 @@
   virtual void ShowOfferNotificationIfApplicable(
       const AutofillOfferData* offer);
 
-  // Shows the manual fallback bubble and displya card information in
-  // |credit_card| and |cvc|.
-  virtual void ShowVirtualCardManualFallbackBubble(
-      const CreditCard* credit_card,
-      const std::u16string& cvc);
+  // Indicates that the virtual card was fetched in order to allow the user to
+  // manually fill payment form with the fetched |credit_card| and |cvc|.
+  virtual void OnVirtualCardFetched(const CreditCard* credit_card,
+                                    const std::u16string& cvc);
 
   // Returns true if the Autofill Assistant UI is currently being shown.
   virtual bool IsAutofillAssistantShowing();
diff --git a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.cc b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.cc
index 6d56793..f5ca074d 100644
--- a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.cc
+++ b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.cc
@@ -9,7 +9,6 @@
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/browser/autofill_address_util.h"
-#include "components/autofill/core/browser/data_model/autofill_profile_comparator.h"
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/grit/components_scaled_resources.h"
 #include "components/infobars/core/infobar.h"
@@ -99,10 +98,10 @@
   return profile_.GetInfo(type, locale_);
 }
 
-base::flat_map<ServerFieldType, std::pair<std::u16string, std::u16string>>
+std::vector<ProfileValueDifference>
 AutofillSaveUpdateAddressProfileDelegateIOS::GetProfileDiff() const {
-  return AutofillProfileComparator::GetSettingsVisibleProfileDifferenceMap(
-      *GetProfile(), *GetOriginalProfile(), locale_);
+  return GetProfileDifferenceForUi(*GetProfile(), *GetOriginalProfile(),
+                                   locale_);
 }
 
 bool AutofillSaveUpdateAddressProfileDelegateIOS::EditAccepted() {
diff --git a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h
index ae3378a..037a82a5 100644
--- a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h
+++ b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h
@@ -11,6 +11,7 @@
 #include "base/containers/flat_map.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/autofill_profile_comparator.h"
 #include "components/infobars/core/confirm_infobar_delegate.h"
 
 namespace autofill {
@@ -54,10 +55,9 @@
   // Returns the data stored in the |profile_| corresponding to |type|.
   std::u16string GetProfileInfo(ServerFieldType type) const;
 
-  // Uses |AutofillProfileComparator::GetSettingsVisibleProfileDifferenceMap| to
-  // get profile difference map between |profile_| and |original_profile_|;
-  base::flat_map<ServerFieldType, std::pair<std::u16string, std::u16string>>
-  GetProfileDiff() const;
+  // Returns the profile difference map between |profile_| and
+  // |original_profile_|.
+  std::vector<ProfileValueDifference> GetProfileDiff() const;
 
   // Calls |RunSaveAddressProfilePromptCallback| with the kEditAccepted|
   // decision.
diff --git a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios_unittest.cc b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios_unittest.cc
index f34fb216..15d20e3 100644
--- a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios_unittest.cc
+++ b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios_unittest.cc
@@ -60,29 +60,4 @@
             std::u16string(u"John Doe, 666 Erebus St."));
 }
 
-// Tests that delegate returns the correct profile difference.
-TEST(AutofillSaveUpdateAddressProfileDelegateIOSTest, TestProfileDiff) {
-  AutofillProfile profile = test::GetFullProfile();
-  AutofillProfile original_profile = test::GetFullProfile2();
-  original_profile.SetInfo(NAME_FULL, u"John Doe", "en-US");
-  auto delegate = std::make_unique<AutofillSaveUpdateAddressProfileDelegateIOS>(
-      profile, &original_profile, /*locale=*/"en-US", base::DoNothing());
-
-  base::flat_map<ServerFieldType, std::pair<std::u16string, std::u16string>>
-      expected_difference;
-  expected_difference.insert({NAME_FULL, {u"John H. Doe", u"John Doe"}});
-  expected_difference.insert(
-      {EMAIL_ADDRESS, {u"johndoe@hades.com", u"jsmith@example.com"}});
-  expected_difference.insert(
-      {PHONE_HOME_WHOLE_NUMBER, {u"16502111111", u"13105557889"}});
-  expected_difference.insert({ADDRESS_HOME_CITY, {u"Elysium", u"Greensdale"}});
-  expected_difference.insert({ADDRESS_HOME_STATE, {u"CA", u"MI"}});
-  expected_difference.insert({ADDRESS_HOME_ZIP, {u"91111", u"48838"}});
-  expected_difference.insert({COMPANY_NAME, {u"Underworld", u"ACME"}});
-  expected_difference.insert(
-      {ADDRESS_HOME_STREET_ADDRESS,
-       {u"666 Erebus St.\nApt 8", u"123 Main Street\nUnit 1"}});
-  EXPECT_EQ(delegate->GetProfileDiff(), expected_difference);
-}
-
 }  // namespace autofill
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index 7f970d9..55789c8 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1471,11 +1471,10 @@
 
   DCHECK(credit_card);
 
-  // If synced down card is a virtual card, show a manual fallback bubble for
-  // it in addition to filling the card.
-  if (credit_card->record_type() == CreditCard::VIRTUAL_CARD) {
-    client()->ShowVirtualCardManualFallbackBubble(credit_card, cvc);
-  }
+  // If synced down card is a virtual card, let the client know so that it can
+  // show the UI to help user to manually fill the form, if needed.
+  if (credit_card->record_type() == CreditCard::VIRTUAL_CARD)
+    client()->OnVirtualCardFetched(credit_card, cvc);
 
   FillCreditCardForm(credit_card_query_id_, credit_card_form_,
                      credit_card_field_, *credit_card, cvc);
diff --git a/components/browser_ui/widget/android/BUILD.gn b/components/browser_ui/widget/android/BUILD.gn
index c574de52..ec8c8cd 100644
--- a/components/browser_ui/widget/android/BUILD.gn
+++ b/components/browser_ui/widget/android/BUILD.gn
@@ -270,6 +270,7 @@
     "java/src/org/chromium/components/browser_ui/widget/RadioButtonWithDescriptionAndAuxButtonTest.java",
     "java/src/org/chromium/components/browser_ui/widget/RadioButtonWithDescriptionLayoutTest.java",
     "java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditTextTest.java",
+    "java/src/org/chromium/components/browser_ui/widget/RadioButtonWithIconRenderTest.java",
     "java/src/org/chromium/components/browser_ui/widget/RoundedIconGeneratorTest.java",
     "java/src/org/chromium/components/browser_ui/widget/WrappingLayoutTest.java",
     "java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighterTest.java",
@@ -320,6 +321,7 @@
     "test/java/res/layout/radio_button_with_description_and_aux_button_test.xml",
     "test/java/res/layout/radio_button_with_description_layout_test.xml",
     "test/java/res/layout/radio_button_with_edit_text_test.xml",
+    "test/java/res/layout/radio_button_with_icon_render_test.xml",
     "test/java/res/values/strings.xml",
   ]
   deps = [
diff --git a/components/browser_ui/widget/android/java/res/layout/radio_button_with_description.xml b/components/browser_ui/widget/android/java/res/layout/radio_button_with_description.xml
index 97f6c58..dffe06b 100644
--- a/components/browser_ui/widget/android/java/res/layout/radio_button_with_description.xml
+++ b/components/browser_ui/widget/android/java/res/layout/radio_button_with_description.xml
@@ -24,11 +24,22 @@
             android:focusable="false"
             android:background="@null" />
 
+        <org.chromium.ui.widget.ChromeImageView
+            android:id="@+id/icon"
+            android:layout_width="@dimen/radio_button_with_description_icon_size"
+            android:layout_height="@dimen/radio_button_with_description_icon_size"
+            android:layout_marginEnd="16dp"
+            android:layout_toEndOf="@id/radio_button"
+            android:layout_centerVertical="true"
+            android:scaleType="fitCenter"
+            android:tint="@color/default_icon_color"
+            android:visibility="gone" />
+
         <TextView
             android:id="@+id/primary"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_toEndOf="@id/radio_button"
+            android:layout_toEndOf="@id/icon"
             android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
 
         <!-- This TextView is hidden if it has no text, so the initial visibility should be "gone". -->
diff --git a/components/browser_ui/widget/android/java/res/values/attrs.xml b/components/browser_ui/widget/android/java/res/values/attrs.xml
index fbfc960..5a0cd87 100644
--- a/components/browser_ui/widget/android/java/res/values/attrs.xml
+++ b/components/browser_ui/widget/android/java/res/values/attrs.xml
@@ -53,6 +53,7 @@
     <declare-styleable name="RadioButtonWithDescription">
         <attr name="primaryText" format="string" />
         <attr name="descriptionText" format="string" />
+        <attr name="iconSrc" format="reference" />
     </declare-styleable>
 
     <declare-styleable name="RadioButtonWithEditText">
diff --git a/components/browser_ui/widget/android/java/res/values/dimens.xml b/components/browser_ui/widget/android/java/res/values/dimens.xml
index 80a8990..5233ea8 100644
--- a/components/browser_ui/widget/android/java/res/values/dimens.xml
+++ b/components/browser_ui/widget/android/java/res/values/dimens.xml
@@ -26,6 +26,7 @@
     <!-- RadioButtonWithDescription -->
     <dimen name="radio_button_with_description_lateral_padding">16dp</dimen>
     <dimen name="radio_button_with_description_vertical_padding">10dp</dimen>
+    <dimen name="radio_button_with_description_icon_size">24dp</dimen>
 
     <!-- RadioButtonWithDescriptionAndAuxButton -->
     <dimen name="radio_button_with_description_and_aux_button_spacing">10dp</dimen>
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithDescription.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithDescription.java
index 143a520..faec3e02 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithDescription.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithDescription.java
@@ -6,6 +6,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -20,6 +21,9 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import org.chromium.ui.UiUtils;
+import org.chromium.ui.widget.ChromeImageView;
+
 import java.util.List;
 
 /**
@@ -48,6 +52,7 @@
  *      android:id="@+id/system_default"
  *      android:layout_width="match_parent"
  *      android:layout_height="wrap_content"
+ *      app:iconSrc="@drawable/ic_foo"    <-- optional -->
  *      app:primaryText="@string/feature_foo_option_one"
  *      app:descriptionText="@string/feature_foo_option_one_description" />
  * } </pre>
@@ -66,6 +71,7 @@
     }
 
     private RadioButton mRadioButton;
+    private ChromeImageView mIcon;
     private TextView mPrimary;
     private TextView mDescription;
 
@@ -133,6 +139,7 @@
      */
     protected void setViewsInternal() {
         mRadioButton = getRadioButtonView();
+        mIcon = getIcon();
         mPrimary = getPrimaryTextView();
         mDescription = getDescriptionTextView();
 
@@ -159,6 +166,13 @@
     }
 
     /**
+     * @return ChromeImageView inside this {@link RadioButtonWithDescription}.
+     */
+    protected ChromeImageView getIcon() {
+        return (ChromeImageView) findViewById(R.id.icon);
+    }
+
+    /**
      * @return TextView displayed as primary inside this {@link RadioButtonWithDescription}.
      */
     protected TextView getPrimaryTextView() {
@@ -188,6 +202,14 @@
         TypedArray a = getContext().getTheme().obtainStyledAttributes(
                 attrs, R.styleable.RadioButtonWithDescription, 0, 0);
 
+        Drawable iconDrawable = UiUtils.getDrawable(
+                getContext(), a, R.styleable.RadioButtonWithDescription_iconSrc);
+
+        if (iconDrawable != null) {
+            mIcon.setImageDrawable(iconDrawable);
+            mIcon.setVisibility(View.VISIBLE);
+        }
+
         String primaryText = a.getString(R.styleable.RadioButtonWithDescription_primaryText);
         if (primaryText != null) mPrimary.setText(primaryText);
 
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithIconRenderTest.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithIconRenderTest.java
new file mode 100644
index 0000000..c04f678
--- /dev/null
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithIconRenderTest.java
@@ -0,0 +1,91 @@
+// 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.components.browser_ui.widget;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
+import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
+import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
+import org.chromium.base.test.params.ParameterSet;
+import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.util.Feature;
+import org.chromium.components.browser_ui.widget.test.R;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.test.util.DummyUiActivityTestCase;
+import org.chromium.ui.test.util.NightModeTestUtils;
+import org.chromium.ui.test.util.RenderTestRule;
+
+import java.util.List;
+
+/**
+ * Render test for {@link RadioButtonWithDescription} with the icon.
+ */
+@RunWith(ParameterizedRunner.class)
+@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
+public class RadioButtonWithIconRenderTest extends DummyUiActivityTestCase {
+    @ClassParameter
+    private static List<ParameterSet> sClassParams =
+            new NightModeTestUtils.NightModeParams().getParameters();
+
+    @Rule
+    public RenderTestRule mRenderTestRule = RenderTestRule.Builder.withPublicCorpus().build();
+
+    private RadioButtonWithDescriptionLayout mLayout;
+
+    private RadioButtonWithDescription mRadioButtonWithIcon1;
+    private RadioButtonWithDescription mRadioButtonWithIcon2;
+    private RadioButtonWithDescription mRadioButtonWithIcon3;
+
+    private final int mFakeBgColor;
+
+    public RadioButtonWithIconRenderTest(boolean nightModeEnabled) {
+        mFakeBgColor = nightModeEnabled ? Color.BLACK : Color.WHITE;
+        NightModeTestUtils.setUpNightModeForDummyUiActivity(nightModeEnabled);
+        mRenderTestRule.setNightModeEnabled(nightModeEnabled);
+    }
+
+    @Override
+    public void setUpTest() throws Exception {
+        super.setUpTest();
+        Activity activity = getActivity();
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            View content = LayoutInflater.from(activity).inflate(
+                    R.layout.radio_button_with_icon_render_test, null, false);
+            activity.setContentView(content);
+
+            mLayout = content.findViewById(R.id.test_radio_button_layout);
+            mLayout.setBackgroundColor(mFakeBgColor);
+
+            mRadioButtonWithIcon1 = content.findViewById(R.id.test_radio_icon_1);
+            mRadioButtonWithIcon2 = content.findViewById(R.id.test_radio_icon_2);
+            mRadioButtonWithIcon3 = content.findViewById(R.id.test_radio_icon_3);
+        });
+
+        Assert.assertNotNull(mLayout);
+        Assert.assertNotNull(mRadioButtonWithIcon1);
+        Assert.assertNotNull(mRadioButtonWithIcon2);
+        Assert.assertNotNull(mRadioButtonWithIcon3);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"RenderTest", "RadioButton"})
+    public void testRadioButtonWithIcon() throws Exception {
+        mRenderTestRule.render(mRadioButtonWithIcon1, "test_radio_icon_1");
+        mRenderTestRule.render(mRadioButtonWithIcon2, "test_radio_icon_2");
+        mRenderTestRule.render(mRadioButtonWithIcon3, "test_radio_icon_3");
+    }
+}
\ No newline at end of file
diff --git a/components/browser_ui/widget/android/test/java/res/layout/radio_button_with_icon_render_test.xml b/components/browser_ui/widget/android/test/java/res/layout/radio_button_with_icon_render_test.xml
new file mode 100644
index 0000000..1d479a0c
--- /dev/null
+++ b/components/browser_ui/widget/android/test/java/res/layout/radio_button_with_icon_render_test.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
+        android:id="@+id/test_radio_button_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <!-- RadioButtonWithDescription - With icon and primary, without description. -->
+        <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
+              android:id="@+id/test_radio_icon_1"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              app:iconSrc="@drawable/test_ic_more_vert_black_24dp"
+              app:primaryText="@string/test_string" />
+
+        <!-- RadioButtonWithDescription - With icon, primary and description. -->
+        <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
+            android:id="@+id/test_radio_icon_2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:iconSrc="@drawable/test_ic_more_vert_black_24dp"
+            app:primaryText="@string/test_string"
+            app:descriptionText="@string/test_string" />
+
+        <!-- RadioButtonWithDescription - With background override. -->
+        <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
+            android:id="@+id/test_radio_icon_3"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/background_color_non_empty"
+            app:iconSrc="@drawable/test_ic_more_vert_black_24dp"
+            app:primaryText="@string/test_string"
+            app:descriptionText="@string/test_string" />
+
+    </org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/components/full_restore/arc_save_handler.cc b/components/full_restore/arc_save_handler.cc
index 45480cd..51f28af3 100644
--- a/components/full_restore/arc_save_handler.cc
+++ b/components/full_restore/arc_save_handler.cc
@@ -19,7 +19,7 @@
 // Repeat timer interval between each checking that whether a task is created
 // for each app launching.
 constexpr base::TimeDelta kCheckCycleInterval =
-    base::TimeDelta::FromSeconds(30);
+    base::TimeDelta::FromSeconds(600);
 
 }  // namespace
 
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 f53808c6..7ec3817 100644
--- a/components/full_restore/full_restore_read_and_save_unittest.cc
+++ b/components/full_restore/full_restore_read_and_save_unittest.cc
@@ -116,11 +116,11 @@
     if (it == session_id_to_app_launch_info.end())
       return;
 
-    // If there is no task created for the session id in 30 seconds, the session
-    // id record is removed. So set the record time as 31 seconds ago, so that
-    // CheckTasksForAppLaunching can remove the session id record to simulate
-    // the task is not created for the session id.
-    it->second.second = it->second.second - base::TimeDelta::FromSeconds(31);
+    // If there is no task created for the session id in 600 seconds, the
+    // session id record is removed. So set the record time as 601 seconds ago,
+    // so that CheckTasksForAppLaunching can remove the session id record to
+    // simulate the task is not created for the session id.
+    it->second.second = it->second.second - base::TimeDelta::FromSeconds(601);
   }
 
   base::RepeatingTimer* GetArcCheckTimer() {
diff --git a/components/keyed_service/core/keyed_service.h b/components/keyed_service/core/keyed_service.h
index 2bd32cf..df300b3 100644
--- a/components/keyed_service/core/keyed_service.h
+++ b/components/keyed_service/core/keyed_service.h
@@ -30,6 +30,8 @@
   virtual ~KeyedService();
 
   // The first pass is to call Shutdown on a KeyedService.
+  // Shutdown will be called automatically for you. Don't directly invoke this
+  // unless you have a specific reason and understand the implications.
   virtual void Shutdown();
 
  private:
diff --git a/components/os_crypt/key_storage_config_linux.h b/components/os_crypt/key_storage_config_linux.h
index a8566047..72c16682 100644
--- a/components/os_crypt/key_storage_config_linux.h
+++ b/components/os_crypt/key_storage_config_linux.h
@@ -26,6 +26,12 @@
   std::string store;
   // The product name to use for permission prompts.
   std::string product_name;
+  // The application name to store the key under. For Chromium/Chrome builds
+  // leave this unset and it will default correctly.  This config option is
+  // for embedders to provide their application name in place of "Chromium".
+  // Only used when the allow_runtime_configurable_key_storage feature is
+  // enabled.
+  std::string application_name;
   // A runner on the main thread for gnome-keyring to be called from.
   // TODO(crbug/466975): Libsecret and KWallet don't need this. We can remove
   // this when we stop supporting keyring.
diff --git a/components/os_crypt/key_storage_keyring.cc b/components/os_crypt/key_storage_keyring.cc
index 29720b8e..3ad654c 100644
--- a/components/os_crypt/key_storage_keyring.cc
+++ b/components/os_crypt/key_storage_keyring.cc
@@ -15,12 +15,6 @@
 
 namespace {
 
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-const char kApplicationName[] = "chrome";
-#else
-const char kApplicationName[] = "chromium";
-#endif
-
 const GnomeKeyringPasswordSchema kSchema = {
     GNOME_KEYRING_ITEM_GENERIC_SECRET,
     {{"application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING}, {nullptr}}};
@@ -28,8 +22,10 @@
 }  // namespace
 
 KeyStorageKeyring::KeyStorageKeyring(
-    scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner)
-    : main_thread_runner_(main_thread_runner) {}
+    scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner,
+    std::string application_name)
+    : main_thread_runner_(main_thread_runner),
+      application_name_(std::move(application_name)) {}
 
 KeyStorageKeyring::~KeyStorageKeyring() {}
 
@@ -49,7 +45,8 @@
   gchar* password_c = nullptr;
   GnomeKeyringResult result =
       GnomeKeyringLoader::gnome_keyring_find_password_sync_ptr(
-          &kSchema, &password_c, "application", kApplicationName, nullptr);
+          &kSchema, &password_c, "application", application_name_.c_str(),
+          nullptr);
   if (result == GNOME_KEYRING_RESULT_OK) {
     password = password_c;
     GnomeKeyringLoader::gnome_keyring_free_password_ptr(password_c);
@@ -71,7 +68,7 @@
   GnomeKeyringResult result =
       GnomeKeyringLoader::gnome_keyring_store_password_sync_ptr(
           &kSchema, nullptr /* default keyring */, KeyStorageLinux::kKey,
-          password.c_str(), "application", kApplicationName, nullptr);
+          password.c_str(), "application", application_name_.c_str(), nullptr);
   if (result != GNOME_KEYRING_RESULT_OK) {
     VLOG(1) << "OSCrypt failed to store generated password to gnome-keyring";
     return absl::nullopt;
diff --git a/components/os_crypt/key_storage_keyring.h b/components/os_crypt/key_storage_keyring.h
index 26a3f58..598c8b8a 100644
--- a/components/os_crypt/key_storage_keyring.h
+++ b/components/os_crypt/key_storage_keyring.h
@@ -20,8 +20,9 @@
 // Specialisation of KeyStorageLinux that uses Libsecret.
 class COMPONENT_EXPORT(OS_CRYPT) KeyStorageKeyring : public KeyStorageLinux {
  public:
-  explicit KeyStorageKeyring(
-      scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner);
+  KeyStorageKeyring(
+      scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner,
+      std::string application_name);
   ~KeyStorageKeyring() override;
 
  protected:
@@ -37,6 +38,8 @@
   // Keyring calls need to originate from the main thread.
   scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner_;
 
+  const std::string application_name_;
+
   DISALLOW_COPY_AND_ASSIGN(KeyStorageKeyring);
 };
 
diff --git a/components/os_crypt/key_storage_keyring_unittest.cc b/components/os_crypt/key_storage_keyring_unittest.cc
index aba3197..93fc216 100644
--- a/components/os_crypt/key_storage_keyring_unittest.cc
+++ b/components/os_crypt/key_storage_keyring_unittest.cc
@@ -130,7 +130,7 @@
 };
 
 GnomeKeyringTest::GnomeKeyringTest()
-    : task_runner_(new base::TestSimpleTaskRunner()), keyring_(task_runner_) {
+    : task_runner_(new base::TestSimpleTaskRunner()), keyring_(task_runner_, "chromium") {
   MockGnomeKeyringLoader::ResetForOSCrypt();
 }
 
diff --git a/components/os_crypt/key_storage_libsecret.cc b/components/os_crypt/key_storage_libsecret.cc
index 0857cc1..ccc6052 100644
--- a/components/os_crypt/key_storage_libsecret.cc
+++ b/components/os_crypt/key_storage_libsecret.cc
@@ -14,12 +14,6 @@
 
 namespace {
 
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-const char kApplicationName[] = "chrome";
-#else
-const char kApplicationName[] = "chromium";
-#endif
-
 const SecretSchema kKeystoreSchemaV2 = {
     "chrome_libsecret_os_crypt_password_v2",
     SECRET_SCHEMA_DONT_MATCH_NAME,
@@ -64,6 +58,9 @@
 
 }  // namespace
 
+KeyStorageLibsecret::KeyStorageLibsecret(std::string application_name)
+    : application_name_(std::move(application_name)) {}
+
 absl::optional<std::string>
 KeyStorageLibsecret::AddRandomPasswordInLibsecret() {
   std::string password;
@@ -71,7 +68,7 @@
   GError* error = nullptr;
   bool success = LibsecretLoader::secret_password_store_sync(
       &kKeystoreSchemaV2, nullptr, KeyStorageLinux::kKey, password.c_str(),
-      nullptr, &error, "application", kApplicationName, nullptr);
+      nullptr, &error, "application", application_name_.c_str(), nullptr);
   if (error) {
     VLOG(1) << "Libsecret lookup failed: " << error->message;
     g_error_free(error);
@@ -88,7 +85,7 @@
 
 absl::optional<std::string> KeyStorageLibsecret::GetKeyImpl() {
   LibsecretAttributesBuilder attrs;
-  attrs.Append("application", kApplicationName);
+  attrs.Append("application", application_name_);
 
   LibsecretLoader::SearchHelper helper;
   helper.Search(&kKeystoreSchemaV2, attrs.Get(),
diff --git a/components/os_crypt/key_storage_libsecret.h b/components/os_crypt/key_storage_libsecret.h
index 4759d07..5292e21 100644
--- a/components/os_crypt/key_storage_libsecret.h
+++ b/components/os_crypt/key_storage_libsecret.h
@@ -15,7 +15,7 @@
 // Specialisation of KeyStorageLinux that uses Libsecret.
 class COMPONENT_EXPORT(OS_CRYPT) KeyStorageLibsecret : public KeyStorageLinux {
  public:
-  KeyStorageLibsecret() = default;
+  explicit KeyStorageLibsecret(std::string application_name);
   ~KeyStorageLibsecret() override = default;
 
  protected:
@@ -26,6 +26,8 @@
  private:
   absl::optional<std::string> AddRandomPasswordInLibsecret();
 
+  const std::string application_name_;
+
   DISALLOW_COPY_AND_ASSIGN(KeyStorageLibsecret);
 };
 
diff --git a/components/os_crypt/key_storage_libsecret_unittest.cc b/components/os_crypt/key_storage_libsecret_unittest.cc
index ebe9a6b..a17bbc1f 100644
--- a/components/os_crypt/key_storage_libsecret_unittest.cc
+++ b/components/os_crypt/key_storage_libsecret_unittest.cc
@@ -236,7 +236,7 @@
 };
 
 TEST_F(LibsecretTest, LibsecretRepeats) {
-  KeyStorageLibsecret libsecret;
+  KeyStorageLibsecret libsecret("chromium");
   MockLibsecretLoader::ResetForOSCrypt();
   g_password_store.Pointer()->SetPassword("initial password");
   absl::optional<std::string> password = libsecret.GetKey();
@@ -248,7 +248,7 @@
 }
 
 TEST_F(LibsecretTest, LibsecretCreatesRandomised) {
-  KeyStorageLibsecret libsecret;
+  KeyStorageLibsecret libsecret("chromium");
   MockLibsecretLoader::ResetForOSCrypt();
   absl::optional<std::string> password = libsecret.GetKey();
   MockLibsecretLoader::ResetForOSCrypt();
diff --git a/components/os_crypt/key_storage_linux.cc b/components/os_crypt/key_storage_linux.cc
index 8feb147c..53b1903 100644
--- a/components/os_crypt/key_storage_linux.cc
+++ b/components/os_crypt/key_storage_linux.cc
@@ -11,6 +11,7 @@
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/nix/xdg_util.h"
+#include "base/no_destructor.h"
 #include "base/sequenced_task_runner.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task_runner_util.h"
@@ -147,12 +148,29 @@
 std::unique_ptr<KeyStorageLinux> KeyStorageLinux::CreateServiceInternal(
     os_crypt::SelectedLinuxBackend selected_backend,
     const os_crypt::Config& config) {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  static const base::NoDestructor<std::string> kDefaultApplicationName("chrome");
+#else
+  static const base::NoDestructor<std::string> kDefaultApplicationName("chromium");
+#endif
+
   std::unique_ptr<KeyStorageLinux> key_storage;
 
+#if defined(USE_LIBSECRET) || defined(USE_KEYRING)
+#if defined(ALLOW_RUNTIME_CONFIGURABLE_KEY_STORAGE)
+  std::string application_name = config.application_name;
+  if (application_name.empty()) {
+    application_name = *kDefaultApplicationName;
+  }
+#else
+  std::string application_name = *kDefaultApplicationName;
+#endif
+#endif
+
 #if defined(USE_LIBSECRET)
   if (selected_backend == os_crypt::SelectedLinuxBackend::GNOME_ANY ||
       selected_backend == os_crypt::SelectedLinuxBackend::GNOME_LIBSECRET) {
-    key_storage = std::make_unique<KeyStorageLibsecret>();
+    key_storage = std::make_unique<KeyStorageLibsecret>(std::move(application_name));
     if (key_storage->WaitForInitOnTaskRunner()) {
       VLOG(1) << "OSCrypt using Libsecret as backend.";
       return key_storage;
@@ -164,8 +182,8 @@
 #if defined(USE_KEYRING)
   if (selected_backend == os_crypt::SelectedLinuxBackend::GNOME_ANY ||
       selected_backend == os_crypt::SelectedLinuxBackend::GNOME_KEYRING) {
-    key_storage =
-        std::make_unique<KeyStorageKeyring>(config.main_thread_runner);
+    key_storage = std::make_unique<KeyStorageKeyring>(config.main_thread_runner,
+                                                      std::move(application_name));
     if (key_storage->WaitForInitOnTaskRunner()) {
       VLOG(1) << "OSCrypt using Keyring as backend.";
       return key_storage;
diff --git a/components/page_load_metrics/browser/layout_shift_normalization.cc b/components/page_load_metrics/browser/layout_shift_normalization.cc
index 3ec674bc..d6df8846 100644
--- a/components/page_load_metrics/browser/layout_shift_normalization.cc
+++ b/components/page_load_metrics/browser/layout_shift_normalization.cc
@@ -25,7 +25,7 @@
 void LayoutShiftNormalization::AddNewLayoutShifts(
     const std::vector<page_load_metrics::mojom::LayoutShiftPtr>& new_shifts,
     base::TimeTicks current_time,
-    double cumulative_layout_shift_score) {
+    float cumulative_layout_shift_score) {
   if (new_shifts.empty() || normalized_cls_data_.data_tainted)
     return;
 
@@ -115,7 +115,7 @@
     std::vector<std::pair<base::TimeTicks, double>>::const_iterator begin,
     std::vector<std::pair<base::TimeTicks, double>>::const_iterator end,
     std::vector<base::TimeTicks>& input_timestamps,
-    double& max_score,
+    float& max_score,
     uint32_t& count) {
   for (auto it = begin; it != end; ++it) {
     if ((it->first - session_window->last_time > gap) ||
@@ -156,8 +156,8 @@
     std::vector<std::pair<base::TimeTicks, double>>::const_iterator
         first_non_stale,
     std::vector<std::pair<base::TimeTicks, double>>::const_iterator last,
-    double cumulative_layout_shift_score) {
-  double dummy_max = 0.0;
+    float cumulative_layout_shift_score) {
+  float dummy_max = 0.0;
   uint32_t dummy_count = 0;
   std::vector<base::TimeTicks> dummy_input_timestamps;
   // Update Sliding Windows.
diff --git a/components/page_load_metrics/browser/layout_shift_normalization.h b/components/page_load_metrics/browser/layout_shift_normalization.h
index f202d3ed..4f70683 100644
--- a/components/page_load_metrics/browser/layout_shift_normalization.h
+++ b/components/page_load_metrics/browser/layout_shift_normalization.h
@@ -26,7 +26,7 @@
   void AddNewLayoutShifts(
       const std::vector<page_load_metrics::mojom::LayoutShiftPtr>& new_shifts,
       base::TimeTicks current_time,
-      /*Whole page CLS*/ double cumulative_layout_shift_score);
+      /*Whole page CLS*/ float cumulative_layout_shift_score);
 
   void ClearAllLayoutShifts();
 
@@ -39,7 +39,7 @@
   struct SessionWindow {
     base::TimeTicks start_time;
     base::TimeTicks last_time;
-    double layout_shift_score = 0.0;
+    float layout_shift_score = 0.0;
   };
 
   void UpdateWindowCLS(
@@ -48,7 +48,7 @@
       std::vector<std::pair<base::TimeTicks, double>>::const_iterator
           first_non_stale,
       std::vector<std::pair<base::TimeTicks, double>>::const_iterator last,
-      double cumulative_layout_shift_score);
+      float cumulative_layout_shift_score);
 
   void UpdateSlidingWindow(
       std::vector<SlidingWindow>* sliding_windows,
@@ -65,7 +65,7 @@
       std::vector<std::pair<base::TimeTicks, double>>::const_iterator begin,
       std::vector<std::pair<base::TimeTicks, double>>::const_iterator end,
       std::vector<base::TimeTicks>& input_timestamps,
-      double& max_score,
+      float& max_score,
       uint32_t& count);
 
   // CLS normalization
@@ -87,7 +87,7 @@
   // A new input in non-stale data can split the session window and make the
   // max_cls smaller. We need to store the "max_cls" calculated by the stale
   // data.
-  double potential_max_cls_session_by_inputs_gap1000ms_max5000ms_ = 0.0;
+  float potential_max_cls_session_by_inputs_gap1000ms_max5000ms_ = 0.0;
   uint32_t session_gap5000ms_count_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(LayoutShiftNormalization);
diff --git a/components/page_load_metrics/browser/layout_shift_normalization_unittest.cc b/components/page_load_metrics/browser/layout_shift_normalization_unittest.cc
index 857f9cd3..53617ff 100644
--- a/components/page_load_metrics/browser/layout_shift_normalization_unittest.cc
+++ b/components/page_load_metrics/browser/layout_shift_normalization_unittest.cc
@@ -44,7 +44,7 @@
 
  private:
   page_load_metrics::LayoutShiftNormalization layout_shift_normalization_;
-  double cumulative_layoutshift_score_ = 0.0;
+  float cumulative_layoutshift_score_ = 0.0;
 };
 
 TEST_F(LayoutShiftNormalizationTest, MultipleShifts) {
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer.h b/components/page_load_metrics/browser/page_load_metrics_observer.h
index c67e356e..1be339f 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer.h
@@ -144,20 +144,20 @@
 
   // Maximum CLS of session windows. The gap between two consecutive shifts is
   // not bigger than 1000ms and the maximum window size is 5000ms.
-  double session_windows_gap1000ms_max5000ms_max_cls = 0.0;
+  float session_windows_gap1000ms_max5000ms_max_cls = 0.0;
 
   // Maximum CLS of session windows. The gap between two consecutive shifts is
   // not bigger than 1000ms.
-  double session_windows_gap1000ms_maxMax_max_cls = 0.0;
+  float session_windows_gap1000ms_maxMax_max_cls = 0.0;
 
   // The average CLS of session windows. The gap between two consecutive shifts
   // is not bigger than 5000ms.
-  double session_windows_gap5000ms_maxMax_average_cls = 0.0;
+  float session_windows_gap5000ms_maxMax_average_cls = 0.0;
 
   // Maximum CLS of session windows. The gap between two consecutive shifts is
   // not bigger than 1000ms or segmented by a user input. The maximum window
   // size is 5000ms.
-  double session_windows_by_inputs_gap1000ms_max5000ms_max_cls = 0.0;
+  float session_windows_by_inputs_gap1000ms_max5000ms_max_cls = 0.0;
 
   // If true, will not report the data in UKM.
   bool data_tainted = false;
diff --git a/components/paint_preview/browser/DEPS b/components/paint_preview/browser/DEPS
index 7fb995ef..fea23bd7 100644
--- a/components/paint_preview/browser/DEPS
+++ b/components/paint_preview/browser/DEPS
@@ -7,7 +7,6 @@
   "+components/ukm/content",
   "+components/ukm/test_ukm_recorder.h",
   "+content/public/browser",
-  "+content/public/common",
   "+content/public/test",
   "+services/metrics/public/cpp",
   "+sandbox/policy/sandbox_type.h",
diff --git a/components/paint_preview/browser/compositor_utils.cc b/components/paint_preview/browser/compositor_utils.cc
index 78e95b6..f8ce150a 100644
--- a/components/paint_preview/browser/compositor_utils.cc
+++ b/components/paint_preview/browser/compositor_utils.cc
@@ -17,19 +17,16 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/service_process_host.h"
-#include "content/public/common/content_features.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 namespace paint_preview {
 
 namespace {
 
-void BindDiscardableSharedMemoryManagerOnProcessThread(
+void BindDiscardableSharedMemoryManagerOnIOThread(
     mojo::PendingReceiver<
         discardable_memory::mojom::DiscardableSharedMemoryManager> receiver) {
-  DCHECK_CURRENTLY_ON(base::FeatureList::IsEnabled(features::kProcessHostOnUI)
-                          ? content::BrowserThread::UI
-                          : content::BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
       std::move(receiver));
 }
@@ -87,13 +84,10 @@
       discardable_memory_manager;
 
   // Set up the discardable memory manager.
-  auto task_runner = base::FeatureList::IsEnabled(features::kProcessHostOnUI)
-                         ? content::GetUIThreadTaskRunner({})
-                         : content::GetIOThreadTaskRunner({});
-  task_runner->PostTask(
+  content::GetIOThreadTaskRunner({})->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &BindDiscardableSharedMemoryManagerOnProcessThread,
+          &BindDiscardableSharedMemoryManagerOnIOThread,
           discardable_memory_manager.InitWithNewPipeAndPassReceiver()));
   collection->get()->SetDiscardableSharedMemoryManager(
       std::move(discardable_memory_manager));
diff --git a/components/payments/content/android_app_communication.h b/components/payments/content/android_app_communication.h
index a2a1dda..af99e9b 100644
--- a/components/payments/content/android_app_communication.h
+++ b/components/payments/content/android_app_communication.h
@@ -14,6 +14,7 @@
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 #include "base/supports_user_data.h"
+#include "base/unguessable_token.h"
 #include "components/payments/core/android_app_description.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -44,6 +45,8 @@
                               const std::string& payment_method_identifier,
                               const std::string& stringified_details)>;
 
+  using AbortPaymentAppCallback = base::OnceCallback<void(bool)>;
+
   // Returns a weak pointer to the instance of AndroidAppCommunication that is
   // owned by the given |context|, which should not be null.
   static base::WeakPtr<AndroidAppCommunication> GetForBrowserContext(
@@ -82,9 +85,14 @@
       const GURL& top_level_origin,
       const GURL& payment_request_origin,
       const std::string& payment_request_id,
+      const base::UnguessableToken& request_token,
       content::WebContents* web_contents,
       InvokePaymentAppCallback callback) = 0;
 
+  // Aborts a payment flow which was previously started with InvokePaymentApp().
+  virtual void AbortPaymentApp(const base::UnguessableToken& request_token,
+                               AbortPaymentAppCallback callback) = 0;
+
   // Allows usage of a test browser context.
   virtual void SetForTesting() = 0;
 
diff --git a/components/payments/content/android_app_communication_chrome_os.cc b/components/payments/content/android_app_communication_chrome_os.cc
index 5a060729..34e1aa19 100644
--- a/components/payments/content/android_app_communication_chrome_os.cc
+++ b/components/payments/content/android_app_communication_chrome_os.cc
@@ -279,6 +279,7 @@
                         const GURL& top_level_origin,
                         const GURL& payment_request_origin,
                         const std::string& payment_request_id,
+                        const base::UnguessableToken& request_token,
                         content::WebContents* web_contents,
                         InvokePaymentAppCallback callback) override {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -286,8 +287,8 @@
     // Create and register a token with ArcOverlayManager for the
     // browser window. Doing so is required to allow the Android Play Billing
     // interface to be overlaid on top of the browser window.
-    // TODO(b/172592701): Use base::UnguessableToken::Create().ToString() and
-    // send the same value to the Android service.
+    // TODO(crbug.com/1209716): Reuse the request_token for coordinating the
+    // overlay.
     std::string billing_token =
         payment_request_origin.spec() + "#" + payment_request_id;
     ash::ArcOverlayManager* const overlay_manager =
@@ -323,6 +324,7 @@
                               /*stringified_details=*/kEmptyDictionaryJson);
       return;
     }
+    parameters->request_token = request_token.ToString();
 
     payment_app_service->InvokePaymentApp(
         std::move(parameters),
@@ -330,6 +332,17 @@
                        std::move(overlay_state)));
   }
 
+  void AbortPaymentApp(const base::UnguessableToken& token,
+                       AbortPaymentAppCallback callback) override {
+    auto* payment_app_service = get_app_service_.Run(context());
+    if (!payment_app_service) {
+      std::move(callback).Run(false);
+      return;
+    }
+
+    payment_app_service->AbortPaymentApp(token.ToString(), std::move(callback));
+  }
+
   // AndroidAppCommunication implementation.
   void SetForTesting() override {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/components/payments/content/android_app_communication_stub.cc b/components/payments/content/android_app_communication_stub.cc
index 15120c2..cb695366 100644
--- a/components/payments/content/android_app_communication_stub.cc
+++ b/components/payments/content/android_app_communication_stub.cc
@@ -48,6 +48,7 @@
                         const GURL& top_level_origin,
                         const GURL& payment_request_origin,
                         const std::string& payment_request_id,
+                        const base::UnguessableToken& request_token,
                         content::WebContents* web_contents,
                         InvokePaymentAppCallback callback) override {
     std::move(callback).Run(errors::kUnableToInvokeAndroidPaymentApps,
@@ -56,6 +57,11 @@
                             /*stringified_details=*/"{}");
   }
 
+  void AbortPaymentApp(const base::UnguessableToken& request_token,
+                       AbortPaymentAppCallback callback) override {
+    std::move(callback).Run(false);
+  }
+
   // AndroidAppCommunication implementation.
   void SetForTesting() override {}
 
diff --git a/components/payments/content/android_app_communication_test_support.h b/components/payments/content/android_app_communication_test_support.h
index 22617d8..1b9794f6 100644
--- a/components/payments/content/android_app_communication_test_support.h
+++ b/components/payments/content/android_app_communication_test_support.h
@@ -90,6 +90,16 @@
       const std::string& payment_method_identifier,
       const std::string& stringified_details) = 0;
 
+  // Sets up the expectation that the test case will invoke a PAY activity, and
+  // then subsequently abort that payment. The invoke callback will be called
+  // when the payment is aborted with an error result, and then the abort will
+  // be reported as successful.
+  virtual void ExpectInvokeAndAbortPaymentApp() = 0;
+
+  // Sets up the expectation that the test case will not abort any payment
+  // flows.
+  virtual void ExpectNoAbortPaymentApp() = 0;
+
   // Returns the browser context to use.
   virtual content::BrowserContext* context() = 0;
 
diff --git a/components/payments/content/android_app_communication_test_support_chrome_os.cc b/components/payments/content/android_app_communication_test_support_chrome_os.cc
index 4377752..341a7f3 100644
--- a/components/payments/content/android_app_communication_test_support_chrome_os.cc
+++ b/components/payments/content/android_app_communication_test_support_chrome_os.cc
@@ -116,6 +116,33 @@
             }));
   }
 
+  void ExpectInvokeAndAbortPaymentApp() override {
+    EXPECT_CALL(*support_.instance(), InvokePaymentApp(testing::_, testing::_))
+        .WillOnce(testing::Invoke(
+            [this](
+                arc::mojom::PaymentParametersPtr parameters,
+                arc::ArcPaymentAppBridge::InvokePaymentAppCallback callback) {
+              pending_invoke_callback_ = std::move(callback);
+            }));
+
+    EXPECT_CALL(*support_.instance(), AbortPaymentApp(testing::_, testing::_))
+        .WillOnce(testing::Invoke(
+            [this](const std::string& request_token,
+                   arc::ArcPaymentAppBridge::AbortPaymentAppCallback callback) {
+              if (!pending_invoke_callback_.is_null()) {
+                std::move(pending_invoke_callback_)
+                    .Run(arc::mojom::InvokePaymentAppResult::NewError(
+                        "Payment was aborted."));
+              }
+              std::move(callback).Run(true);
+            }));
+  }
+
+  void ExpectNoAbortPaymentApp() override {
+    EXPECT_CALL(*support_.instance(), AbortPaymentApp(testing::_, testing::_))
+        .Times(0);
+  }
+
   content::BrowserContext* context() override { return support_.context(); }
 
  private:
@@ -147,6 +174,8 @@
   arc::ArcPaymentAppBridgeTestSupport support_;
   std::vector<std::unique_ptr<AndroidAppDescription>> apps_;
   ash::TestArcOverlayManager overlay_manager_;
+
+  arc::ArcPaymentAppBridge::InvokePaymentAppCallback pending_invoke_callback_;
 };
 
 }  // namespace
diff --git a/components/payments/content/android_app_communication_test_support_stub.cc b/components/payments/content/android_app_communication_test_support_stub.cc
index 93df4287..bf7d33f 100644
--- a/components/payments/content/android_app_communication_test_support_stub.cc
+++ b/components/payments/content/android_app_communication_test_support_stub.cc
@@ -45,6 +45,10 @@
       const std::string& payment_method_identifier,
       const std::string& stringified_details) override {}
 
+  void ExpectInvokeAndAbortPaymentApp() override {}
+
+  void ExpectNoAbortPaymentApp() override {}
+
   content::BrowserContext* context() override { return &context_; }
 
  private:
diff --git a/components/payments/content/android_app_communication_unittest.cc b/components/payments/content/android_app_communication_unittest.cc
index 8f1109a..c7075998 100644
--- a/components/payments/content/android_app_communication_unittest.cc
+++ b/components/payments/content/android_app_communication_unittest.cc
@@ -11,6 +11,7 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/unguessable_token.h"
 #include "components/payments/content/android_app_communication_test_support.h"
 #include "components/payments/core/android_app_description.h"
 #include "content/public/browser/web_contents.h"
@@ -472,7 +473,7 @@
       "com.example.app", "com.example.app.Activity", stringified_method_data,
       GURL("https://top-level-origin.com"),
       GURL("https://payment-request-origin.com"), "payment-request-id",
-      web_contents_,
+      base::UnguessableToken::Create(), web_contents_,
       base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
                      base::Unretained(this)));
 
@@ -497,7 +498,7 @@
       "com.example.app", "com.example.app.Activity", stringified_method_data,
       GURL("https://top-level-origin.com"),
       GURL("https://payment-request-origin.com"), "payment-request-id",
-      web_contents_,
+      base::UnguessableToken::Create(), web_contents_,
       base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
                      base::Unretained(this)));
 
@@ -530,7 +531,7 @@
       "com.example.app", "com.example.app.Activity", stringified_method_data,
       GURL("https://top-level-origin.com"),
       GURL("https://payment-request-origin.com"), "payment-request-id",
-      web_contents_,
+      base::UnguessableToken::Create(), web_contents_,
       base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
                      base::Unretained(this)));
 
@@ -566,7 +567,7 @@
       "com.example.app", "com.example.app.Activity", stringified_method_data,
       GURL("https://top-level-origin.com"),
       GURL("https://payment-request-origin.com"), "payment-request-id",
-      web_contents_,
+      base::UnguessableToken::Create(), web_contents_,
       base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
                      base::Unretained(this)));
 
@@ -599,7 +600,7 @@
       "com.example.app", "com.example.app.Activity", stringified_method_data,
       GURL("https://top-level-origin.com"),
       GURL("https://payment-request-origin.com"), "payment-request-id",
-      web_contents_,
+      base::UnguessableToken::Create(), web_contents_,
       base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
                      base::Unretained(this)));
 
@@ -632,7 +633,7 @@
       "com.example.app", "com.example.app.Activity", stringified_method_data,
       GURL("https://top-level-origin.com"),
       GURL("https://payment-request-origin.com"), "payment-request-id",
-      web_contents_,
+      base::UnguessableToken::Create(), web_contents_,
       base::BindOnce(&AndroidAppCommunicationTest::OnPaymentAppResponse,
                      base::Unretained(this)));
 
diff --git a/components/payments/content/android_payment_app.cc b/components/payments/content/android_payment_app.cc
index 6556205..60456849 100644
--- a/components/payments/content/android_payment_app.cc
+++ b/components/payments/content/android_payment_app.cc
@@ -32,7 +32,9 @@
       payment_request_id_(payment_request_id),
       description_(std::move(description)),
       communication_(communication),
-      frame_routing_id_(frame_routing_id) {
+      frame_routing_id_(frame_routing_id),
+      payment_app_token_(base::UnguessableToken::Create()),
+      payment_app_open_(false) {
   DCHECK(!payment_method_names.empty());
   DCHECK_EQ(payment_method_names.size(), stringified_method_data_->size());
   DCHECK_EQ(*payment_method_names.begin(),
@@ -45,7 +47,11 @@
   app_method_names_ = payment_method_names;
 }
 
-AndroidPaymentApp::~AndroidPaymentApp() = default;
+AndroidPaymentApp::~AndroidPaymentApp() {
+  if (payment_app_open_) {
+    AbortPaymentApp(base::DoNothing());
+  }
+}
 
 void AndroidPaymentApp::InvokePaymentApp(base::WeakPtr<Delegate> delegate) {
   // Browser is closing, so no need to invoke a callback.
@@ -61,10 +67,11 @@
   if (!web_contents)
     return;
 
+  payment_app_open_ = true;
   communication_->InvokePaymentApp(
       description_->package, description_->activities.front()->name,
       *stringified_method_data_, top_level_origin_, payment_request_origin_,
-      payment_request_id_, web_contents,
+      payment_request_id_, payment_app_token_, web_contents,
       base::BindOnce(&AndroidPaymentApp::OnPaymentAppResponse,
                      weak_ptr_factory_.GetWeakPtr(), delegate));
 }
@@ -155,6 +162,19 @@
 
 void AndroidPaymentApp::OnPaymentDetailsNotUpdated() {}
 
+void AndroidPaymentApp::AbortPaymentApp(
+    base::OnceCallback<void(bool)> abort_callback) {
+  // Browser is closing or no payment app active, so no need to invoke a
+  // callback.
+  if (!communication_ || !payment_app_open_)
+    return;
+
+  payment_app_open_ = false;
+
+  communication_->AbortPaymentApp(payment_app_token_,
+                                  std::move(abort_callback));
+}
+
 bool AndroidPaymentApp::IsPreferred() const {
   // This class used only on Chrome OS, where the only Android payment app
   // available is the trusted web application (TWA) that launched this instance
@@ -174,6 +194,7 @@
     bool is_activity_result_ok,
     const std::string& payment_method_identifier,
     const std::string& stringified_details) {
+  payment_app_open_ = false;
   if (!delegate)
     return;
 
diff --git a/components/payments/content/android_payment_app.h b/components/payments/content/android_payment_app.h
index 861e9b6..21f31116 100644
--- a/components/payments/content/android_payment_app.h
+++ b/components/payments/content/android_payment_app.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
+#include "base/unguessable_token.h"
 #include "components/payments/content/android_app_communication.h"
 #include "components/payments/content/payment_app.h"
 #include "components/payments/core/android_app_description.h"
@@ -74,6 +75,7 @@
   void UpdateWith(
       mojom::PaymentRequestDetailsUpdatePtr details_update) override;
   void OnPaymentDetailsNotUpdated() override;
+  void AbortPaymentApp(base::OnceCallback<void(bool)> abort_callback) override;
   bool IsPreferred() const override;
 
  private:
@@ -92,6 +94,13 @@
   base::WeakPtr<AndroidAppCommunication> communication_;
   content::GlobalFrameRoutingId frame_routing_id_;
 
+  // Token used to uniquely identify a particular payment app instance between
+  // Android and Chrome.
+  base::UnguessableToken payment_app_token_;
+  // True when InvokePaymentApp() has been called but no response has been
+  // received yet.
+  bool payment_app_open_;
+
   base::WeakPtrFactory<AndroidPaymentApp> weak_ptr_factory_{this};
 };
 
diff --git a/components/payments/content/android_payment_app_unittest.cc b/components/payments/content/android_payment_app_unittest.cc
index 55afa9b..21978b8 100644
--- a/components/payments/content/android_payment_app_unittest.cc
+++ b/components/payments/content/android_payment_app_unittest.cc
@@ -13,6 +13,7 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/test/bind.h"
 #include "components/payments/content/android_app_communication.h"
 #include "components/payments/content/android_app_communication_test_support.h"
 #include "components/payments/core/android_app_description.h"
@@ -171,5 +172,58 @@
   }
 }
 
+TEST_F(AndroidPaymentAppTest, AbortWithPaymentAppOpen) {
+  communication_ =
+      AndroidAppCommunication::GetForBrowserContext(support_->context());
+  communication_->SetForTesting();
+  scoped_initialization_ = support_->CreateScopedInitialization();
+
+  support_->ExpectInvokeAndAbortPaymentApp();
+
+  auto app = CreateAndroidPaymentApp(communication_, web_contents_);
+  app->InvokePaymentApp(/*delegate=*/weak_ptr_factory_.GetWeakPtr());
+
+  bool aborted = false;
+  app->AbortPaymentApp(base::BindLambdaForTesting(
+      [&aborted](bool abort_success) { aborted = abort_success; }));
+
+  if (support_->AreAndroidAppsSupportedOnThisPlatform()) {
+    EXPECT_EQ("Payment was aborted.", error_message_);
+    EXPECT_TRUE(aborted);
+  } else {
+    EXPECT_EQ("Unable to invoke Android apps.", error_message_);
+  }
+}
+
+TEST_F(AndroidPaymentAppTest, AbortWhenAppDestroyed) {
+  communication_ =
+      AndroidAppCommunication::GetForBrowserContext(support_->context());
+  communication_->SetForTesting();
+  scoped_initialization_ = support_->CreateScopedInitialization();
+
+  support_->ExpectInvokeAndAbortPaymentApp();
+
+  auto app = CreateAndroidPaymentApp(communication_, web_contents_);
+  app->InvokePaymentApp(/*delegate=*/weak_ptr_factory_.GetWeakPtr());
+  // Payment app will be aborted when |app| is destroyed.
+}
+
+TEST_F(AndroidPaymentAppTest, NoAbortWhenDestroyedWithCompletedFlow) {
+  communication_ =
+      AndroidAppCommunication::GetForBrowserContext(support_->context());
+  communication_->SetForTesting();
+  scoped_initialization_ = support_->CreateScopedInitialization();
+
+  support_->ExpectInvokePaymentAppAndRespond(
+      /*is_activity_result_ok=*/false,
+      /*payment_method_identifier=*/methods::kGooglePlayBilling,
+      /*stringified_details=*/"{}");
+  support_->ExpectNoAbortPaymentApp();
+
+  auto app = CreateAndroidPaymentApp(communication_, web_contents_);
+  app->InvokePaymentApp(/*delegate=*/weak_ptr_factory_.GetWeakPtr());
+  // Payment app will not be aborted when |app| is destroyed.
+}
+
 }  // namespace
 }  // namespace payments
diff --git a/components/printing/browser/print_composite_client.cc b/components/printing/browser/print_composite_client.cc
index 8acc4a8..df55bb2b 100644
--- a/components/printing/browser/print_composite_client.cc
+++ b/components/printing/browser/print_composite_client.cc
@@ -19,7 +19,6 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/service_process_host.h"
-#include "content/public/common/content_features.h"
 #include "printing/common/metafile_utils.h"
 #include "printing/printing_utils.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
@@ -63,12 +62,10 @@
   return content_frame_map;
 }
 
-void BindDiscardableSharedMemoryManagerOnProcessThread(
+void BindDiscardableSharedMemoryManagerOnIOThread(
     mojo::PendingReceiver<
         discardable_memory::mojom::DiscardableSharedMemoryManager> receiver) {
-  DCHECK_CURRENTLY_ON(base::FeatureList::IsEnabled(features::kProcessHostOnUI)
-                          ? content::BrowserThread::UI
-                          : content::BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
       std::move(receiver));
 }
@@ -373,13 +370,10 @@
 
   mojo::PendingRemote<discardable_memory::mojom::DiscardableSharedMemoryManager>
       discardable_memory_manager;
-  auto task_runner = base::FeatureList::IsEnabled(features::kProcessHostOnUI)
-                         ? content::GetUIThreadTaskRunner({})
-                         : content::GetIOThreadTaskRunner({});
-  task_runner->PostTask(
+  content::GetIOThreadTaskRunner({})->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &BindDiscardableSharedMemoryManagerOnProcessThread,
+          &BindDiscardableSharedMemoryManagerOnIOThread,
           discardable_memory_manager.InitWithNewPipeAndPassReceiver()));
   compositor_->SetDiscardableSharedMemoryManager(
       std::move(discardable_memory_manager));
diff --git a/components/printing/browser/print_manager.cc b/components/printing/browser/print_manager.cc
index 3e95b473..44ddf64 100644
--- a/components/printing/browser/print_manager.cc
+++ b/components/printing/browser/print_manager.cc
@@ -24,9 +24,8 @@
 
 void PrintManager::DidGetPrintedPagesCount(int32_t cookie,
                                            uint32_t number_pages) {
-  if (!IsValidCookie(cookie) || number_pages == 0)
-    return;
-
+  DCHECK_GT(cookie, 0);
+  DCHECK_GT(number_pages, 0u);
   number_pages_ = number_pages;
 }
 
diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn
index fb82ba643..9b87aa1 100644
--- a/components/resources/BUILD.gn
+++ b/components/resources/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//build/config/android/config.gni")
-import("//build/config/python.gni")
 import("//components/safe_browsing/buildflags.gni")
 import("//printing/buildflags/buildflags.gni")
 import("//tools/grit/grit_rule.gni")
@@ -84,8 +83,7 @@
   output_dir = "$root_gen_dir/components"
 }
 
-# TODO(crbug.com/1112471): Get this to run cleanly under Python 3.
-python2_action("about_credits") {
+action("about_credits") {
   script = "//tools/licenses.py"
   depfile = "$target_gen_dir/$target_name.d"
 
diff --git a/components/safe_browsing/DEPS b/components/safe_browsing/DEPS
index 09bc03f..09fb3f1 100644
--- a/components/safe_browsing/DEPS
+++ b/components/safe_browsing/DEPS
@@ -31,6 +31,8 @@
   "+testing/gtest",
   "+third_party/blink/public/common/loader/url_loader_throttle.h",
   "+third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h",
+  "+third_party/tflite-support",
+  "+third_party/tflite",
   "+third_party/protobuf",
   "+ui/base/resource/resource_bundle.h",
   "+ui/android/view_android.h",
diff --git a/components/safe_browsing/content/browser/BUILD.gn b/components/safe_browsing/content/browser/BUILD.gn
index 1eb4af6..070aa6f 100644
--- a/components/safe_browsing/content/browser/BUILD.gn
+++ b/components/safe_browsing/content/browser/BUILD.gn
@@ -16,6 +16,8 @@
     "threat_details_cache.h",
     "threat_details_history.cc",
     "threat_details_history.h",
+    "web_api_handshake_checker.cc",
+    "web_api_handshake_checker.h",
   ]
   deps = [
     "//components/back_forward_cache",
@@ -69,6 +71,7 @@
   sources = [
     "client_side_model_loader_unittest.cc",
     "client_side_phishing_model_unittest.cc",
+    "web_api_handshake_checker_unittest.cc",
   ]
 
   deps = [
@@ -77,9 +80,13 @@
     "//base:base",
     "//base/test:test_support",
     "//components/safe_browsing:buildflags",
+    "//components/safe_browsing/content/browser:browser",
     "//components/safe_browsing/core:client_model_proto",
     "//components/safe_browsing/core:csd_proto",
     "//components/safe_browsing/core:features",
+    "//components/safe_browsing/core/browser:browser",
+    "//components/safe_browsing/core/db:test_database_manager",
+    "//components/security_interstitials/core:unsafe_resource",
     "//components/variations",
     "//content/test:test_support",
     "//services/network:test_support",
diff --git a/components/safe_browsing/content/browser/web_api_handshake_checker.cc b/components/safe_browsing/content/browser/web_api_handshake_checker.cc
new file mode 100644
index 0000000..c27d442
--- /dev/null
+++ b/components/safe_browsing/content/browser/web_api_handshake_checker.cc
@@ -0,0 +1,133 @@
+// 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/content/browser/web_api_handshake_checker.h"
+
+#include "components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h"
+#include "components/safe_browsing/core/browser/url_checker_delegate.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/http/http_request_headers.h"
+
+namespace safe_browsing {
+
+class WebApiHandshakeChecker::CheckerOnIO
+    : public base::SupportsWeakPtr<WebApiHandshakeChecker::CheckerOnIO> {
+ public:
+  CheckerOnIO(base::WeakPtr<WebApiHandshakeChecker> handshake_checker,
+              GetDelegateCallback delegate_getter,
+              const GetWebContentsCallback& web_contents_getter,
+              int frame_tree_node_id)
+      : handshake_checker_(std::move(handshake_checker)),
+        delegate_getter_(std::move(delegate_getter)),
+        web_contents_getter_(web_contents_getter),
+        frame_tree_node_id_(frame_tree_node_id) {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+    DCHECK(handshake_checker_);
+    DCHECK(delegate_getter_);
+    DCHECK(web_contents_getter_);
+  }
+
+  void Check(const GURL& url) {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+    DCHECK(delegate_getter_);
+    DCHECK(web_contents_getter_);
+
+    scoped_refptr<UrlCheckerDelegate> url_checker_delegate =
+        std::move(delegate_getter_).Run();
+    bool skip_checks =
+        !url_checker_delegate ||
+        !url_checker_delegate->GetDatabaseManager()->IsSupported() ||
+        url_checker_delegate->ShouldSkipRequestCheck(
+            url, frame_tree_node_id_, /*render_process_id=*/-1,
+            /*render_frame_id=*/-1, /*originated_from_service_worker=*/false);
+    if (skip_checks) {
+      OnCompleteCheck(/*slow_check=*/false, /*proceed=*/true,
+                      /*showed_interstitial=*/false);
+      return;
+    }
+
+    url_checker_ = std::make_unique<SafeBrowsingUrlCheckerImpl>(
+        net::HttpRequestHeaders(), /*load_flags=*/0,
+        network::mojom::RequestDestination::kEmpty, /*has_user_gesture=*/false,
+        url_checker_delegate, web_contents_getter_,
+        /*real_time_lookup_enabled=*/false,
+        /*can_rt_check_subresource_url=*/false,
+        /*can_check_db=*/true, /*url_lookup_service=*/nullptr);
+    url_checker_->CheckUrl(
+        url, "GET",
+        base::BindOnce(&WebApiHandshakeChecker::CheckerOnIO::OnCheckUrlResult,
+                       base::Unretained(this)));
+  }
+
+ private:
+  // See comments in BrowserUrlLoaderThrottle::OnCheckUrlResult().
+  void OnCheckUrlResult(
+      SafeBrowsingUrlCheckerImpl::NativeUrlCheckNotifier* slow_check_notifier,
+      bool proceed,
+      bool showed_interstitial) {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+    if (!slow_check_notifier) {
+      OnCompleteCheck(/*slow_check=*/false, proceed, showed_interstitial);
+      return;
+    }
+
+    *slow_check_notifier =
+        base::BindOnce(&WebApiHandshakeChecker::CheckerOnIO::OnCompleteCheck,
+                       base::Unretained(this), /*slow_check=*/true);
+  }
+
+  void OnCompleteCheck(bool slow_check,
+                       bool proceed,
+                       bool showed_interstitial) {
+    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+    content::GetUIThreadTaskRunner({})->PostTask(
+        FROM_HERE, base::BindOnce(&WebApiHandshakeChecker::OnCompleteCheck,
+                                  handshake_checker_, slow_check, proceed,
+                                  showed_interstitial));
+  }
+
+  base::WeakPtr<WebApiHandshakeChecker> handshake_checker_;
+  GetDelegateCallback delegate_getter_;
+  GetWebContentsCallback web_contents_getter_;
+  const int frame_tree_node_id_;
+  std::unique_ptr<SafeBrowsingUrlCheckerImpl> url_checker_;
+};
+
+WebApiHandshakeChecker::WebApiHandshakeChecker(
+    GetDelegateCallback delegate_getter,
+    const GetWebContentsCallback& web_contents_getter,
+    int frame_tree_node_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  io_checker_ = std::make_unique<CheckerOnIO>(
+      weak_factory_.GetWeakPtr(), std::move(delegate_getter),
+      web_contents_getter, frame_tree_node_id);
+}
+
+WebApiHandshakeChecker::~WebApiHandshakeChecker() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  content::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
+                                                 std::move(io_checker_));
+}
+
+void WebApiHandshakeChecker::Check(const GURL& url, CheckCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(!check_callback_);
+  check_callback_ = std::move(callback);
+  content::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&WebApiHandshakeChecker::CheckerOnIO::Check,
+                                io_checker_->AsWeakPtr(), url));
+}
+
+void WebApiHandshakeChecker::OnCompleteCheck(bool slow_check,
+                                             bool proceed,
+                                             bool showed_interstitial) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(check_callback_);
+
+  CheckResult result = proceed ? CheckResult::kProceed : CheckResult::kBlocked;
+  std::move(check_callback_).Run(result);
+}
+
+}  // namespace safe_browsing
diff --git a/components/safe_browsing/content/browser/web_api_handshake_checker.h b/components/safe_browsing/content/browser/web_api_handshake_checker.h
new file mode 100644
index 0000000..381aa13
--- /dev/null
+++ b/components/safe_browsing/content/browser/web_api_handshake_checker.h
@@ -0,0 +1,64 @@
+// 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_CONTENT_BROWSER_WEB_API_HANDSHAKE_CHECKER_H_
+#define COMPONENTS_SAFE_BROWSING_CONTENT_BROWSER_WEB_API_HANDSHAKE_CHECKER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "url/gurl.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace safe_browsing {
+
+class UrlCheckerDelegate;
+
+// Performs SafeBrowsing checks for Web API handshakes such as WebTransport.
+class WebApiHandshakeChecker {
+ public:
+  using GetDelegateCallback =
+      base::OnceCallback<scoped_refptr<UrlCheckerDelegate>()>;
+  using GetWebContentsCallback =
+      base::RepeatingCallback<content::WebContents*()>;
+
+  enum class CheckResult {
+    kProceed,
+    kBlocked,
+  };
+  using CheckCallback = base::OnceCallback<void(CheckResult)>;
+
+  WebApiHandshakeChecker(GetDelegateCallback delegate_getter,
+                         const GetWebContentsCallback& web_contents_getter,
+                         int frame_tree_node_id);
+  ~WebApiHandshakeChecker();
+
+  WebApiHandshakeChecker(const WebApiHandshakeChecker&) = delete;
+  WebApiHandshakeChecker& operator=(const WebApiHandshakeChecker&) = delete;
+  WebApiHandshakeChecker(WebApiHandshakeChecker&&) = delete;
+  WebApiHandshakeChecker& operator=(WebApiHandshakeChecker&&) = delete;
+
+  void Check(const GURL& url, CheckCallback callback);
+
+ private:
+  // Performs checks on the IO thread by using SafeBrowsingUrlCheckerImpl, which
+  // must live on the IO thread.
+  class CheckerOnIO;
+
+  void OnCompleteCheck(bool slow_check, bool proceed, bool showed_interstitial);
+
+  std::unique_ptr<CheckerOnIO> io_checker_;
+  CheckCallback check_callback_;
+
+  base::WeakPtrFactory<WebApiHandshakeChecker> weak_factory_{this};
+};
+
+}  // namespace safe_browsing
+
+#endif  // COMPONENTS_SAFE_BROWSING_CONTENT_BROWSER_WEB_API_HANDSHAKE_CHECKER_H_
diff --git a/components/safe_browsing/content/browser/web_api_handshake_checker_unittest.cc b/components/safe_browsing/content/browser/web_api_handshake_checker_unittest.cc
new file mode 100644
index 0000000..e261cc2a
--- /dev/null
+++ b/components/safe_browsing/content/browser/web_api_handshake_checker_unittest.cc
@@ -0,0 +1,129 @@
+// 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/content/browser/web_api_handshake_checker.h"
+
+#include "base/memory/scoped_refptr.h"
+#include "base/test/bind.h"
+#include "components/safe_browsing/core/browser/url_checker_delegate.h"
+#include "components/safe_browsing/core/db/fake_database_manager.h"
+#include "components/security_interstitials/core/unsafe_resource.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+
+class FakeUrlCheckerDelegate : public UrlCheckerDelegate {
+ public:
+  explicit FakeUrlCheckerDelegate(
+      scoped_refptr<SafeBrowsingDatabaseManager> database_manager)
+      : database_manager_(database_manager),
+        threat_types_(
+            SBThreatTypeSet({safe_browsing::SB_THREAT_TYPE_URL_PHISHING})) {}
+
+  // UrlCheckerDelegate overrides:
+  void MaybeDestroyNoStatePrefetchContents(
+      base::OnceCallback<content::WebContents*()> web_contents_getter)
+      override {}
+
+  void StartDisplayingBlockingPageHelper(
+      const security_interstitials::UnsafeResource& resource,
+      const std::string& method,
+      const net::HttpRequestHeaders& headers,
+      bool is_main_frame,
+      bool has_user_gesture) override {
+    resource.callback.Run(/*proceed=*/false, /*showed_intersitial=*/false);
+  }
+
+  void StartObservingInteractionsForDelayedBlockingPageHelper(
+      const security_interstitials::UnsafeResource& resource,
+      bool is_main_frame) override {}
+
+  bool IsUrlAllowlisted(const GURL& url) override { return false; }
+
+  void SetPolicyAllowlistDomains(
+      const std::vector<std::string>& allowlist_domains) override {}
+
+  bool ShouldSkipRequestCheck(const GURL& original_url,
+                              int frame_tree_node_id,
+                              int render_process_id,
+                              int render_frame_id,
+                              bool originated_from_service_worker) override {
+    return false;
+  }
+
+  void NotifySuspiciousSiteDetected(
+      const base::RepeatingCallback<content::WebContents*()>&
+          web_contents_getter) override {}
+
+  const SBThreatTypeSet& GetThreatTypes() override { return threat_types_; }
+
+  SafeBrowsingDatabaseManager* GetDatabaseManager() override {
+    return database_manager_.get();
+  }
+
+  BaseUIManager* GetUIManager() override { return nullptr; }
+
+ protected:
+  friend class base::RefCountedThreadSafe<FakeUrlCheckerDelegate>;
+  ~FakeUrlCheckerDelegate() override = default;
+
+ private:
+  scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
+  SBThreatTypeSet threat_types_;
+};
+
+class WebApiHandshakeCheckerTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    database_manager_ = base::MakeRefCounted<FakeSafeBrowsingDatabaseManager>();
+    delegate_ = base::MakeRefCounted<FakeUrlCheckerDelegate>(database_manager_);
+    handshake_checker_ = std::make_unique<WebApiHandshakeChecker>(
+        base::BindOnce(&WebApiHandshakeCheckerTest::GetDelegate,
+                       base::Unretained(this)),
+        base::BindRepeating(&WebApiHandshakeCheckerTest::GetWebContents,
+                            base::Unretained(this)),
+        /*frame_tree_node_id=*/-1);
+  }
+
+  FakeSafeBrowsingDatabaseManager* database_manager() {
+    return database_manager_.get();
+  }
+
+  WebApiHandshakeChecker::CheckResult Check(const GURL& url) {
+    WebApiHandshakeChecker::CheckResult out;
+    base::RunLoop loop;
+    handshake_checker_->Check(
+        url, base::BindLambdaForTesting(
+                 [&](WebApiHandshakeChecker::CheckResult result) {
+                   out = result;
+                   loop.Quit();
+                 }));
+    loop.Run();
+    return out;
+  }
+
+ private:
+  scoped_refptr<UrlCheckerDelegate> GetDelegate() { return delegate_; }
+
+  content::WebContents* GetWebContents() { return nullptr; }
+
+  content::BrowserTaskEnvironment task_environment_;
+  scoped_refptr<FakeSafeBrowsingDatabaseManager> database_manager_;
+  scoped_refptr<FakeUrlCheckerDelegate> delegate_;
+  std::unique_ptr<WebApiHandshakeChecker> handshake_checker_;
+};
+
+TEST_F(WebApiHandshakeCheckerTest, CheckSafeUrl) {
+  const GURL kUrl("https://example.test");
+  EXPECT_EQ(Check(kUrl), WebApiHandshakeChecker::CheckResult::kProceed);
+}
+
+TEST_F(WebApiHandshakeCheckerTest, CheckDangerousUrl) {
+  const GURL kUrl("https://example.test");
+  database_manager()->AddDangerousUrl(kUrl, SB_THREAT_TYPE_URL_PHISHING);
+  EXPECT_EQ(Check(kUrl), WebApiHandshakeChecker::CheckResult::kBlocked);
+}
+
+}  // namespace safe_browsing
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/BUILD.gn b/components/safe_browsing/content/renderer/phishing_classifier/BUILD.gn
index 77d460c..c07127a 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/BUILD.gn
+++ b/components/safe_browsing/content/renderer/phishing_classifier/BUILD.gn
@@ -39,6 +39,10 @@
       "//skia",
       "//third_party/blink/public:blink_headers",
       "//third_party/smhasher:murmurhash3",
+      "//third_party/tflite",
+      "//third_party/tflite:tflite_public_headers",
+      "//third_party/tflite-support",
+      "//third_party/tflite-support:tflite-support-proto",
       "//ui/base",
       "//ui/gfx/geometry:geometry",
       "//url",
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
index 3db29d2..8854544 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
@@ -166,7 +166,11 @@
 #if BUILDFLAG(FULL_SAFE_BROWSING)
     ExtractVisualFeatures();
 #else
-    VisualExtractionFinished(true);
+    if (scorer_->HasVisualTfLiteModel()) {
+      ExtractVisualFeatures();
+    } else {
+      VisualExtractionFinished(true);
+    }
 #endif
   } else {
     RunFailureCallback();
@@ -243,7 +247,9 @@
       base::BindOnce(&PhishingClassifier::OnVisualTargetsMatched,
                      weak_factory_.GetWeakPtr()));
 #else
-  RunCallback(*verdict);
+  scorer_->ApplyVisualTfLiteModel(
+      *bitmap_, base::BindOnce(&PhishingClassifier::OnVisualTfLiteModelDone,
+                               weak_factory_.GetWeakPtr(), std::move(verdict)));
 #endif
 }
 
@@ -256,6 +262,33 @@
   base::UmaHistogramTimes("SBClientPhishing.VisualComparisonTime",
                           base::TimeTicks::Now() - visual_matching_start_);
 
+  scorer_->ApplyVisualTfLiteModel(
+      *bitmap_, base::BindOnce(&PhishingClassifier::OnVisualTfLiteModelDone,
+                               weak_factory_.GetWeakPtr(), std::move(verdict)));
+}
+
+void PhishingClassifier::OnVisualTfLiteModelDone(
+    std::unique_ptr<ClientPhishingRequest> verdict,
+    std::vector<double> result) {
+  if (static_cast<int>(result.size()) > scorer_->tflite_thresholds().size()) {
+    // Model is misconfigured, so bail out.
+    RunFailureCallback();
+    return;
+  }
+
+  verdict->set_tflite_model_version(scorer_->tflite_model_version());
+  for (size_t i = 0; i < result.size(); i++) {
+    ClientPhishingRequest::CategoryScore* category =
+        verdict->add_tflite_model_scores();
+    category->set_label(scorer_->tflite_thresholds().at(i).label());
+    category->set_value(result[i]);
+
+    if (result[i] >= scorer_->tflite_thresholds().at(i).threshold()) {
+      verdict->set_is_phishing(true);
+      verdict->set_is_tflite_match(true);
+    }
+  }
+
   RunCallback(*verdict);
 }
 
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.h b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.h
index cd4e943..a15faa0 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.h
+++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.h
@@ -125,6 +125,11 @@
   // model.
   void OnVisualTargetsMatched(std::unique_ptr<ClientPhishingRequest> verdict);
 
+  // Callback when the visual TFLite model has been applied, and returned a list
+  // of scores.
+  void OnVisualTfLiteModelDone(std::unique_ptr<ClientPhishingRequest> verdict,
+                               std::vector<double> result);
+
   // Helper method to run the DoneCallback and clear the state.
   void RunCallback(const ClientPhishingRequest& verdict);
 
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc
index 659a29f..c6b03f9 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc
@@ -83,10 +83,11 @@
     const std::string& model,
     base::File tflite_visual_model) {
   safe_browsing::Scorer* scorer = nullptr;
-  // An empty model string means we should disable client-side phishing
-  // detection.
-  if (!model.empty()) {
-    scorer = safe_browsing::Scorer::Create(model);
+  // An empty model string and invalid model file means we should disable
+  // client-side phishing detection.
+  if (!model.empty() || tflite_visual_model.IsValid()) {
+    scorer =
+        safe_browsing::Scorer::Create(model, std::move(tflite_visual_model));
     if (!scorer)
       return;
   }
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/scorer.cc b/components/safe_browsing/content/renderer/phishing_classifier/scorer.cc
index 2d73557a..440bb3b 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/scorer.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/scorer.cc
@@ -22,10 +22,15 @@
 #include "content/public/renderer/render_thread.h"
 #include "crypto/sha2.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/tflite-support/src/tensorflow_lite_support/cc/task/core/task_api_factory.h"
+#include "third_party/tflite-support/src/tensorflow_lite_support/cc/task/vision/image_classifier.h"
+#include "third_party/tflite/src/tensorflow/lite/kernels/builtin_op_kernels.h"
+#include "third_party/tflite/src/tensorflow/lite/op_resolver.h"
 
 namespace safe_browsing {
 
 namespace {
+
 // Enum used to keep stats about the status of the Scorer creation.
 enum ScorerCreationStatus {
   SCORER_SUCCESS,
@@ -34,6 +39,7 @@
   SCORER_FAIL_MODEL_FILE_TOO_LARGE,  // Not used anymore
   SCORER_FAIL_MODEL_PARSE_ERROR,
   SCORER_FAIL_MODEL_MISSING_FIELDS,
+  SCORER_FAIL_MAP_VISUAL_TFLITE_MODEL,
   SCORER_STATUS_MAX  // Always add new values before this one.
 };
 
@@ -79,6 +85,104 @@
   return request;
 }
 
+std::unique_ptr<tflite::MutableOpResolver> CreateOpResolver() {
+  tflite::MutableOpResolver resolver;
+  // The minimal set of OPs required to run the visual model.
+  resolver.AddBuiltin(tflite::BuiltinOperator_ADD,
+                      tflite::ops::builtin::Register_ADD());
+  resolver.AddBuiltin(tflite::BuiltinOperator_CONV_2D,
+                      tflite::ops::builtin::Register_CONV_2D());
+  resolver.AddBuiltin(tflite::BuiltinOperator_DEPTHWISE_CONV_2D,
+                      tflite::ops::builtin::Register_DEPTHWISE_CONV_2D());
+  resolver.AddBuiltin(tflite::BuiltinOperator_FULLY_CONNECTED,
+                      tflite::ops::builtin::Register_FULLY_CONNECTED());
+  resolver.AddBuiltin(tflite::BuiltinOperator_MEAN,
+                      tflite::ops::builtin::Register_MEAN());
+  resolver.AddBuiltin(tflite::BuiltinOperator_SOFTMAX,
+                      tflite::ops::builtin::Register_SOFTMAX());
+  return std::make_unique<tflite::MutableOpResolver>(resolver);
+}
+
+std::unique_ptr<tflite::task::vision::ImageClassifier> CreateClassifier(
+    const std::string& model_data) {
+  tflite::task::vision::ImageClassifierOptions options;
+  options.mutable_model_file_with_metadata()->set_file_content(model_data);
+  auto statusor_classifier =
+      tflite::task::vision::ImageClassifier::CreateFromOptions(
+          options, CreateOpResolver());
+  if (!statusor_classifier.ok()) {
+    VLOG(1) << statusor_classifier.status().ToString();
+    return nullptr;
+  }
+
+  return std::move(*statusor_classifier);
+}
+
+std::string GetModelInput(const SkBitmap& bitmap, int width, int height) {
+  // Use the Rec. 2020 color space, in case the user input is wide-gamut.
+  sk_sp<SkColorSpace> rec2020 = SkColorSpace::MakeRGB(
+      {2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0},
+      SkNamedGamut::kRec2020);
+
+  SkImageInfo downsampled_info = SkImageInfo::MakeN32(
+      width, height, SkAlphaType::kUnpremul_SkAlphaType, rec2020);
+  SkBitmap downsampled;
+  if (!downsampled.tryAllocPixels(downsampled_info))
+    return std::string();
+  bitmap.pixmap().scalePixels(
+      downsampled.pixmap(),
+      SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest));
+
+  // Format as an RGB buffer for input into the model
+  std::string data;
+  for (int y = 0; y < height; ++y) {
+    for (int x = 0; x < width; ++x) {
+      SkColor color = downsampled.getColor(x, y);
+      data += static_cast<char>(SkColorGetR(color));
+      data += static_cast<char>(SkColorGetG(color));
+      data += static_cast<char>(SkColorGetB(color));
+    }
+  }
+
+  return data;
+}
+
+std::vector<double> ApplyVisualTfLiteModelHelper(
+    const SkBitmap& bitmap,
+    int input_width,
+    int input_height,
+    const std::string& model_data) {
+  std::unique_ptr<tflite::task::vision::ImageClassifier> classifier =
+      CreateClassifier(model_data);
+  if (!classifier)
+    return std::vector<double>();
+
+  std::string model_input = GetModelInput(bitmap, input_width, input_height);
+  if (model_input.empty())
+    return std::vector<double>();
+
+  tflite::task::vision::FrameBuffer::Plane plane{
+      reinterpret_cast<const tflite::uint8*>(model_input.data()),
+      {3 * input_width, 3}};
+  auto frame_buffer = tflite::task::vision::FrameBuffer::Create(
+      {plane}, {input_width, input_height},
+      tflite::task::vision::FrameBuffer::Format::kRGB,
+      tflite::task::vision::FrameBuffer::Orientation::kTopLeft);
+  auto statusor_result = classifier->Classify(*frame_buffer);
+  if (!statusor_result.ok()) {
+    VLOG(1) << statusor_result.status().ToString();
+    return std::vector<double>();
+  } else {
+    std::vector<double> scores(
+        statusor_result->classifications(0).classes().size());
+    for (const tflite::task::vision::Class& clas :
+         statusor_result->classifications(0).classes()) {
+      scores[clas.index()] = clas.score();
+    }
+    return scores;
+  }
+}
+
 }  // namespace
 
 // Helper function which converts log odds to a probability in the range
@@ -98,25 +202,40 @@
 Scorer::~Scorer() {}
 
 /* static */
-Scorer* Scorer::Create(const base::StringPiece& model_str) {
+Scorer* Scorer::Create(const base::StringPiece& model_str,
+                       base::File visual_tflite_model) {
   std::unique_ptr<Scorer> scorer(new Scorer());
   ClientSideModel& model = scorer->model_;
   // Parse the phishing model.
-  if (!model.ParseFromArray(model_str.data(), model_str.size())) {
+  if (!model_str.empty() &&
+      !model.ParseFromArray(model_str.data(), model_str.size())) {
     RecordScorerCreationStatus(SCORER_FAIL_MODEL_PARSE_ERROR);
-    return NULL;
-  } else if (!model.IsInitialized()) {
+    return nullptr;
+  }
+
+  if (!model_str.empty() && !model.IsInitialized()) {
     // The model may be missing some required fields.
     RecordScorerCreationStatus(SCORER_FAIL_MODEL_MISSING_FIELDS);
-    return NULL;
+    return nullptr;
   }
+
+  if (!model_str.empty()) {
+    for (int i = 0; i < model.page_term_size(); ++i) {
+      scorer->page_terms_.insert(model.hashes(model.page_term(i)));
+    }
+    for (int i = 0; i < model.page_word_size(); ++i) {
+      scorer->page_words_.insert(model.page_word(i));
+    }
+  }
+
+  // Only do this part if the visual model file exists
+  if (visual_tflite_model.IsValid() && !scorer->visual_tflite_model_.Initialize(
+                                           std::move(visual_tflite_model))) {
+    RecordScorerCreationStatus(SCORER_FAIL_MAP_VISUAL_TFLITE_MODEL);
+    return nullptr;
+  }
+
   RecordScorerCreationStatus(SCORER_SUCCESS);
-  for (int i = 0; i < model.page_term_size(); ++i) {
-    scorer->page_terms_.insert(model.hashes(model.page_term(i)));
-  }
-  for (int i = 0; i < model.page_word_size(); ++i) {
-    scorer->page_words_.insert(model.page_word(i));
-  }
   return scorer.release();
 }
 
@@ -143,10 +262,33 @@
       std::move(callback));
 }
 
+void Scorer::ApplyVisualTfLiteModel(
+    const SkBitmap& bitmap,
+    base::OnceCallback<void(std::vector<double>)> callback) const {
+  DCHECK(content::RenderThread::IsMainThread());
+  if (visual_tflite_model_.IsValid()) {
+    base::ThreadPool::PostTaskAndReplyWithResult(
+        FROM_HERE, {base::TaskPriority::BEST_EFFORT},
+        base::BindOnce(&ApplyVisualTfLiteModelHelper, bitmap,
+                       model_.tflite_model_input_width(),
+                       model_.tflite_model_input_height(),
+                       std::string(reinterpret_cast<const char*>(
+                                       visual_tflite_model_.data()),
+                                   visual_tflite_model_.length())),
+        std::move(callback));
+  } else {
+    std::move(callback).Run(std::vector<double>());
+  }
+}
+
 int Scorer::model_version() const {
   return model_.version();
 }
 
+bool Scorer::HasVisualTfLiteModel() const {
+  return visual_tflite_model_.IsValid();
+}
+
 const std::unordered_set<std::string>& Scorer::page_terms() const {
   return page_terms_;
 }
@@ -175,6 +317,15 @@
   return model_.threshold_probability();
 }
 
+int Scorer::tflite_model_version() const {
+  return model_.tflite_model_version();
+}
+
+const google::protobuf::RepeatedPtrField<ClientSideModel::Threshold>&
+Scorer::tflite_thresholds() const {
+  return model_.tflite_thresholds();
+}
+
 double Scorer::ComputeRuleScore(const ClientSideModel::Rule& rule,
                                 const FeatureMap& features) const {
   const std::unordered_map<std::string, double>& feature_map =
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/scorer.h b/components/safe_browsing/content/renderer/phishing_classifier/scorer.h
index cbfb4f6..52e1ae5 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/scorer.h
+++ b/components/safe_browsing/content/renderer/phishing_classifier/scorer.h
@@ -21,6 +21,8 @@
 #include <unordered_set>
 
 #include "base/callback.h"
+#include "base/files/file.h"
+#include "base/files/memory_mapped_file.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string_piece.h"
@@ -38,7 +40,8 @@
 
   // Factory method which creates a new Scorer object by parsing the given
   // model.  If parsing fails this method returns NULL.
-  static Scorer* Create(const base::StringPiece& model_str);
+  static Scorer* Create(const base::StringPiece& model_str,
+                        base::File visual_tflite_model);
 
   // This method computes the probability that the given features are indicative
   // of phishing.  It returns a score value that falls in the range [0.0,1.0]
@@ -55,9 +58,18 @@
       base::OnceCallback<void(std::unique_ptr<ClientPhishingRequest>)> callback)
       const;
 
+  // This method applies the TfLite visual model to the given bitmap. It
+  // asynchronously returns the list of scores for each category, in the same
+  // order as `tflite_thresholds()`.
+  void ApplyVisualTfLiteModel(
+      const SkBitmap& bitmap,
+      base::OnceCallback<void(std::vector<double>)> callback) const;
+
   // Returns the version number of the loaded client model.
   int model_version() const;
 
+  bool HasVisualTfLiteModel() const;
+
   // -- Accessors used by the page feature extractor ---------------------------
 
   // Returns a set of hashed page terms that appear in the model in binary
@@ -83,6 +95,13 @@
   // Returns the threshold probability above which we send a CSD ping.
   float threshold_probability() const;
 
+  // Returns the version of the visual TFLite model.
+  int tflite_model_version() const;
+
+  // Returns the thresholds configured for the visual TFLite model categories.
+  const google::protobuf::RepeatedPtrField<ClientSideModel::Threshold>&
+  tflite_thresholds() const;
+
  protected:
   // Most clients should use the factory method.  This constructor is public
   // to allow for mock implementations.
@@ -103,6 +122,8 @@
   std::unordered_set<std::string> page_terms_;
   std::unordered_set<uint32_t> page_words_;
 
+  base::MemoryMappedFile visual_tflite_model_;
+
   base::WeakPtrFactory<Scorer> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(Scorer);
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/scorer_unittest.cc b/components/safe_browsing/content/renderer/phishing_classifier/scorer_unittest.cc
index 3aa1927..0c56273 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/scorer_unittest.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/scorer_unittest.cc
@@ -110,21 +110,22 @@
 
 TEST_F(PhishingScorerTest, HasValidModel) {
   std::unique_ptr<Scorer> scorer;
-  scorer.reset(Scorer::Create(model_.SerializeAsString()));
-  EXPECT_TRUE(scorer.get() != NULL);
+  scorer.reset(Scorer::Create(model_.SerializeAsString(), base::File()));
+  EXPECT_TRUE(scorer.get() != nullptr);
 
   // Invalid model string.
-  scorer.reset(Scorer::Create("bogus string"));
+  scorer.reset(Scorer::Create("bogus string", base::File()));
   EXPECT_FALSE(scorer.get());
 
   // Mode is missing a required field.
   model_.clear_max_words_per_term();
-  scorer.reset(Scorer::Create(model_.SerializePartialAsString()));
+  scorer.reset(Scorer::Create(model_.SerializePartialAsString(), base::File()));
   EXPECT_FALSE(scorer.get());
 }
 
 TEST_F(PhishingScorerTest, PageTerms) {
-  std::unique_ptr<Scorer> scorer(Scorer::Create(model_.SerializeAsString()));
+  std::unique_ptr<Scorer> scorer(
+      Scorer::Create(model_.SerializeAsString(), base::File()));
   ASSERT_TRUE(scorer.get());
 
   // Use std::vector instead of std::unordered_set for comparison.
@@ -143,7 +144,8 @@
 }
 
 TEST_F(PhishingScorerTest, PageWords) {
-  std::unique_ptr<Scorer> scorer(Scorer::Create(model_.SerializeAsString()));
+  std::unique_ptr<Scorer> scorer(
+      Scorer::Create(model_.SerializeAsString(), base::File()));
   ASSERT_TRUE(scorer.get());
   std::vector<uint32_t> expected_page_words;
   expected_page_words.push_back(1000U);
@@ -164,7 +166,8 @@
 }
 
 TEST_F(PhishingScorerTest, ComputeScore) {
-  std::unique_ptr<Scorer> scorer(Scorer::Create(model_.SerializeAsString()));
+  std::unique_ptr<Scorer> scorer(
+      Scorer::Create(model_.SerializeAsString(), base::File()));
   ASSERT_TRUE(scorer.get());
 
   // An empty feature map should match the empty rule.
@@ -191,7 +194,8 @@
 }
 
 TEST_F(PhishingScorerTest, GetMatchingVisualTargetsMatchOne) {
-  std::unique_ptr<Scorer> scorer(Scorer::Create(model_.SerializeAsString()));
+  std::unique_ptr<Scorer> scorer(
+      Scorer::Create(model_.SerializeAsString(), base::File()));
 
   // Make the whole image white
   for (int x = 0; x < 1000; x++)
@@ -220,7 +224,8 @@
 }
 
 TEST_F(PhishingScorerTest, GetMatchingVisualTargetsMatchBoth) {
-  std::unique_ptr<Scorer> scorer(Scorer::Create(model_.SerializeAsString()));
+  std::unique_ptr<Scorer> scorer(
+      Scorer::Create(model_.SerializeAsString(), base::File()));
 
   // Make the whole image white
   for (int x = 0; x < 1000; x++)
diff --git a/components/safe_browsing/core/proto/client_model.proto b/components/safe_browsing/core/proto/client_model.proto
index d924076..4a89a31 100644
--- a/components/safe_browsing/core/proto/client_model.proto
+++ b/components/safe_browsing/core/proto/client_model.proto
@@ -119,7 +119,11 @@
   // Safe Browsing for a more definitive classification.
   repeated Threshold tflite_thresholds = 14;
 
-  // next available tag number: 15
+  // The width and height of the input tensor to the corresponding TFLite model.
+  optional int32 tflite_model_input_width = 15;
+  optional int32 tflite_model_input_height = 16;
+
+  // next available tag number: 17
 }
 
 // Wrapper of the vision model for the similarity check target images.
diff --git a/components/segmentation_platform/internal/BUILD.gn b/components/segmentation_platform/internal/BUILD.gn
index ce85f6d..7b7f9b0 100644
--- a/components/segmentation_platform/internal/BUILD.gn
+++ b/components/segmentation_platform/internal/BUILD.gn
@@ -26,59 +26,30 @@
   ]
 
   deps = [
-    ":content",
     "//base",
     "//components/keyed_service/core",
     "//components/leveldb_proto",
-    "//components/optimization_guide/proto:optimization_guide_proto",
     "//components/segmentation_platform/internal/proto",
     "//components/segmentation_platform/public",
   ]
-}
 
-source_set("content") {
-  visibility = [ ":*" ]
+  public_deps =
+      [ "//components/optimization_guide/proto:optimization_guide_proto" ]
 
   if (build_with_tflite_lib) {
-    sources = [
-      "content/segmentation_model_executor.cc",
-      "content/segmentation_model_executor.h",
-      "content/segmentation_model_handler.cc",
-      "content/segmentation_model_handler.h",
+    sources += [
+      "execution/segmentation_model_executor.cc",
+      "execution/segmentation_model_executor.h",
+      "execution/segmentation_model_handler.cc",
+      "execution/segmentation_model_handler.h",
     ]
 
-    deps = [
-      "//base",
+    deps += [
       "//third_party/tflite:tflite_public_headers",
       "//third_party/tflite-support",
     ]
 
-    public_deps = [
-      "//components/optimization_guide/core",
-      "//components/optimization_guide/proto:optimization_guide_proto",
-    ]
-  }
-}
-
-source_set("content_unit_tests") {
-  testonly = true
-
-  visibility = [ ":unit_tests" ]
-
-  if (build_with_tflite_lib) {
-    # IMPORTANT NOTE: When adding new tests, also remember to update the list of
-    # tests in //components/segmentation_platform/components_unittests.filter
-    sources = [ "content/segmentation_model_executor_unittest.cc" ]
-
-    deps = [
-      ":content",
-      ":internal",
-      "//base",
-      "//base/test:test_support",
-      "//components/optimization_guide/core:test_support",
-      "//testing/gmock",
-      "//testing/gtest",
-    ]
+    public_deps += [ "//components/optimization_guide/core" ]
   }
 }
 
@@ -98,7 +69,6 @@
   ]
 
   deps = [
-    ":content_unit_tests",
     ":internal",
     "//base/test:test_support",
     "//components/leveldb_proto:test_support",
@@ -106,6 +76,17 @@
     "//testing/gmock",
     "//testing/gtest",
   ]
+
+  if (build_with_tflite_lib) {
+    # IMPORTANT NOTE: When adding new tests, also remember to update the list of
+    # tests in //components/segmentation_platform/components_unittests.filter
+    sources += [ "execution/segmentation_model_executor_unittest.cc" ]
+
+    deps += [
+      "//base",
+      "//components/optimization_guide/core:test_support",
+    ]
+  }
 }
 
 if (is_android) {
diff --git a/components/segmentation_platform/internal/content/DEPS b/components/segmentation_platform/internal/content/DEPS
deleted file mode 100644
index 3477af99..0000000
--- a/components/segmentation_platform/internal/content/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
-  "+components/optimization_guide/content",
-]
diff --git a/components/segmentation_platform/internal/content/segmentation_model_executor.cc b/components/segmentation_platform/internal/execution/segmentation_model_executor.cc
similarity index 94%
rename from components/segmentation_platform/internal/content/segmentation_model_executor.cc
rename to components/segmentation_platform/internal/execution/segmentation_model_executor.cc
index becdd8910..54ed888 100644
--- a/components/segmentation_platform/internal/content/segmentation_model_executor.cc
+++ b/components/segmentation_platform/internal/execution/segmentation_model_executor.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/segmentation_platform/internal/content/segmentation_model_executor.h"
+#include "components/segmentation_platform/internal/execution/segmentation_model_executor.h"
 
 #include <vector>
 
diff --git a/components/segmentation_platform/internal/content/segmentation_model_executor.h b/components/segmentation_platform/internal/execution/segmentation_model_executor.h
similarity index 85%
rename from components/segmentation_platform/internal/content/segmentation_model_executor.h
rename to components/segmentation_platform/internal/execution/segmentation_model_executor.h
index a3cf4c72..f03fc51e 100644
--- a/components/segmentation_platform/internal/content/segmentation_model_executor.h
+++ b/components/segmentation_platform/internal/execution/segmentation_model_executor.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_EXECUTOR_H_
-#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_EXECUTOR_H_
+#ifndef COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_SEGMENTATION_MODEL_EXECUTOR_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_SEGMENTATION_MODEL_EXECUTOR_H_
 
 #include <memory>
 #include <vector>
@@ -45,4 +45,4 @@
 
 }  // namespace segmentation_platform
 
-#endif  // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_EXECUTOR_H_
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_SEGMENTATION_MODEL_EXECUTOR_H_
diff --git a/components/segmentation_platform/internal/content/segmentation_model_executor_unittest.cc b/components/segmentation_platform/internal/execution/segmentation_model_executor_unittest.cc
similarity index 94%
rename from components/segmentation_platform/internal/content/segmentation_model_executor_unittest.cc
rename to components/segmentation_platform/internal/execution/segmentation_model_executor_unittest.cc
index 27a454f..511a427 100644
--- a/components/segmentation_platform/internal/content/segmentation_model_executor_unittest.cc
+++ b/components/segmentation_platform/internal/execution/segmentation_model_executor_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/segmentation_platform/internal/content/segmentation_model_executor.h"
+#include "components/segmentation_platform/internal/execution/segmentation_model_executor.h"
 
 #include <memory>
 
@@ -15,7 +15,7 @@
 #include "components/optimization_guide/core/optimization_guide_model_provider.h"
 #include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
 #include "components/optimization_guide/proto/models.pb.h"
-#include "components/segmentation_platform/internal/content/segmentation_model_handler.h"
+#include "components/segmentation_platform/internal/execution/segmentation_model_handler.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
diff --git a/components/segmentation_platform/internal/content/segmentation_model_handler.cc b/components/segmentation_platform/internal/execution/segmentation_model_handler.cc
similarity index 84%
rename from components/segmentation_platform/internal/content/segmentation_model_handler.cc
rename to components/segmentation_platform/internal/execution/segmentation_model_handler.cc
index 9af032d1..5762f60 100644
--- a/components/segmentation_platform/internal/content/segmentation_model_handler.cc
+++ b/components/segmentation_platform/internal/execution/segmentation_model_handler.cc
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/segmentation_platform/internal/content/segmentation_model_handler.h"
+#include "components/segmentation_platform/internal/execution/segmentation_model_handler.h"
 
 #include <memory>
 #include <vector>
 
 #include "components/optimization_guide/core/model_executor.h"
 #include "components/optimization_guide/proto/models.pb.h"
-#include "components/segmentation_platform/internal/content/segmentation_model_executor.h"
+#include "components/segmentation_platform/internal/execution/segmentation_model_executor.h"
 
 namespace segmentation_platform {
 
diff --git a/components/segmentation_platform/internal/content/segmentation_model_handler.h b/components/segmentation_platform/internal/execution/segmentation_model_handler.h
similarity index 84%
rename from components/segmentation_platform/internal/content/segmentation_model_handler.h
rename to components/segmentation_platform/internal/execution/segmentation_model_handler.h
index 0c6412f..0f6f6ea 100644
--- a/components/segmentation_platform/internal/content/segmentation_model_handler.h
+++ b/components/segmentation_platform/internal/execution/segmentation_model_handler.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_HANDLER_H_
-#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_HANDLER_H_
+#ifndef COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_SEGMENTATION_MODEL_HANDLER_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_SEGMENTATION_MODEL_HANDLER_H_
 
 #include <memory>
 #include <vector>
@@ -39,4 +39,4 @@
 
 }  // namespace segmentation_platform
 
-#endif  // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_CONTENT_SEGMENTATION_MODEL_HANDLER_H_
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_INTERNAL_EXECUTION_SEGMENTATION_MODEL_HANDLER_H_
diff --git a/components/soda/soda_installer.h b/components/soda/soda_installer.h
index 6d5f2bb..efc790c 100644
--- a/components/soda/soda_installer.h
+++ b/components/soda/soda_installer.h
@@ -80,11 +80,11 @@
   // other platforms.
   virtual base::FilePath GetLanguagePath() const = 0;
 
-  // Installs the user-selected SODA language model. Called by CaptionController
-  // when the kLiveCaptionEnabled or kLiveCaptionLanguageCode preferences
-  // change. `language` is a localized language e.g. "en-US". `global_prefs` is
-  // passed as part of component registration for the non-ChromeOS
-  // implementation.
+  // Installs the user-selected SODA language model. Called by
+  // LiveCaptionController when the kLiveCaptionEnabled or
+  // kLiveCaptionLanguageCode preferences change. `language` is a localized
+  // language e.g. "en-US". `global_prefs` is passed as part of component
+  // registration for the non-ChromeOS implementation.
   virtual void InstallLanguage(const std::string& language,
                                PrefService* global_prefs) = 0;
 
diff --git a/components/variations/service/variations_field_trial_creator.cc b/components/variations/service/variations_field_trial_creator.cc
index bbbc279..30611ab6 100644
--- a/components/variations/service/variations_field_trial_creator.cc
+++ b/components/variations/service/variations_field_trial_creator.cc
@@ -28,6 +28,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "components/language/core/browser/locale_util.h"
+#include "components/metrics/metrics_state_manager.h"
 #include "components/prefs/pref_service.h"
 #include "components/variations/field_trial_config/field_trial_util.h"
 #include "components/variations/platform_field_trials.h"
@@ -40,6 +41,7 @@
 #include "components/variations/variations_ids_provider.h"
 #include "components/variations/variations_seed_processor.h"
 #include "components/variations/variations_switches.h"
+#include "components/version_info/channel.h"
 #include "ui/base/device_form_factor.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -180,6 +182,7 @@
     std::unique_ptr<const base::FieldTrial::EntropyProvider>
         low_entropy_provider,
     std::unique_ptr<base::FeatureList> feature_list,
+    metrics::MetricsStateManager* metrics_state_manager,
     PlatformFieldTrials* platform_field_trials,
     SafeSeedManager* safe_seed_manager,
     absl::optional<int> low_entropy_source_value) {
diff --git a/components/variations/service/variations_field_trial_creator.h b/components/variations/service/variations_field_trial_creator.h
index da5a1f5..093a5d0 100644
--- a/components/variations/service/variations_field_trial_creator.h
+++ b/components/variations/service/variations_field_trial_creator.h
@@ -19,6 +19,10 @@
 #include "components/variations/service/ui_string_overrider.h"
 #include "components/variations/variations_seed_store.h"
 
+namespace metrics {
+class MetricsStateManager;
+}
+
 namespace variations {
 
 enum LoadPermanentConsistencyCountryResult {
@@ -59,25 +63,29 @@
 
   // Sets up field trials based on stored variations seed data. Returns whether
   // setup completed successfully.
+  //
   // |kEnableGpuBenchmarking|, |kEnableFeatures|, |kDisableFeatures| are
-  // feature controlling flags not directly accesible from variations.
+  // feature-controlling flags not directly accessible from variations.
   // |variation_ids| allows for forcing ids selected in chrome://flags and/or
   // specified using the command-line flag.
+  // |extra_overrides| gives a list of feature overrides that should be applied
+  // after the features explicitly disabled/enabled from the command line via
+  // --disable-features and --enable-features, but before field trials.
   // |low_entropy_provider| allows for field trial randomization.
   // |feature_list| contains the list of all active features for this client.
-  // |platform_field_trials| provides the platform specific field trial set up
+  // |metrics_state_manager| facilitates signaling that Chrome has not yet
+  // exited cleanly.
+  // |platform_field_trials| provides the platform-specific field trial set up
   // for Chrome.
   // |safe_seed_manager| should be notified of the combined server and client
   // state that was activated to create the field trials (only when the return
   // value is true).
   // |low_entropy_source_value| contains the low entropy source value that was
   // used for client-side randomization of variations.
-  // |extra_overrides| gives a list of feature overrides that should be applied
-  // after the features explicitly disabled/enabled from the command line via
-  // --disable-features and --enable-features, but before field trials.
-  // Note: The ordering of the FeatureList method calls is such that the
+  //
+  // NOTE: The ordering of the FeatureList method calls is such that the
   // explicit --disable-features and --enable-features from the command line
-  // take precedence over the |extra_overrides|, which take precedence over the
+  // take precedence over |extra_overrides|, which takes precedence over the
   // field trials.
   bool SetupFieldTrials(
       const char* kEnableGpuBenchmarking,
@@ -89,6 +97,7 @@
       std::unique_ptr<const base::FieldTrial::EntropyProvider>
           low_entropy_provider,
       std::unique_ptr<base::FeatureList> feature_list,
+      metrics::MetricsStateManager* metrics_state_manager,
       PlatformFieldTrials* platform_field_trials,
       SafeSeedManager* safe_seed_manager,
       absl::optional<int> low_entropy_source_value);
diff --git a/components/variations/service/variations_field_trial_creator_unittest.cc b/components/variations/service/variations_field_trial_creator_unittest.cc
index 0118dbc..86c6d0cd 100644
--- a/components/variations/service/variations_field_trial_creator_unittest.cc
+++ b/components/variations/service/variations_field_trial_creator_unittest.cc
@@ -16,6 +16,9 @@
 #include "base/version.h"
 #include "build/build_config.h"
 #include "components/metrics/clean_exit_beacon.h"
+#include "components/metrics/client_info.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/test/test_enabled_state_provider.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/variations/platform_field_trials.h"
 #include "components/variations/pref_names.h"
@@ -51,6 +54,12 @@
 const char kTestSeedData[] = "a serialized seed, 100% realistic";
 const char kTestSeedSignature[] = "a totally valid signature, I swear!";
 
+// No-op functions used to create a MetricsStateManager.
+void NoOpStoreClientInfoBackup(const metrics::ClientInfo&) {}
+std::unique_ptr<metrics::ClientInfo> NoOpLoadClientInfoBackup() {
+  return nullptr;
+}
+
 // Populates |seed| with simple test data. The resulting seed will contain one
 // study called "test", which contains one experiment called "abc" with
 // probability weight 100.
@@ -238,8 +247,14 @@
             client,
             std::make_unique<VariationsSeedStore>(local_state),
             UIStringOverrider()),
+        enabled_state_provider_(/*consent=*/true, /*enabled=*/true),
         seed_store_(local_state),
-        safe_seed_manager_(safe_seed_manager) {}
+        safe_seed_manager_(safe_seed_manager) {
+    metrics_state_manager_ = metrics::MetricsStateManager::Create(
+        local_state, &enabled_state_provider_, std::wstring(),
+        base::BindRepeating(&NoOpStoreClientInfoBackup),
+        base::BindRepeating(&NoOpLoadClientInfoBackup));
+  }
 
   ~TestVariationsFieldTrialCreator() override = default;
 
@@ -250,8 +265,8 @@
     return VariationsFieldTrialCreator::SetupFieldTrials(
         "", "", "", std::vector<std::string>(),
         std::vector<base::FeatureList::FeatureOverrideInfo>(), nullptr,
-        std::make_unique<base::FeatureList>(), &platform_field_trials,
-        safe_seed_manager_, absl::nullopt);
+        std::make_unique<base::FeatureList>(), metrics_state_manager_.get(),
+        &platform_field_trials, safe_seed_manager_, absl::nullopt);
   }
 
   TestVariationsSeedStore* seed_store() { return &seed_store_; }
@@ -259,8 +274,10 @@
  private:
   VariationsSeedStore* GetSeedStore() override { return &seed_store_; }
 
+  metrics::TestEnabledStateProvider enabled_state_provider_;
   TestVariationsSeedStore seed_store_;
   SafeSeedManager* const safe_seed_manager_;
+  std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager_;
 
   DISALLOW_COPY_AND_ASSIGN(TestVariationsFieldTrialCreator);
 };
@@ -271,6 +288,7 @@
  protected:
   FieldTrialCreatorTest() {
     metrics::CleanExitBeacon::RegisterPrefs(prefs_.registry());
+    metrics::MetricsStateManager::RegisterPrefs(prefs_.registry());
     VariationsService::RegisterPrefs(prefs_.registry());
     global_feature_list_ = base::FeatureList::ClearInstanceForTesting();
   }
@@ -495,6 +513,13 @@
       &prefs_, &variations_service_client, std::move(seed_store),
       UIStringOverrider());
 
+  metrics::TestEnabledStateProvider enabled_state_provider(/*consent=*/true,
+                                                           /*enabled=*/true);
+  auto metrics_state_manager = metrics::MetricsStateManager::Create(
+      &prefs_, &enabled_state_provider, std::wstring(),
+      base::BindRepeating(&NoOpStoreClientInfoBackup),
+      base::BindRepeating(&NoOpLoadClientInfoBackup));
+
   // Check that field trials are created from the seed. The test seed contains a
   // single study with an experiment targeting 100% of users in India. Since
   // |initial_seed| included the country code for India, this study should be
@@ -502,8 +527,8 @@
   EXPECT_TRUE(field_trial_creator.SetupFieldTrials(
       "", "", "", std::vector<std::string>(),
       std::vector<base::FeatureList::FeatureOverrideInfo>(), nullptr,
-      std::make_unique<base::FeatureList>(), &platform_field_trials,
-      &safe_seed_manager, absl::nullopt));
+      std::make_unique<base::FeatureList>(), metrics_state_manager.get(),
+      &platform_field_trials, &safe_seed_manager, absl::nullopt));
 
   EXPECT_EQ(kTestSeedExperimentName,
             base::FieldTrialList::FindFullName(kTestSeedStudyName));
diff --git a/components/variations/service/variations_service.cc b/components/variations/service/variations_service.cc
index 98baea86..e7bf9b1 100644
--- a/components/variations/service/variations_service.cc
+++ b/components/variations/service/variations_service.cc
@@ -1005,7 +1005,7 @@
   return field_trial_creator_.SetupFieldTrials(
       kEnableGpuBenchmarking, kEnableFeatures, kDisableFeatures, variation_ids,
       extra_overrides, CreateLowEntropyProvider(), std::move(feature_list),
-      platform_field_trials, &safe_seed_manager_,
+      state_manager_, platform_field_trials, &safe_seed_manager_,
       state_manager_->GetLowEntropySource());
 }
 
diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn
index 80047676..2cafc730 100644
--- a/components/vector_icons/BUILD.gn
+++ b/components/vector_icons/BUILD.gn
@@ -8,7 +8,6 @@
   icon_directory = "."
 
   sources = [
-    "add_cellular_network.icon",
     "ads.icon",
     "back_arrow.icon",
     "blocked_badge.icon",
diff --git a/content/app/content_main.cc b/content/app/content_main.cc
index 2c2619b..e953579 100644
--- a/content/app/content_main.cc
+++ b/content/app/content_main.cc
@@ -62,6 +62,10 @@
 #include "base/posix/global_descriptors.h"
 #endif
 
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#include "base/files/scoped_file.h"
+#endif
+
 #if defined(OS_MAC)
 #include "base/mac/scoped_nsautorelease_pool.h"
 #include "content/app/mac_init.h"
@@ -304,6 +308,10 @@
     InitializeMac();
 #endif
 
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+    base::subtle::EnableFDOwnershipEnforcement(true);
+#endif
+
     mojo::core::Configuration mojo_config;
     mojo_config.max_message_num_bytes = kMaximumMojoMessageSize;
     InitializeMojo(&mojo_config);
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 44225be..4d7432f 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1021,8 +1021,6 @@
     "indexed_db/transaction_impl.h",
     "installedapp/installed_app_provider_impl.cc",
     "installedapp/installed_app_provider_impl.h",
-    "interest_group/ad_auction.cc",
-    "interest_group/ad_auction.h",
     "interest_group/ad_auction_service_impl.cc",
     "interest_group/ad_auction_service_impl.h",
     "interest_group/auction_runner.cc",
diff --git a/content/browser/accessibility/hit_testing_browsertest.cc b/content/browser/accessibility/hit_testing_browsertest.cc
index 2bc35ae..56f6025 100644
--- a/content/browser/accessibility/hit_testing_browsertest.cc
+++ b/content/browser/accessibility/hit_testing_browsertest.cc
@@ -56,6 +56,8 @@
       base::StringPrintf("%.2f", device_scale_factor));
   base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
       switches::kEnableUseZoomForDSF, use_zoom_for_dsf ? "true" : "false");
+  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+      switches::kEnableBlinkFeatures, "AccessibilityAriaTouchPassthrough");
 }
 
 std::string AccessibilityHitTestingBrowserTest::TestPassToString::operator()(
diff --git a/content/browser/browser_child_process_host_impl_receiver_bindings.cc b/content/browser/browser_child_process_host_impl_receiver_bindings.cc
index a10a83f..c1a7cf3 100644
--- a/content/browser/browser_child_process_host_impl_receiver_bindings.cc
+++ b/content/browser/browser_child_process_host_impl_receiver_bindings.cc
@@ -18,6 +18,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/device_service.h"
+#include "content/public/common/content_features.h"
 #include "services/device/public/mojom/power_monitor.mojom.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "services/metrics/public/mojom/ukm_interface.mojom.h"
@@ -95,12 +96,26 @@
 
   if (auto r = receiver.As<
                discardable_memory::mojom::DiscardableSharedMemoryManager>()) {
-    discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
-        std::move(r));
+    if (base::FeatureList::IsEnabled(features::kProcessHostOnUI)) {
+      GetIOThreadTaskRunner({})->PostTask(
+          FROM_HERE,
+          base::BindOnce(
+              [](mojo::PendingReceiver<
+                  discardable_memory::mojom::DiscardableSharedMemoryManager>
+                     r) {
+                discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
+                    std::move(r));
+              },
+              std::move(r)));
+    } else {
+      discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
+          std::move(r));
+    }
     return;
   }
 
   if (auto r = receiver.As<device::mojom::PowerMonitor>()) {
+    // TODO(jam): When ProcessHostOnUI is the default just remove this PostTask.
     GetUIThreadTaskRunner({})->PostTask(
         FROM_HERE,
         base::BindOnce(
diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc
index ec825b66..a7734981 100644
--- a/content/browser/browser_context.cc
+++ b/content/browser/browser_context.cc
@@ -111,13 +111,10 @@
   return impl()->GetDownloadManager();
 }
 
-// static
-storage::ExternalMountPoints* BrowserContext::GetMountPoints(
-    BrowserContext* self) {
-  return self->impl()->GetMountPoints();
+storage::ExternalMountPoints* BrowserContext::GetMountPoints() {
+  return impl()->GetMountPoints();
 }
 
-// static
 BrowsingDataRemover* BrowserContext::GetBrowsingDataRemover() {
   return impl()->GetBrowsingDataRemover();
 }
@@ -246,13 +243,11 @@
       std::move(old_subscription), std::move(callback));
 }
 
-// static
-void BrowserContext::NotifyWillBeDestroyed(BrowserContext* self) {
-  self->impl()->NotifyWillBeDestroyed();
+void BrowserContext::NotifyWillBeDestroyed() {
+  impl()->NotifyWillBeDestroyed();
 }
 
-// static
-void BrowserContext::EnsureResourceContextInitialized(BrowserContext* self) {
+void BrowserContext::EnsureResourceContextInitialized() {
   // This will be enough to tickle initialization of BrowserContext if
   // necessary, which initializes ResourceContext. The reason we don't call
   // ResourceContext::InitializeResourceContext() directly here is that
@@ -261,12 +256,11 @@
   // end up rewriting the same value but this still causes a race condition.
   //
   // See http://crbug.com/115678.
-  self->GetDefaultStoragePartition();
+  GetDefaultStoragePartition();
 }
 
-// static
-void BrowserContext::SaveSessionState(BrowserContext* self) {
-  StoragePartition* storage_partition = self->GetDefaultStoragePartition();
+void BrowserContext::SaveSessionState() {
+  StoragePartition* storage_partition = GetDefaultStoragePartition();
 
   storage::DatabaseTracker* database_tracker =
       storage_partition->GetDatabaseTracker();
@@ -310,10 +304,8 @@
       std::move(permission_controller));
 }
 
-// static
-SharedCorsOriginAccessList* BrowserContext::GetSharedCorsOriginAccessList(
-    BrowserContext* self) {
-  return self->impl()->shared_cors_origin_access_list();
+SharedCorsOriginAccessList* BrowserContext::GetSharedCorsOriginAccessList() {
+  return impl()->shared_cors_origin_access_list();
 }
 
 void BrowserContext::ShutdownStoragePartitions() {
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index dda4cd01..d548b3d 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -2083,6 +2083,10 @@
       if (!matches_profile)
         continue;
 
+      // Do not include origins that only apply to specific BrowsingInstances.
+      if (!isolated_origin_entry.applies_to_future_browsing_instances())
+        continue;
+
       origins.push_back(isolated_origin_entry.origin());
     }
   }
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index 049f1ef..d62a36a 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -1300,7 +1300,7 @@
         std::make_unique<network::WrapperPendingSharedURLLoaderFactory>(
             FileURLLoaderFactory::Create(
                 browser_context_->GetPath(),
-                BrowserContext::GetSharedCorsOriginAccessList(browser_context_),
+                browser_context_->GetSharedCorsOriginAccessList(),
                 // USER_VISIBLE because download should progress
                 // even when there is high priority work to do.
                 base::TaskPriority::USER_VISIBLE));
diff --git a/content/browser/download/save_file_manager.cc b/content/browser/download/save_file_manager.cc
index 0a6f1b0..bb32282 100644
--- a/content/browser/download/save_file_manager.cc
+++ b/content/browser/download/save_file_manager.cc
@@ -266,8 +266,7 @@
       factory = factory_remote.get();
     } else if (url.SchemeIsFile()) {
       factory_remote.Bind(FileURLLoaderFactory::Create(
-          context->GetPath(),
-          BrowserContext::GetSharedCorsOriginAccessList(context),
+          context->GetPath(), context->GetSharedCorsOriginAccessList(),
           base::TaskPriority::USER_VISIBLE));
       factory = factory_remote.get();
     } else if (url.SchemeIsFileSystem() && rfh) {
diff --git a/content/browser/file_system/browser_file_system_helper.cc b/content/browser/file_system/browser_file_system_helper.cc
index 4ed9e3a..c55a873 100644
--- a/content/browser/file_system/browser_file_system_helper.cc
+++ b/content/browser/file_system/browser_file_system_helper.cc
@@ -137,7 +137,7 @@
   scoped_refptr<storage::FileSystemContext> file_system_context =
       new storage::FileSystemContext(
           GetIOThreadTaskRunner({}).get(), g_fileapi_task_runner.Get().get(),
-          BrowserContext::GetMountPoints(browser_context),
+          browser_context->GetMountPoints(),
           browser_context->GetSpecialStoragePolicy(), quota_manager_proxy,
           std::move(additional_backends), url_request_auto_mount_handlers,
           profile_path, options);
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index f0689a03..4f7de561 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -1120,8 +1120,7 @@
     mojo::PendingReceiver<
         discardable_memory::mojom::DiscardableSharedMemoryManager> receiver) {
   if (base::FeatureList::IsEnabled(features::kProcessHostOnUI)) {
-    discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
-        std::move(receiver));
+    BindDiscardableMemoryReceiverOnUI(std::move(receiver));
     return;
   }
 
diff --git a/content/browser/interest_group/ad_auction.cc b/content/browser/interest_group/ad_auction.cc
deleted file mode 100644
index 4ef9d7a..0000000
--- a/content/browser/interest_group/ad_auction.cc
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/interest_group/ad_auction.h"
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/check.h"
-#include "base/memory/weak_ptr.h"
-#include "base/strings/stringprintf.h"
-#include "content/browser/devtools/devtools_instrumentation.h"
-#include "content/browser/interest_group/ad_auction_service_impl.h"
-#include "content/browser/interest_group/auction_runner.h"
-#include "content/browser/interest_group/auction_url_loader_factory_proxy.h"
-#include "content/browser/interest_group/interest_group_manager.h"
-#include "content/browser/renderer_host/render_frame_host_impl.h"
-#include "content/public/browser/content_browser_client.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/common/content_client.h"
-#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
-#include "services/network/public/mojom/url_loader_factory.mojom.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
-#include "url/gurl.h"
-#include "url/origin.h"
-#include "url/url_constants.h"
-
-namespace content {
-
-namespace {
-
-bool IsAuctionValid(const blink::mojom::AuctionAdConfig& config) {
-  // The seller origin has to be HTTPS and match the `decision_logic_url`
-  // origin.
-  if (config.seller.scheme() != url::kHttpsScheme ||
-      !config.decision_logic_url.SchemeIs(url::kHttpsScheme) ||
-      config.seller != url::Origin::Create(config.decision_logic_url)) {
-    return false;
-  }
-
-  if (!config.interest_group_buyers ||
-      config.interest_group_buyers->is_all_buyers()) {
-    return false;
-  }
-  DCHECK(config.interest_group_buyers->is_buyers());
-
-  // All interest group owners must be HTTPS.
-  for (const url::Origin& buyer : config.interest_group_buyers->get_buyers()) {
-    if (buyer.scheme() != url::kHttpsScheme)
-      return false;
-  }
-
-  // All buyer signals must be for listed buyers.
-  if (config.per_buyer_signals) {
-    for (const auto& it : config.per_buyer_signals.value()) {
-      if (!base::Contains(config.interest_group_buyers->get_buyers(),
-                          it.first)) {
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
-}  // namespace
-
-AdAuction::AdAuction(AdAuctionServiceImpl* ad_auction_service,
-                     blink::mojom::AuctionAdConfigPtr config,
-                     AuctionCompleteCallback callback)
-    : ad_auction_service_(ad_auction_service),
-      config_(std::move(config)),
-      callback_(std::move(callback)) {
-  DCHECK(ad_auction_service_);
-  DCHECK(config_);
-  DCHECK(callback_);
-}
-
-AdAuction::~AdAuction() = default;
-
-void AdAuction::StartAuction() {
-  if (!IsAuctionValid(*config_)) {
-    OnAuctionFailed();
-    return;
-  }
-
-  const url::Origin& frame_origin = ad_auction_service_->origin();
-  BrowserContext* browser_context =
-      ad_auction_service_->render_frame_host()->GetBrowserContext();
-  // If the interest group API is not allowed for this seller do nothing.
-  if (!GetContentClient()->browser()->IsInterestGroupAPIAllowed(
-          browser_context, frame_origin, config_->seller.GetURL())) {
-    OnAuctionFailed();
-    return;
-  }
-
-  // Filter out buyers for whom the interest group API is not allowed.
-  const auto& buyers = config_->interest_group_buyers->get_buyers();
-  std::copy_if(
-      buyers.begin(), buyers.end(), std::back_inserter(pending_buyers_),
-      [browser_context, &frame_origin](const url::Origin& buyer) {
-        return GetContentClient()->browser()->IsInterestGroupAPIAllowed(
-            browser_context, frame_origin, buyer.GetURL());
-      });
-
-  // If there are no buyers (either due to filtering, or in the original auction
-  // request), fail the auction.
-  if (pending_buyers_.empty()) {
-    OnAuctionFailed();
-    return;
-  }
-
-  ReadNextInterestGroup();
-}
-
-void AdAuction::ReadNextInterestGroup() {
-  DCHECK(!pending_buyers_.empty());
-
-  url::Origin buyer = std::move(pending_buyers_.back());
-  pending_buyers_.pop_back();
-
-  ad_auction_service_->GetInterestGroupManager()->GetInterestGroupsForOwner(
-      buyer, base::BindOnce(&AdAuction::OnInterestGroupRead,
-                            weak_ptr_factory_.GetWeakPtr()));
-}
-
-void AdAuction::OnInterestGroupRead(
-    std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
-        interest_groups) {
-  bidders_.insert(bidders_.end(),
-                  std::make_move_iterator(interest_groups.begin()),
-                  std::make_move_iterator(interest_groups.end()));
-
-  // If more buyers in the queue, load the next one.
-  if (!pending_buyers_.empty()) {
-    ReadNextInterestGroup();
-    return;
-  }
-
-  // If there are no found interest groups, end the auction without a winner.
-  if (bidders_.empty()) {
-    OnAuctionFailed();
-    return;
-  }
-
-  StartWorklets();
-}
-
-void AdAuction::StartWorklets() {
-  DCHECK(pending_buyers_.empty());
-  DCHECK(!bidders_.empty());
-
-  // TODO(mmenke): This should be top frame origin, not frame origin.
-  auto browser_signals = auction_worklet::mojom::BrowserSignals::New(
-      ad_auction_service_->origin(), config_->seller);
-
-  std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders_copy;
-  bidders_copy.reserve(bidders_.size());
-  for (auto& bidder : bidders_)
-    bidders_copy.emplace_back(bidder.Clone());
-
-  // `config_` is no longer needed after this point, so pass ownership of it
-  // over to the AuctionRunner, instead of copying it.
-  auction_runner_ = AuctionRunner::CreateAndStart(
-      ad_auction_service_, std::move(config_), std::move(bidders_copy),
-      std::move(browser_signals), ad_auction_service_->origin(),
-      base::BindOnce(&AdAuction::WorkletComplete,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
-void AdAuction::WorkletComplete(const GURL& render_url,
-                                const std::string& ad_metadata,
-                                const url::Origin& owner,
-                                const std::string& name,
-                                const GURL& bidder_report_url,
-                                const GURL& seller_report_url,
-                                const std::vector<std::string>& errors) {
-  DCHECK(callback_);
-
-  // Forward debug information to devtools.
-  for (const std::string& error : errors) {
-    devtools_instrumentation::LogWorkletError(
-        static_cast<RenderFrameHostImpl*>(
-            ad_auction_service_->render_frame_host()),
-        error);
-  }
-
-  if (!render_url.is_valid()) {
-    OnAuctionFailed();
-    return;
-  }
-
-  absl::optional<GURL> opt_bidder_report_url;
-  if (bidder_report_url.is_valid())
-    opt_bidder_report_url = bidder_report_url;
-
-  absl::optional<GURL> opt_seller_report_url;
-  if (seller_report_url.is_valid())
-    opt_seller_report_url = seller_report_url;
-
-  ad_auction_service_->GetInterestGroupManager()->RecordInterestGroupWin(
-      owner, name, ad_metadata);
-  // TODO(qingxin): Decide if we should record a bid if the auction fails, or
-  // the interest group doesn't make a bid.
-  for (const auto& bidder : bidders_) {
-    ad_auction_service_->GetInterestGroupManager()->RecordInterestGroupBid(
-        bidder->group->owner, bidder->group->name);
-  }
-
-  std::move(callback_).Run(this, render_url, opt_bidder_report_url,
-                           opt_seller_report_url);
-}
-
-void AdAuction::OnAuctionFailed() {
-  DCHECK(callback_);
-
-  std::move(callback_).Run(this, absl::nullopt, absl::nullopt, absl::nullopt);
-}
-
-}  // namespace content
diff --git a/content/browser/interest_group/ad_auction.h b/content/browser/interest_group/ad_auction.h
deleted file mode 100644
index bd066b5..0000000
--- a/content/browser/interest_group/ad_auction.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_INTEREST_GROUP_AD_AUCTION_H_
-#define CONTENT_BROWSER_INTEREST_GROUP_AD_AUCTION_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/callback_forward.h"
-#include "base/memory/weak_ptr.h"
-#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom-forward.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
-#include "url/gurl.h"
-#include "url/origin.h"
-
-namespace content {
-
-class AdAuctionServiceImpl;
-class AuctionRunner;
-
-// Class for running a single FLEDGE auction.
-class AdAuction {
- public:
-  // Callback for completion, invoked on both success and error. On success,
-  // when the auction had a winner, `render_url` is non-null. On failure or when
-  // an auction had no winner, all URLs are null.
-  using AuctionCompleteCallback =
-      base::OnceCallback<void(AdAuction* auction,
-                              absl::optional<GURL> render_url,
-                              absl::optional<GURL> bidder_report_url,
-                              absl::optional<GURL> seller_report_url)>;
-
-  // `ad_auction_service` must remain valid for the lifetime of the AdAuction.
-  AdAuction(AdAuctionServiceImpl* ad_auction_service,
-            blink::mojom::AuctionAdConfigPtr config,
-            AuctionCompleteCallback callback);
-  ~AdAuction();
-
-  // May invoke `AuctionCompleteCallback` synchronously.
-  void StartAuction();
-
- private:
-  // Retrieves the next interest group in `pending_buyers_` from storage,
-  // removing it from the vector. OnInterestGroupRead() will be invoked
-  // with the lookup results.
-  void ReadNextInterestGroup();
-
-  // Adds `interest_groups` to `bidders_`. Continues retrieving bidders from
-  // `pending_buyers_` if non-empty. Otherwise, starts running the worklets.
-  void OnInterestGroupRead(
-      std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
-          interest_groups);
-
-  // Starts running the auction out of process.
-  void StartWorklets();
-
-  // Called from the worklet service once a worklet is complete.
-  //
-  // Validates the results, reports them to `callback_`, and updates the
-  // InterestGroupStorage as needed.
-  void WorkletComplete(const GURL& render_url,
-                       const std::string& ad_metadata,
-                       const url::Origin& owner,
-                       const std::string& name,
-                       const GURL& bidder_report_url,
-                       const GURL& seller_report_url,
-                       const std::vector<std::string>& errors);
-
-  // Invokes `callback_` with empty parameters, to inform it of the failure.
-  void OnAuctionFailed();
-
-  AdAuctionServiceImpl* ad_auction_service_;
-
-  // Populated on construction, moved to the worklet service when running an
-  // auction, since it's not used afterwards.
-  blink::mojom::AuctionAdConfigPtr config_;
-
-  // List of loaded interest groups. Populated by the calls to
-  // OnInterestGroupRead().
-  std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders_;
-
-  AuctionCompleteCallback callback_;
-
-  // Buyers from `config->interest_group_buyers` that have yet to be retrieved
-  // from interest group storage.
-  std::vector<url::Origin> pending_buyers_;
-
-  std::unique_ptr<AuctionRunner> auction_runner_;
-
-  base::WeakPtrFactory<AdAuction> weak_ptr_factory_{this};
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_INTEREST_GROUP_AD_AUCTION_H_
diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc
index 3d676b2f..669745d 100644
--- a/content/browser/interest_group/ad_auction_service_impl.cc
+++ b/content/browser/interest_group/ad_auction_service_impl.cc
@@ -11,7 +11,7 @@
 #include "base/containers/contains.h"
 #include "base/strings/stringprintf.h"
 #include "content/browser/devtools/devtools_instrumentation.h"
-#include "content/browser/interest_group/ad_auction.h"
+#include "content/browser/interest_group/auction_runner.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/service_sandbox_type.h"
 #include "content/browser/storage_partition_impl.h"
@@ -91,6 +91,40 @@
           std::move(simple_url_loader)));
 }
 
+bool IsAuctionValid(const blink::mojom::AuctionAdConfig& config) {
+  // The seller origin has to be HTTPS and match the `decision_logic_url`
+  // origin.
+  if (config.seller.scheme() != url::kHttpsScheme ||
+      !config.decision_logic_url.SchemeIs(url::kHttpsScheme) ||
+      config.seller != url::Origin::Create(config.decision_logic_url)) {
+    return false;
+  }
+
+  if (!config.interest_group_buyers ||
+      config.interest_group_buyers->is_all_buyers()) {
+    return false;
+  }
+  DCHECK(config.interest_group_buyers->is_buyers());
+
+  // All interest group owners must be HTTPS.
+  for (const url::Origin& buyer : config.interest_group_buyers->get_buyers()) {
+    if (buyer.scheme() != url::kHttpsScheme)
+      return false;
+  }
+
+  // All buyer signals must be for listed buyers.
+  if (config.per_buyer_signals) {
+    for (const auto& it : config.per_buyer_signals.value()) {
+      if (!base::Contains(config.interest_group_buyers->get_buyers(),
+                          it.first)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
 }  // namespace
 
 AdAuctionServiceImpl::AdAuctionServiceImpl(
@@ -113,19 +147,51 @@
 
 void AdAuctionServiceImpl::RunAdAuction(blink::mojom::AuctionAdConfigPtr config,
                                         RunAdAuctionCallback callback) {
-  std::unique_ptr<AdAuction> auction = std::make_unique<AdAuction>(
-      this, std::move(config),
+  if (!IsAuctionValid(*config)) {
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
+
+  const url::Origin& frame_origin = origin();
+  BrowserContext* browser_context = render_frame_host()->GetBrowserContext();
+  // If the interest group API is not allowed for this seller do nothing.
+  if (!GetContentClient()->browser()->IsInterestGroupAPIAllowed(
+          browser_context, frame_origin, config->seller.GetURL())) {
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
+
+  // Filter out buyers for whom the interest group API is not allowed.
+  std::vector<url::Origin> filtered_buyers;
+  const auto& buyers = config->interest_group_buyers->get_buyers();
+  std::copy_if(
+      buyers.begin(), buyers.end(), std::back_inserter(filtered_buyers),
+      [browser_context, &frame_origin](const url::Origin& buyer) {
+        return GetContentClient()->browser()->IsInterestGroupAPIAllowed(
+            browser_context, frame_origin, buyer.GetURL());
+      });
+
+  // If there are no buyers (either due to filtering, or in the original auction
+  // request), fail the auction.
+  if (filtered_buyers.empty()) {
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
+
+  // TODO(mmenke): This should be top frame origin, not frame origin.
+  auto browser_signals =
+      auction_worklet::mojom::BrowserSignals::New(frame_origin, config->seller);
+
+  std::unique_ptr<AuctionRunner> auction = AuctionRunner::CreateAndStart(
+      this,
+      static_cast<StoragePartitionImpl*>(
+          render_frame_host()->GetStoragePartition())
+          ->GetInterestGroupStorage(),
+      std::move(config), std::move(filtered_buyers), std::move(browser_signals),
+      frame_origin,
       base::BindOnce(&AdAuctionServiceImpl::OnAuctionComplete,
                      base::Unretained(this), std::move(callback)));
-  AdAuction* auction_ptr = auction.get();
   auctions_.insert(std::move(auction));
-  auction_ptr->StartAuction();
-}
-
-InterestGroupManager* AdAuctionServiceImpl::GetInterestGroupManager() {
-  return static_cast<StoragePartitionImpl*>(
-             render_frame_host()->GetStoragePartition())
-      ->GetInterestGroupStorage();
 }
 
 network::mojom::URLLoaderFactory*
@@ -186,10 +252,13 @@
 
 void AdAuctionServiceImpl::OnAuctionComplete(
     RunAdAuctionCallback callback,
-    AdAuction* auction,
+    AuctionRunner* auction,
     absl::optional<GURL> render_url,
     absl::optional<GURL> bidder_report_url,
-    absl::optional<GURL> seller_report_url) {
+    absl::optional<GURL> seller_report_url,
+    std::vector<std::string> errors) {
+  // Delete the AuctionRunner. Since all arguments are passed by value, they're
+  // all safe to used after this has been done.
   auto auction_it = auctions_.find(auction);
   DCHECK(auction_it != auctions_.end());
   auctions_.erase(auction_it);
@@ -197,6 +266,12 @@
   if (auctions_.empty())
     auction_worklet_service_.reset();
 
+  // Forward debug information to devtools.
+  for (const std::string& error : errors) {
+    devtools_instrumentation::LogWorkletError(
+        static_cast<RenderFrameHostImpl*>(render_frame_host()), error);
+  }
+
   std::move(callback).Run(render_url);
 
   if (!render_url) {
diff --git a/content/browser/interest_group/ad_auction_service_impl.h b/content/browser/interest_group/ad_auction_service_impl.h
index 0acf619..13bf2fc 100644
--- a/content/browser/interest_group/ad_auction_service_impl.h
+++ b/content/browser/interest_group/ad_auction_service_impl.h
@@ -25,7 +25,7 @@
 
 namespace content {
 
-class AdAuction;
+class AuctionRunner;
 class RenderFrameHost;
 
 // Implements the AdAuctionService service called by Blink code.
@@ -43,8 +43,6 @@
   void RunAdAuction(blink::mojom::AuctionAdConfigPtr config,
                     RunAdAuctionCallback callback) override;
 
-  InterestGroupManager* GetInterestGroupManager();
-
   // AuctionRunner::Delegate implementation:
   network::mojom::URLLoaderFactory* GetFrameURLLoaderFactory() override;
   network::mojom::URLLoaderFactory* GetTrustedURLLoaderFactory() override;
@@ -65,15 +63,16 @@
 
   // Deletes `auction`.
   void OnAuctionComplete(RunAdAuctionCallback callback,
-                         AdAuction* auction,
+                         AuctionRunner* auction,
                          absl::optional<GURL> render_url,
                          absl::optional<GURL> bidder_report_url,
-                         absl::optional<GURL> seller_report_url);
+                         absl::optional<GURL> seller_report_url,
+                         std::vector<std::string> errors);
 
   // This must be above `auction_worklet_service_`, since auctions may own
   // callbacks over the AuctionWorkletService pipe, and mojo pipes must be
   // destroyed before any callbacks that are bound to them.
-  std::set<std::unique_ptr<AdAuction>, base::UniquePtrComparator> auctions_;
+  std::set<std::unique_ptr<AuctionRunner>, base::UniquePtrComparator> auctions_;
 
   mojo::Remote<auction_worklet::mojom::AuctionWorkletService>
       auction_worklet_service_;
diff --git a/content/browser/interest_group/auction_runner.cc b/content/browser/interest_group/auction_runner.cc
index 8955c24..b71daa3 100644
--- a/content/browser/interest_group/auction_runner.cc
+++ b/content/browser/interest_group/auction_runner.cc
@@ -13,6 +13,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "content/browser/interest_group/auction_url_loader_factory_proxy.h"
+#include "content/browser/interest_group/interest_group_manager.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
@@ -74,34 +75,74 @@
 
 std::unique_ptr<AuctionRunner> AuctionRunner::CreateAndStart(
     Delegate* delegate,
+    InterestGroupManager* interest_group_manager,
     blink::mojom::AuctionAdConfigPtr auction_config,
-    std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders,
+    std::vector<url::Origin> filtered_buyers,
     auction_worklet::mojom::BrowserSignalsPtr browser_signals,
     const url::Origin& frame_origin,
     RunAuctionCallback callback) {
+  DCHECK(!filtered_buyers.empty());
   std::unique_ptr<AuctionRunner> instance(new AuctionRunner(
-      delegate, std::move(auction_config), std::move(bidders),
-      std::move(browser_signals), frame_origin, std::move(callback)));
-  instance->StartBidding();
+      delegate, interest_group_manager, std::move(auction_config),
+      std::move(filtered_buyers), std::move(browser_signals), frame_origin,
+      std::move(callback)));
+  instance->ReadNextInterestGroup();
   return instance;
 }
 
 AuctionRunner::AuctionRunner(
     Delegate* delegate,
+    InterestGroupManager* interest_group_manager,
     blink::mojom::AuctionAdConfigPtr auction_config,
-    std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders,
+    std::vector<url::Origin> filtered_buyers,
     auction_worklet::mojom::BrowserSignalsPtr browser_signals,
     const url::Origin& frame_origin,
     RunAuctionCallback callback)
     : delegate_(delegate),
+      interest_group_manager_(interest_group_manager),
       auction_config_(std::move(auction_config)),
-      bidders_(std::move(bidders)),
+      pending_buyers_(std::move(filtered_buyers)),
       browser_signals_(std::move(browser_signals)),
       frame_origin_(frame_origin),
       callback_(std::move(callback)) {}
 
 AuctionRunner::~AuctionRunner() = default;
 
+void AuctionRunner::ReadNextInterestGroup() {
+  DCHECK_LT(next_pending_buyer_, pending_buyers_.size());
+
+  interest_group_manager_->GetInterestGroupsForOwner(
+      pending_buyers_[next_pending_buyer_],
+      base::BindOnce(&AuctionRunner::OnInterestGroupRead,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void AuctionRunner::OnInterestGroupRead(
+    std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
+        interest_groups) {
+  bidders_.insert(bidders_.end(),
+                  std::make_move_iterator(interest_groups.begin()),
+                  std::make_move_iterator(interest_groups.end()));
+  next_pending_buyer_++;
+
+  // If more buyers in the queue, load the next one.
+  if (next_pending_buyer_ < pending_buyers_.size()) {
+    ReadNextInterestGroup();
+    return;
+  }
+
+  // Pending buyers are no longer needed.
+  pending_buyers_.clear();
+
+  // If no interest groups were found, end the auction without a winner.
+  if (bidders_.empty()) {
+    FailAuction();
+    return;
+  }
+
+  StartBidding();
+}
+
 void AuctionRunner::StartBidding() {
   // Auctions are only run when there are bidders participating. As-is, and
   // empty bidder vector here would result in synchronously calling back into
@@ -406,8 +447,8 @@
   DCHECK(callback_);
   ClosePipes();
 
-  std::move(callback_).Run(GURL(), std::string(), url::Origin(), std::string(),
-                           GURL(), GURL(), errors_);
+  std::move(callback_).Run(this, absl::nullopt, absl::nullopt, absl::nullopt,
+                           errors_);
 }
 
 void AuctionRunner::FailAuctionWithError(std::string error) {
@@ -432,11 +473,18 @@
         R"({"render_url":"%s"})", state->bid_result->render_url.spec().c_str());
   }
 
-  std::move(callback_).Run(
-      state->bid_result->render_url, ad_metadata, state->bidder->group->owner,
-      state->bidder->group->name,
-      bidder_report_url_.has_value() ? *bidder_report_url_ : GURL(),
-      seller_report_url_.has_value() ? *seller_report_url_ : GURL(), errors_);
+  interest_group_manager_->RecordInterestGroupWin(
+      state->bidder->group->owner, state->bidder->group->name, ad_metadata);
+
+  // TODO(mmenke): Don't record a bid if the interest group doesn't make a bid.
+  for (const auto& bidder : bidders_) {
+    interest_group_manager_->RecordInterestGroupBid(bidder->group->owner,
+                                                    bidder->group->name);
+  }
+
+  std::move(callback_).Run(this, state->bid_result->render_url,
+                           std::move(bidder_report_url_),
+                           std::move(seller_report_url_), std::move(errors_));
 }
 
 void AuctionRunner::ClosePipes() {
diff --git a/content/browser/interest_group/auction_runner.h b/content/browser/interest_group/auction_runner.h
index e2fc0db..70ac7e0 100644
--- a/content/browser/interest_group/auction_runner.h
+++ b/content/browser/interest_group/auction_runner.h
@@ -27,6 +27,7 @@
 namespace content {
 
 class AuctionURLLoaderFactoryProxy;
+class InterestGroupManager;
 
 // An AuctionRunner loads and runs the bidder and seller worklets, along with
 // their reporting phases and produces the result via a callback.
@@ -47,33 +48,23 @@
  public:
   // Invoked when a FLEDGE auction is complete.
   //
-  // `render_url` URL of auction winning ad to render.
-  //  An empty URL is used if there is no winner.
+  // `render_url` URL of auction winning ad to render. Null if there is no
+  // winner.
   //
-  // `ad_metadata` The metadata for the winning ad.
-  //
-  // `winning_interest_group_owner` owner of the winning interest group.
-  //  An opaque origin if there is no winner.
-  //
-  // `winning_interest_group_name` name of winning interest group. Empty if
-  //  there is no winner.
-  //
-  // `bidder_report_url` URL to use for reporting result to the bidder. Empty if
+  // `bidder_report_url` URL to use for reporting result to the bidder. Null if
   //  no report should be sent.
   //
-  // `seller_report`  URL to use for reporting result to the seller. Empty if no
+  // `seller_report`  URL to use for reporting result to the seller. Null if no
   //  report should be sent.
   //
   // `errors` are various error messages to be used for debugging. These are too
   //  sensitive for the renderers to see.
   using RunAuctionCallback =
-      base::OnceCallback<void(const GURL& render_url,
-                              const std::string& ad_metadata,
-                              const url::Origin& winning_interest_group_owner,
-                              const std::string& winning_interest_group_name,
-                              const GURL& bidder_report_url,
-                              const GURL& seller_report_url,
-                              const std::vector<std::string>& errors)>;
+      base::OnceCallback<void(AuctionRunner* auction_runner,
+                              const absl::optional<GURL> render_url,
+                              const absl::optional<GURL> bidder_report_url,
+                              const absl::optional<GURL> seller_report_url,
+                              std::vector<std::string> errors)>;
 
   // Delegate class to allow dependency injection in tests. Note that all
   // objects this returns can crash and be restarted, so passing in raw pointers
@@ -101,17 +92,16 @@
   // Runs an entire FLEDGE auction.
   //
   // Arguments:
-  // `delegate` must remain valid until the AuctionRunner is destroyed.
+  // `delegate` and `interest_group_manager` must remain valid until the
+  //  AuctionRunner is destroyed.
   //
   // `auction_config` is the configuration provided by client JavaScript in
   //  the renderer in order to initiate the auction.
   //
-  // `bidders` includes definitions of the interest groups that are selected to
-  //  participate in this auction (initially added by client JS in the renderer,
-  //  but managed by the browser's interest group store), as well as some
-  //  bidding history collected by the interest group store. The bidding
-  //  worklets of these groups will be fetched and executed. `bidders` must not
-  //  be empty.
+  // `filtered_buyers` owners of bidders allowed to participate in this auction.
+  //  These should be a subset of `auction_config`'s `interest_group_buyers`,
+  //  filtered to account for browser configuration (like cookie blocking). Must
+  //  not be empty.
   //
   // `browser_signals` signals from the browser about the auction that are the
   //  same for all worklets.
@@ -120,8 +110,9 @@
   // origin), used as the initiator in network requests.
   static std::unique_ptr<AuctionRunner> CreateAndStart(
       Delegate* delegate,
+      InterestGroupManager* interest_group_manager,
       blink::mojom::AuctionAdConfigPtr auction_config,
-      std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders,
+      std::vector<url::Origin> filtered_buyers,
       auction_worklet::mojom::BrowserSignalsPtr browser_signals,
       const url::Origin& frame_origin,
       RunAuctionCallback callback);
@@ -149,15 +140,28 @@
     double seller_score = 0;
   };
 
-  AuctionRunner(
-      Delegate* delegate,
-      blink::mojom::AuctionAdConfigPtr auction_config,
-      std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders,
-      auction_worklet::mojom::BrowserSignalsPtr browser_signals,
-      const url::Origin& frame_origin,
-      RunAuctionCallback callback);
+  AuctionRunner(Delegate* delegate,
+                InterestGroupManager* interest_group_manager,
+                blink::mojom::AuctionAdConfigPtr auction_config,
+                std::vector<url::Origin> filtered_buyers,
+                auction_worklet::mojom::BrowserSignalsPtr browser_signals,
+                const url::Origin& frame_origin,
+                RunAuctionCallback callback);
 
+  // Retrieves the next interest group in `pending_buyers_` from storage.
+  // OnInterestGroupRead() will be invoked with the lookup results.
+  void ReadNextInterestGroup();
+
+  // Adds `interest_groups` to `bidders_`. Continues retrieving bidders from
+  // `pending_buyers_` if any have not been retrieved yet. Otherwise, invokes
+  // StartBidding().
+  void OnInterestGroupRead(
+      std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
+          interest_groups);
+
+  // Starts loading worklets and generating bids.
   void StartBidding();
+
   void OnGenerateBidCrashed(BidState* state);
   void OnGenerateBidComplete(BidState* state,
                              auction_worklet::mojom::BidderWorkletBidPtr bid,
@@ -210,9 +214,15 @@
   void ClosePipes();
 
   Delegate* const delegate_;
+  InterestGroupManager* const interest_group_manager_;
 
   // Configuration.
   blink::mojom::AuctionAdConfigPtr auction_config_;
+  // Buyers whose interest groups need to be looked up to be added to
+  // `bidders_`.
+  std::vector<url::Origin> pending_buyers_;
+  // Next entry in `pending_buyers_` to fetch the interest group for.
+  size_t next_pending_buyer_ = 0;
   std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders_;
   auction_worklet::mojom::BrowserSignalsPtr browser_signals_;
   const url::Origin frame_origin_;
diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc
index fa519bb..98fd069 100644
--- a/content/browser/interest_group/auction_runner_unittest.cc
+++ b/content/browser/interest_group/auction_runner_unittest.cc
@@ -8,11 +8,13 @@
 #include <vector>
 
 #include "base/callback_helpers.h"
+#include "base/files/file_path.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
+#include "content/browser/interest_group/interest_group_manager.h"
 #include "content/services/auction_worklet/auction_worklet_service_impl.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
@@ -210,6 +212,16 @@
   return std::string(kAuctionScriptRejects2) + kReportResultScript;
 }
 
+// Sorts a vector of PreviousWinPtr so that the most recent wins are last.
+void SortPrevWins(
+    std::vector<auction_worklet::mojom::PreviousWinPtr>& prev_wins) {
+  std::sort(prev_wins.begin(), prev_wins.end(),
+            [](const auction_worklet::mojom::PreviousWinPtr& prev_win1,
+               const auction_worklet::mojom::PreviousWinPtr& prev_win2) {
+              return prev_win1->time < prev_win2->time;
+            });
+}
+
 // BidderWorklet that holds onto passed in callbacks, to let the test fixture
 // invoke them.
 class MockBidderWorklet : public auction_worklet::mojom::BidderWorklet {
@@ -558,17 +570,29 @@
  protected:
   // Output of the RunAuctionCallback passed to AuctionRunner::CreateAndStart().
   struct Result {
-    GURL ad_url;
-    std::string ad_metadata;
-    url::Origin interest_group_owner;
-    std::string interest_group_name;
-    GURL bidder_report_url;
-    GURL seller_report_url;
+    Result() = default;
+    // Can't use default copy logic, since it contains Mojo types.
+    Result(const Result&) = delete;
+    Result& operator=(const Result&) = delete;
+
+    absl::optional<GURL> ad_url;
+    absl::optional<GURL> bidder_report_url;
+    absl::optional<GURL> seller_report_url;
     std::vector<std::string> errors;
+
+    // Metadata about `bidder1` and `bidder2`, pulled from the
+    // InterestGroupManager on auction complete. Used to make sure number of
+    // bids and win list are updated on auction complete. Previous wins arrays
+    // are guaranteed to be sorted in chronological order.
+    int bidder1_bid_count;
+    std::vector<auction_worklet::mojom::PreviousWinPtr> bidder1_prev_wins;
+    int bidder2_bid_count;
+    std::vector<auction_worklet::mojom::PreviousWinPtr> bidder2_prev_wins;
   };
 
   AuctionRunnerTest()
-      : auction_worklet_service_(
+      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
+        auction_worklet_service_(
             auction_worklet_service_remote_.BindNewPipeAndPassReceiver()) {
     mojo::SetDefaultProcessErrorHandler(base::BindRepeating(
         &AuctionRunnerTest::OnBadMessage, base::Unretained(this)));
@@ -597,6 +621,12 @@
 
   // Starts an auction without waiting for it to complete. Useful when using
   // MockAuctionWorkletService.
+  //
+  // `bidders` are added to a new InterestGroupManager before running the
+  // auction. The times of their previous wins are ignored, as the
+  // InterestGroupManager automatically attaches the current time, though their
+  // wins will be added in order, with chronologically increasing times within
+  // each InterestGroup.
   void StartAuction(
       const GURL& seller_decision_logic_url,
       std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders,
@@ -608,6 +638,8 @@
         blink::mojom::AuctionAdConfig::New();
     auction_config->seller = url::Origin::Create(seller_decision_logic_url);
     auction_config->decision_logic_url = seller_decision_logic_url;
+    // This is ignored by AuctionRunner, in favor of its `filtered_buyers`
+    // parameter.
     auction_config->interest_group_buyers =
         blink::mojom::InterestGroupBuyers::NewAllBuyers(
             blink::mojom::AllBuyers::New());
@@ -619,16 +651,38 @@
     per_buyer_signals[kBidder2] = R"({"signalsFor": ")" + kBidder2Name + "\"}";
     auction_config->per_buyer_signals = std::move(per_buyer_signals);
 
+    interest_group_manager_ = std::make_unique<InterestGroupManager>(
+        base::FilePath(), true /* in_memory */);
+
+    // Add previous wins and bids to the interest group manager.
+    for (auto& bidder : bidders) {
+      for (int i = 0; i < bidder->signals->join_count; i++) {
+        interest_group_manager_->JoinInterestGroup(bidder->group.Clone());
+      }
+      for (int i = 0; i < bidder->signals->bid_count; i++) {
+        interest_group_manager_->RecordInterestGroupBid(bidder->group->owner,
+                                                        bidder->group->name);
+      }
+      for (const auto& prev_win : bidder->signals->prev_wins) {
+        interest_group_manager_->RecordInterestGroupWin(
+            bidder->group->owner, bidder->group->name, prev_win->ad_json);
+        // Add some time between interest group wins, so that they'll be added
+        // to the database in the order they appear. Their times will *not*
+        // match those in `prev_wins`.
+        task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+      }
+    }
+
     auction_run_loop_ = std::make_unique<base::RunLoop>();
-    Result result;
     auction_runner_ = AuctionRunner::CreateAndStart(
-        this, std::move(auction_config), std::move(bidders),
+        this, interest_group_manager_.get(), std::move(auction_config),
+        std::vector<url::Origin>{kBidder1, kBidder2},
         std::move(browser_signals), frame_origin_,
         base::BindOnce(&AuctionRunnerTest::OnAuctionComplete,
                        base::Unretained(this)));
   }
 
-  Result RunAuctionAndWait(
+  const Result& RunAuctionAndWait(
       const GURL& seller_decision_logic_url,
       std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders,
       const std::string& auction_signals_json,
@@ -639,21 +693,60 @@
     return result_;
   }
 
-  void OnAuctionComplete(const GURL& ad_url,
-                         const std::string& ad_metadata,
-                         const url::Origin& interest_group_owner,
-                         const std::string& interest_group_name,
-                         const GURL& bidder_report_url,
-                         const GURL& seller_report_url,
-                         const std::vector<std::string>& errors) {
+  void OnAuctionComplete(AuctionRunner* auction_runner,
+                         absl::optional<GURL> ad_url,
+                         absl::optional<GURL> bidder_report_url,
+                         absl::optional<GURL> seller_report_url,
+                         std::vector<std::string> errors) {
+    DCHECK(auction_run_loop_);
+    DCHECK(!auction_complete_);
+
     auction_complete_ = true;
-    result_.ad_url = ad_url;
-    result_.ad_metadata = ad_metadata;
-    result_.interest_group_owner = interest_group_owner;
-    result_.interest_group_name = interest_group_name;
-    result_.bidder_report_url = bidder_report_url;
-    result_.seller_report_url = seller_report_url;
-    result_.errors = errors;
+    result_.ad_url = std::move(ad_url);
+    result_.bidder_report_url = std::move(bidder_report_url);
+    result_.seller_report_url = std::move(seller_report_url);
+    result_.errors = std::move(errors);
+    result_.bidder1_bid_count = -1;
+    result_.bidder1_prev_wins.clear();
+    result_.bidder2_bid_count = -1;
+    result_.bidder2_prev_wins.clear();
+
+    // Retrieve bid count and previous wins for kBidder1 (and subsequently
+    // kBidder2).
+    interest_group_manager_->GetInterestGroupsForOwner(
+        kBidder1, base::BindOnce(&AuctionRunnerTest::OnBidder1GroupsRetrieved,
+                                 base::Unretained(this)));
+  }
+
+  void OnBidder1GroupsRetrieved(
+      std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
+          bidding_interest_groups) {
+    for (auto& bidder : bidding_interest_groups) {
+      if (bidder->group->owner == kBidder1 &&
+          bidder->group->name == kBidder1Name) {
+        result_.bidder1_bid_count = bidder->signals->bid_count;
+        result_.bidder1_prev_wins = std::move(bidder->signals->prev_wins);
+        SortPrevWins(result_.bidder1_prev_wins);
+        break;
+      }
+    }
+    interest_group_manager_->GetInterestGroupsForOwner(
+        kBidder2, base::BindOnce(&AuctionRunnerTest::OnBidder2GroupsRetrieved,
+                                 base::Unretained(this)));
+  }
+
+  void OnBidder2GroupsRetrieved(
+      std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
+          bidding_interest_groups) {
+    for (auto& bidder : bidding_interest_groups) {
+      if (bidder->group->owner == kBidder2 &&
+          bidder->group->name == kBidder2Name) {
+        result_.bidder2_bid_count = bidder->signals->bid_count;
+        result_.bidder2_prev_wins = std::move(bidder->signals->prev_wins);
+        SortPrevWins(result_.bidder2_prev_wins);
+        break;
+      }
+    }
     auction_run_loop_->Quit();
   }
 
@@ -674,13 +767,13 @@
       ads.push_back(blink::mojom::InterestGroupAd::New(ad_url, absl::nullopt));
     }
 
+    // Create fake previous wins. The time of these wins is ignored, since the
+    // InterestGroupManager attaches the current time when logging a win.
     std::vector<auction_worklet::mojom::PreviousWinPtr> previous_wins;
     previous_wins.push_back(auction_worklet::mojom::PreviousWin::New(
-        base::Time::Now() - base::TimeDelta::FromSeconds(2),
-        R"({"winner": 0})"));
+        base::Time::Now(), R"({"winner": 0})"));
     previous_wins.push_back(auction_worklet::mojom::PreviousWin::New(
-        base::Time::Now() - base::TimeDelta::FromSeconds(1),
-        R"({"winner": -1})"));
+        base::Time::Now(), R"({"winner": -1})"));
     previous_wins.push_back(auction_worklet::mojom::PreviousWin::New(
         base::Time::Now(), R"({"winner": -2})"));
 
@@ -709,7 +802,7 @@
                      url::Origin::Create(kSellerUrl)));
   }
 
-  Result RunStandardAuction() {
+  const Result& RunStandardAuction() {
     StartStandardAuction();
     auction_run_loop_->Run();
     return result_;
@@ -718,7 +811,7 @@
   // Starts the standard auction with the mock worklet service, and waits for
   // the service to receive the worklet construction calls.
   void StartStandardAuctionWithMockService() {
-    use_mock_service_ = true;
+    UseMockWorkletService();
     StartStandardAuction();
     mock_worklet_service_->WaitForWorklets(2 /* num_bidders */);
   }
@@ -731,35 +824,35 @@
     return &url_loader_factory_;
   }
   auction_worklet::mojom::AuctionWorkletService* GetWorkletService() override {
-    if (use_mock_service_) {
-      if (!mock_worklet_service_) {
-        mock_worklet_service_remote_.reset();
-        mock_worklet_service_ = std::make_unique<MockAuctionWorkletService>(
-            mock_worklet_service_remote_.BindNewPipeAndPassReceiver());
-      }
+    if (mock_worklet_service_) {
+      DCHECK(mock_worklet_service_remote_.is_connected());
       return mock_worklet_service_remote_.get();
     }
     return auction_worklet_service_remote_.get();
   }
 
+  // Enables use of a mock worklet service, destroying any previously used mock
+  // worklet service.
+  void UseMockWorkletService() {
+    mock_worklet_service_remote_.reset();
+    mock_worklet_service_ = std::make_unique<MockAuctionWorkletService>(
+        mock_worklet_service_remote_.BindNewPipeAndPassReceiver());
+  }
+
   const url::Origin frame_origin_ =
       url::Origin::Create(GURL("https://frame.origin.test"));
   const GURL kSellerUrl{"https://adstuff.publisher1.com/auction.js"};
   const GURL kBidder1Url{"https://adplatform.com/offers.js"};
-  const url::Origin kBidder1 =
-      url::Origin::Create(GURL("https://adplatform.com"));
+  const url::Origin kBidder1 = url::Origin::Create(kBidder1Url);
   const std::string kBidder1Name{"Ad Platform"};
   const GURL kBidder2Url{"https://anotheradthing.com/bids.js"};
-  const url::Origin kBidder2 =
-      url::Origin::Create(GURL("https://anotheradthing.com"));
+  const url::Origin kBidder2 = url::Origin::Create(kBidder2Url);
   const std::string kBidder2Name{"Another Ad Thing"};
 
   const GURL kTrustedSignalsUrl{"https://trustedsignaller.org/signals"};
 
   base::test::TaskEnvironment task_environment_;
 
-  bool use_mock_service_ = false;
-
   // RunLoop that's quit on auction completion.
   std::unique_ptr<base::RunLoop> auction_run_loop_;
   // True if the most recently started auction has completed.
@@ -770,17 +863,80 @@
   network::TestURLLoaderFactory url_loader_factory_;
   mojo::Remote<auction_worklet::mojom::AuctionWorkletService>
       mock_worklet_service_remote_;
+  // Mock service is used in favor of `auction_worklet_service_`, if non-null.
   std::unique_ptr<MockAuctionWorkletService> mock_worklet_service_;
 
   mojo::Remote<auction_worklet::mojom::AuctionWorkletService>
       auction_worklet_service_remote_;
   auction_worklet::AuctionWorkletServiceImpl auction_worklet_service_;
 
+  // The InterestGroupManager is recreated and repopulated for each auction.
+  std::unique_ptr<InterestGroupManager> interest_group_manager_;
+
   std::unique_ptr<AuctionRunner> auction_runner_;
   // This should be inspected using TakeBadMessage(), which also clears it.
   std::string bad_message_;
 };
 
+// Runs the standard auction, but without adding any interest groups to the
+// manager.
+TEST_F(AuctionRunnerTest, NoInterestGroups) {
+  RunAuctionAndWait(
+      kSellerUrl,
+      std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>(),
+      R"({"isAuctionSignals": true})" /* auction_signals_json */,
+      auction_worklet::mojom::BrowserSignals::New(
+          url::Origin::Create(GURL("https://publisher1.com")),
+          url::Origin::Create(kSellerUrl)));
+
+  EXPECT_FALSE(result_.ad_url);
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(-1, result_.bidder1_bid_count);
+  EXPECT_EQ(0u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(-1, result_.bidder2_bid_count);
+  EXPECT_EQ(0u, result_.bidder2_prev_wins.size());
+  EXPECT_THAT(result_.errors, testing::ElementsAre());
+}
+
+// Runs the standard auction, but with only adding one of the two standard
+// interest groups to the manager.
+TEST_F(AuctionRunnerTest, OneInterestGroup) {
+  auction_worklet::AddJavascriptResponse(
+      &url_loader_factory_, kBidder1Url,
+      MakeBidScript("1", "https://ad1.com/", kBidder1, kBidder1Name,
+                    true /* has_signals */, "k1", "a"));
+  auction_worklet::AddJavascriptResponse(&url_loader_factory_, kSellerUrl,
+                                         MakeAuctionScript());
+  auction_worklet::AddJsonResponse(
+      &url_loader_factory_,
+      GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=k1,k2"),
+      R"({"k1":"a", "k2": "b", "extra": "c"})");
+
+  std::vector<auction_worklet::mojom::BiddingInterestGroupPtr> bidders;
+  bidders.push_back(MakeInterestGroup(kBidder1, kBidder1Name, kBidder1Url,
+                                      kTrustedSignalsUrl, {"k1", "k2"},
+                                      GURL("https://ad1.com")));
+
+  RunAuctionAndWait(kSellerUrl, std::move(bidders),
+                    R"({"isAuctionSignals": true})" /* auction_signals_json */,
+                    auction_worklet::mojom::BrowserSignals::New(
+                        url::Origin::Create(GURL("https://publisher1.com")),
+                        url::Origin::Create(kSellerUrl)));
+
+  EXPECT_EQ(GURL("https://ad1.com/"), result_.ad_url);
+  EXPECT_EQ(GURL("https://reporting.example.com/"), result_.seller_report_url);
+  EXPECT_EQ(GURL("https://buyer-reporting.example.com/"),
+            result_.bidder_report_url);
+  EXPECT_EQ(6, result_.bidder1_bid_count);
+  ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
+            result_.bidder1_prev_wins[3]->ad_json);
+  EXPECT_EQ(-1, result_.bidder2_bid_count);
+  EXPECT_EQ(0u, result_.bidder2_prev_wins.size());
+  EXPECT_THAT(result_.errors, testing::ElementsAre());
+}
+
 // An auction with two successful bids.
 TEST_F(AuctionRunnerTest, Basic) {
   auction_worklet::AddJavascriptResponse(
@@ -802,14 +958,17 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra": "c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_EQ("https://ad2.com/", res.ad_url.spec());
-  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})", res.ad_metadata);
-  EXPECT_EQ("https://anotheradthing.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Another Ad Thing", res.interest_group_name);
-  EXPECT_EQ("https://reporting.example.com/", res.seller_report_url.spec());
-  EXPECT_EQ("https://buyer-reporting.example.com/",
-            res.bidder_report_url.spec());
+  const Result& res = RunStandardAuction();
+  EXPECT_EQ(GURL("https://ad2.com/"), res.ad_url);
+  EXPECT_EQ(GURL("https://reporting.example.com/"), res.seller_report_url);
+  EXPECT_EQ(GURL("https://buyer-reporting.example.com/"),
+            res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  ASSERT_EQ(4u, res.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            res.bidder2_prev_wins[3]->ad_json);
   EXPECT_THAT(res.errors, testing::ElementsAre());
 }
 
@@ -831,15 +990,17 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra": "c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_EQ("https://ad1.com/", res.ad_url.spec());
+  const Result& res = RunStandardAuction();
+  EXPECT_EQ(GURL("https://ad1.com/"), res.ad_url);
+  EXPECT_EQ(GURL("https://reporting.example.com/"), res.seller_report_url);
+  EXPECT_EQ(GURL("https://buyer-reporting.example.com/"),
+            res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  ASSERT_EQ(4u, res.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
-            res.ad_metadata);
-  EXPECT_EQ("https://adplatform.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Ad Platform", res.interest_group_name);
-  EXPECT_EQ("https://reporting.example.com/", res.seller_report_url.spec());
-  EXPECT_EQ("https://buyer-reporting.example.com/",
-            res.bidder_report_url.spec());
+            res.bidder1_prev_wins[3]->ad_json);
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  EXPECT_EQ(3u, res.bidder2_prev_wins.size());
   EXPECT_THAT(
       res.errors,
       testing::ElementsAre("Failed to load https://anotheradthing.com/bids.js "
@@ -868,15 +1029,17 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra": "c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_EQ("https://ad1.com/", res.ad_url.spec());
+  const Result& res = RunStandardAuction();
+  EXPECT_EQ(GURL("https://ad1.com/"), res.ad_url);
+  EXPECT_EQ(GURL("https://reporting.example.com/"), res.seller_report_url);
+  EXPECT_EQ(GURL("https://buyer-reporting.example.com/"),
+            res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  ASSERT_EQ(4u, res.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
-            res.ad_metadata);
-  EXPECT_EQ("https://adplatform.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Ad Platform", res.interest_group_name);
-  EXPECT_EQ("https://reporting.example.com/", res.seller_report_url.spec());
-  EXPECT_EQ("https://buyer-reporting.example.com/",
-            res.bidder_report_url.spec());
+            res.bidder1_prev_wins[3]->ad_json);
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  EXPECT_EQ(3u, res.bidder2_prev_wins.size());
   EXPECT_THAT(res.errors,
               testing::ElementsAre("https://anotheradthing.com/bids.js "
                                    "`generateBid` is not a function."));
@@ -897,19 +1060,20 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra":"c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_TRUE(res.ad_url.is_empty());
-  EXPECT_TRUE(res.ad_metadata.empty());
-  EXPECT_TRUE(res.interest_group_owner.opaque());
-  EXPECT_EQ("", res.interest_group_name);
-  EXPECT_TRUE(res.seller_report_url.is_empty());
-  EXPECT_TRUE(res.bidder_report_url.is_empty());
-  EXPECT_THAT(
-      res.errors,
-      testing::ElementsAre("Failed to load https://adplatform.com/offers.js "
-                           "HTTP status = 404 Not Found.",
-                           "Failed to load https://anotheradthing.com/bids.js "
-                           "HTTP status = 404 Not Found."));
+  const Result& res = RunStandardAuction();
+  EXPECT_FALSE(res.ad_url);
+  EXPECT_FALSE(res.seller_report_url);
+  EXPECT_FALSE(res.bidder_report_url);
+  EXPECT_EQ(5, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(5, res.bidder2_bid_count);
+  EXPECT_EQ(3u, res.bidder2_prev_wins.size());
+  EXPECT_THAT(res.errors,
+              testing::UnorderedElementsAre(
+                  "Failed to load https://adplatform.com/offers.js "
+                  "HTTP status = 404 Not Found.",
+                  "Failed to load https://anotheradthing.com/bids.js "
+                  "HTTP status = 404 Not Found."));
 }
 
 // An auction where none of the bidding scripts has a valid bidding function.
@@ -930,16 +1094,17 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra":"c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_TRUE(res.ad_url.is_empty());
-  EXPECT_TRUE(res.ad_metadata.empty());
-  EXPECT_TRUE(res.interest_group_owner.opaque());
-  EXPECT_EQ("", res.interest_group_name);
-  EXPECT_TRUE(res.seller_report_url.is_empty());
-  EXPECT_TRUE(res.bidder_report_url.is_empty());
+  const Result& res = RunStandardAuction();
+  EXPECT_FALSE(res.ad_url);
+  EXPECT_FALSE(res.seller_report_url);
+  EXPECT_FALSE(res.bidder_report_url);
+  EXPECT_EQ(5, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(5, res.bidder2_bid_count);
+  EXPECT_EQ(3u, res.bidder2_prev_wins.size());
   EXPECT_THAT(
       res.errors,
-      testing::ElementsAre(
+      testing::UnorderedElementsAre(
           "https://adplatform.com/offers.js `generateBid` is not a function.",
           "https://anotheradthing.com/bids.js `generateBid` is not a "
           "function."));
@@ -969,18 +1134,19 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra":"c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_TRUE(res.ad_url.is_empty());
-  EXPECT_TRUE(res.ad_metadata.empty());
-  EXPECT_TRUE(res.interest_group_owner.opaque());
-  EXPECT_EQ("", res.interest_group_name);
-  EXPECT_TRUE(res.seller_report_url.is_empty());
-  EXPECT_TRUE(res.bidder_report_url.is_empty());
-  EXPECT_THAT(res.errors,
-              testing::ElementsAre("https://adstuff.publisher1.com/auction.js "
-                                   "`scoreAd` is not a function.",
-                                   "https://adstuff.publisher1.com/auction.js "
-                                   "`scoreAd` is not a function."));
+  const Result& res = RunStandardAuction();
+  EXPECT_FALSE(res.ad_url);
+  EXPECT_FALSE(res.seller_report_url);
+  EXPECT_FALSE(res.bidder_report_url);
+  EXPECT_EQ(5, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(5, res.bidder2_bid_count);
+  EXPECT_EQ(3u, res.bidder2_prev_wins.size());
+  EXPECT_THAT(res.errors, testing::UnorderedElementsAre(
+                              "https://adstuff.publisher1.com/auction.js "
+                              "`scoreAd` is not a function.",
+                              "https://adstuff.publisher1.com/auction.js "
+                              "`scoreAd` is not a function."));
 }
 
 // An auction where seller rejects one bid when scoring.
@@ -1004,15 +1170,18 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra": "c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_EQ("https://ad1.com/", res.ad_url.spec());
+  const Result& res = RunStandardAuction();
+  EXPECT_EQ(GURL("https://ad1.com/"), res.ad_url);
+  EXPECT_EQ(GURL("https://reporting.example.com/"), res.seller_report_url);
+  EXPECT_EQ(GURL("https://buyer-reporting.example.com/"),
+            res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  ASSERT_EQ(4u, res.bidder1_prev_wins.size());
   EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
-            res.ad_metadata);
-  EXPECT_EQ("https://adplatform.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Ad Platform", res.interest_group_name);
-  EXPECT_EQ("https://reporting.example.com/", res.seller_report_url.spec());
-  EXPECT_EQ("https://buyer-reporting.example.com/",
-            res.bidder_report_url.spec());
+            res.bidder1_prev_wins[3]->ad_json);
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  EXPECT_EQ(3u, res.bidder2_prev_wins.size());
+  EXPECT_THAT(res.errors, testing::ElementsAre());
 }
 
 // An auction where the seller script fails to load.
@@ -1020,22 +1189,23 @@
   // Tests to make sure that if seller script fails the other fetches are
   // cancelled, too.
   url_loader_factory_.AddResponse(kSellerUrl.spec(), "", net::HTTP_NOT_FOUND);
-  Result res = RunStandardAuction();
-  EXPECT_TRUE(res.ad_url.is_empty());
-  EXPECT_TRUE(res.ad_metadata.empty());
-  EXPECT_TRUE(res.interest_group_owner.opaque());
-  EXPECT_EQ("", res.interest_group_name);
-  EXPECT_TRUE(res.seller_report_url.is_empty());
-  EXPECT_TRUE(res.bidder_report_url.is_empty());
+  const Result& res = RunStandardAuction();
+  EXPECT_FALSE(res.ad_url);
+  EXPECT_FALSE(res.seller_report_url);
+  EXPECT_FALSE(res.bidder_report_url);
 
   EXPECT_EQ(0, url_loader_factory_.NumPending());
+  EXPECT_EQ(5, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(5, res.bidder2_bid_count);
+  EXPECT_EQ(3u, res.bidder2_prev_wins.size());
   EXPECT_THAT(res.errors,
               testing::ElementsAre(
                   "Failed to load https://adstuff.publisher1.com/auction.js "
                   "HTTP status = 404 Not Found."));
 }
 
-// An auction where bidders don't requested trusted bidding signals.
+// An auction where bidders don't request trusted bidding signals.
 TEST_F(AuctionRunnerTest, NoTrustedBiddingSignals) {
   auction_worklet::AddJavascriptResponse(
       &url_loader_factory_, kBidder1Url,
@@ -1056,20 +1226,23 @@
                                       absl::nullopt, {"l1", "l2"},
                                       GURL("https://ad2.com")));
 
-  Result res = RunAuctionAndWait(
+  const Result& res = RunAuctionAndWait(
       kSellerUrl, std::move(bidders),
       R"({"isAuctionSignals": true})", /* auction_signals_json */
       auction_worklet::mojom::BrowserSignals::New(
           url::Origin::Create(GURL("https://publisher1.com")),
           url::Origin::Create(kSellerUrl)));
 
-  EXPECT_EQ("https://ad2.com/", res.ad_url.spec());
-  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})", res.ad_metadata);
-  EXPECT_EQ("https://anotheradthing.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Another Ad Thing", res.interest_group_name);
-  EXPECT_EQ("https://reporting.example.com/", res.seller_report_url.spec());
-  EXPECT_EQ("https://buyer-reporting.example.com/",
-            res.bidder_report_url.spec());
+  EXPECT_EQ(GURL("https://ad2.com/"), res.ad_url);
+  EXPECT_EQ(GURL("https://reporting.example.com/"), res.seller_report_url);
+  EXPECT_EQ(GURL("https://buyer-reporting.example.com/"),
+            res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  ASSERT_EQ(4u, res.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            res.bidder2_prev_wins[3]->ad_json);
   EXPECT_THAT(res.errors, testing::ElementsAre());
 }
 
@@ -1092,23 +1265,26 @@
   auction_worklet::AddJavascriptResponse(&url_loader_factory_, kSellerUrl,
                                          MakeAuctionScript());
 
-  Result res = RunStandardAuction();
-  EXPECT_EQ("https://ad2.com/", res.ad_url.spec());
-  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})", res.ad_metadata);
-  EXPECT_EQ("https://anotheradthing.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Another Ad Thing", res.interest_group_name);
-  EXPECT_EQ("https://reporting.example.com/", res.seller_report_url.spec());
-  EXPECT_EQ("https://buyer-reporting.example.com/",
-            res.bidder_report_url.spec());
-  EXPECT_THAT(res.errors,
-              testing::ElementsAre("Failed to load "
-                                   "https://trustedsignaller.org/"
-                                   "signals?hostname=publisher1.com&keys=k1,k2 "
-                                   "HTTP status = 404 Not Found.",
-                                   "Failed to load "
-                                   "https://trustedsignaller.org/"
-                                   "signals?hostname=publisher1.com&keys=l1,l2 "
-                                   "HTTP status = 404 Not Found."));
+  const Result& res = RunStandardAuction();
+  EXPECT_EQ(GURL("https://ad2.com/"), res.ad_url);
+  EXPECT_EQ(GURL("https://reporting.example.com/"), res.seller_report_url);
+  EXPECT_EQ(GURL("https://buyer-reporting.example.com/"),
+            res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  ASSERT_EQ(4u, res.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            res.bidder2_prev_wins[3]->ad_json);
+  EXPECT_THAT(res.errors, testing::UnorderedElementsAre(
+                              "Failed to load "
+                              "https://trustedsignaller.org/"
+                              "signals?hostname=publisher1.com&keys=k1,k2 "
+                              "HTTP status = 404 Not Found.",
+                              "Failed to load "
+                              "https://trustedsignaller.org/"
+                              "signals?hostname=publisher1.com&keys=l1,l2 "
+                              "HTTP status = 404 Not Found."));
 }
 
 // A successful auction where seller reporting worklet doesn't set a URL.
@@ -1132,14 +1308,17 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra": "c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_EQ("https://ad2.com/", res.ad_url.spec());
-  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})", res.ad_metadata);
-  EXPECT_EQ("https://anotheradthing.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Another Ad Thing", res.interest_group_name);
-  EXPECT_TRUE(res.seller_report_url.is_empty());
-  EXPECT_EQ("https://buyer-reporting.example.com/",
-            res.bidder_report_url.spec());
+  const Result& res = RunStandardAuction();
+  EXPECT_EQ(GURL("https://ad2.com/"), res.ad_url);
+  EXPECT_FALSE(res.seller_report_url);
+  EXPECT_EQ(GURL("https://buyer-reporting.example.com/"),
+            res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  ASSERT_EQ(4u, res.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            res.bidder2_prev_wins[3]->ad_json);
   EXPECT_THAT(res.errors, testing::ElementsAre());
 }
 
@@ -1166,13 +1345,16 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra": "c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_EQ("https://ad2.com/", res.ad_url.spec());
-  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})", res.ad_metadata);
-  EXPECT_EQ("https://anotheradthing.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Another Ad Thing", res.interest_group_name);
-  EXPECT_EQ("https://reporting.example.com/", res.seller_report_url.spec());
-  EXPECT_TRUE(res.bidder_report_url.is_empty());
+  const Result& res = RunStandardAuction();
+  EXPECT_EQ(GURL("https://ad2.com/"), res.ad_url);
+  EXPECT_EQ(GURL("https://reporting.example.com/"), res.seller_report_url);
+  EXPECT_FALSE(res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  ASSERT_EQ(4u, res.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            res.bidder2_prev_wins[3]->ad_json);
   EXPECT_THAT(res.errors, testing::ElementsAre());
 }
 
@@ -1199,13 +1381,16 @@
       GURL(kTrustedSignalsUrl.spec() + "?hostname=publisher1.com&keys=l1,l2"),
       R"({"l1":"a", "l2": "b", "extra": "c"})");
 
-  Result res = RunStandardAuction();
-  EXPECT_EQ("https://ad2.com/", res.ad_url.spec());
-  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})", res.ad_metadata);
-  EXPECT_EQ("https://anotheradthing.com", res.interest_group_owner.Serialize());
-  EXPECT_EQ("Another Ad Thing", res.interest_group_name);
-  EXPECT_TRUE(res.seller_report_url.is_empty());
-  EXPECT_TRUE(res.bidder_report_url.is_empty());
+  const Result& res = RunStandardAuction();
+  EXPECT_EQ(GURL("https://ad2.com/"), res.ad_url);
+  EXPECT_FALSE(res.seller_report_url);
+  EXPECT_FALSE(res.bidder_report_url);
+  EXPECT_EQ(6, res.bidder1_bid_count);
+  EXPECT_EQ(3u, res.bidder1_prev_wins.size());
+  EXPECT_EQ(6, res.bidder2_bid_count);
+  ASSERT_EQ(4u, res.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            res.bidder2_prev_wins[3]->ad_json);
   EXPECT_THAT(res.errors, testing::ElementsAre());
 }
 
@@ -1246,13 +1431,13 @@
 
     auction_run_loop_->Run();
 
-    EXPECT_FALSE(result_.ad_url.is_valid());
-    EXPECT_TRUE(result_.ad_metadata.empty());
-    EXPECT_TRUE(result_.ad_url.is_empty());
-    EXPECT_TRUE(result_.interest_group_owner.opaque());
-    EXPECT_EQ("", result_.interest_group_name);
-    EXPECT_TRUE(result_.seller_report_url.is_empty());
-    EXPECT_TRUE(result_.bidder_report_url.is_empty());
+    EXPECT_FALSE(result_.ad_url);
+    EXPECT_FALSE(result_.seller_report_url);
+    EXPECT_FALSE(result_.bidder_report_url);
+    EXPECT_EQ(5, result_.bidder1_bid_count);
+    EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+    EXPECT_EQ(5, result_.bidder2_bid_count);
+    EXPECT_EQ(3u, result_.bidder2_prev_wins.size());
     EXPECT_THAT(
         result_.errors,
         testing::UnorderedElementsAre(
@@ -1324,11 +1509,14 @@
       // Bidder2 won, Bidder1 crashed.
       auction_run_loop_->Run();
       EXPECT_EQ(GURL("https://ad2.com/"), result_.ad_url);
-      EXPECT_EQ(R"({"render_url":"https://ad2.com/"})", result_.ad_metadata);
-      EXPECT_EQ(kBidder2, result_.interest_group_owner);
-      EXPECT_EQ(kBidder2Name, result_.interest_group_name);
-      EXPECT_TRUE(result_.seller_report_url.is_empty());
-      EXPECT_TRUE(result_.bidder_report_url.is_empty());
+      EXPECT_FALSE(result_.seller_report_url);
+      EXPECT_FALSE(result_.bidder_report_url);
+      EXPECT_EQ(6, result_.bidder1_bid_count);
+      EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+      EXPECT_EQ(6, result_.bidder2_bid_count);
+      ASSERT_EQ(4u, result_.bidder2_prev_wins.size());
+      EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+                result_.bidder2_prev_wins[3]->ad_json);
       EXPECT_THAT(result_.errors,
                   testing::ElementsAre(base::StringPrintf(
                       "%s crashed while trying to run generateBid().",
@@ -1380,11 +1568,14 @@
 
   // Bidder2 won.
   EXPECT_EQ(GURL("https://ad2.com/"), result_.ad_url);
-  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})", result_.ad_metadata);
-  EXPECT_EQ(kBidder2, result_.interest_group_owner);
-  EXPECT_EQ(kBidder2Name, result_.interest_group_name);
-  EXPECT_TRUE(result_.seller_report_url.is_empty());
-  EXPECT_TRUE(result_.bidder_report_url.is_empty());
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(6, result_.bidder1_bid_count);
+  EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(6, result_.bidder2_bid_count);
+  ASSERT_EQ(4u, result_.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            result_.bidder2_prev_wins[3]->ad_json);
   // Since Bidder1 crashed after bidding, don't report anything.
   EXPECT_THAT(result_.errors, testing::ElementsAre());
 }
@@ -1427,12 +1618,13 @@
   auction_run_loop_->Run();
 
   // No bidder won, Bidder1 crashed.
-  EXPECT_TRUE(result_.ad_url.is_empty());
-  EXPECT_TRUE(result_.ad_metadata.empty());
-  EXPECT_TRUE(result_.interest_group_owner.opaque());
-  EXPECT_EQ("", result_.interest_group_name);
-  EXPECT_TRUE(result_.seller_report_url.is_empty());
-  EXPECT_TRUE(result_.bidder_report_url.is_empty());
+  EXPECT_FALSE(result_.ad_url);
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(5, result_.bidder1_bid_count);
+  EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(5, result_.bidder2_bid_count);
+  EXPECT_EQ(3u, result_.bidder2_prev_wins.size());
   EXPECT_THAT(result_.errors,
               testing::ElementsAre(base::StringPrintf(
                   "%s crashed while idle.", kBidder1Url.spec().c_str())));
@@ -1475,12 +1667,13 @@
   auction_run_loop_->Run();
 
   // No bidder won, Bidder1 crashed.
-  EXPECT_TRUE(result_.ad_url.is_empty());
-  EXPECT_TRUE(result_.ad_metadata.empty());
-  EXPECT_TRUE(result_.interest_group_owner.opaque());
-  EXPECT_EQ("", result_.interest_group_name);
-  EXPECT_TRUE(result_.seller_report_url.is_empty());
-  EXPECT_TRUE(result_.bidder_report_url.is_empty());
+  EXPECT_FALSE(result_.ad_url);
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(5, result_.bidder1_bid_count);
+  EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(5, result_.bidder2_bid_count);
+  EXPECT_EQ(3u, result_.bidder2_prev_wins.size());
   EXPECT_THAT(result_.errors, testing::ElementsAre(base::StringPrintf(
                                   "%s crashed while trying to run reportWin().",
                                   kBidder1Url.spec().c_str())));
@@ -1565,12 +1758,13 @@
     auction_run_loop_->Run();
 
     // No bidder won, seller crashed.
-    EXPECT_TRUE(result_.ad_url.is_empty());
-    EXPECT_TRUE(result_.ad_metadata.empty());
-    EXPECT_TRUE(result_.interest_group_owner.opaque());
-    EXPECT_EQ("", result_.interest_group_name);
-    EXPECT_TRUE(result_.seller_report_url.is_empty());
-    EXPECT_TRUE(result_.bidder_report_url.is_empty());
+    EXPECT_FALSE(result_.ad_url);
+    EXPECT_FALSE(result_.seller_report_url);
+    EXPECT_FALSE(result_.bidder_report_url);
+    EXPECT_EQ(5, result_.bidder1_bid_count);
+    EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+    EXPECT_EQ(5, result_.bidder2_bid_count);
+    EXPECT_EQ(3u, result_.bidder2_prev_wins.size());
     EXPECT_THAT(result_.errors, testing::ElementsAre(base::StringPrintf(
                                     "%s crashed.", kSellerUrl.spec().c_str())));
   }
@@ -1675,12 +1869,13 @@
     EXPECT_EQ(test_case.expected_error_message, TakeBadMessage());
 
     // No bidder won.
-    EXPECT_TRUE(result_.ad_url.is_empty());
-    EXPECT_TRUE(result_.ad_metadata.empty());
-    EXPECT_TRUE(result_.interest_group_owner.opaque());
-    EXPECT_EQ("", result_.interest_group_name);
-    EXPECT_TRUE(result_.seller_report_url.is_empty());
-    EXPECT_TRUE(result_.bidder_report_url.is_empty());
+    EXPECT_FALSE(result_.ad_url);
+    EXPECT_FALSE(result_.seller_report_url);
+    EXPECT_FALSE(result_.bidder_report_url);
+    EXPECT_EQ(5, result_.bidder1_bid_count);
+    EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+    EXPECT_EQ(5, result_.bidder2_bid_count);
+    EXPECT_EQ(3u, result_.bidder2_prev_wins.size());
     EXPECT_THAT(result_.errors, testing::ElementsAre());
   }
 }
@@ -1719,12 +1914,13 @@
   EXPECT_EQ("Invalid seller report URL", TakeBadMessage());
 
   // No bidder won.
-  EXPECT_TRUE(result_.ad_url.is_empty());
-  EXPECT_TRUE(result_.ad_metadata.empty());
-  EXPECT_TRUE(result_.interest_group_owner.opaque());
-  EXPECT_EQ("", result_.interest_group_name);
-  EXPECT_TRUE(result_.seller_report_url.is_empty());
-  EXPECT_TRUE(result_.bidder_report_url.is_empty());
+  EXPECT_FALSE(result_.ad_url);
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(5, result_.bidder1_bid_count);
+  EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(5, result_.bidder2_bid_count);
+  EXPECT_EQ(3u, result_.bidder2_prev_wins.size());
   EXPECT_THAT(result_.errors, testing::ElementsAre());
 }
 
@@ -1763,12 +1959,13 @@
   EXPECT_EQ("Invalid bidder report URL", TakeBadMessage());
 
   // No bidder won.
-  EXPECT_TRUE(result_.ad_url.is_empty());
-  EXPECT_TRUE(result_.ad_metadata.empty());
-  EXPECT_TRUE(result_.interest_group_owner.opaque());
-  EXPECT_EQ("", result_.interest_group_name);
-  EXPECT_TRUE(result_.seller_report_url.is_empty());
-  EXPECT_TRUE(result_.bidder_report_url.is_empty());
+  EXPECT_FALSE(result_.ad_url);
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(5, result_.bidder1_bid_count);
+  EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(5, result_.bidder2_bid_count);
+  EXPECT_EQ(3u, result_.bidder2_prev_wins.size());
   EXPECT_THAT(result_.errors, testing::ElementsAre());
 }
 
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index e087d00..f9b7eed 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -5282,6 +5282,9 @@
   GURL coop_url = https_server()->GetURL(
       "coop.com", "/set-header?Cross-Origin-Opener-Policy: same-origin");
   EXPECT_TRUE(NavigateToURL(shell(), coop_url));
+  // Simulate user activation, which normally triggers COOP isolation for
+  // future BrowsingInstances.
+  EXPECT_TRUE(ExecJs(shell(), "// no-op"));
   EXPECT_EQ(web_contents()->GetMainFrame()->cross_origin_opener_policy().value,
             network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin);
   SiteInstanceImpl* coop_instance =
diff --git a/content/browser/loader/cors_origin_pattern_setter_unittest.cc b/content/browser/loader/cors_origin_pattern_setter_unittest.cc
index 343752c..43ba7bb 100644
--- a/content/browser/loader/cors_origin_pattern_setter_unittest.cc
+++ b/content/browser/loader/cors_origin_pattern_setter_unittest.cc
@@ -44,8 +44,7 @@
 
   {
     content::TestBrowserContext browser_context;
-    shared_list = content::BrowserContext::GetSharedCorsOriginAccessList(
-        &browser_context);
+    shared_list = browser_context.GetSharedCorsOriginAccessList();
     ASSERT_TRUE(shared_list);
 
     // The list is empty (in absence of calls to CorsOriginPatternSetter::Set).
@@ -108,8 +107,7 @@
     run_loop.Run();
 
     // Verify that the results got properly stored.
-    shared_list = content::BrowserContext::GetSharedCorsOriginAccessList(
-        &browser_context);
+    shared_list = browser_context.GetSharedCorsOriginAccessList();
     ASSERT_TRUE(shared_list);
     std::vector<network::mojom::CorsOriginAccessPatternsPtr> patterns =
         shared_list->GetOriginAccessList().CreateCorsOriginAccessPatternsList();
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 54d5a879..178a234 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -1241,11 +1241,10 @@
   // Loading and rendering a web page after the user clicks a link.
   base::TaskPriority file_factory_priority = base::TaskPriority::USER_BLOCKING;
   non_network_url_loader_factories_.emplace(
-      url::kFileScheme,
-      FileURLLoaderFactory::Create(
-          browser_context_->GetPath(),
-          BrowserContext::GetSharedCorsOriginAccessList(browser_context_),
-          file_factory_priority));
+      url::kFileScheme, FileURLLoaderFactory::Create(
+                            browser_context_->GetPath(),
+                            browser_context_->GetSharedCorsOriginAccessList(),
+                            file_factory_priority));
 
 #if defined(OS_ANDROID)
   non_network_url_loader_factories_.emplace(url::kContentScheme,
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 77062f0c..7f4dc27 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -7068,7 +7068,7 @@
           url::kFileScheme,
           FileURLLoaderFactory::Create(
               browser_context->GetPath(),
-              BrowserContext::GetSharedCorsOriginAccessList(browser_context),
+              browser_context->GetSharedCorsOriginAccessList(),
               file_factory_priority));
     }
 
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 617e5d59..1512a77 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -1373,15 +1373,11 @@
     }
 #endif
 
-    // If other child processes live on the UI thread then
-    // DiscardableSharedMemoryManager will have to be used only on UI thread.
-    if (!base::FeatureList::IsEnabled(features::kProcessHostOnUI)) {
-      if (auto r = receiver.As<discardable_memory::mojom::
-                                   DiscardableSharedMemoryManager>()) {
-        discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
-            std::move(r));
-        return;
-      }
+    if (auto r = receiver.As<
+                 discardable_memory::mojom::DiscardableSharedMemoryManager>()) {
+      discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
+          std::move(r));
+      return;
     }
 
     if (auto r = receiver.As<ukm::mojom::UkmRecorderInterface>()) {
@@ -5067,15 +5063,6 @@
   }
 #endif
 
-  if (base::FeatureList::IsEnabled(features::kProcessHostOnUI)) {
-    if (auto r = receiver.As<
-                 discardable_memory::mojom::DiscardableSharedMemoryManager>()) {
-      discardable_memory::DiscardableSharedMemoryManager::Get()->Bind(
-          std::move(r));
-      return;
-    }
-  }
-
   GetContentClient()->browser()->BindHostReceiverForRenderer(
       this, std::move(receiver));
 }
diff --git a/content/browser/webtransport/web_transport_connector_impl.cc b/content/browser/webtransport/web_transport_connector_impl.cc
index 9c6147c..de503b85 100644
--- a/content/browser/webtransport/web_transport_connector_impl.cc
+++ b/content/browser/webtransport/web_transport_connector_impl.cc
@@ -4,9 +4,13 @@
 
 #include "content/browser/webtransport/web_transport_connector_impl.h"
 #include "content/browser/devtools/devtools_instrumentation.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_client.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "net/quic/quic_transport_client.h"
 
@@ -77,6 +81,28 @@
     return;
   }
 
+  GetContentClient()->browser()->WillCreateWebTransport(
+      frame_.get(), url,
+      base::BindOnce(
+          &WebTransportConnectorImpl::OnWillCreateWebTransportCompleted,
+          weak_factory_.GetWeakPtr(), url, std::move(fingerprints),
+          std::move(handshake_client)));
+}
+
+void WebTransportConnectorImpl::OnWillCreateWebTransportCompleted(
+    const GURL& url,
+    std::vector<network::mojom::WebTransportCertificateFingerprintPtr>
+        fingerprints,
+    mojo::PendingRemote<network::mojom::WebTransportHandshakeClient>
+        handshake_client,
+    absl::optional<network::mojom::WebTransportErrorPtr> error) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+  RenderProcessHost* process = RenderProcessHost::FromID(process_id_);
+  if (!process) {
+    return;
+  }
+
   mojo::PendingRemote<WebTransportHandshakeClient> handshake_client_to_pass;
   // TODO(yhirano): Stop using MakeSelfOwnedReceiver here, because the
   // WebTransport implementation in the network service won't notice that
@@ -86,6 +112,16 @@
           frame_, url, std::move(handshake_client)),
       handshake_client_to_pass.InitWithNewPipeAndPassReceiver());
 
+  if (error) {
+    mojo::Remote<WebTransportHandshakeClient> remote(
+        std::move(handshake_client_to_pass));
+    remote->OnHandshakeFailed(net::WebTransportError(
+        error.value()->net_error,
+        static_cast<quic::QuicErrorCode>(error.value()->quic_error),
+        error.value()->details, error.value()->safe_to_report_details));
+    return;
+  }
+
   process->GetStoragePartition()->GetNetworkContext()->CreateWebTransport(
       url, origin_, network_isolation_key_, std::move(fingerprints),
       std::move(handshake_client_to_pass));
diff --git a/content/browser/webtransport/web_transport_connector_impl.h b/content/browser/webtransport/web_transport_connector_impl.h
index 323ad82..0eef84d 100644
--- a/content/browser/webtransport/web_transport_connector_impl.h
+++ b/content/browser/webtransport/web_transport_connector_impl.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_WEBTRANSPORT_WEB_TRANSPORT_CONNECTOR_IMPL_H_
 
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/base/network_isolation_key.h"
 #include "services/network/public/mojom/network_context.mojom.h"
@@ -38,10 +39,20 @@
           handshake_client) override;
 
  private:
+  void OnWillCreateWebTransportCompleted(
+      const GURL& url,
+      std::vector<network::mojom::WebTransportCertificateFingerprintPtr>
+          fingerprints,
+      mojo::PendingRemote<network::mojom::WebTransportHandshakeClient>
+          handshake_client,
+      absl::optional<network::mojom::WebTransportErrorPtr> error);
+
   const int process_id_;
   const base::WeakPtr<RenderFrameHostImpl> frame_;
   const url::Origin origin_;
   const net::NetworkIsolationKey network_isolation_key_;
+
+  base::WeakPtrFactory<WebTransportConnectorImpl> weak_factory_{this};
 };
 
 }  // namespace content
diff --git a/content/browser/worker_host/worker_script_fetch_initiator.cc b/content/browser/worker_host/worker_script_fetch_initiator.cc
index 07a2e4e0..dd7fa0c 100644
--- a/content/browser/worker_host/worker_script_fetch_initiator.cc
+++ b/content/browser/worker_host/worker_script_fetch_initiator.cc
@@ -230,8 +230,8 @@
     non_network_factories.emplace(
         url::kFileScheme, FileURLLoaderFactory::Create(
                               storage_partition->browser_context()->GetPath(),
-                              BrowserContext::GetSharedCorsOriginAccessList(
-                                  storage_partition->browser_context()),
+                              storage_partition->browser_context()
+                                  ->GetSharedCorsOriginAccessList(),
                               file_factory_priority));
   }
 
diff --git a/content/common/partition_alloc_support.cc b/content/common/partition_alloc_support.cc
index 35f0a640..ed45764 100644
--- a/content/common/partition_alloc_support.cc
+++ b/content/common/partition_alloc_support.cc
@@ -55,7 +55,7 @@
 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && PA_ALLOW_PCSCAN
   DCHECK(base::FeatureList::GetInstance());
   if (base::FeatureList::IsEnabled(base::features::kPartitionAllocPCScan)) {
-    base::allocator::EnablePCScan();
+    base::allocator::EnablePCScan(/*dcscan*/ false);
     return true;
   }
 #endif
@@ -67,7 +67,13 @@
   DCHECK(base::FeatureList::GetInstance());
   if (base::FeatureList::IsEnabled(
           base::features::kPartitionAllocPCScanBrowserOnly)) {
-    base::allocator::EnablePCScan();
+    const bool dcscan_wanted =
+        base::FeatureList::IsEnabled(base::features::kPartitionAllocDCScan);
+#if !defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
+    CHECK(!dcscan_wanted)
+        << "DCScan is currently only supported on Linux based systems";
+#endif
+    base::allocator::EnablePCScan(dcscan_wanted);
     return true;
   }
 #endif
diff --git a/content/public/browser/browser_context.h b/content/public/browser/browser_context.h
index b96752c4..e42b740 100644
--- a/content/public/browser/browser_context.h
+++ b/content/public/browser/browser_context.h
@@ -100,11 +100,6 @@
   // The currently recommended practice is to make the methods in this section
   // non-virtual instance methods.
   //
-  // TODO(https://crbug.com/1179776): Finish converting the methods in this
-  // section into non-virtual instance methods.  (The old, abandoned practice
-  // was to make the methods in this section `static` and have them take
-  // `BrowserContext* self` as the first parameter.)
-  //
   // TODO(https://crbug.com/1179776): Consider moving these methods to
   // BrowserContext::Impl or (in the future) BrowserContextImpl class.
 
@@ -116,7 +111,7 @@
   // Returns BrowserContext specific external mount points. It may return
   // nullptr if the context doesn't have any BrowserContext specific external
   // mount points. Currently, non-nullptr value is returned only on ChromeOS.
-  static storage::ExternalMountPoints* GetMountPoints(BrowserContext* self);
+  storage::ExternalMountPoints* GetMountPoints();
 
   // Returns a BrowsingDataRemover that can schedule data deletion tasks
   // for this |context|.
@@ -207,18 +202,18 @@
       blink::mojom::PushSubscriptionPtr old_subscription,
       base::OnceCallback<void(blink::mojom::PushEventStatus)> callback);
 
-  static void NotifyWillBeDestroyed(BrowserContext* self);
+  void NotifyWillBeDestroyed();
 
   // Ensures that the corresponding ResourceContext is initialized. Normally the
   // BrowserContext initializs the corresponding getters when its objects are
   // created, but if the embedder wants to pass the ResourceContext to another
   // thread before they use BrowserContext, they should call this to make sure
   // that the ResourceContext is ready.
-  static void EnsureResourceContextInitialized(BrowserContext* self);
+  void EnsureResourceContextInitialized();
 
   // Tells the HTML5 objects on this context to persist their session state
   // across the next restart.
-  static void SaveSessionState(BrowserContext* self);
+  void SaveSessionState();
 
   void SetDownloadManagerForTesting(
       std::unique_ptr<DownloadManager> download_manager);
@@ -231,8 +226,7 @@
   // network::mojom::NetworkContextParams::cors_origin_access_list) and 2)
   // consulted by CORS-aware factories (e.g. passed when constructing
   // FileURLLoaderFactory).
-  static SharedCorsOriginAccessList* GetSharedCorsOriginAccessList(
-      BrowserContext* self);
+  SharedCorsOriginAccessList* GetSharedCorsOriginAccessList();
 
   // Shuts down the storage partitions associated to this browser context.
   // This must be called before the browser context is actually destroyed
diff --git a/content/public/browser/child_process_security_policy.h b/content/public/browser/child_process_security_policy.h
index ab01cf0..eff0b82 100644
--- a/content/public/browser/child_process_security_policy.h
+++ b/content/public/browser/child_process_security_policy.h
@@ -325,6 +325,13 @@
   // within that particular BrowserContext will be returned (note that this
   // includes both matching per-profile isolated origins as well as globally
   // applicable origins which apply to |browser_context| by definition).
+  //
+  // Origins returned by this function only include origins that would apply to
+  // any future BrowsingInstance (browsing context group).  Origins that were
+  // isolated only in specific BrowsingInstances are not included.  (In
+  // particular, this excludes BrowsingInstance-specific isolated origins for
+  // Origin-Agent-Cluster as well as COOP documents loaded without user
+  // activation.)
   virtual std::vector<url::Origin> GetIsolatedOrigins(
       absl::optional<IsolatedOriginSource> source = absl::nullopt,
       BrowserContext* browser_context = nullptr) = 0;
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 839be65..0a22650 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -816,6 +816,13 @@
   NOTREACHED();
 }
 
+void ContentBrowserClient::WillCreateWebTransport(
+    RenderFrameHost* frame,
+    const GURL& url,
+    WillCreateWebTransportCallback callback) {
+  std::move(callback).Run(absl::nullopt);
+}
+
 bool ContentBrowserClient::WillCreateRestrictedCookieManager(
     network::mojom::RestrictedCookieManagerRole role,
     BrowserContext* browser_context,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 42158d28..71d4002 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -49,6 +49,7 @@
 #include "services/network/public/mojom/network_context.mojom-forward.h"
 #include "services/network/public/mojom/restricted_cookie_manager.mojom-forward.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
+#include "services/network/public/mojom/web_transport.mojom-forward.h"
 #include "services/network/public/mojom/websocket.mojom-forward.h"
 #include "storage/browser/file_system/file_system_context.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -1477,6 +1478,14 @@
       mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
           handshake_client);
 
+  // Allows the embedder to control if establishing a WebTransport connection is
+  // allowed. When the connection is blocked, `callback` is called with `error`.
+  using WillCreateWebTransportCallback = base::OnceCallback<void(
+      absl::optional<network::mojom::WebTransportErrorPtr> error)>;
+  virtual void WillCreateWebTransport(RenderFrameHost* frame,
+                                      const GURL& url,
+                                      WillCreateWebTransportCallback callback);
+
   // Allows the embedder to intercept or replace the mojo objects used for
   // preference-following access to cookies. This is primarily used for objects
   // vended to renderer processes for limited, origin-locked (to |origin|),
diff --git a/content/public/browser/cors_origin_pattern_setter.cc b/content/public/browser/cors_origin_pattern_setter.cc
index a1ea156..1d2ad94 100644
--- a/content/public/browser/cors_origin_pattern_setter.cc
+++ b/content/public/browser/cors_origin_pattern_setter.cc
@@ -60,9 +60,9 @@
   // Keep the per-profile access list up to date so that we can use this to
   // restore NetworkContext settings at anytime, e.g. on restarting the
   // network service.
-  content::BrowserContext::GetSharedCorsOriginAccessList(browser_context)
-      ->SetForOrigin(source_origin, std::move(allow_patterns),
-                     std::move(block_patterns), barrier_closure);
+  browser_context->GetSharedCorsOriginAccessList()->SetForOrigin(
+      source_origin, std::move(allow_patterns), std::move(block_patterns),
+      barrier_closure);
 }
 
 }  // namespace content
diff --git a/content/public/test/test_browser_context.cc b/content/public/test/test_browser_context.cc
index 44fa38f..d4f572a 100644
--- a/content/public/test/test_browser_context.cc
+++ b/content/public/test/test_browser_context.cc
@@ -40,7 +40,7 @@
       << "the BrowserTaskEnvironment instance.  "
       << BrowserThread::GetDCheckCurrentlyOnErrorMessage(BrowserThread::UI);
 
-  NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
   ShutdownStoragePartitions();
 
   // Various things that were just torn down above post tasks to other
diff --git a/content/shell/browser/shell_browser_context.cc b/content/shell/browser/shell_browser_context.cc
index 815f995..91eb6df 100644
--- a/content/shell/browser/shell_browser_context.cc
+++ b/content/shell/browser/shell_browser_context.cc
@@ -59,7 +59,7 @@
 }
 
 ShellBrowserContext::~ShellBrowserContext() {
-  NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
 
   // The SimpleDependencyManager should always be passed after the
   // BrowserContextDependencyManager. This is because the KeyedService instances
diff --git a/content/web_test/browser/web_test_browser_context.cc b/content/web_test/browser/web_test_browser_context.cc
index eab5c7e..5c278ee4e 100644
--- a/content/web_test/browser/web_test_browser_context.cc
+++ b/content/web_test/browser/web_test_browser_context.cc
@@ -45,7 +45,7 @@
 }
 
 WebTestBrowserContext::~WebTestBrowserContext() {
-  BrowserContext::NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
 }
 
 DownloadManagerDelegate* WebTestBrowserContext::GetDownloadManagerDelegate() {
diff --git a/device/DIR_METADATA b/device/DIR_METADATA
index f4708701..78f0fb6 100644
--- a/device/DIR_METADATA
+++ b/device/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Services>Device"
diff --git a/device/bluetooth/DIR_METADATA b/device/bluetooth/DIR_METADATA
index 4879b90..249d388a 100644
--- a/device/bluetooth/DIR_METADATA
+++ b/device/bluetooth/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "IO>Bluetooth"
diff --git a/device/bluetooth/dbus/DIR_METADATA b/device/bluetooth/dbus/DIR_METADATA
index 661058d..ca1c895 100644
--- a/device/bluetooth/dbus/DIR_METADATA
+++ b/device/bluetooth/dbus/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "OS>Systems>Bluetooth"
diff --git a/device/bluetooth/test/README.md b/device/bluetooth/test/README.md
index 7544bb3..fdcd44c3 100644
--- a/device/bluetooth/test/README.md
+++ b/device/bluetooth/test/README.md
@@ -84,6 +84,6 @@
 
 Bluetooth controller system tests generating radio signals are run and managed
 by the Chrome OS team. See:
-https://chromium.googlesource.com/chromiumos/third_party/autotest/+/master/server/site_tests/
-https://chromium.googlesource.com/chromiumos/third_party/autotest/+/master/server/cros/bluetooth/
-https://chromium.googlesource.com/chromiumos/third_party/autotest/+/master/client/cros/bluetooth/
+https://chromium.googlesource.com/chromiumos/third_party/autotest/+/main/server/site_tests/
+https://chromium.googlesource.com/chromiumos/third_party/autotest/+/main/server/cros/bluetooth/
+https://chromium.googlesource.com/chromiumos/third_party/autotest/+/main/client/cros/bluetooth/
diff --git a/device/fido/DIR_METADATA b/device/fido/DIR_METADATA
index 6152f17..0373478a 100644
--- a/device/fido/DIR_METADATA
+++ b/device/fido/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Blink>WebAuthentication"
diff --git a/device/gamepad/DIR_METADATA b/device/gamepad/DIR_METADATA
index 60e948d..9ea741b 100644
--- a/device/gamepad/DIR_METADATA
+++ b/device/gamepad/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "IO>Gamepad"
diff --git a/device/vr/DIR_METADATA b/device/vr/DIR_METADATA
index 203eb72a..bbaaf424 100644
--- a/device/vr/DIR_METADATA
+++ b/device/vr/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>XR"
diff --git a/device/vr/android/arcore/DIR_METADATA b/device/vr/android/arcore/DIR_METADATA
index 27ea7f1..54441bf 100644
--- a/device/vr/android/arcore/DIR_METADATA
+++ b/device/vr/android/arcore/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Blink>WebXR>AR"
diff --git a/extensions/browser/api/storage/storage_api.cc b/extensions/browser/api/storage/storage_api.cc
index 88a6ca9e..433f0644 100644
--- a/extensions/browser/api/storage/storage_api.cc
+++ b/extensions/browser/api/storage/storage_api.cc
@@ -33,18 +33,6 @@
 constexpr char kSessionStorageManagerKeyName[] =
     "StorageAPI SessionStorageManager";
 
-// Adds all StringValues from a ListValue to a vector of strings.
-void AddAllStringValues(const base::ListValue& from,
-                        std::vector<std::string>* to) {
-  DCHECK(to->empty());
-  std::string as_string;
-  for (const auto& entry : from.GetList()) {
-    if (entry.GetAsString(&as_string)) {
-      to->push_back(as_string);
-    }
-  }
-}
-
 // Returns a vector of any strings within the given list.
 std::vector<std::string> GetKeysFromList(const base::Value& list) {
   DCHECK(list.is_list());
@@ -58,15 +46,6 @@
   return keys;
 }
 
-// Gets the keys of a DictionaryValue.
-std::vector<std::string> GetKeys(const base::DictionaryValue& dict) {
-  std::vector<std::string> keys;
-  for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
-    keys.push_back(it.key());
-  }
-  return keys;
-}
-
 // Returns a vector of keys within the given dict.
 std::vector<std::string> GetKeysFromDict(const base::Value& dict) {
   DCHECK(dict.is_dict());
@@ -230,7 +209,7 @@
     ValueStore* storage) {
   TRACE_EVENT1("browser", "StorageStorageAreaGetFunction::RunWithStorage",
                "extension_id", extension_id());
-  base::Value* input = NULL;
+  base::Value* input = nullptr;
   if (!args_->Get(0, &input))
     return BadMessage();
 
@@ -238,33 +217,19 @@
     case base::Value::Type::NONE:
       return UseReadResult(storage->Get());
 
-    case base::Value::Type::STRING: {
-      std::string as_string;
-      input->GetAsString(&as_string);
-      return UseReadResult(storage->Get(as_string));
-    }
+    case base::Value::Type::STRING:
+      return UseReadResult(storage->Get(input->GetString()));
 
-    case base::Value::Type::LIST: {
-      // TODO(crbug.com/1200931): Replace with GetKeysFromList() and delete
-      // AddAllStringValues().
-      std::vector<std::string> as_string_list;
-      AddAllStringValues(*static_cast<base::ListValue*>(input),
-                         &as_string_list);
-      return UseReadResult(storage->Get(as_string_list));
-    }
+    case base::Value::Type::LIST:
+      return UseReadResult(storage->Get(GetKeysFromList(*input)));
 
     case base::Value::Type::DICTIONARY: {
-      // TODO(crbug.com/1200931): Replace with GetKeysFromDict() and delete
-      // GetKeys().
-      base::DictionaryValue* as_dict =
-          static_cast<base::DictionaryValue*>(input);
-      ValueStore::ReadResult result = storage->Get(GetKeys(*as_dict));
+      ValueStore::ReadResult result = storage->Get(GetKeysFromDict(*input));
       if (!result.status().ok()) {
         return UseReadResult(std::move(result));
       }
-
       std::unique_ptr<base::DictionaryValue> with_default_values =
-          as_dict->CreateDeepCopy();
+          static_cast<base::DictionaryValue*>(input)->CreateDeepCopy();
       with_default_values->MergeDictionary(&result.settings());
       return UseReadResult(ValueStore::ReadResult(
           std::move(with_default_values), result.PassStatus()));
@@ -325,7 +290,7 @@
                "StorageStorageAreaGetBytesInUseFunction::RunWithStorage",
                "extension_id", extension_id());
 
-  base::Value* input = NULL;
+  base::Value* input = nullptr;
   if (!args_->Get(0, &input))
     return BadMessage();
 
@@ -336,20 +301,13 @@
       bytes_in_use = storage->GetBytesInUse();
       break;
 
-    case base::Value::Type::STRING: {
-      std::string as_string;
-      input->GetAsString(&as_string);
-      bytes_in_use = storage->GetBytesInUse(as_string);
+    case base::Value::Type::STRING:
+      bytes_in_use = storage->GetBytesInUse(input->GetString());
       break;
-    }
 
-    case base::Value::Type::LIST: {
-      std::vector<std::string> as_string_list;
-      AddAllStringValues(*static_cast<base::ListValue*>(input),
-                         &as_string_list);
-      bytes_in_use = storage->GetBytesInUse(as_string_list);
+    case base::Value::Type::LIST:
+      bytes_in_use = storage->GetBytesInUse(GetKeysFromList(*input));
       break;
-    }
 
     default:
       return BadMessage();
@@ -369,7 +327,7 @@
     ValueStore* storage) {
   TRACE_EVENT1("browser", "StorageStorageAreaSetFunction::RunWithStorage",
                "extension_id", extension_id());
-  base::DictionaryValue* input = NULL;
+  base::DictionaryValue* input = nullptr;
   if (!args_->GetDictionary(0, &input))
     return BadMessage();
   return UseWriteResult(storage->Set(ValueStore::DEFAULTS, *input));
@@ -415,23 +373,16 @@
 StorageStorageAreaRemoveFunction::RunWithStorage(ValueStore* storage) {
   TRACE_EVENT1("browser", "StorageStorageAreaRemoveFunction::RunWithStorage",
                "extension_id", extension_id());
-  base::Value* input = NULL;
+  base::Value* input = nullptr;
   if (!args_->Get(0, &input))
     return BadMessage();
 
   switch (input->type()) {
-    case base::Value::Type::STRING: {
-      std::string as_string;
-      input->GetAsString(&as_string);
-      return UseWriteResult(storage->Remove(as_string));
-    }
+    case base::Value::Type::STRING:
+      return UseWriteResult(storage->Remove(input->GetString()));
 
-    case base::Value::Type::LIST: {
-      std::vector<std::string> as_string_list;
-      AddAllStringValues(*static_cast<base::ListValue*>(input),
-                         &as_string_list);
-      return UseWriteResult(storage->Remove(as_string_list));
-    }
+    case base::Value::Type::LIST:
+      return UseWriteResult(storage->Remove(GetKeysFromList(*input)));
 
     default:
       return BadMessage();
diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h
index 2f5f048..a0fb8f09 100644
--- a/extensions/browser/extension_event_histogram_value.h
+++ b/extensions/browser/extension_event_histogram_value.h
@@ -345,9 +345,9 @@
   WINDOWS_ON_REMOVED = 324,
   FILE_SYSTEM_PROVIDER_ON_EXECUTE_ACTION_REQUESTED = 325,
   FILE_SYSTEM_PROVIDER_ON_GET_ACTIONS_REQUESTED = 326,
-  LAUNCHER_SEARCH_PROVIDER_ON_QUERY_STARTED = 327,
-  LAUNCHER_SEARCH_PROVIDER_ON_QUERY_ENDED = 328,
-  LAUNCHER_SEARCH_PROVIDER_ON_OPEN_RESULT = 329,
+  DELETED_LAUNCHER_SEARCH_PROVIDER_ON_QUERY_STARTED = 327,
+  DELETED_LAUNCHER_SEARCH_PROVIDER_ON_QUERY_ENDED = 328,
+  DELETED_LAUNCHER_SEARCH_PROVIDER_ON_OPEN_RESULT = 329,
   CHROME_WEB_VIEW_INTERNAL_ON_CLICKED = 330,
   WEB_VIEW_INTERNAL_CONTEXT_MENUS = 331,
   CONTEXT_MENUS = 332,
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index f6d5805..d7e7fb9 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1053,7 +1053,7 @@
   DELETED_EASYUNLOCKPRIVATE_HIDEERRORBUBBLE = 992,
   WEBVIEWINTERNAL_SETZOOMMODE = 993,
   WEBVIEWINTERNAL_GETZOOMMODE = 994,
-  LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS = 995,
+  DELETED_LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS = 995,
   DELETED_DATAREDUCTIONPROXY_CLEARDATASAVINGS = 996,
   BLUETOOTHPRIVATE_SETDISCOVERYFILTER = 997,
   FILESYSTEM_GETVOLUMELIST = 998,
diff --git a/extensions/common/mojom/api_permission_id.mojom b/extensions/common/mojom/api_permission_id.mojom
index 63e1cad..96e136b1 100644
--- a/extensions/common/mojom/api_permission_id.mojom
+++ b/extensions/common/mojom/api_permission_id.mojom
@@ -118,7 +118,7 @@
   kInput = 92,
   kInputMethodPrivate = 93,
   kDeleted_InterceptAllKeys = 94,
-  kLauncherSearchProvider = 95,
+  kDeleted_LauncherSearchProvider = 95,
   kLocation = 96,
   kDeleted_LogPrivate = 97,
   kManagement = 98,
diff --git a/extensions/shell/browser/shell_browser_context.cc b/extensions/shell/browser/shell_browser_context.cc
index 665bd9b..f625fd2 100644
--- a/extensions/shell/browser/shell_browser_context.cc
+++ b/extensions/shell/browser/shell_browser_context.cc
@@ -21,7 +21,7 @@
       storage_policy_(new ShellSpecialStoragePolicy) {}
 
 ShellBrowserContext::~ShellBrowserContext() {
-  content::BrowserContext::NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
 }
 
 content::BrowserPluginGuestManager* ShellBrowserContext::GetGuestManager() {
diff --git a/fuchsia/DIR_METADATA b/fuchsia/DIR_METADATA
index de0c11f0..11f5ec9 100644
--- a/fuchsia/DIR_METADATA
+++ b/fuchsia/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Fuchsia"
diff --git a/fuchsia/engine/browser/accessibility_bridge_browsertest.cc b/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
index 5062b6b..4b840ed 100644
--- a/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
+++ b/fuchsia/engine/browser/accessibility_bridge_browsertest.cc
@@ -7,6 +7,7 @@
 #include <zircon/types.h>
 
 #include "base/strings/stringprintf.h"
+#include "base/test/bind.h"
 #include "content/public/test/browser_test.h"
 #include "fuchsia/base/mem_buffer_util.h"
 #include "fuchsia/base/test/frame_test_util.h"
@@ -1041,10 +1042,17 @@
       ->GetMainFrame()
       ->AccessibilityPerformAction(action_data);
 
-  base::RunLoop run_loop;
-  semantics_manager_.semantic_tree()->SetNodeUpdatedCallback(
-      0u, run_loop.QuitClosure());
-  run_loop.Run();
+  auto* semantic_tree = semantics_manager_.semantic_tree();
+
+  semantic_tree->RunUntilCondititionIsTrue(
+      base::BindLambdaForTesting([semantic_tree]() {
+        auto* node = semantic_tree->GetNodeWithId(0u);
+        if (!node)
+          return false;
+
+        return node->has_states() && node->states().has_has_input_focus() &&
+               node->states().has_input_focus();
+      }));
 
   ASSERT_TRUE(semantics_manager_.semantic_tree()
                   ->GetNodeWithId(0u)
@@ -1063,10 +1071,20 @@
       ->GetMainFrame()
       ->AccessibilityPerformAction(action_data);
 
-  base::RunLoop run_loop2;
-  semantics_manager_.semantic_tree()->SetNodeUpdatedCallback(
-      new_focus_id, run_loop2.QuitClosure());
-  run_loop2.Run();
+  semantic_tree->RunUntilCondititionIsTrue(
+      base::BindLambdaForTesting([semantic_tree, new_focus_id]() {
+        auto* root = semantic_tree->GetNodeWithId(0u);
+        auto* node = semantic_tree->GetNodeWithId(new_focus_id);
+
+        if (!node || !root)
+          return false;
+
+        // Node has the focus, root does not.
+        return (node->has_states() && node->states().has_has_input_focus() &&
+                node->states().has_input_focus()) &&
+               (root->has_states() && root->states().has_has_input_focus() &&
+                !root->states().has_input_focus());
+      }));
 
   ASSERT_FALSE(semantics_manager_.semantic_tree()
                    ->GetNodeWithId(0u)
diff --git a/fuchsia/engine/browser/fake_semantic_tree.cc b/fuchsia/engine/browser/fake_semantic_tree.cc
index 87c49e0..261af059 100644
--- a/fuchsia/engine/browser/fake_semantic_tree.cc
+++ b/fuchsia/engine/browser/fake_semantic_tree.cc
@@ -177,3 +177,19 @@
 void FakeSemanticTree::Clear() {
   nodes_.clear();
 }
+
+void FakeSemanticTree::RunUntilCondititionIsTrue(
+    base::RepeatingCallback<bool()> condition) {
+  DCHECK(!on_commit_updates_);
+  if (condition.Run())
+    return;
+
+  base::RunLoop run_loop;
+  base::AutoReset<base::RepeatingClosure> auto_reset(
+      &on_commit_updates_,
+      base::BindLambdaForTesting([&condition, &run_loop]() {
+        if (condition.Run())
+          run_loop.Quit();
+      }));
+  run_loop.Run();
+}
diff --git a/fuchsia/engine/browser/fake_semantic_tree.h b/fuchsia/engine/browser/fake_semantic_tree.h
index 8c79858..04ae950 100644
--- a/fuchsia/engine/browser/fake_semantic_tree.h
+++ b/fuchsia/engine/browser/fake_semantic_tree.h
@@ -38,6 +38,7 @@
   void RunUntilNodeCountAtLeast(size_t count);
   void RunUntilNodeWithLabelIsInTree(base::StringPiece label);
   void RunUntilCommitCountIs(size_t count);
+  void RunUntilCondititionIsTrue(base::RepeatingCallback<bool()> condition);
   void SetNodeUpdatedCallback(uint32_t node_id,
                               base::OnceClosure node_updated_callback);
   fuchsia::accessibility::semantics::Node* GetNodeWithId(uint32_t id);
diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc
index ca562b9b..bfed581 100644
--- a/fuchsia/engine/browser/frame_impl.cc
+++ b/fuchsia/engine/browser/frame_impl.cc
@@ -23,6 +23,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/host_zoom_map.h"
 #include "content/public/browser/media_session.h"
 #include "content/public/browser/message_port_provider.h"
 #include "content/public/browser/navigation_entry.h"
@@ -30,6 +31,7 @@
 #include "content/public/browser/permission_controller_delegate.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/renderer_preferences_util.h"
@@ -53,6 +55,7 @@
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/logging/logging_utils.h"
 #include "third_party/blink/public/common/messaging/web_message_port.h"
+#include "third_party/blink/public/common/page/page_zoom.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/compositor.h"
@@ -581,6 +584,15 @@
       std::move(cast_streaming_receiver));
 }
 
+void FrameImpl::UpdateRenderViewZoomLevel(
+    content::RenderViewHost* render_view_host) {
+  content::HostZoomMap* host_zoom_map =
+      content::HostZoomMap::GetForWebContents(web_contents_.get());
+  host_zoom_map->SetTemporaryZoomLevel(
+      render_view_host->GetProcess()->GetID(), render_view_host->GetRoutingID(),
+      blink::PageZoomFactorToZoomLevel(page_scale_));
+}
+
 void FrameImpl::CreateView(fuchsia::ui::views::ViewToken view_token) {
   scenic::ViewRefPair view_ref_pair = scenic::ViewRefPair::New();
   CreateViewWithViewRef(std::move(view_token),
@@ -948,6 +960,19 @@
                                      base::Unretained(this)));
 }
 
+void FrameImpl::SetPageScale(float scale) {
+  if (scale <= 0.0) {
+    LOG(ERROR) << "SetPageScale() called with nonpositive scale.";
+    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
+    return;
+  }
+
+  if (scale == page_scale_)
+    return;
+  page_scale_ = scale;
+  UpdateRenderViewZoomLevel(web_contents_->GetRenderViewHost());
+}
+
 void FrameImpl::ForceContentDimensions(
     std::unique_ptr<fuchsia::ui::gfx::vec2> web_dips) {
   if (!web_dips) {
@@ -959,7 +984,7 @@
 
   gfx::Size web_dips_converted(web_dips->x, web_dips->y);
   if (web_dips_converted.IsEmpty()) {
-    LOG(WARNING) << "Rejecting zero-area size for ForceContentDimensions().";
+    LOG(ERROR) << "Rejecting zero-area size for ForceContentDimensions().";
     CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
     return;
   }
@@ -1146,8 +1171,17 @@
 void FrameImpl::RenderFrameCreated(content::RenderFrameHost* frame_host) {
   // The top-level frame is given a transparent background color.
   // GetView() is guaranteed to be non-null until |frame_host| teardown.
-  if (frame_host == web_contents()->GetMainFrame())
+  if (frame_host == web_contents()->GetMainFrame()) {
     frame_host->GetView()->SetBackgroundColor(SK_AlphaTRANSPARENT);
+  }
+}
+
+void FrameImpl::RenderViewHostChanged(content::RenderViewHost* old_host,
+                                      content::RenderViewHost* new_host) {
+  // UpdateRenderViewZoomLevel() sets temporary zoom level for the current
+  // RenderView. It needs to be called again whenever main RenderView is
+  // changed.
+  UpdateRenderViewZoomLevel(new_host);
 }
 
 void FrameImpl::DidFirstVisuallyNonEmptyPaint() {
diff --git a/fuchsia/engine/browser/frame_impl.h b/fuchsia/engine/browser/frame_impl.h
index 5ae4257..73f2d1c 100644
--- a/fuchsia/engine/browser/frame_impl.h
+++ b/fuchsia/engine/browser/frame_impl.h
@@ -168,6 +168,9 @@
 
   void MaybeStartCastStreaming(content::NavigationHandle* navigation_handle);
 
+  // Updates zoom level for the specified |render_view_host|.
+  void UpdateRenderViewZoomLevel(content::RenderViewHost* render_view_host);
+
   // fuchsia::web::Frame implementation.
   void CreateView(fuchsia::ui::views::ViewToken view_token) override;
   void CreateViewWithViewRef(fuchsia::ui::views::ViewToken view_token,
@@ -228,6 +231,7 @@
       fidl::InterfaceHandle<fuchsia::web::NavigationPolicyProvider> provider)
       override;
   void SetPreferredTheme(fuchsia::settings::ThemeType theme) override;
+  void SetPageScale(float scale) override;
 
   // content::WebContentsDelegate implementation.
   void CloseContents(content::WebContents* source) override;
@@ -270,6 +274,8 @@
   void DidFinishLoad(content::RenderFrameHost* render_frame_host,
                      const GURL& validated_url) override;
   void RenderFrameCreated(content::RenderFrameHost* frame_host) override;
+  void RenderViewHostChanged(content::RenderViewHost* old_host,
+                             content::RenderViewHost* new_host) override;
   void DidFirstVisuallyNonEmptyPaint() override;
   void ResourceLoadComplete(
       content::RenderFrameHost* render_frame_host,
@@ -306,6 +312,9 @@
   FramePermissionController permission_controller_;
   std::unique_ptr<NavigationPolicyHandler> navigation_policy_handler_;
 
+  // Current page scale. Updated by calling SetPageScale().
+  float page_scale_ = 1.0;
+
   // Session ID to use for fuchsia.media.AudioConsumer. Set with
   // SetMediaSessionId().
   uint64_t media_session_id_ = 0;
diff --git a/fuchsia/engine/browser/frame_impl_browsertest.cc b/fuchsia/engine/browser/frame_impl_browsertest.cc
index 8ae66daa..652fba3 100644
--- a/fuchsia/engine/browser/frame_impl_browsertest.cc
+++ b/fuchsia/engine/browser/frame_impl_browsertest.cc
@@ -72,6 +72,7 @@
 const char kPopupRedirectPath[] = "/popup_child.html";
 const char kPopupMultiplePath[] = "/popup_multiple.html";
 const char kVisibilityPath[] = "/visibility.html";
+const char kWaitSizePath[] = "/wait-size.html";
 const char kPage1Title[] = "title 1";
 const char kPage2Title[] = "title 2";
 const char kPage3Title[] = "websql not available";
@@ -1329,6 +1330,116 @@
   }
 }
 
+// TODO(crbug.com/1058247): Re-enable this test on Arm64 when femu is available
+// for that architecture. This test requires Vulkan and Scenic to properly
+// signal the Views visibility.
+#if defined(ARCH_CPU_ARM_FAMILY)
+#define MAYBE_SetPageScale DISABLED_SetPageScale
+#else
+#define MAYBE_SetPageScale SetPageScale
+#endif
+IN_PROC_BROWSER_TEST_F(FrameImplTest, MAYBE_SetPageScale) {
+  fuchsia::web::FramePtr frame = CreateFrame();
+
+  auto view_tokens = scenic::ViewTokenPair::New();
+  frame->CreateView(std::move(view_tokens.view_token));
+
+  // Attach the View to a Presenter, the page should be visible.
+  auto presenter = base::ComponentContextForProcess()
+                       ->svc()
+                       ->Connect<::fuchsia::ui::policy::Presenter>();
+  presenter.set_error_handler(
+      [](zx_status_t) { ADD_FAILURE() << "Presenter disconnected."; });
+  presenter->PresentOrReplaceView(std::move(view_tokens.view_holder_token),
+                                  nullptr);
+
+  fuchsia::web::NavigationControllerPtr controller;
+  frame->GetNavigationController(controller.NewRequest());
+
+  net::test_server::EmbeddedTestServerHandle test_server_handle;
+  ASSERT_TRUE(test_server_handle =
+                  embedded_test_server()->StartAndReturnHandle());
+  GURL url = embedded_test_server()->GetURL(kWaitSizePath);
+
+  EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
+      controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
+  navigation_listener_.RunUntilUrlAndTitleEquals(url, "done");
+
+  absl::optional<base::Value> default_dpr =
+      cr_fuchsia::ExecuteJavaScript(frame.get(), "window.devicePixelRatio");
+  ASSERT_TRUE(default_dpr);
+
+  // Update scale and verify that devicePixelRatio is updated accordingly.
+  const float kZoomInScale = 1.5;
+  frame->SetPageScale(kZoomInScale);
+
+  absl::optional<base::Value> scaled_dpr =
+      cr_fuchsia::ExecuteJavaScript(frame.get(), "window.devicePixelRatio");
+  ASSERT_TRUE(scaled_dpr);
+
+  EXPECT_NEAR(scaled_dpr->GetDouble() / default_dpr->GetDouble(), kZoomInScale,
+              1e-6);
+
+  // Navigate to the same page on http://localhost. This is a different site,
+  // so it will be loaded in a new renderer process. Page scale value should be
+  // preserved.
+  GURL url2 = embedded_test_server()->GetURL("localhost", kWaitSizePath);
+  EXPECT_NE(url.host(), url2.host());
+  EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
+      controller.get(), fuchsia::web::LoadUrlParams(), url2.spec()));
+  navigation_listener_.RunUntilUrlAndTitleEquals(url2, "done");
+
+  absl::optional<base::Value> dpr_after_navigation =
+      cr_fuchsia::ExecuteJavaScript(frame.get(), "window.devicePixelRatio");
+  ASSERT_TRUE(scaled_dpr);
+
+  EXPECT_EQ(dpr_after_navigation, scaled_dpr);
+
+  // Reset the scale to 1.0 (default) and verify that reported DPR is equal to
+  // the same as when the frame was created.
+  frame->SetPageScale(1.0);
+  absl::optional<base::Value> dpr_after_reset =
+      cr_fuchsia::ExecuteJavaScript(frame.get(), "window.devicePixelRatio");
+  ASSERT_TRUE(dpr_after_reset);
+
+  EXPECT_EQ(dpr_after_reset.value(), default_dpr.value());
+
+  // Zoom out by setting scale to 0.5.
+  const float kZoomOutScale = 0.5;
+  frame->SetPageScale(kZoomOutScale);
+
+  absl::optional<base::Value> zoomed_out_dpr =
+      cr_fuchsia::ExecuteJavaScript(frame.get(), "window.devicePixelRatio");
+  ASSERT_TRUE(zoomed_out_dpr);
+
+  EXPECT_NEAR(zoomed_out_dpr->GetDouble() / default_dpr->GetDouble(),
+              kZoomOutScale, 1e-6);
+
+  // Create another frame. Verify that the scale factor is not applied to the
+  // new frame.
+  cr_fuchsia::TestNavigationListener navigation_listener2;
+  fuchsia::web::FramePtr frame2 =
+      WebEngineBrowserTest::CreateFrame(&navigation_listener2);
+
+  view_tokens = scenic::ViewTokenPair::New();
+  frame2->CreateView(std::move(view_tokens.view_token));
+
+  presenter->PresentOrReplaceView(std::move(view_tokens.view_holder_token),
+                                  nullptr);
+
+  fuchsia::web::NavigationControllerPtr controller2;
+  frame2->GetNavigationController(controller2.NewRequest());
+  EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
+      controller2.get(), fuchsia::web::LoadUrlParams(), url.spec()));
+  navigation_listener2.RunUntilUrlAndTitleEquals(url, "done");
+
+  absl::optional<base::Value> frame2_dpr =
+      cr_fuchsia::ExecuteJavaScript(frame2.get(), "window.devicePixelRatio");
+  ASSERT_TRUE(frame2_dpr);
+
+  EXPECT_EQ(frame2_dpr.value(), default_dpr.value());
+}
+
 // Send a MessagePort to the content, then perform bidirectional messaging
 // over its channel.
 IN_PROC_BROWSER_TEST_F(FrameImplTest, PostMessageMessagePortDisconnected) {
diff --git a/fuchsia/engine/browser/web_engine_browser_context.cc b/fuchsia/engine/browser/web_engine_browser_context.cc
index 47ac7e7e..f9a725e7 100644
--- a/fuchsia/engine/browser/web_engine_browser_context.cc
+++ b/fuchsia/engine/browser/web_engine_browser_context.cc
@@ -71,7 +71,7 @@
 
 WebEngineBrowserContext::~WebEngineBrowserContext() {
   SimpleKeyMap::GetInstance()->Dissociate(this);
-  NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
 
   if (resource_context_) {
     content::GetIOThreadTaskRunner({})->DeleteSoon(
diff --git a/fuchsia/engine/test/data/wait-size.html b/fuchsia/engine/test/data/wait-size.html
new file mode 100644
index 0000000..74c41db
--- /dev/null
+++ b/fuchsia/engine/test/data/wait-size.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <script>
+    // Set title to "done" as soon as the page size is set.
+    function checkWindowSize() {
+      if (window.innerWidth > 0 && window.innerHeight > 0)
+        document.title = "done";
+    }
+    window.onresize = checkWindowSize;
+    checkWindowSize();
+  </script>
+  <body></body>
+</html>
diff --git a/headless/DIR_METADATA b/headless/DIR_METADATA
index 477ee6e..5eb4acf 100644
--- a/headless/DIR_METADATA
+++ b/headless/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Headless"
diff --git a/headless/lib/browser/headless_browser_context_impl.cc b/headless/lib/browser/headless_browser_context_impl.cc
index 45caa59..e7e650a 100644
--- a/headless/lib/browser/headless_browser_context_impl.cc
+++ b/headless/lib/browser/headless_browser_context_impl.cc
@@ -61,7 +61,7 @@
 HeadlessBrowserContextImpl::~HeadlessBrowserContextImpl() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   SimpleKeyMap::GetInstance()->Dissociate(this);
-  NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
 
   // Destroy all web contents before shutting down storage partitions.
   web_contents_map_.clear();
diff --git a/headless/public/headless_browser.h b/headless/public/headless_browser.h
index 80441a32..dfd6be4 100644
--- a/headless/public/headless_browser.h
+++ b/headless/public/headless_browser.h
@@ -295,7 +295,7 @@
 // }
 //
 // [1]
-// https://chromium.googlesource.com/chromium/src/+/master/docs/linux/zygote.md
+// https://chromium.googlesource.com/chromium/src/+/main/docs/linux/zygote.md
 void RunChildProcessIfNeeded(int argc, const char** argv);
 #else
 // In Windows, the headless browser may need to create child processes. This is
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.h b/ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.h
index 01c8960..0da4f1c 100644
--- a/ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.h
+++ b/ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.h
@@ -8,6 +8,7 @@
 #import <UIKit/UIKit.h>
 #include "base/containers/flat_map.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/autofill_profile_comparator.h"
 #include "ios/chrome/browser/overlays/public/overlay_request_config.h"
 
 class InfoBarIOS;
@@ -64,9 +65,7 @@
   // Computes |profile_diff_| based on the map of
   // profile difference data fetched from the delegate.
   void StoreProfileDiff(
-      const base::flat_map<autofill::ServerFieldType,
-                           std::pair<std::u16string, std::u16string>>&
-          diff_map);
+      const std::vector<autofill::ProfileValueDifference>& profile_diff);
 
   // The InfoBar causing this modal.
   InfoBarIOS* infobar_ = nullptr;
diff --git a/ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.mm b/ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.mm
index e429186..18aedab0a 100644
--- a/ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.mm
+++ b/ios/chrome/browser/overlays/public/infobar_modal/save_address_profile_infobar_modal_overlay_request_config.mm
@@ -52,16 +52,15 @@
 }
 
 void SaveAddressProfileModalRequestConfig::StoreProfileDiff(
-    const base::flat_map<autofill::ServerFieldType,
-                         std::pair<std::u16string, std::u16string>>& diff_map) {
-  for (const auto& row : diff_map) {
+    const std::vector<autofill::ProfileValueDifference>& profile_diff) {
+  for (const auto& row : profile_diff) {
     [profile_diff_
         setObject:@[
-          base::SysUTF16ToNSString(row.second.first),
-          base::SysUTF16ToNSString(row.second.second)
+          base::SysUTF16ToNSString(row.first_value),
+          base::SysUTF16ToNSString(row.second_value)
         ]
            forKey:[NSNumber
-                      numberWithInt:AutofillUITypeFromAutofillType(row.first)]];
+                      numberWithInt:AutofillUITypeFromAutofillType(row.type)]];
   }
 }
 
diff --git a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator.mm
index 3844ac1..1488ae55 100644
--- a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator.mm
@@ -391,6 +391,21 @@
   return self.interactionTransition;
 }
 
+- (void)navigationController:(UINavigationController*)navigationController
+       didShowViewController:(UIViewController*)viewController
+                    animated:(BOOL)animated {
+  DCHECK(navigationController == self.navigationController);
+  DCHECK(navigationController.viewControllers.count > 0);
+  DCHECK(navigationController.viewControllers[0] ==
+         self.defaultAccountCoordinator.viewController);
+  if (self.navigationController.viewControllers.count == 1 &&
+      self.accountChooserCoordinator) {
+    // AccountChooserCoordinator has been removed by "Back" button.
+    [self.accountChooserCoordinator stop];
+    self.accountChooserCoordinator = nil;
+  }
+}
+
 #pragma mark - UIViewControllerTransitioningDelegate
 
 - (UIPresentationController*)
diff --git a/ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_table_view_controller.mm b/ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_table_view_controller.mm
index 95cfc06c..915fed2 100644
--- a/ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_table_view_controller.mm
+++ b/ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_table_view_controller.mm
@@ -8,6 +8,8 @@
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "ios/chrome/browser/infobars/infobar_metrics_recorder.h"
+#import "ios/chrome/browser/ui/autofill/autofill_ui_type.h"
+#import "ios/chrome/browser/ui/autofill/autofill_ui_type_util.h"
 #import "ios/chrome/browser/ui/infobars/modals/infobar_modal_constants.h"
 #import "ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_modal_delegate.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
@@ -32,8 +34,6 @@
 // Height of the space used by header/footer when none is set. Default is
 // |estimatedSection{Header|Footer}Height|.
 const CGFloat kDefaultHeaderFooterHeight = 10;
-// Estimated height of the header/footer, used to speed the constraints.
-const CGFloat kEstimatedHeaderFooterHeight = 50;
 
 }  // namespace
 
@@ -98,16 +98,13 @@
 
 - (void)viewDidLoad {
   [super viewDidLoad];
+  self.styler.tableViewBackgroundColor = [UIColor colorNamed:kBackgroundColor];
   self.view.backgroundColor = [UIColor colorNamed:kBackgroundColor];
   self.styler.cellBackgroundColor = [UIColor colorNamed:kBackgroundColor];
-  if (self.isUpdateModal) {
-    self.tableView.estimatedSectionHeaderHeight = kEstimatedHeaderFooterHeight;
-    self.tableView.estimatedSectionFooterHeight = kEstimatedHeaderFooterHeight;
-  } else {
-    self.tableView.sectionHeaderHeight = 0;
-  }
-  [self.tableView
-      setSeparatorInset:UIEdgeInsetsMake(0, kTableViewHorizontalSpacing, 0, 0)];
+  self.tableView.sectionHeaderHeight = 0;
+
+  self.tableView.separatorInset =
+      UIEdgeInsetsMake(0, kTableViewSeparatorInsetWithIcon, 0, 0);
 
   // Configure the NavigationBar.
   UIBarButtonItem* cancelButton = [[UIBarButtonItem alloc]
@@ -182,11 +179,28 @@
                addTarget:self
                   action:@selector(saveAddressProfileButtonWasPressed:)
         forControlEvents:UIControlEventTouchUpInside];
-  } else if (itemType == ItemTypeAddress) {
+  } else if (itemType == ItemTypeAddress || itemType == ItemTypeUpdateOld) {
     TableViewImageCell* managedcell =
         base::mac::ObjCCastStrict<TableViewImageCell>(cell);
-    managedcell.textLabel.numberOfLines =
-        [[self.address componentsSeparatedByString:@"\n"] count];
+    managedcell.textLabel.numberOfLines = 0;
+    managedcell.imageView.image = [managedcell.imageView.image
+        imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
+    [managedcell.imageView setTintColor:[UIColor colorNamed:kGrey400Color]];
+  } else if (itemType == ItemTypePhoneNumber ||
+             itemType == ItemTypeEmailAddress) {
+    TableViewImageCell* managedcell =
+        base::mac::ObjCCastStrict<TableViewImageCell>(cell);
+    managedcell.imageView.image = [managedcell.imageView.image
+        imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
+    [managedcell.imageView setTintColor:[UIColor colorNamed:kGrey400Color]];
+  } else if (itemType == ItemTypeUpdateNew) {
+    TableViewImageCell* managedcell =
+        base::mac::ObjCCastStrict<TableViewImageCell>(cell);
+    managedcell.textLabel.numberOfLines = 0;
+    managedcell.imageView.image = [managedcell.imageView.image
+        imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
+    // Color is blue.
+    [managedcell.imageView setTintColor:[UIColor colorNamed:kBlueColor]];
   }
   return cell;
 }
@@ -253,8 +267,6 @@
     }
   }
 
-  // TODO(crbug.com/1167062): Add image icons for the fields.
-  // TODO(crbug.com/1167062): Add line separators between sections.
   TableViewModel* model = self.tableViewModel;
 
   [model addSectionWithIdentifier:SectionIdentifierUpdateDescription];
@@ -271,12 +283,10 @@
   }
   for (NSNumber* type in self.profileDataDiff) {
     if ([self.profileDataDiff[type][0] length] > 0) {
-      TableViewImageItem* item =
-          [[TableViewImageItem alloc] initWithType:ItemTypeUpdateNew];
-      // TODO(crbug.com/1167062): Use type for determining the icons.
-      item.title = self.profileDataDiff[type][0];
-      item.useCustomSeparator = YES;
-      [model addItem:item
+      [model addItem:[self detailItemWithType:ItemTypeUpdateNew
+                                         text:self.profileDataDiff[type][0]
+                                iconImageName:
+                                    [self iconForAutofillInputTypeNumber:type]]
           toSectionWithIdentifier:SectionIdentifierUpdateModalNewFields];
     }
   }
@@ -290,12 +300,11 @@
         forSectionWithIdentifier:SectionIdentifierUpdateModalOldFields];
     for (NSNumber* type in self.profileDataDiff) {
       if ([self.profileDataDiff[type][1] length] > 0) {
-        TableViewImageItem* item =
-            [[TableViewImageItem alloc] initWithType:ItemTypeUpdateOld];
-        // TODO(crbug.com/1167062): Use type for determining the icons.
-        item.title = self.profileDataDiff[type][1];
-        item.useCustomSeparator = YES;
-        [model addItem:item
+        [model addItem:[self
+                           detailItemWithType:ItemTypeUpdateOld
+                                         text:self.profileDataDiff[type][1]
+                                iconImageName:
+                                    [self iconForAutofillInputTypeNumber:type]]
             toSectionWithIdentifier:SectionIdentifierUpdateModalOldFields];
       }
     }
@@ -311,23 +320,33 @@
   TableViewModel* model = self.tableViewModel;
   [model addSectionWithIdentifier:SectionIdentifierSaveModalFields];
 
-  TableViewImageItem* addressImageItem =
-      [[TableViewImageItem alloc] initWithType:ItemTypeAddress];
-  addressImageItem.title = self.address;
-  [model addItem:addressImageItem
+  [model addItem:[self
+                     detailItemWithType:ItemTypeAddress
+                                   text:self.address
+                          iconImageName:
+                              [self iconForAutofillUIType:
+                                        AutofillUITypeProfileHomeAddressStreet]]
       toSectionWithIdentifier:SectionIdentifierSaveModalFields];
 
-  TableViewImageItem* emailImageItem =
-      [[TableViewImageItem alloc] initWithType:ItemTypeEmailAddress];
-  emailImageItem.title = self.emailAddress;
-  [model addItem:emailImageItem
-      toSectionWithIdentifier:SectionIdentifierSaveModalFields];
-
-  TableViewImageItem* phoneImageItem =
-      [[TableViewImageItem alloc] initWithType:ItemTypePhoneNumber];
-  phoneImageItem.title = self.phoneNumber;
-  [model addItem:phoneImageItem
-      toSectionWithIdentifier:SectionIdentifierSaveModalFields];
+  if ([self.emailAddress length]) {
+    [model addItem:[self detailItemWithType:ItemTypeEmailAddress
+                                       text:self.emailAddress
+                              iconImageName:
+                                  [self iconForAutofillUIType:
+                                            AutofillUITypeProfileEmailAddress]]
+        toSectionWithIdentifier:SectionIdentifierSaveModalFields];
+  }
+  if ([self.phoneNumber length]) {
+    [model addItem:
+               [self
+                   detailItemWithType:ItemTypePhoneNumber
+                                 text:self.phoneNumber
+                        iconImageName:
+                            [self
+                                iconForAutofillUIType:
+                                    AutofillUITypeProfileHomePhoneWholeNumber]]
+        toSectionWithIdentifier:SectionIdentifierSaveModalFields];
+  }
 
   [model addItem:[self saveUpdateButton]
       toSectionWithIdentifier:SectionIdentifierSaveModalFields];
@@ -364,4 +383,42 @@
   return footer;
 }
 
+- (NSString*)iconForAutofillUIType:(AutofillUIType)type {
+  switch (type) {
+    case AutofillUITypeNameFullWithHonorificPrefix:
+      return @"infobar_profile_icon";
+    case AutofillUITypeAddressHomeAddress:
+    case AutofillUITypeProfileHomeAddressStreet:
+      return @"infobar_autofill_address_icon";
+    case AutofillUITypeProfileEmailAddress:
+      return @"infobar_email_icon";
+    case AutofillUITypeProfileHomePhoneWholeNumber:
+      return @"infobar_phone_icon";
+    default:
+      NOTREACHED();
+      return @"";
+  }
+}
+
+- (NSString*)iconForAutofillInputTypeNumber:(NSNumber*)val {
+  return [self iconForAutofillUIType:(AutofillUIType)[val intValue]];
+}
+
+#pragma mark Item Constructors
+
+- (TableViewImageItem*)detailItemWithType:(NSInteger)type
+                                     text:(NSString*)text
+                            iconImageName:(NSString*)iconImageName {
+  TableViewImageItem* detailItem =
+      [[TableViewImageItem alloc] initWithType:type];
+  detailItem.title = text;
+  detailItem.enabled = NO;
+  detailItem.useCustomSeparator = YES;
+  if ([iconImageName length]) {
+    detailItem.image = [UIImage imageNamed:iconImageName];
+  }
+
+  return detailItem;
+}
+
 @end
diff --git a/ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_table_view_controller_unittest.mm b/ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_table_view_controller_unittest.mm
index f738a04..38b8da3 100644
--- a/ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/infobars/modals/infobar_save_address_profile_table_view_controller_unittest.mm
@@ -38,7 +38,7 @@
       kCurrentAddressProfileSavedPrefKey : @(false),
       kIsUpdateModalPrefKey : @(true),
       kProfileDataDiffKey : @{
-        @[ [NSNumber numberWithInt:AutofillUITypeProfileFullName] ] :
+        [NSNumber numberWithInt:AutofillUITypeNameFullWithHonorificPrefix] :
             @[ @"John Doe", @"John H. Doe" ]
       },
       kUpdateModalDescriptionKey : @"For John Doe, 345 Spear Street"
diff --git a/ipc/DIR_METADATA b/ipc/DIR_METADATA
index fb07a25..6d266b0 100644
--- a/ipc/DIR_METADATA
+++ b/ipc/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Core"
diff --git a/media/DIR_METADATA b/media/DIR_METADATA
index 9f769ce..2dc14a4 100644
--- a/media/DIR_METADATA
+++ b/media/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Media"
diff --git a/media/audio/DIR_METADATA b/media/audio/DIR_METADATA
index 99031e0..e80d7931 100644
--- a/media/audio/DIR_METADATA
+++ b/media/audio/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Media>Audio"
diff --git a/media/audio/fuchsia/DIR_METADATA b/media/audio/fuchsia/DIR_METADATA
index e88f6232..abc57ac0f 100644
--- a/media/audio/fuchsia/DIR_METADATA
+++ b/media/audio/fuchsia/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 team_email: "cr-fuchsia@chromium.org"
 os: FUCHSIA
\ No newline at end of file
diff --git a/media/capture/content/DIR_METADATA b/media/capture/content/DIR_METADATA
index c46eabd..f689e00 100644
--- a/media/capture/content/DIR_METADATA
+++ b/media/capture/content/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Media>SurfaceCapture"
diff --git a/media/capture/video/DIR_METADATA b/media/capture/video/DIR_METADATA
index dc21b38..ca45fde 100644
--- a/media/capture/video/DIR_METADATA
+++ b/media/capture/video/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Media>CameraCapture"
diff --git a/media/cast/DIR_METADATA b/media/cast/DIR_METADATA
index 9e255559..7c577c4 100644
--- a/media/cast/DIR_METADATA
+++ b/media/cast/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Cast>Streaming"
diff --git a/media/filters/fuchsia/DIR_METADATA b/media/filters/fuchsia/DIR_METADATA
index e88f6232..abc57ac0f 100644
--- a/media/filters/fuchsia/DIR_METADATA
+++ b/media/filters/fuchsia/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 team_email: "cr-fuchsia@chromium.org"
 os: FUCHSIA
\ No newline at end of file
diff --git a/media/filters/fuchsia/fuchsia_video_decoder.cc b/media/filters/fuchsia/fuchsia_video_decoder.cc
index 44ad52ac..e5397b82 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder.cc
+++ b/media/filters/fuchsia/fuchsia_video_decoder.cc
@@ -55,24 +55,28 @@
 // Number of output buffers allocated "for camping". This value is passed to
 // sysmem to ensure that we get one output buffer for the frame currently
 // displayed on the screen.
-const uint32_t kOutputBuffersForCamping = 1;
+constexpr uint32_t kOutputBuffersForCamping = 1;
 
 // Maximum number of frames we expect to have queued up while playing video.
 // Higher values require more memory for output buffers. Lower values make it
 // more likely that renderer will stall because decoded frames are not available
 // on time.
-const uint32_t kMaxUsedOutputBuffers = 5;
+constexpr uint32_t kMaxUsedOutputBuffers = 5;
 
 // Use 2 buffers for decoder input. Limiting total number of buffers to 2 allows
 // to minimize required memory without significant effect on performance.
-const size_t kNumInputBuffers = 2;
+constexpr size_t kNumInputBuffers = 2;
 
 // Some codecs do not support splitting video frames across multiple input
 // buffers, so the buffers need to be large enough to fit all video frames. The
-// buffer size is calculated to fit 1080p frame with MinCR=2 (per H264 spec),
-// plus 128KiB for SEI/SPS/PPS. (note that the same size is used for all codecs,
-// not just H264).
-const size_t kInputBufferSize = 1920 * 1080 * 3 / 2 / 2 + 128 * 1024;
+// buffer size is calculated to fit 1080p I420 frame with MinCR=2 (per H264
+// spec), plus 128KiB for SEI/SPS/PPS. (note that the same size is used for all
+// codecs, not just H264).
+constexpr size_t kInputBufferSize = 1920 * 1080 * 3 / 2 / 2 + 128 * 1024;
+
+// Input buffers are allocate once per decoder, the |buffer_lifetime_ordinal| is
+// always the same.
+constexpr uint64_t kInputBufferLifetimeOrdinal = 1;
 
 // Helper used to hold mailboxes for the output textures. OutputMailbox may
 // outlive FuchsiaVideoDecoder if is referenced by a VideoFrame.
@@ -224,8 +228,6 @@
   // Event handlers for |decoder_|.
   void OnStreamFailed(uint64_t stream_lifetime_ordinal,
                       fuchsia::media::StreamError error);
-  void OnInputConstraints(
-      fuchsia::media::StreamBufferConstraints input_constraints);
   void OnFreeInputPacket(fuchsia::media::PacketHeader free_input_packet);
   void OnOutputConstraints(
       fuchsia::media::StreamOutputConstraints output_constraints);
@@ -236,6 +238,8 @@
   void OnOutputEndOfStream(uint64_t stream_lifetime_ordinal,
                            bool error_detected_before);
 
+  void AllocateInputBuffers();
+
   // Drops all pending input buffers and then calls all pending DecodeCB with
   // |status|. Returns true if the decoder still exists.
   bool DropInputQueue(DecodeStatus status);
@@ -310,7 +314,6 @@
   VmoBufferWriterQueue input_writer_queue_;
 
   // Input buffers for |decoder_|.
-  uint64_t input_buffer_lifetime_ordinal_ = 1;
   std::unique_ptr<SysmemCollectionClient> input_buffer_collection_;
   base::flat_map<size_t, InputDecoderPacket> in_flight_input_packets_;
 
@@ -462,8 +465,6 @@
 
   decoder_.events().OnStreamFailed =
       fit::bind_member(this, &FuchsiaVideoDecoder::OnStreamFailed);
-  decoder_.events().OnInputConstraints =
-      fit::bind_member(this, &FuchsiaVideoDecoder::OnInputConstraints);
   decoder_.events().OnFreeInputPacket =
       fit::bind_member(this, &FuchsiaVideoDecoder::OnFreeInputPacket);
   decoder_.events().OnOutputConstraints =
@@ -477,6 +478,8 @@
 
   decoder_->EnableOnStreamFailed();
 
+  AllocateInputBuffers();
+
   current_codec_ = config.codec();
 
   std::move(done_callback).Run(OkStatus());
@@ -581,27 +584,19 @@
   OnError();
 }
 
-void FuchsiaVideoDecoder::OnInputConstraints(
-    fuchsia::media::StreamBufferConstraints stream_constraints) {
-  // Buffer lifetime ordinal is an odd number incremented by 2 for each buffer
-  // generation as required by StreamProcessor.
-  input_buffer_lifetime_ordinal_ += 2;
-  decoder_input_constraints_ = std::move(stream_constraints);
-
-  ReleaseInputBuffers();
+void FuchsiaVideoDecoder::AllocateInputBuffers() {
+  DCHECK(!input_buffer_collection_);
 
   input_buffer_collection_ = sysmem_allocator_.AllocateNewCollection();
-
   input_buffer_collection_->CreateSharedToken(base::BindOnce(
       &FuchsiaVideoDecoder::SetInputBufferCollection, base::Unretained(this)));
-
   if (decryptor_) {
     input_buffer_collection_->CreateSharedToken(base::BindOnce(
         &FuchsiaSecureStreamDecryptor::SetOutputBufferCollectionToken,
         base::Unretained(decryptor_.get())));
   }
 
-  // Create buffer constrains for the input buffer collection.
+  // Create buffer constraints for the input buffer collection.
   fuchsia::sysmem::BufferCollectionConstraints buffer_constraints;
   if (decryptor_) {
     buffer_constraints.usage.none = fuchsia::sysmem::noneUsage;
@@ -630,9 +625,8 @@
 void FuchsiaVideoDecoder::SetInputBufferCollection(
     fuchsia::sysmem::BufferCollectionTokenPtr token) {
   fuchsia::media::StreamBufferPartialSettings settings;
-  settings.set_buffer_lifetime_ordinal(input_buffer_lifetime_ordinal_);
-  settings.set_buffer_constraints_version_ordinal(
-      decoder_input_constraints_->buffer_constraints_version_ordinal());
+  settings.set_buffer_lifetime_ordinal(kInputBufferLifetimeOrdinal);
+  settings.set_buffer_constraints_version_ordinal(0);
   settings.set_sysmem_token(std::move(token));
   decoder_->SetInputBufferPartialSettings(std::move(settings));
 }
@@ -658,7 +652,7 @@
     StreamProcessorHelper::IoPacket packet) {
   fuchsia::media::Packet media_packet;
   media_packet.mutable_header()->set_buffer_lifetime_ordinal(
-      input_buffer_lifetime_ordinal_);
+      kInputBufferLifetimeOrdinal);
   media_packet.mutable_header()->set_packet_index(packet.buffer_index());
   media_packet.set_buffer_index(packet.buffer_index());
   media_packet.set_timestamp_ish(packet.timestamp().InNanoseconds());
@@ -685,18 +679,12 @@
 
 void FuchsiaVideoDecoder::OnFreeInputPacket(
     fuchsia::media::PacketHeader free_input_packet) {
-  if (!free_input_packet.has_buffer_lifetime_ordinal() ||
-      !free_input_packet.has_packet_index()) {
+  if (!free_input_packet.has_packet_index()) {
     DLOG(ERROR) << "Received OnFreeInputPacket() with missing required fields.";
     OnError();
     return;
   }
 
-  if (free_input_packet.buffer_lifetime_ordinal() !=
-      input_buffer_lifetime_ordinal_) {
-    return;
-  }
-
   auto it = in_flight_input_packets_.find(free_input_packet.packet_index());
   if (it == in_flight_input_packets_.end()) {
     DLOG(ERROR) << "Received OnFreeInputPacket() with invalid packet index.";
diff --git a/media/fuchsia/cdm/fuchsia_stream_decryptor.cc b/media/fuchsia/cdm/fuchsia_stream_decryptor.cc
index 586d22ac..82b2754 100644
--- a/media/fuchsia/cdm/fuchsia_stream_decryptor.cc
+++ b/media/fuchsia/cdm/fuchsia_stream_decryptor.cc
@@ -100,7 +100,9 @@
     size_t min_buffer_size)
     : processor_(std::move(processor), this),
       min_buffer_size_(min_buffer_size),
-      allocator_("CrFuchsiaStreamDecryptorBase") {}
+      allocator_("CrFuchsiaStreamDecryptorBase") {
+  AllocateInputBuffers();
+}
 
 FuchsiaStreamDecryptorBase::~FuchsiaStreamDecryptorBase() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -126,14 +128,12 @@
   input_writer_queue_.ResetQueue();
 }
 
-// StreamProcessorHelper::Client implementation:
-void FuchsiaStreamDecryptorBase::AllocateInputBuffers(
-    const fuchsia::media::StreamBufferConstraints& stream_constraints) {
+void FuchsiaStreamDecryptorBase::AllocateInputBuffers() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   input_buffer_collection_ = allocator_.AllocateNewCollection();
   input_buffer_collection_->CreateSharedToken(
-      base::BindOnce(&StreamProcessorHelper::CompleteInputBuffersAllocation,
+      base::BindOnce(&StreamProcessorHelper::SetInputBufferCollectionToken,
                      base::Unretained(&processor_)));
   input_buffer_collection_->Initialize(
       VmoBuffer::GetRecommendedConstraints(kMinBufferCount, min_buffer_size_,
diff --git a/media/fuchsia/cdm/fuchsia_stream_decryptor.h b/media/fuchsia/cdm/fuchsia_stream_decryptor.h
index 377c69a..792c5e6 100644
--- a/media/fuchsia/cdm/fuchsia_stream_decryptor.h
+++ b/media/fuchsia/cdm/fuchsia_stream_decryptor.h
@@ -28,10 +28,9 @@
 
  protected:
   // StreamProcessorHelper::Client overrides.
-  void AllocateInputBuffers(
-      const fuchsia::media::StreamBufferConstraints& stream_constraints) final;
   void OnOutputFormat(fuchsia::media::StreamOutputFormat format) final;
 
+  void AllocateInputBuffers();
   void DecryptInternal(scoped_refptr<DecoderBuffer> encrypted);
   void ResetStream();
 
diff --git a/media/fuchsia/common/stream_processor_helper.cc b/media/fuchsia/common/stream_processor_helper.cc
index 17b95be..51e9cec 100644
--- a/media/fuchsia/common/stream_processor_helper.cc
+++ b/media/fuchsia/common/stream_processor_helper.cc
@@ -9,6 +9,14 @@
 
 namespace media {
 
+namespace {
+
+// Input buffers are allocate once per decoder, the |buffer_lifetime_ordinal| is
+// always the same.
+constexpr uint64_t kInputBufferLifetimeOrdinal = 1;
+
+}  // namespace
+
 StreamProcessorHelper::IoPacket::IoPacket(size_t index,
                                           size_t offset,
                                           size_t size,
@@ -68,8 +76,6 @@
 
   processor_.events().OnStreamFailed =
       fit::bind_member(this, &StreamProcessorHelper::OnStreamFailed);
-  processor_.events().OnInputConstraints =
-      fit::bind_member(this, &StreamProcessorHelper::OnInputConstraints);
   processor_.events().OnFreeInputPacket =
       fit::bind_member(this, &StreamProcessorHelper::OnFreeInputPacket);
   processor_.events().OnOutputConstraints =
@@ -94,7 +100,7 @@
 
   fuchsia::media::Packet packet;
   packet.mutable_header()->set_buffer_lifetime_ordinal(
-      input_buffer_lifetime_ordinal_);
+      kInputBufferLifetimeOrdinal);
   packet.mutable_header()->set_packet_index(input.buffer_index());
   packet.set_buffer_index(packet.header().packet_index());
   packet.set_timestamp_ish(input.timestamp().InNanoseconds());
@@ -161,36 +167,16 @@
   OnError();
 }
 
-void StreamProcessorHelper::OnInputConstraints(
-    fuchsia::media::StreamBufferConstraints constraints) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  // Buffer lifetime ordinal is an odd number incremented by 2 for each buffer
-  // generation as required by StreamProcessor.
-  input_buffer_lifetime_ordinal_ += 2;
-
-  DCHECK(input_packets_.empty());
-  input_buffer_constraints_ = std::move(constraints);
-
-  client_->AllocateInputBuffers(input_buffer_constraints_);
-}
-
 void StreamProcessorHelper::OnFreeInputPacket(
     fuchsia::media::PacketHeader free_input_packet) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (!free_input_packet.has_buffer_lifetime_ordinal() ||
-      !free_input_packet.has_packet_index()) {
+  if (!free_input_packet.has_packet_index()) {
     DLOG(ERROR) << "Received OnFreeInputPacket() with missing required fields.";
     OnError();
     return;
   }
 
-  if (free_input_packet.buffer_lifetime_ordinal() !=
-      input_buffer_lifetime_ordinal_) {
-    return;
-  }
-
   auto it = input_packets_.find(free_input_packet.packet_index());
   if (it == input_packets_.end()) {
     DLOG(ERROR) << "Received OnFreeInputPacket() with invalid packet index.";
@@ -323,14 +309,13 @@
   client_->OnError();
 }
 
-void StreamProcessorHelper::CompleteInputBuffersAllocation(
+void StreamProcessorHelper::SetInputBufferCollectionToken(
     fuchsia::sysmem::BufferCollectionTokenPtr sysmem_token) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK(!input_buffer_constraints_.IsEmpty());
+
   fuchsia::media::StreamBufferPartialSettings settings;
-  settings.set_buffer_lifetime_ordinal(input_buffer_lifetime_ordinal_);
-  settings.set_buffer_constraints_version_ordinal(
-      input_buffer_constraints_.buffer_constraints_version_ordinal());
+  settings.set_buffer_lifetime_ordinal(kInputBufferLifetimeOrdinal);
+  settings.set_buffer_constraints_version_ordinal(0);
   settings.set_sysmem_token(std::move(sysmem_token));
   processor_->SetInputBufferPartialSettings(std::move(settings));
 }
diff --git a/media/fuchsia/common/stream_processor_helper.h b/media/fuchsia/common/stream_processor_helper.h
index 78f24701..ad16a2d 100644
--- a/media/fuchsia/common/stream_processor_helper.h
+++ b/media/fuchsia/common/stream_processor_helper.h
@@ -76,11 +76,8 @@
 
   class Client {
    public:
-    // Allocate input/output buffers with the given constraints. Client should
-    // call ProvideInput/OutputBufferCollectionToken to finish the buffer
-    // allocation flow.
-    virtual void AllocateInputBuffers(
-        const fuchsia::media::StreamBufferConstraints& stream_constraints) = 0;
+    // Allocate output buffers with the given constraints. Client should call
+    // ProvideIOutputBufferCollectionToken to finish the buffer allocation flow.
     virtual void AllocateOutputBuffers(
         const fuchsia::media::StreamBufferConstraints& stream_constraints) = 0;
 
@@ -118,10 +115,12 @@
   // StreamProcessor without calling Reset.
   void ProcessEos();
 
-  // Provide input/output BufferCollectionToken to finish StreamProcessor buffer
-  // setup flow.
-  void CompleteInputBuffersAllocation(
+  // Sets buffer collection tocken to use for input buffers.
+  void SetInputBufferCollectionToken(
       fuchsia::sysmem::BufferCollectionTokenPtr token);
+
+  // Provide output BufferCollectionToken to finish StreamProcessor buffer
+  // setup flow. Should be called only after AllocateOutputBuffers.
   void CompleteOutputBuffersAllocation(
       fuchsia::sysmem::BufferCollectionTokenPtr token);
 
@@ -157,10 +156,6 @@
   // stream_lifetime_ordinal_.
   bool active_stream_ = false;
 
-  // Input buffers.
-  uint64_t input_buffer_lifetime_ordinal_ = 1;
-  fuchsia::media::StreamBufferConstraints input_buffer_constraints_;
-
   // Map from packet index to corresponding input IoPacket. IoPacket should be
   // owned by this class until StreamProcessor released the buffer.
   base::flat_map<size_t, IoPacket> input_packets_;
diff --git a/media/gpu/vaapi/test/av1_decoder.cc b/media/gpu/vaapi/test/av1_decoder.cc
index cbe666e..c7e2f7f 100644
--- a/media/gpu/vaapi/test/av1_decoder.cc
+++ b/media/gpu/vaapi/test/av1_decoder.cc
@@ -735,7 +735,7 @@
     default:
       // The OBU Parser can only produce bit depths of 8, 10, and 12; we should
       // not hit any other cases. See
-      // https://source.chromium.org/chromium/chromium/src/+/master:third_party/libgav1/src/src/obu_parser.cc;l=144-150;drc=7880d0cc1d1976012dbec8a1bb982191ac49b7f4
+      // https://source.chromium.org/chromium/chromium/src/+/main:third_party/libgav1/src/src/obu_parser.cc;l=144-150;drc=7880d0cc1d1976012dbec8a1bb982191ac49b7f4
       NOTREACHED() << "Invalid color bit depth: "
                    << current_sequence_header_->color_config.bitdepth;
   }
diff --git a/media/gpu/vaapi/test/vp9_decoder.cc b/media/gpu/vaapi/test/vp9_decoder.cc
index 322d65a..7f93201 100644
--- a/media/gpu/vaapi/test/vp9_decoder.cc
+++ b/media/gpu/vaapi/test/vp9_decoder.cc
@@ -127,7 +127,7 @@
 
   const VAProfile profile = GetProfile(frame_hdr);
   // Note: some streams may fail to decode; see
-  // https://source.chromium.org/chromium/chromium/src/+/master:media/gpu/vp9_decoder.cc;l=249-285;drc=3893688a88eb1b4cf39e346fd8f8c743ad255469
+  // https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/vp9_decoder.cc;l=249-285;drc=3893688a88eb1b4cf39e346fd8f8c743ad255469
   if (!va_config_ || va_config_->profile() != profile) {
     va_context_.reset();
     va_config_ = std::make_unique<ScopedVAConfig>(va_device_, profile,
diff --git a/media/midi/DIR_METADATA b/media/midi/DIR_METADATA
index d6aec82b..b44dd56 100644
--- a/media/midi/DIR_METADATA
+++ b/media/midi/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Blink>WebMIDI"
diff --git a/media/muxers/DIR_METADATA b/media/muxers/DIR_METADATA
index af1480e..25b8514 100644
--- a/media/muxers/DIR_METADATA
+++ b/media/muxers/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Blink>MediaRecording"
diff --git a/media/remoting/DIR_METADATA b/media/remoting/DIR_METADATA
index 9e255559..7c577c4 100644
--- a/media/remoting/DIR_METADATA
+++ b/media/remoting/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Cast>Streaming"
diff --git a/media/test/data/README.md b/media/test/data/README.md
index f0c090c6..d96170b2 100644
--- a/media/test/data/README.md
+++ b/media/test/data/README.md
@@ -1215,4 +1215,4 @@
 * opus-trimming-test.webm
 
 [libaom test vectors]: https://aomedia.googlesource.com/aom/+/master/test/test_vectors.cc
-[libaom LICENSE]: https://source.chromium.org/chromium/chromium/src/+/master:media/test/data/licenses/AOM-LICENSE
+[libaom LICENSE]: https://source.chromium.org/chromium/chromium/src/+/main:media/test/data/licenses/AOM-LICENSE
diff --git a/media/webrtc/DIR_METADATA b/media/webrtc/DIR_METADATA
index 7ae006c..e28b9ae8 100644
--- a/media/webrtc/DIR_METADATA
+++ b/media/webrtc/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Blink>WebRTC>Audio"
diff --git a/printing/backend/cups_helper.cc b/printing/backend/cups_helper.cc
index c2c8746..6503ad5 100644
--- a/printing/backend/cups_helper.cc
+++ b/printing/backend/cups_helper.cc
@@ -15,7 +15,6 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
 #include "base/logging.h"
-#include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/time/time.h"
@@ -598,18 +597,21 @@
     return false;
   }
 
-  ppd_file_t* ppd = ppdOpenFd(ppd_fd.get());
+  // We release ownership of `ppd_fd` here because ppdOpenFd() assumes ownership
+  // of it in all but one case (see below).
+  int unowned_ppd_fd = ppd_fd.release();
+  ppd_file_t* ppd = ppdOpenFd(unowned_ppd_fd);
   if (!ppd) {
     int line = 0;
     ppd_status_t ppd_status = ppdLastError(&line);
     LOG(ERROR) << "Failed to open PDD file: error " << ppd_status << " at line "
                << line << ", " << ppdErrorString(ppd_status);
-    if (ppd_status != PPD_FILE_OPEN_ERROR) {
-      // When the error is not from opening the file then the CUPS library
-      // internals will have already closed the file descriptor.  It is
-      // important to not close the file a second time (when ScopedFD destructor
-      // fires), so we release the descriptor prior to that.
-      ignore_result(ppd_fd.release());
+    if (ppd_status == PPD_FILE_OPEN_ERROR) {
+      // Normally ppdOpenFd assumes ownership of the file descriptor we give it,
+      // regardless of success or failure. The one exception is when it fails
+      // with PPD_FILE_OPEN_ERROR. In that case ownership is retained by the
+      // caller, so we must explicitly close it.
+      close(unowned_ppd_fd);
     }
     return false;
   }
@@ -693,10 +695,6 @@
   }
 
   ppdClose(ppd);
-  // The CUPS library internals close the file descriptor upon successfully
-  // reading it.  Explicitly release the `ScopedFD` to prevent a crash caused
-  // by a bad file descriptor.
-  ignore_result(ppd_fd.release());
 
   *printer_info = caps;
   return true;
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index 1fea71a..143c7bd 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -623,6 +623,7 @@
   auto config = std::make_unique<os_crypt::Config>();
   config->store = crypt_config->store;
   config->product_name = crypt_config->product_name;
+  config->application_name = crypt_config->application_name;
   config->main_thread_runner = base::ThreadTaskRunnerHandle::Get();
   config->should_use_preference = crypt_config->should_use_preference;
   config->user_data_path = crypt_config->user_data_path;
diff --git a/services/network/public/mojom/network_service.mojom b/services/network/public/mojom/network_service.mojom
index 8752c4b..f7b1e99 100644
--- a/services/network/public/mojom/network_service.mojom
+++ b/services/network/public/mojom/network_service.mojom
@@ -99,6 +99,13 @@
   // The product name to use for permission prompts.
   string product_name;
 
+  // The application name to store the crypto key against. For Chromium/Chrome
+  // builds leave this unset and it will default correctly.  This config option
+  // is for embedders to provide their application name in place of "Chromium".
+  // Only used when the allow_runtime_configurable_key_storage feature is
+  // enabled
+  string application_name;
+
   // Controls whether preference on using or ignoring backends is used.
   bool should_use_preference;
 
diff --git a/sql/DIR_METADATA b/sql/DIR_METADATA
index 3576726..0e61939 100644
--- a/sql/DIR_METADATA
+++ b/sql/DIR_METADATA
@@ -1,10 +1,10 @@
 # Metadata information for this directory.
 #
 # For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
 #
 # For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
 
 monorail {
   component: "Internals>Storage"
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index 2fde9c8..768abbc157 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -553,7 +553,8 @@
         "trigger_script": {
           "args": [
             "--multiple-dimension-script-verbose",
-            "True"
+            "True",
+            "--use-dynamic-shards"
           ],
           "requires_simultaneous_shard_dispatch": true,
           "script": "//testing/trigger_scripts/perf_device_trigger.py"
diff --git a/testing/buildbot/internal.chromeos.fyi.json b/testing/buildbot/internal.chromeos.fyi.json
index 7273358..f53c74e 100644
--- a/testing/buildbot/internal.chromeos.fyi.json
+++ b/testing/buildbot/internal.chromeos.fyi.json
@@ -1237,80 +1237,88 @@
         "args": [],
         "cros_board": "coral",
         "cros_img": "coral-release/R92-13970.0.0",
-        "name": "mainline_lacros_tast_tests_CORAL_TOT",
+        "name": "lacros_fyi_tast_tests_CORAL_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\")",
-        "test": "mainline_lacros_tast_tests",
+        "test": "lacros_fyi_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_fyi_tast_tests/",
         "timeout_sec": 10800
       },
       {
         "args": [],
         "cros_board": "eve",
         "cros_img": "eve-release/R92-13970.0.0",
-        "name": "mainline_lacros_tast_tests_EVE_TOT",
+        "name": "lacros_fyi_tast_tests_EVE_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\")",
-        "test": "mainline_lacros_tast_tests",
+        "test": "lacros_fyi_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_fyi_tast_tests/",
         "timeout_sec": 10800
       },
       {
         "args": [],
         "cros_board": "kefka",
         "cros_img": "kefka-release/R92-13970.0.0",
-        "name": "mainline_lacros_tast_tests_KEFKA_TOT",
+        "name": "lacros_fyi_tast_tests_KEFKA_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\")",
-        "test": "mainline_lacros_tast_tests",
+        "test": "lacros_fyi_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_fyi_tast_tests/",
         "timeout_sec": 10800
       },
       {
         "args": [],
         "cros_board": "kip",
         "cros_img": "kip-release/R92-13970.0.0",
-        "name": "mainline_lacros_tast_tests_KIP_TOT",
+        "name": "lacros_fyi_tast_tests_KIP_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\")",
-        "test": "mainline_lacros_tast_tests",
+        "test": "lacros_fyi_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_fyi_tast_tests/",
         "timeout_sec": 10800
       },
       {
         "args": [],
         "cros_board": "octopus",
         "cros_img": "octopus-release/R92-13970.0.0",
-        "name": "mainline_lacros_tast_tests_OCTOPUS_TOT",
+        "name": "lacros_fyi_tast_tests_OCTOPUS_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\")",
-        "test": "mainline_lacros_tast_tests",
+        "test": "lacros_fyi_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_fyi_tast_tests/",
         "timeout_sec": 10800
       },
       {
         "args": [],
         "cros_board": "snappy",
         "cros_img": "snappy-release/R92-13970.0.0",
-        "name": "mainline_lacros_tast_tests_SNAPPY_TOT",
+        "name": "lacros_fyi_tast_tests_SNAPPY_TOT",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\")",
-        "test": "mainline_lacros_tast_tests",
+        "test": "lacros_fyi_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_fyi_tast_tests/",
         "timeout_sec": 10800
       },
       {
         "args": [],
         "cros_board": "octopus",
         "cros_img": "octopus-release/R91-13904.22.0",
-        "name": "mainline_lacros_tast_tests_OCTOPUS_TOT-1",
+        "name": "lacros_fyi_tast_tests_OCTOPUS_TOT-1",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\")",
-        "test": "mainline_lacros_tast_tests",
+        "test": "lacros_fyi_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_fyi_tast_tests/",
         "timeout_sec": 10800
       },
       {
         "args": [],
         "cros_board": "octopus",
         "cros_img": "octopus-release/R90-13816.80.0",
-        "name": "mainline_lacros_tast_tests_OCTOPUS_TOT-2",
+        "name": "lacros_fyi_tast_tests_OCTOPUS_TOT-2",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\")",
-        "test": "mainline_lacros_tast_tests",
+        "test": "lacros_fyi_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_fyi_tast_tests/",
         "timeout_sec": 10800
       }
     ]
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index ea44486..cca7021d 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -4218,7 +4218,7 @@
     },
 
     'lacros_skylab_poc': {
-      'mainline_lacros_tast_tests': {
+      'lacros_fyi_tast_tests': {
         'tast_expr': '("group:mainline" && "dep:lacros")',
         'timeout_sec': 10800,
       },
diff --git a/testing/trigger_scripts/perf_device_trigger.py b/testing/trigger_scripts/perf_device_trigger.py
index d12a977..9f26a94 100755
--- a/testing/trigger_scripts/perf_device_trigger.py
+++ b/testing/trigger_scripts/perf_device_trigger.py
@@ -114,6 +114,9 @@
           builder=builder,
           num_of_shards=num_of_shards
       )
+      for shard_index, bot_index in selected_config:
+        bot_id = self._bot_configs[bot_index]['id']
+        shard_map['extra_infos']['bot #%s' % shard_index] = bot_id
     return shard_map
 
   def append_additional_args(self, args, shard_index):
diff --git a/third_party/blink/public/mojom/link_to_text/link_to_text.mojom b/third_party/blink/public/mojom/link_to_text/link_to_text.mojom
index cc29a81f..319b693 100644
--- a/third_party/blink/public/mojom/link_to_text/link_to_text.mojom
+++ b/third_party/blink/public/mojom/link_to_text/link_to_text.mojom
@@ -5,6 +5,8 @@
 [JavaPackage="org.chromium.blink.mojom"]
 module blink.mojom;
 
+import "ui/gfx/geometry/mojom/geometry.mojom";
+
 // TextFragmentReceiver is used for requesting renderer to perform text fragment
 // operations on the main frame, mainly generating and removing fragments.
 // Implemented in renderer.
@@ -25,4 +27,7 @@
 
   // Request text fragment selectors for existing highlights.
   GetExistingSelectors() => (array<string> selectors);
+
+  // Request the first text fragment rectangle relative to the viewport
+  ExtractFirstFragmentRect() => (gfx.mojom.Rect bounds);
 };
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 22fa14a..76a092c 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -3230,6 +3230,9 @@
   kClientHintsPrefersColorScheme = 3915,
   kOverscrollBehaviorWillBeFixed = 3916,
   kControlledWorkerWillBeUncontrolled = 3917,
+  kARIATouchpassthroughAttribute = 3918,
+  kARIAVirtualcontentAttribute = 3919,
+  kAccessibilityTouchPassthroughSet = 3920,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/core/animation/color_property_functions.cc b/third_party/blink/renderer/core/animation/color_property_functions.cc
index 0975b09..b1a4d4a4 100644
--- a/third_party/blink/renderer/core/animation/color_property_functions.cc
+++ b/third_party/blink/renderer/core/animation/color_property_functions.cc
@@ -18,6 +18,10 @@
     const CSSProperty& property,
     const ComputedStyle& style) {
   switch (property.PropertyID()) {
+    case CSSPropertyID::kAccentColor:
+      if (style.AccentColor().IsAutoColor())
+        return nullptr;
+      return style.AccentColor().ToStyleColor();
     case CSSPropertyID::kBackgroundColor:
       return style.BackgroundColor();
     case CSSPropertyID::kBorderLeftColor:
@@ -64,6 +68,8 @@
     const CSSProperty& property,
     const ComputedStyle& style) {
   switch (property.PropertyID()) {
+    case CSSPropertyID::kAccentColor:
+      return style.AccentColor();
     case CSSPropertyID::kBackgroundColor:
       return style.InternalVisitedBackgroundColor();
     case CSSPropertyID::kBorderLeftColor:
@@ -113,6 +119,9 @@
                                                const Color& color) {
   StyleColor style_color(color);
   switch (property.PropertyID()) {
+    case CSSPropertyID::kAccentColor:
+      style.SetAccentColor(StyleAutoColor(color));
+      return;
     case CSSPropertyID::kBackgroundColor:
       style.SetBackgroundColor(style_color);
       return;
@@ -166,6 +175,9 @@
                                              const Color& color) {
   StyleColor style_color(color);
   switch (property.PropertyID()) {
+    case CSSPropertyID::kAccentColor:
+      // The accent-color property is not valid for :visited.
+      return;
     case CSSPropertyID::kBackgroundColor:
       style.SetInternalVisitedBackgroundColor(style_color);
       return;
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc b/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
index 4caf781..5e248b03 100644
--- a/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
+++ b/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc
@@ -204,6 +204,7 @@
         applicable_types->push_back(
             std::make_unique<CSSNumberInterpolationType>(used_property));
         break;
+      case CSSPropertyID::kAccentColor:
       case CSSPropertyID::kBackgroundColor:
       case CSSPropertyID::kBorderBottomColor:
       case CSSPropertyID::kBorderLeftColor:
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 683d981..44cdfea 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -1093,7 +1093,7 @@
     },
     {
       name: "accent-color",
-      property_methods: ["ParseSingleValue"],
+      property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
       interpolable: true,
       inherited: true,
       field_group: "*",
@@ -1108,7 +1108,7 @@
       runtime_flag: "CSSAccentColor",
       default_value: "StyleAutoColor::AutoColor()",
       style_builder_custom_functions: ["initial", "inherit", "value"],
-      computable: false,
+      computable: true,
     },
     {
       name: "align-content",
diff --git a/third_party/blink/renderer/core/css/cssom/style_value_factory.cc b/third_party/blink/renderer/core/css/cssom/style_value_factory.cc
index 4e8f713..db3ae43 100644
--- a/third_party/blink/renderer/core/css/cssom/style_value_factory.cc
+++ b/third_party/blink/renderer/core/css/cssom/style_value_factory.cc
@@ -85,8 +85,9 @@
       }
       return nullptr;
     }
+    case CSSPropertyID::kAccentColor:
     case CSSPropertyID::kCaretColor: {
-      // caret-color also supports 'auto'
+      // caret-color and accent-color also support 'auto'
       auto* identifier_value = DynamicTo<CSSIdentifierValue>(value);
       if (identifier_value &&
           identifier_value->GetValueID() == CSSValueID::kAuto)
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 7b1d5520..99473f5 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -4911,6 +4911,19 @@
   return css_parsing_utils::ConsumeColor(range, context);
 }
 
+const CSSValue* AccentColor::CSSValueFromComputedStyleInternal(
+    const ComputedStyle& style,
+    const LayoutObject*,
+    bool allow_visited_style) const {
+  StyleAutoColor auto_color = style.AccentColor();
+  if (auto_color.IsAutoColor()) {
+    return CSSIdentifierValue::Create(CSSValueID::kAuto);
+  }
+
+  return ComputedStyleUtils::ValueForStyleAutoColor(
+      style, style.AccentColor(), CSSValuePhase::kComputedValue);
+}
+
 void AccentColor::ApplyInitial(StyleResolverState& state) const {
   state.Style()->SetAccentColor(StyleAutoColor::AutoColor());
 }
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 1d380f4..182eaca 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,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h"
 
+#include "third_party/blink/renderer/core/dom/text.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 
 namespace blink {
@@ -15,7 +16,38 @@
   EXPECT_EQ(2u, FirstLetterPseudoElement::FirstLetterLength(emoji));
 }
 
-// http://crbug.com/1161370
+// http://crbug.com/1187834
+TEST_F(FirstLetterPseudoElementTest, AppendDataToSpace) {
+  InsertStyleElement("div::first-letter { color: red; }");
+  SetBodyContent("<div><b id=sample> <!---->xyz</b></div>");
+  const auto& sample = *GetElementById("sample");
+  const auto& sample_layout_object = *sample.GetLayoutObject();
+  auto& first_text = *To<Text>(sample.firstChild());
+
+  EXPECT_EQ(R"DUMP(
+LayoutInline B id="sample"
+  +--LayoutText #text " "
+  +--LayoutInline ::first-letter
+  |  +--LayoutTextFragment (anonymous) ("x")
+  +--LayoutTextFragment #text "xyz" ("yz")
+)DUMP",
+            ToSimpleLayoutTree(sample_layout_object));
+
+  // Change leading white space " " to " AB".
+  first_text.appendData("AB");
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(R"DUMP(
+LayoutInline B id="sample"
+  +--LayoutInline ::first-letter
+  |  +--LayoutTextFragment (anonymous) (" A")
+  +--LayoutTextFragment #text " AB" ("B")
+  +--LayoutTextFragment #text "xyz" ("xyz")
+)DUMP",
+            ToSimpleLayoutTree(sample_layout_object));
+}
+
+// http://crbug.com/1159762
 TEST_F(FirstLetterPseudoElementTest, EmptySpanOnly) {
   InsertStyleElement("p::first-letter { color: red; }");
   SetBodyContent("<div><p id=sample><b></b></p>abc</div>");
diff --git a/third_party/blink/renderer/core/dom/text.cc b/third_party/blink/renderer/core/dom/text.cc
index 7a56d42..6ff0ca1c 100644
--- a/third_party/blink/renderer/core/dom/text.cc
+++ b/third_party/blink/renderer/core/dom/text.cc
@@ -447,6 +447,14 @@
     return text_fragment_layout_object.GetFirstLetterPseudoElement() ||
            !text_fragment_layout_object.IsRemainingTextLayoutObject();
   }
+  if (auto* next = text_layout_object->NextSibling()) {
+    if (IsA<FirstLetterPseudoElement>(next->GetNode())) {
+      // This |Text| node is not a first-letter part, but it may be changed.
+      // So, we should rebuild first-letter part and remaining part.
+      // See FirstLetterPseudoElementTest.AppendDataToSpace
+      return true;
+    }
+  }
   return false;
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/base_button_input_type.cc b/third_party/blink/renderer/core/html/forms/base_button_input_type.cc
index cf415b2..fface02 100644
--- a/third_party/blink/renderer/core/html/forms/base_button_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/base_button_input_type.cc
@@ -77,7 +77,7 @@
 LayoutObject* BaseButtonInputType::CreateLayoutObject(
     const ComputedStyle& style,
     LegacyLayout legacy) const {
-  return LayoutObjectFactory::CreateButton(GetElement(), legacy);
+  return LayoutObjectFactory::CreateButton(GetElement(), style, legacy);
 }
 
 InputType::ValueMode BaseButtonInputType::GetValueMode() const {
diff --git a/third_party/blink/renderer/core/html/forms/file_input_type.cc b/third_party/blink/renderer/core/html/forms/file_input_type.cc
index 121aa25..3b04500 100644
--- a/third_party/blink/renderer/core/html/forms/file_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/file_input_type.cc
@@ -209,7 +209,8 @@
 
 LayoutObject* FileInputType::CreateLayoutObject(const ComputedStyle& style,
                                                 LegacyLayout legacy) const {
-  return LayoutObjectFactory::CreateFileUploadControl(GetElement(), legacy);
+  return LayoutObjectFactory::CreateFileUploadControl(GetElement(), style,
+                                                      legacy);
 }
 
 InputType::ValueMode FileInputType::GetValueMode() const {
diff --git a/third_party/blink/renderer/core/html/forms/html_button_element.cc b/third_party/blink/renderer/core/html/forms/html_button_element.cc
index 3d178fb..5b52558 100644
--- a/third_party/blink/renderer/core/html/forms/html_button_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_button_element.cc
@@ -57,7 +57,7 @@
       display == EDisplay::kInlineLayoutCustom ||
       display == EDisplay::kLayoutCustom)
     return HTMLFormControlElement::CreateLayoutObject(style, legacy);
-  return LayoutObjectFactory::CreateButton(*this, legacy);
+  return LayoutObjectFactory::CreateButton(*this, style, legacy);
 }
 
 const AtomicString& HTMLButtonElement::FormControlType() const {
diff --git a/third_party/blink/renderer/core/html/forms/html_field_set_element.cc b/third_party/blink/renderer/core/html/forms/html_field_set_element.cc
index bf9a257..93ab4d0 100644
--- a/third_party/blink/renderer/core/html/forms/html_field_set_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_field_set_element.cc
@@ -121,7 +121,7 @@
 LayoutObject* HTMLFieldSetElement::CreateLayoutObject(
     const ComputedStyle& style,
     LegacyLayout legacy) {
-  return LayoutObjectFactory::CreateFieldset(*this, legacy);
+  return LayoutObjectFactory::CreateFieldset(*this, style, legacy);
 }
 
 LayoutBox* HTMLFieldSetElement::GetLayoutBoxForScrolling() const {
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index cbc847f8..88e95ee 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -406,7 +406,7 @@
     const ComputedStyle& style,
     LegacyLayout legacy_layout) {
   if (UsesMenuList())
-    return LayoutObjectFactory::CreateFlexibleBox(*this, legacy_layout);
+    return LayoutObjectFactory::CreateFlexibleBox(*this, style, legacy_layout);
   return LayoutObjectFactory::CreateBlockFlow(*this, style, legacy_layout);
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
index d9e756d..89d0fcc 100644
--- a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
@@ -263,7 +263,7 @@
 LayoutObject* HTMLTextAreaElement::CreateLayoutObject(
     const ComputedStyle& style,
     LegacyLayout legacy) {
-  return LayoutObjectFactory::CreateTextControlMultiLine(*this, legacy);
+  return LayoutObjectFactory::CreateTextControlMultiLine(*this, style, legacy);
 }
 
 void HTMLTextAreaElement::AppendToFormData(FormData& form_data) {
diff --git a/third_party/blink/renderer/core/html/forms/range_input_type.cc b/third_party/blink/renderer/core/html/forms/range_input_type.cc
index f58a5393..f978b04 100644
--- a/third_party/blink/renderer/core/html/forms/range_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/range_input_type.cc
@@ -254,7 +254,7 @@
                                                  LegacyLayout legacy) const {
   // TODO(crbug.com/1131352): input[type=range] should not use
   // LayoutFlexibleBox.
-  return LayoutObjectFactory::CreateFlexibleBox(GetElement(), legacy);
+  return LayoutObjectFactory::CreateFlexibleBox(GetElement(), style, legacy);
 }
 
 Decimal RangeInputType::ParseToNumber(const String& src,
diff --git a/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc b/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc
index 872142a..21676c6 100644
--- a/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc
+++ b/third_party/blink/renderer/core/html/forms/slider_thumb_element.cc
@@ -327,7 +327,7 @@
 LayoutObject* SliderContainerElement::CreateLayoutObject(
     const ComputedStyle& style,
     LegacyLayout legacy) {
-  return LayoutObjectFactory::CreateFlexibleBox(*this, legacy);
+  return LayoutObjectFactory::CreateFlexibleBox(*this, style, legacy);
 }
 
 void SliderContainerElement::DefaultEventHandler(Event& event) {
diff --git a/third_party/blink/renderer/core/html/forms/slider_track_element.cc b/third_party/blink/renderer/core/html/forms/slider_track_element.cc
index 01b6f77..55a7d074 100644
--- a/third_party/blink/renderer/core/html/forms/slider_track_element.cc
+++ b/third_party/blink/renderer/core/html/forms/slider_track_element.cc
@@ -13,7 +13,7 @@
 
 LayoutObject* SliderTrackElement::CreateLayoutObject(const ComputedStyle& style,
                                                      LegacyLayout legacy) {
-  return LayoutObjectFactory::CreateSliderTrack(*this, legacy);
+  return LayoutObjectFactory::CreateSliderTrack(*this, style, legacy);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc b/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
index a434fb46..17ead81c 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_inner_elements.cc
@@ -131,7 +131,8 @@
 LayoutObject* TextControlInnerEditorElement::CreateLayoutObject(
     const ComputedStyle& style,
     LegacyLayout legacy) {
-  return LayoutObjectFactory::CreateTextControlInnerEditor(*this, legacy);
+  return LayoutObjectFactory::CreateTextControlInnerEditor(*this, style,
+                                                           legacy);
 }
 
 scoped_refptr<ComputedStyle>
diff --git a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
index b55cc1c..bf44e5b 100644
--- a/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/text_field_input_type.cc
@@ -285,7 +285,8 @@
 LayoutObject* TextFieldInputType::CreateLayoutObject(
     const ComputedStyle& style,
     LegacyLayout legacy) const {
-  return LayoutObjectFactory::CreateTextControlSingleLine(GetElement(), legacy);
+  return LayoutObjectFactory::CreateTextControlSingleLine(GetElement(), style,
+                                                          legacy);
 }
 
 void TextFieldInputType::CreateShadowSubtree() {
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 5696ca6..d1994f67 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -599,6 +599,7 @@
       {html_names::kOnwheelAttr, kNoWebFeature, event_type_names::kWheel,
        nullptr},
 
+      // Begin ARIA attributes.
       {html_names::kAriaActivedescendantAttr,
        WebFeature::kARIAActiveDescendantAttribute, kNoEvent, nullptr},
       {html_names::kAriaAtomicAttr, WebFeature::kARIAAtomicAttribute, kNoEvent,
@@ -691,6 +692,8 @@
        kNoEvent, nullptr},
       {html_names::kAriaSortAttr, WebFeature::kARIASortAttribute, kNoEvent,
        nullptr},
+      {html_names::kAriaTouchpassthroughAttr,
+       WebFeature::kARIATouchpassthroughAttribute, kNoEvent, nullptr},
       {html_names::kAriaValuemaxAttr, WebFeature::kARIAValueMaxAttribute,
        kNoEvent, nullptr},
       {html_names::kAriaValueminAttr, WebFeature::kARIAValueMinAttribute,
@@ -699,6 +702,10 @@
        kNoEvent, nullptr},
       {html_names::kAriaValuetextAttr, WebFeature::kARIAValueTextAttribute,
        kNoEvent, nullptr},
+      {html_names::kAriaVirtualcontentAttr,
+       WebFeature::kARIAVirtualcontentAttribute, kNoEvent, nullptr},
+      // End ARIA attributes.
+
       {html_names::kAutocapitalizeAttr, WebFeature::kAutocapitalizeAttribute,
        kNoEvent, nullptr},
   };
diff --git a/third_party/blink/renderer/core/html/html_progress_element.cc b/third_party/blink/renderer/core/html/html_progress_element.cc
index 7410e1e..e7ba5e9a 100644
--- a/third_party/blink/renderer/core/html/html_progress_element.cc
+++ b/third_party/blink/renderer/core/html/html_progress_element.cc
@@ -54,7 +54,7 @@
   }
   UseCounter::Count(GetDocument(),
                     WebFeature::kProgressElementWithProgressBarAppearance);
-  return LayoutObjectFactory::CreateProgress(this, legacy);
+  return LayoutObjectFactory::CreateProgress(this, style, legacy);
 }
 
 LayoutProgress* HTMLProgressElement::GetLayoutProgress() const {
diff --git a/third_party/blink/renderer/core/html/html_rt_element.cc b/third_party/blink/renderer/core/html/html_rt_element.cc
index 8b3921c7..548e222 100644
--- a/third_party/blink/renderer/core/html/html_rt_element.cc
+++ b/third_party/blink/renderer/core/html/html_rt_element.cc
@@ -17,7 +17,7 @@
 LayoutObject* HTMLRTElement::CreateLayoutObject(const ComputedStyle& style,
                                                 LegacyLayout legacy) {
   if (style.Display() == EDisplay::kBlock)
-    return LayoutObjectFactory::CreateRubyText(this, legacy);
+    return LayoutObjectFactory::CreateRubyText(this, style, legacy);
   return LayoutObject::CreateObject(this, style, legacy);
 }
 
diff --git a/third_party/blink/renderer/core/html/html_ruby_element.cc b/third_party/blink/renderer/core/html/html_ruby_element.cc
index d0cbef5..96c3ae0 100644
--- a/third_party/blink/renderer/core/html/html_ruby_element.cc
+++ b/third_party/blink/renderer/core/html/html_ruby_element.cc
@@ -20,7 +20,7 @@
     return new LayoutRubyAsInline(this);
   if (style.Display() == EDisplay::kBlock) {
     UseCounter::Count(GetDocument(), WebFeature::kRubyElementWithDisplayBlock);
-    return LayoutObjectFactory::CreateRubyAsBlock(this, legacy);
+    return LayoutObjectFactory::CreateRubyAsBlock(this, style, legacy);
   }
   return LayoutObject::CreateObject(this, style, legacy);
 }
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc
index 011d045..bddde61 100644
--- a/third_party/blink/renderer/core/layout/layout_block.cc
+++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -2270,11 +2270,11 @@
   parent->UpdateAnonymousChildStyle(nullptr, *new_style);
   LayoutBlock* layout_block;
   if (new_display == EDisplay::kFlex) {
-    layout_block =
-        LayoutObjectFactory::CreateFlexibleBox(parent->GetDocument(), legacy);
+    layout_block = LayoutObjectFactory::CreateFlexibleBox(parent->GetDocument(),
+                                                          *new_style, legacy);
   } else if (new_display == EDisplay::kGrid) {
-    layout_block =
-        LayoutObjectFactory::CreateGrid(parent->GetDocument(), legacy);
+    layout_block = LayoutObjectFactory::CreateGrid(parent->GetDocument(),
+                                                   *new_style, legacy);
   } else {
     DCHECK(new_display == EDisplay::kBlock ||
            new_display == EDisplay::kFlowRoot);
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index f53e6b9..564d550 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -288,37 +288,38 @@
       return LayoutObjectFactory::CreateBlockFlow(*element, style, legacy);
     case EDisplay::kTable:
     case EDisplay::kInlineTable:
-      return LayoutObjectFactory::CreateTable(*element, legacy);
+      return LayoutObjectFactory::CreateTable(*element, style, legacy);
     case EDisplay::kTableRowGroup:
     case EDisplay::kTableHeaderGroup:
     case EDisplay::kTableFooterGroup:
-      return LayoutObjectFactory::CreateTableSection(*element, legacy);
+      return LayoutObjectFactory::CreateTableSection(*element, style, legacy);
     case EDisplay::kTableRow:
-      return LayoutObjectFactory::CreateTableRow(*element, legacy);
+      return LayoutObjectFactory::CreateTableRow(*element, style, legacy);
     case EDisplay::kTableColumnGroup:
     case EDisplay::kTableColumn:
-      return LayoutObjectFactory::CreateTableColumn(*element, legacy);
+      return LayoutObjectFactory::CreateTableColumn(*element, style, legacy);
     case EDisplay::kTableCell:
-      return LayoutObjectFactory::CreateTableCell(*element, legacy);
+      return LayoutObjectFactory::CreateTableCell(*element, style, legacy);
     case EDisplay::kTableCaption:
-      return LayoutObjectFactory::CreateTableCaption(*element, legacy);
+      return LayoutObjectFactory::CreateTableCaption(*element, style, legacy);
     case EDisplay::kWebkitBox:
     case EDisplay::kWebkitInlineBox:
       if (style.IsDeprecatedWebkitBoxWithVerticalLineClamp()) {
-        return LayoutObjectFactory::CreateBlockForLineClamp(*element, legacy);
+        return LayoutObjectFactory::CreateBlockForLineClamp(*element, style,
+                                                            legacy);
       }
-      return LayoutObjectFactory::CreateFlexibleBox(*element, legacy);
+      return LayoutObjectFactory::CreateFlexibleBox(*element, style, legacy);
     case EDisplay::kFlex:
     case EDisplay::kInlineFlex:
       UseCounter::Count(element->GetDocument(), WebFeature::kCSSFlexibleBox);
-      return LayoutObjectFactory::CreateFlexibleBox(*element, legacy);
+      return LayoutObjectFactory::CreateFlexibleBox(*element, style, legacy);
     case EDisplay::kGrid:
     case EDisplay::kInlineGrid:
       UseCounter::Count(element->GetDocument(), WebFeature::kCSSGridLayout);
-      return LayoutObjectFactory::CreateGrid(*element, legacy);
+      return LayoutObjectFactory::CreateGrid(*element, style, legacy);
     case EDisplay::kMath:
     case EDisplay::kBlockMath:
-      return LayoutObjectFactory::CreateMath(*element, legacy);
+      return LayoutObjectFactory::CreateMath(*element, style, legacy);
     case EDisplay::kLayoutCustom:
     case EDisplay::kInlineLayoutCustom:
       DCHECK(RuntimeEnabledFeatures::LayoutNGEnabled());
diff --git a/third_party/blink/renderer/core/layout/layout_object_factory.cc b/third_party/blink/renderer/core/layout/layout_object_factory.cc
index 66dad88b..abc044d 100644
--- a/third_party/blink/renderer/core/layout/layout_object_factory.cc
+++ b/third_party/blink/renderer/core/layout/layout_object_factory.cc
@@ -122,18 +122,21 @@
 // static
 LayoutBlock* LayoutObjectFactory::CreateBlockForLineClamp(
     Node& node,
+    const ComputedStyle& style,
     LegacyLayout legacy) {
   return CreateObject<LayoutBlock, LayoutNGBlockFlow,
                       LayoutDeprecatedFlexibleBox>(node, legacy);
 }
 
 LayoutBlock* LayoutObjectFactory::CreateFlexibleBox(Node& node,
+                                                    const ComputedStyle& style,
                                                     LegacyLayout legacy) {
   return CreateObject<LayoutBlock, LayoutNGFlexibleBox, LayoutFlexibleBox>(
       node, legacy);
 }
 
 LayoutBlock* LayoutObjectFactory::CreateGrid(Node& node,
+                                             const ComputedStyle& style,
                                              LegacyLayout legacy) {
   bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGGridEnabled();
   if (disable_ng_for_type)
@@ -143,6 +146,7 @@
 }
 
 LayoutBlock* LayoutObjectFactory::CreateMath(Node& node,
+                                             const ComputedStyle& style,
                                              LegacyLayout legacy) {
   DCHECK(IsA<MathMLElement>(node));
   DCHECK_NE(legacy, LegacyLayout::kForce);
@@ -180,6 +184,7 @@
 }
 
 LayoutBlock* LayoutObjectFactory::CreateTable(Node& node,
+                                              const ComputedStyle& style,
                                               LegacyLayout legacy) {
   bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGTableEnabled();
   if (disable_ng_for_type)
@@ -190,12 +195,14 @@
 
 LayoutTableCaption* LayoutObjectFactory::CreateTableCaption(
     Node& node,
+    const ComputedStyle& style,
     LegacyLayout legacy) {
   return CreateObject<LayoutTableCaption, LayoutNGTableCaption>(node, legacy);
 }
 
 LayoutBlockFlow* LayoutObjectFactory::CreateTableCell(
     Node& node,
+    const ComputedStyle& style,
     LegacyLayout legacy) {
   if (RuntimeEnabledFeatures::LayoutNGTableEnabled()) {
     return CreateObject<LayoutBlockFlow, LayoutNGTableCell, LayoutTableCell>(
@@ -207,6 +214,7 @@
 }
 
 LayoutBox* LayoutObjectFactory::CreateTableColumn(Node& node,
+                                                  const ComputedStyle& style,
                                                   LegacyLayout legacy) {
   bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGTableEnabled();
   if (disable_ng_for_type)
@@ -216,6 +224,7 @@
 }
 
 LayoutBox* LayoutObjectFactory::CreateTableRow(Node& node,
+                                               const ComputedStyle& style,
                                                LegacyLayout legacy) {
   bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGTableEnabled();
   if (disable_ng_for_type)
@@ -225,6 +234,7 @@
 }
 
 LayoutBox* LayoutObjectFactory::CreateTableSection(Node& node,
+                                                   const ComputedStyle& style,
                                                    LegacyLayout legacy) {
   bool disable_ng_for_type = !RuntimeEnabledFeatures::LayoutNGTableEnabled();
   if (disable_ng_for_type)
@@ -234,11 +244,13 @@
 }
 
 LayoutObject* LayoutObjectFactory::CreateButton(Node& node,
+                                                const ComputedStyle& style,
                                                 LegacyLayout legacy) {
   return CreateObject<LayoutBlock, LayoutNGButton, LayoutButton>(node, legacy);
 }
 
 LayoutBlock* LayoutObjectFactory::CreateFieldset(Node& node,
+                                                 const ComputedStyle& style,
                                                  LegacyLayout legacy) {
   return CreateObject<LayoutBlock, LayoutNGFieldset, LayoutFieldset>(node,
                                                                      legacy);
@@ -246,12 +258,14 @@
 
 LayoutBlockFlow* LayoutObjectFactory::CreateFileUploadControl(
     Node& node,
+    const ComputedStyle& style,
     LegacyLayout legacy) {
   return CreateObject<LayoutBlockFlow, LayoutNGBlockFlow,
                       LayoutFileUploadControl>(node, legacy);
 }
 
 LayoutObject* LayoutObjectFactory::CreateSliderTrack(Node& node,
+                                                     const ComputedStyle& style,
                                                      LegacyLayout legacy) {
   return CreateObject<LayoutBlock, LayoutNGBlockFlow, LayoutSliderTrack>(
       node, legacy);
@@ -259,6 +273,7 @@
 
 LayoutObject* LayoutObjectFactory::CreateTextControlInnerEditor(
     Node& node,
+    const ComputedStyle& style,
     LegacyLayout legacy) {
   return CreateObject<LayoutBlockFlow, LayoutNGTextControlInnerEditor,
                       LayoutTextControlInnerEditor>(node, legacy);
@@ -266,6 +281,7 @@
 
 LayoutObject* LayoutObjectFactory::CreateTextControlMultiLine(
     Node& node,
+    const ComputedStyle& style,
     LegacyLayout legacy) {
   return CreateObject<LayoutBlockFlow, LayoutNGTextControlMultiLine,
                       LayoutTextControlMultiLine>(node, legacy);
@@ -273,6 +289,7 @@
 
 LayoutObject* LayoutObjectFactory::CreateTextControlSingleLine(
     Node& node,
+    const ComputedStyle& style,
     LegacyLayout legacy) {
   return CreateObject<LayoutBlockFlow, LayoutNGTextControlSingleLine,
                       LayoutTextControlSingleLine>(node, legacy);
@@ -320,22 +337,26 @@
 }
 
 LayoutProgress* LayoutObjectFactory::CreateProgress(Node* node,
+                                                    const ComputedStyle& style,
                                                     LegacyLayout legacy) {
   return CreateObject<LayoutProgress, LayoutNGProgress>(*node, legacy);
 }
 
 LayoutRubyAsBlock* LayoutObjectFactory::CreateRubyAsBlock(
     Node* node,
+    const ComputedStyle& style,
     LegacyLayout legacy) {
   return CreateObject<LayoutRubyAsBlock, LayoutNGRubyAsBlock>(*node, legacy);
 }
 
 LayoutObject* LayoutObjectFactory::CreateRubyText(Node* node,
+                                                  const ComputedStyle& style,
                                                   LegacyLayout legacy) {
   return CreateObject<LayoutRubyText, LayoutNGRubyText>(*node, legacy);
 }
 
 LayoutObject* LayoutObjectFactory::CreateSVGText(Node& node,
+                                                 const ComputedStyle& style,
                                                  LegacyLayout legacy) {
   const bool disable_ng_for_type = !RuntimeEnabledFeatures::SVGTextNGEnabled();
   return CreateObject<LayoutBlockFlow, LayoutNGSVGText, LayoutSVGText>(
@@ -363,7 +384,8 @@
                             ? LegacyLayout::kForce
                             : LegacyLayout::kAuto;
 
-  LayoutBlock* new_table = CreateTable(parent.GetDocument(), legacy);
+  LayoutBlock* new_table =
+      CreateTable(parent.GetDocument(), *new_style, legacy);
   new_table->SetDocumentForAnonymous(&parent.GetDocument());
   new_table->SetStyle(std::move(new_style));
   return new_table;
@@ -377,7 +399,8 @@
   LegacyLayout legacy =
       parent.ForceLegacyLayout() ? LegacyLayout::kForce : LegacyLayout::kAuto;
 
-  LayoutBox* new_section = CreateTableSection(parent.GetDocument(), legacy);
+  LayoutBox* new_section =
+      CreateTableSection(parent.GetDocument(), *new_style, legacy);
   new_section->SetDocumentForAnonymous(&parent.GetDocument());
   new_section->SetStyle(std::move(new_style));
   return new_section;
@@ -390,7 +413,7 @@
           parent.StyleRef(), EDisplay::kTableRow);
   LegacyLayout legacy =
       parent.ForceLegacyLayout() ? LegacyLayout::kForce : LegacyLayout::kAuto;
-  LayoutBox* new_row = CreateTableRow(parent.GetDocument(), legacy);
+  LayoutBox* new_row = CreateTableRow(parent.GetDocument(), *new_style, legacy);
   new_row->SetDocumentForAnonymous(&parent.GetDocument());
   new_row->SetStyle(std::move(new_style));
   return new_row;
@@ -403,7 +426,8 @@
           parent.StyleRef(), EDisplay::kTableCell);
   LegacyLayout legacy =
       parent.ForceLegacyLayout() ? LegacyLayout::kForce : LegacyLayout::kAuto;
-  LayoutBlockFlow* new_cell = CreateTableCell(parent.GetDocument(), legacy);
+  LayoutBlockFlow* new_cell =
+      CreateTableCell(parent.GetDocument(), *new_style, legacy);
   new_cell->SetDocumentForAnonymous(&parent.GetDocument());
   new_cell->SetStyle(std::move(new_style));
   return new_cell;
diff --git a/third_party/blink/renderer/core/layout/layout_object_factory.h b/third_party/blink/renderer/core/layout/layout_object_factory.h
index 8d06da65..19fb64c 100644
--- a/third_party/blink/renderer/core/layout/layout_object_factory.h
+++ b/third_party/blink/renderer/core/layout/layout_object_factory.h
@@ -41,38 +41,50 @@
                                           const ComputedStyle&,
                                           LegacyLayout);
   static LayoutBlock* CreateBlockForLineClamp(Node& node,
+                                              const ComputedStyle& style,
                                               LegacyLayout legacy);
   static LayoutBlock* CreateFlexibleBox(Node&,
+                                        const ComputedStyle&,
                                         LegacyLayout);
-  static LayoutBlock* CreateGrid(Node&, LegacyLayout);
-  static LayoutBlock* CreateMath(Node&, LegacyLayout);
+  static LayoutBlock* CreateGrid(Node&, const ComputedStyle&, LegacyLayout);
+  static LayoutBlock* CreateMath(Node&, const ComputedStyle&, LegacyLayout);
   static LayoutObject* CreateListMarker(Node&,
                                         const ComputedStyle&,
                                         LegacyLayout);
-  static LayoutBlock* CreateTable(Node&, LegacyLayout);
+  static LayoutBlock* CreateTable(Node&, const ComputedStyle&, LegacyLayout);
   static LayoutTableCaption* CreateTableCaption(Node&,
+                                                const ComputedStyle&,
                                                 LegacyLayout);
   static LayoutBlockFlow* CreateTableCell(Node&,
+                                          const ComputedStyle&,
                                           LegacyLayout);
   static LayoutBox* CreateTableColumn(Node&,
+                                      const ComputedStyle&,
                                       LegacyLayout);
 
-  static LayoutBox* CreateTableRow(Node&, LegacyLayout);
+  static LayoutBox* CreateTableRow(Node&, const ComputedStyle&, LegacyLayout);
   static LayoutBox* CreateTableSection(Node&,
+                                       const ComputedStyle&,
                                        LegacyLayout);
 
   static LayoutObject* CreateButton(Node& node,
+                                    const ComputedStyle& style,
                                     LegacyLayout legacy);
-  static LayoutBlock* CreateFieldset(Node&, LegacyLayout);
+  static LayoutBlock* CreateFieldset(Node&, const ComputedStyle&, LegacyLayout);
   static LayoutBlockFlow* CreateFileUploadControl(Node& node,
+                                                  const ComputedStyle& style,
                                                   LegacyLayout legacy);
   static LayoutObject* CreateSliderTrack(Node& node,
+                                         const ComputedStyle& style,
                                          LegacyLayout legacy);
   static LayoutObject* CreateTextControlInnerEditor(Node& node,
+                                                    const ComputedStyle& style,
                                                     LegacyLayout legacy);
   static LayoutObject* CreateTextControlMultiLine(Node& node,
+                                                  const ComputedStyle& style,
                                                   LegacyLayout legacy);
   static LayoutObject* CreateTextControlSingleLine(Node& node,
+                                                   const ComputedStyle& style,
                                                    LegacyLayout legacy);
 
   static LayoutText* CreateText(Node*, scoped_refptr<StringImpl>, LegacyLayout);
@@ -85,13 +97,17 @@
                                                 int length,
                                                 LegacyLayout);
   static LayoutProgress* CreateProgress(Node* node,
+                                        const ComputedStyle& style,
                                         LegacyLayout legacy);
   static LayoutRubyAsBlock* CreateRubyAsBlock(Node* node,
+                                              const ComputedStyle& style,
                                               LegacyLayout legacy);
   static LayoutObject* CreateRubyText(Node* node,
+                                      const ComputedStyle& style,
                                       LegacyLayout legacy);
 
   static LayoutObject* CreateSVGText(Node& node,
+                                     const ComputedStyle& style,
                                      LegacyLayout legacy);
 
   static LayoutObject* CreateBR(Node*, LegacyLayout);
diff --git a/third_party/blink/renderer/core/layout/layout_table_cell.cc b/third_party/blink/renderer/core/layout/layout_table_cell.cc
index 9b4f0fcc..e30bc902 100644
--- a/third_party/blink/renderer/core/layout/layout_table_cell.cc
+++ b/third_party/blink/renderer/core/layout/layout_table_cell.cc
@@ -1227,7 +1227,7 @@
     scoped_refptr<ComputedStyle> style,
     LegacyLayout legacy) {
   LayoutBlockFlow* layout_object =
-      LayoutObjectFactory::CreateTableCell(*document, legacy);
+      LayoutObjectFactory::CreateTableCell(*document, *style, legacy);
   layout_object->SetDocumentForAnonymous(document);
   layout_object->SetStyle(std::move(style));
   return To<LayoutTableCell>(layout_object);
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
index f0fd60b..2eaee00 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
@@ -584,6 +584,9 @@
     const Length& specified_length_in_main_axis =
         is_horizontal_flow_ ? child_style.Width() : child_style.Height();
     const Length& flex_basis = child_style.FlexBasis();
+    if (is_column_ && flex_basis.IsPercentOrCalc())
+      has_column_percent_flex_basis_ = true;
+
     Length length_to_resolve = Length::Auto();
     if (flex_basis.IsAuto()) {
       if (!is_column_ || IsItemMainSizeDefinite(child))
@@ -1059,6 +1062,9 @@
   container_builder_.SetIntrinsicBlockSize(intrinsic_block_size);
   container_builder_.SetFragmentsTotalBlockSize(block_size);
 
+  if (has_column_percent_flex_basis_)
+    container_builder_.SetHasDescendantThatDependsOnPercentageBlockSize(true);
+
   bool success = GiveLinesAndItemsFinalPositionAndSize();
   if (!success)
     return nullptr;
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
index 5667a87..478eef3 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
@@ -86,6 +86,7 @@
   const bool is_cross_size_definite_;
   const LogicalSize child_percentage_size_;
 
+  bool has_column_percent_flex_basis_ = false;
   bool ignore_child_scrollbar_changes_ = false;
   FlexLayoutAlgorithm algorithm_;
   DevtoolsFlexInfo* layout_info_for_devtools_;
diff --git a/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.h b/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.h
index 7f19992..3c38470 100644
--- a/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.h
+++ b/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.h
@@ -58,6 +58,8 @@
 
   void Trace(Visitor*) const override;
 
+  bool IsTextFragmentAnchor() override { return false; }
+
  private:
   FRIEND_TEST_ALL_PREFIXES(ElementFragmentAnchorTest,
                            AnchorRemovedBeforeBeginFrameCrash);
diff --git a/third_party/blink/renderer/core/page/scrolling/fragment_anchor.h b/third_party/blink/renderer/core/page/scrolling/fragment_anchor.h
index 0c7e3cc..6681171 100644
--- a/third_party/blink/renderer/core/page/scrolling/fragment_anchor.h
+++ b/third_party/blink/renderer/core/page/scrolling/fragment_anchor.h
@@ -57,6 +57,8 @@
   virtual bool Dismiss() = 0;
 
   virtual void Trace(Visitor*) const {}
+
+  virtual bool IsTextFragmentAnchor() = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
index d570db10..c520078 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
@@ -91,6 +91,8 @@
     return text_fragment_finders_;
   }
 
+  bool IsTextFragmentAnchor() override { return true; }
+
  private:
   // Called when the search is finished. Reports metrics and activates the
   // element fragment anchor if we didn't find a match.
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.cc
index 0eb4687f3..71aad18 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/core/editing/markers/document_marker.h"
 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
 #include "third_party/blink/renderer/core/editing/position_with_affinity.h"
+#include "third_party/blink/renderer/core/editing/visible_units.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h"
 
@@ -43,17 +44,16 @@
 void TextFragmentHandler::GetExistingSelectors(
     GetExistingSelectorsCallback callback) {
   Vector<String> text_fragment_selectors;
-  TextFragmentAnchor* anchor(
-      static_cast<TextFragmentAnchor*>(GetTextFragmentSelectorGenerator()
-                                           ->GetFrame()
-                                           ->View()
-                                           ->GetFragmentAnchor()));
 
-  if (anchor) {
-    for (auto& finder : anchor->TextFragmentFinders()) {
-      if (finder->FirstMatch()) {
-        text_fragment_selectors.push_back(finder->GetSelector().ToString());
-      }
+  TextFragmentAnchor* anchor = GetTextFragmentAnchor();
+  if (!anchor) {
+    std::move(callback).Run(Vector<String>());
+    return;
+  }
+
+  for (auto& finder : anchor->TextFragmentFinders()) {
+    if (finder->FirstMatch()) {
+      text_fragment_selectors.push_back(finder->GetSelector().ToString());
     }
   }
 
@@ -90,28 +90,66 @@
     ExtractTextFragmentsMatchesCallback callback) {
   DCHECK(
       base::FeatureList::IsEnabled(shared_highlighting::kSharedHighlightingV2));
-
   Vector<String> text_fragment_matches;
-  TextFragmentAnchor* anchor(
-      static_cast<TextFragmentAnchor*>(GetTextFragmentSelectorGenerator()
-                                           ->GetFrame()
-                                           ->View()
-                                           ->GetFragmentAnchor()));
 
-  if (anchor) {
-    for (auto& finder : anchor->TextFragmentFinders()) {
-      EphemeralRangeInFlatTree potential_match =
-          finder->FirstMatch()->ToEphemeralRange();
-      text_fragment_matches.push_back(PlainText(potential_match));
+  TextFragmentAnchor* anchor = GetTextFragmentAnchor();
+  if (!anchor) {
+    std::move(callback).Run(Vector<String>());
+    return;
+  }
+
+  for (auto& finder : anchor->TextFragmentFinders()) {
+    if (finder->FirstMatch()) {
+      text_fragment_matches.push_back(
+          PlainText(finder->FirstMatch()->ToEphemeralRange()));
     }
   }
 
   std::move(callback).Run(text_fragment_matches);
 }
 
+void TextFragmentHandler::ExtractFirstFragmentRect(
+    ExtractFirstFragmentRectCallback callback) {
+  DCHECK(
+      base::FeatureList::IsEnabled(shared_highlighting::kSharedHighlightingV2));
+  IntRect rect_in_viewport;
+
+  TextFragmentAnchor* anchor = GetTextFragmentAnchor();
+  if (!anchor || anchor->TextFragmentFinders().size() <= 0) {
+    std::move(callback).Run(gfx::Rect());
+    return;
+  }
+
+  for (auto& finder : anchor->TextFragmentFinders()) {
+    if (finder->FirstMatch() == nullptr) {
+      continue;
+    }
+
+    PhysicalRect bounding_box(
+        ComputeTextRect(finder->FirstMatch()->ToEphemeralRange()));
+    rect_in_viewport =
+        GetTextFragmentSelectorGenerator()->GetFrame()->View()->FrameToViewport(
+            EnclosingIntRect(bounding_box));
+    break;
+  }
+
+  std::move(callback).Run(gfx::Rect(rect_in_viewport));
+}
+
 void TextFragmentHandler::Trace(Visitor* visitor) const {
   visitor->Trace(text_fragment_selector_generator_);
   visitor->Trace(selector_producer_);
 }
 
+TextFragmentAnchor* TextFragmentHandler::GetTextFragmentAnchor() {
+  FragmentAnchor* fragmentAnchor = GetTextFragmentSelectorGenerator()
+                                       ->GetFrame()
+                                       ->View()
+                                       ->GetFragmentAnchor();
+  if (!fragmentAnchor || !fragmentAnchor->IsTextFragmentAnchor()) {
+    return nullptr;
+  }
+  return static_cast<TextFragmentAnchor*>(fragmentAnchor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.h
index af6fbabd..82ec06fe 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_HANDLER_H_
 
 #include "third_party/blink/public/mojom/link_to_text/link_to_text.mojom-blink.h"
+#include "third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h"
 #include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
 
@@ -44,6 +45,11 @@
   void ExtractTextFragmentsMatches(
       ExtractTextFragmentsMatchesCallback callback) override;
 
+  // Request the bounding rectangle, relative to the viewport, of the first
+  // found match. It will accept an empty rectangle if no matches are found.
+  void ExtractFirstFragmentRect(
+      ExtractFirstFragmentRectCallback callback) override;
+
   void Trace(Visitor*) const;
 
   TextFragmentSelectorGenerator* GetTextFragmentSelectorGenerator();
@@ -60,6 +66,8 @@
       selector_producer_{this, nullptr};
 
   DISALLOW_COPY_AND_ASSIGN(TextFragmentHandler);
+
+  TextFragmentAnchor* GetTextFragmentAnchor();
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_handler_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_handler_test.cc
index 12e0f61..c46686b4 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_handler_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_handler_test.cc
@@ -10,7 +10,14 @@
 #include "components/shared_highlighting/core/common/shared_highlighting_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
+#include "third_party/blink/renderer/bindings/core/v8/string_or_array_buffer_or_array_buffer_view.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_font_face_descriptors.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_mouse_event_init.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_union_arraybuffer_arraybufferview_string.h"
+#include "third_party/blink/renderer/core/css/css_font_face.h"
+#include "third_party/blink/renderer/core/css/font_face_set_document.h"
 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
+#include "third_party/blink/renderer/core/editing/visible_units.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
@@ -55,6 +62,49 @@
     EXPECT_TRUE(callback_called);
     return target_texts;
   }
+
+  gfx::Rect ExtractFirstTextFragmentsRect() {
+    bool callback_called = false;
+    gfx::Rect text_fragment_rect;
+    auto lambda = [](bool& callback_called, gfx::Rect& text_fragment_rect,
+                     const gfx::Rect& fetched_text_fragment_rect) {
+      text_fragment_rect = fetched_text_fragment_rect;
+      callback_called = true;
+    };
+    auto callback = WTF::Bind(lambda, std::ref(callback_called),
+                              std::ref(text_fragment_rect));
+
+    GetDocument()
+        .GetFrame()
+        ->GetTextFragmentHandler()
+        ->ExtractFirstFragmentRect(std::move(callback));
+
+    EXPECT_TRUE(callback_called);
+    return text_fragment_rect;
+  }
+
+  void LoadAhem() {
+    scoped_refptr<SharedBuffer> shared_buffer =
+        test::ReadFromFile(test::CoreTestDataPath("Ahem.ttf"));
+#if defined(USE_BLINK_V8_BINDING_NEW_IDL_UNION)
+    auto* buffer =
+        MakeGarbageCollected<V8UnionArrayBufferOrArrayBufferViewOrString>(
+            DOMArrayBuffer::Create(shared_buffer));
+#else   // defined(USE_BLINK_V8_BINDING_NEW_IDL_UNION)
+    StringOrArrayBufferOrArrayBufferView buffer =
+        StringOrArrayBufferOrArrayBufferView::FromArrayBuffer(
+            DOMArrayBuffer::Create(shared_buffer));
+#endif  // defined(USE_BLINK_V8_BINDING_NEW_IDL_UNION)
+    FontFace* ahem =
+        FontFace::Create(GetDocument().GetExecutionContext(), "Ahem", buffer,
+                         FontFaceDescriptors::Create());
+
+    ScriptState* script_state =
+        ToScriptStateForMainWorld(GetDocument().GetFrame());
+    DummyExceptionStateForTesting exception_state;
+    FontFaceSetDocument::From(GetDocument())
+        ->addForBinding(script_state, ahem, exception_state);
+  }
 };
 
 TEST_F(TextFragmentHandlerTest, RemoveTextFragments) {
@@ -258,6 +308,244 @@
   EXPECT_EQ("nothing at", target_texts[3]);
 }
 
+TEST_F(TextFragmentHandlerTest, ExtractFirstTextFragmentRect) {
+  base::test::ScopedFeatureList feature_list_;
+  feature_list_.InitAndEnableFeature(
+      shared_highlighting::kSharedHighlightingV2);
+  SimRequest request(
+      "https://example.com/"
+      "test.html#:~:text=This,page",
+      "text/html");
+  LoadURL(
+      "https://example.com/"
+      "test.html#:~:text=This,page");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <meta name="viewport" content="width=device-width">
+    <style>p { font: 10px/1 Ahem; }</style>
+    <p id="first">This is a test page</p>
+    <p id="second">with some more text</p>
+  )HTML");
+  RunAsyncMatchingTasks();
+  LoadAhem();
+
+  Compositor().BeginFrame();
+
+  EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
+
+  Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
+  const auto& start = Position(first_paragraph, 0);
+  const auto& end = Position(first_paragraph, 19);
+  ASSERT_EQ("This is a test page", PlainText(EphemeralRange(start, end)));
+  IntRect rect(ComputeTextRect(EphemeralRange(start, end)));
+  gfx::Rect expected_rect =
+      gfx::Rect(GetDocument().GetFrame()->View()->FrameToViewport(rect));
+  // ExtractFirstTextFragmentsRect should return the first matched viewport
+  // relative location.
+  ASSERT_EQ(expected_rect.ToString(), "8,10 190x10");
+
+  gfx::Rect text_fragment_rect = ExtractFirstTextFragmentsRect();
+
+  EXPECT_EQ(expected_rect.ToString(), text_fragment_rect.ToString());
+}
+
+TEST_F(TextFragmentHandlerTest, ExtractFirstTextFragmentRectScroll) {
+  base::test::ScopedFeatureList feature_list_;
+  feature_list_.InitAndEnableFeature(
+      shared_highlighting::kSharedHighlightingV2);
+  // Android settings to correctly extract the rect when the page is loaded
+  // zoomed in
+  WebView().GetPage()->GetSettings().SetViewportEnabled(true);
+  WebView().GetPage()->GetSettings().SetViewportMetaEnabled(true);
+  WebView().GetPage()->GetSettings().SetShrinksViewportContentToFit(true);
+  WebView().GetPage()->GetSettings().SetMainFrameResizesAreOrientationChanges(
+      true);
+  SimRequest request("https://example.com/test.html#:~:text=test,page",
+                     "text/html");
+  LoadURL("https://example.com/test.html#:~:text=test,page");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <meta name="viewport" content="initial-scale=4">
+    <style>
+      body {
+        height: 2200px;
+      }
+      p {
+        position: absolute;
+        top: 2000px;
+        font: 10px/1 Ahem;
+      }
+    </style>
+    <p id="first">This is a test page</p>
+  )HTML");
+  RunAsyncMatchingTasks();
+  LoadAhem();
+
+  Compositor().BeginFrame();
+
+  EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
+
+  Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
+  const auto& start = Position(first_paragraph, 10);
+  const auto& end = Position(first_paragraph, 19);
+  ASSERT_EQ("test page", PlainText(EphemeralRange(start, end)));
+  IntRect rect(ComputeTextRect(EphemeralRange(start, end)));
+  gfx::Rect expected_rect =
+      gfx::Rect(GetDocument().GetFrame()->View()->FrameToViewport(rect));
+  // ExtractFirstTextFragmentsRect should return the first matched scaled
+  // viewport relative location since the page is loaded zoomed in 4X
+  ASSERT_EQ(expected_rect.ToString(), "432,296 360x44");
+
+  gfx::Rect text_fragment_rect = ExtractFirstTextFragmentsRect();
+
+  EXPECT_EQ(expected_rect.ToString(), text_fragment_rect.ToString());
+}
+
+TEST_F(TextFragmentHandlerTest, ExtractFirstTextFragmentRectMultipleHighlight) {
+  base::test::ScopedFeatureList feature_list_;
+  feature_list_.InitAndEnableFeature(
+      shared_highlighting::kSharedHighlightingV2);
+  SimRequest request(
+      "https://example.com/"
+      "test.html#:~:text=test%20page&text=more%20text",
+      "text/html");
+  LoadURL(
+      "https://example.com/"
+      "test.html#:~:text=test%20page&text=more%20text");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <meta name="viewport" content="width=device-width">
+    <style>
+      p {
+        font: 10px/1 Ahem;
+      }
+      body {
+        height: 1200px;
+      }
+      #second {
+        position: absolute;
+        top: 1000px;
+      }
+    </style>
+    <p id="first">This is a test page</p>
+    <p id="second">With some more text</p>
+  )HTML");
+  RunAsyncMatchingTasks();
+  LoadAhem();
+
+  Compositor().BeginFrame();
+
+  EXPECT_EQ(2u, GetDocument().Markers().Markers().size());
+
+  Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
+  const auto& start = Position(first_paragraph, 10);
+  const auto& end = Position(first_paragraph, 19);
+  ASSERT_EQ("test page", PlainText(EphemeralRange(start, end)));
+  IntRect rect(ComputeTextRect(EphemeralRange(start, end)));
+  gfx::Rect expected_rect =
+      gfx::Rect(GetDocument().GetFrame()->View()->FrameToViewport(rect));
+  // ExtractFirstTextFragmentsRect should return the first matched viewport
+  // relative location.
+  ASSERT_EQ(expected_rect.ToString(), "108,10 90x10");
+
+  gfx::Rect text_fragment_rect = ExtractFirstTextFragmentsRect();
+
+  EXPECT_EQ(expected_rect.ToString(), text_fragment_rect.ToString());
+}
+
+TEST_F(TextFragmentHandlerTest,
+       ExtractFirstTextFragmentRectMultipleHighlightWithNoFoundText) {
+  base::test::ScopedFeatureList feature_list_;
+  feature_list_.InitAndEnableFeature(
+      shared_highlighting::kSharedHighlightingV2);
+  SimRequest request(
+      "https://example.com/"
+      "test.html#:~:text=fake&text=test%20page",
+      "text/html");
+  LoadURL(
+      "https://example.com/"
+      "test.html#:~:text=fake&text=test%20page");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <meta name="viewport" content="width=device-width">
+    <style>
+      p {
+        font: 10px/1 Ahem;
+      }
+      body {
+        height: 1200px;
+      }
+      #second {
+        position: absolute;
+        top: 1000px;
+      }
+    </style>
+    <p id="first">This is a test page</p>
+  )HTML");
+  RunAsyncMatchingTasks();
+  LoadAhem();
+
+  Compositor().BeginFrame();
+
+  EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
+
+  Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
+  const auto& start = Position(first_paragraph, 10);
+  const auto& end = Position(first_paragraph, 19);
+  ASSERT_EQ("test page", PlainText(EphemeralRange(start, end)));
+  IntRect rect(ComputeTextRect(EphemeralRange(start, end)));
+  gfx::Rect expected_rect =
+      gfx::Rect(GetDocument().GetFrame()->View()->FrameToViewport(rect));
+  // ExtractFirstTextFragmentsRect should return the first matched viewport
+  // relative location.
+  ASSERT_EQ(expected_rect.ToString(), "108,10 90x10");
+
+  gfx::Rect text_fragment_rect = ExtractFirstTextFragmentsRect();
+
+  EXPECT_EQ(expected_rect.ToString(), text_fragment_rect.ToString());
+}
+
+TEST_F(TextFragmentHandlerTest, RejectExtractFirstTextFragmentRect) {
+  base::test::ScopedFeatureList feature_list_;
+  feature_list_.InitAndEnableFeature(
+      shared_highlighting::kSharedHighlightingV2);
+  SimRequest request(
+      "https://example.com/"
+      "test.html#:~:text=not%20on%20the%20page",
+      "text/html");
+  LoadURL(
+      "https://example.com/"
+      "test.html#:~:text=not%20on%20the%20page");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <meta name="viewport" content="width=device-width">
+    <style>
+      p {
+        font: 10px/1 Ahem;
+      }
+      body {
+        height: 1200px;
+      }
+      #second {
+        position: absolute;
+        top: 1000px;
+      }
+    </style>
+    <p id="first">This is a test page</p>
+    <p id="second">With some more text</p>
+  )HTML");
+  RunAsyncMatchingTasks();
+  LoadAhem();
+
+  Compositor().BeginFrame();
+
+  EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
+
+  gfx::Rect text_fragment_rect = ExtractFirstTextFragmentsRect();
+
+  EXPECT_TRUE(text_fragment_rect.IsEmpty());
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index bbd75a4..52b16de 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -2431,7 +2431,7 @@
 
 absl::optional<Color> ComputedStyle::AccentColorResolved() const {
   const StyleAutoColor& auto_color = AccentColor();
-  if (auto_color.IsAutoColor())
+  if (auto_color.IsAutoColor() || ShouldForceColor(auto_color))
     return absl::nullopt;
   return auto_color.Resolve(GetCurrentColor(), UsedColorScheme());
 }
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index d69671d..d868f42 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -208,6 +208,7 @@
   // Accesses GetColor().
   friend class ComputedStyleUtils;
   // These get visited and unvisited colors separately.
+  friend class css_longhand::AccentColor;
   friend class css_longhand::BackgroundColor;
   friend class css_longhand::BorderBottomColor;
   friend class css_longhand::BorderLeftColor;
diff --git a/third_party/blink/renderer/core/style/filter_operation.h b/third_party/blink/renderer/core/style/filter_operation.h
index 0865cb6..afda061 100644
--- a/third_party/blink/renderer/core/style/filter_operation.h
+++ b/third_party/blink/renderer/core/style/filter_operation.h
@@ -349,8 +349,7 @@
 
 class CORE_EXPORT ConvolveMatrixFilterOperation : public FilterOperation {
  public:
-  ConvolveMatrixFilterOperation(Filter* filter,
-                                const IntSize& kernel_size,
+  ConvolveMatrixFilterOperation(const IntSize& kernel_size,
                                 float divisor,
                                 float bias,
                                 const IntPoint& target_offset,
diff --git a/third_party/blink/renderer/core/svg/svg_text_element.cc b/third_party/blink/renderer/core/svg/svg_text_element.cc
index 0e9a2d1..e311c65 100644
--- a/third_party/blink/renderer/core/svg/svg_text_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_text_element.cc
@@ -29,7 +29,7 @@
 
 LayoutObject* SVGTextElement::CreateLayoutObject(const ComputedStyle& style,
                                                  LegacyLayout legacy) {
-  return LayoutObjectFactory::CreateSVGText(*this, legacy);
+  return LayoutObjectFactory::CreateSVGText(*this, style, legacy);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/testing/page_test_base.cc b/third_party/blink/renderer/core/testing/page_test_base.cc
index 6565285..97f9ac0 100644
--- a/third_party/blink/renderer/core/testing/page_test_base.cc
+++ b/third_party/blink/renderer/core/testing/page_test_base.cc
@@ -22,6 +22,7 @@
 #include "third_party/blink/renderer/core/html/html_collection.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
+#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
 #include "third_party/blink/renderer/core/testing/mock_policy_container_host.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
@@ -51,6 +52,8 @@
     ostream << *node;
   else
     ostream << "(anonymous)";
+  if (auto* layout_text_fragment = DynamicTo<LayoutTextFragment>(layout_object))
+    ostream << " (" << layout_text_fragment->GetText() << ")";
   ostream << std::endl;
   for (auto* child = layout_object.SlowFirstChild(); child;
        child = child->NextSibling()) {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.cc b/third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.cc
index 66089e80..af587c98 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.cc
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.h"
+#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
 #include "third_party/blink/renderer/core/dom/qualified_name.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 
@@ -21,6 +23,18 @@
                       AXObject* object,
                       ui::AXNodeData* node_data,
                       const AtomicString& value) {
+  // Don't set kTouchPassthrough unless the feature is enabled in this
+  // context.
+  if (attribute == ax::mojom::blink::BoolAttribute::kTouchPassthrough) {
+    auto* context = object->AXObjectCache().GetDocument().GetExecutionContext();
+    if (RuntimeEnabledFeatures::AccessibilityAriaTouchPassthroughEnabled(
+            context)) {
+      UseCounter::Count(context, WebFeature::kAccessibilityTouchPassthroughSet);
+    } else {
+      return;
+    }
+  }
+
   // ARIA booleans are true if not "false" and not specifically undefined.
   bool is_true = !AccessibleNode::IsUndefinedAttrValue(value) &&
                  !EqualIgnoringASCIICase(value, "false");
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_dictionary.idl b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_dictionary.idl
index 17d2df35..6421cc3 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_dictionary.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_dictionary.idl
@@ -6,4 +6,5 @@
 dictionary CanvasFilterDictionary {
   object blur;
   object colorMatrix;
+  object convolveMatrix;
 };
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_operation_resolver.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_operation_resolver.cc
index 6968aaa..f51498f 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_operation_resolver.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_filter_operation_resolver.cc
@@ -8,24 +8,41 @@
 namespace blink {
 
 namespace {
-double GetDoubleValueOrZero(v8::Local<v8::Object> v8_object,
-                            WTF::String key,
-                            ScriptState* script_state) {
+double GetDoubleOr(double default_value,
+                   v8::Local<v8::Object> v8_object,
+                   WTF::String key,
+                   ScriptState* script_state) {
   v8::Local<v8::Value> v8_value;
   if (!v8_object
            ->Get(script_state->GetContext(),
                  V8String(script_state->GetIsolate(), key))
            .ToLocal(&v8_value) ||
       !v8_value->IsNumber()) {
-    return 0;
+    return default_value;
   }
 
   double result = v8_value.As<v8::Number>()->Value();
   if (!std::isfinite(result))
-    return 0;
+    return default_value;
   return result;
 }
 
+bool GetBooleanOr(bool default_value,
+                  v8::Local<v8::Object> v8_object,
+                  WTF::String key,
+                  ScriptState* script_state) {
+  v8::Local<v8::Value> v8_value;
+  if (!v8_object
+           ->Get(script_state->GetContext(),
+                 V8String(script_state->GetIsolate(), key))
+           .ToLocal(&v8_value) ||
+      !v8_value->IsBoolean()) {
+    return default_value;
+  }
+
+  return v8_value.As<v8::Boolean>()->Value();
+}
+
 String GetStringValue(v8::Local<v8::Object> v8_object,
                       WTF::String key,
                       ScriptState* script_state) {
@@ -37,14 +54,13 @@
     return String();
   return ToCoreStringWithUndefinedOrNullCheck(v8_type);
 }
-}  // namespace
 
-BlurFilterOperation* ResolveBlur(v8::Local<v8::Object> v8_filter_object,
+BlurFilterOperation* ResolveBlur(v8::Local<v8::Object> v8_filter_obj,
                                  ScriptState* script_state,
                                  ExceptionState& exception_state) {
   Length std_deviation = Length::Fixed(0);
   v8::Local<v8::Value> v8_std_deviation;
-  if (v8_filter_object
+  if (v8_filter_obj
           ->Get(script_state->GetContext(),
                 V8String(script_state->GetIsolate(), "stdDeviation"))
           .ToLocal(&v8_std_deviation)) {
@@ -69,12 +85,12 @@
 }
 
 ColorMatrixFilterOperation* ResolveColorMatrix(
-    v8::Local<v8::Object> v8_filter_object,
+    v8::Local<v8::Object> v8_filter_obj,
     ScriptState* script_state,
     ExceptionState& exception_state) {
   v8::Local<v8::Value> v8_value;
   v8::Local<v8::Array> v8_array;
-  if (v8_filter_object
+  if (v8_filter_obj
           ->Get(script_state->GetContext(),
                 V8String(script_state->GetIsolate(), "values"))
           .ToLocal(&v8_value)) {
@@ -121,6 +137,122 @@
       values, FilterOperation::COLOR_MATRIX);
 }
 
+struct KernelMatrix {
+  Vector<float> values;
+  int width = 0;
+  int height = 0;
+};
+
+// For resolving feConvolveMatrix type filters
+KernelMatrix* GetKernelMatrix(v8::Local<v8::Object> v8_filter_obj,
+                              ScriptState* script_state,
+                              ExceptionState& exception_state) {
+  v8::Local<v8::Value> v8_value;
+  v8::Local<v8::Array> v8_kernel_matrix;
+  if (v8_filter_obj
+          ->Get(script_state->GetContext(),
+                V8String(script_state->GetIsolate(), "kernelMatrix"))
+          .ToLocal(&v8_value)) {
+    if (!v8_value->IsArray()) {
+      exception_state.ThrowTypeError(
+          "Failed to construct convolve matrix filter. 'kernelMatrix' must be "
+          "an array of arrays representing an n by m matrix.");
+      return nullptr;
+    }
+    v8_kernel_matrix = v8_value.As<v8::Array>();
+  }
+
+  KernelMatrix* kernel_matrix = new KernelMatrix();
+  kernel_matrix->height = v8_kernel_matrix->Length();
+  v8::Local<v8::Array> v8_kernel_matrix_row;
+  if (!v8_kernel_matrix->Get(script_state->GetContext(), 0)
+           .ToLocal(&v8_value) ||
+      !v8_value->IsArray()) {
+    exception_state.ThrowTypeError(
+        "Failed to construct convolve matrix filter. 'kernelMatrix' must be an "
+        "array of arrays representing an n by m matrix.");
+    return nullptr;
+  }
+  v8_kernel_matrix_row = v8_value.As<v8::Array>();
+  kernel_matrix->width = v8_kernel_matrix_row->Length();
+  kernel_matrix->values.ReserveInitialCapacity(kernel_matrix->width *
+                                               kernel_matrix->height);
+
+  for (int y = 0; y < kernel_matrix->height; ++y) {
+    if (!v8_kernel_matrix->Get(script_state->GetContext(), y)
+             .ToLocal(&v8_value) ||
+        !v8_value->IsArray()) {
+      exception_state.ThrowTypeError(
+          "Failed to construct convolve matrix filter. 'kernelMatrix' must be "
+          "an array of arrays representing an n by m matrix.");
+      return nullptr;
+    }
+    v8_kernel_matrix_row = v8_value.As<v8::Array>();
+    if (int(v8_kernel_matrix_row->Length()) != kernel_matrix->width) {
+      exception_state.ThrowTypeError(
+          "Failed to construct convolve matrix filter. All rows of the "
+          "'kernelMatrix' must be the same length.");
+      return nullptr;
+    }
+
+    for (int x = 0; x < kernel_matrix->width; ++x) {
+      if (!v8_kernel_matrix_row->Get(script_state->GetContext(), x)
+               .ToLocal(&v8_value) ||
+          !v8_value->IsNumber()) {
+        exception_state.ThrowTypeError(
+            "Failed to construct convolve matrix filter. All 'kernelMatrix' "
+            "values must be numbers.");
+        return nullptr;
+      }
+      const float value = v8_value.As<v8::Number>()->Value();
+      if (!std::isfinite(value)) {
+        exception_state.ThrowTypeError(
+            "Failed to construct convolve matrix filter, 'kernel_matrix' must "
+            "have finite values.");
+        return kernel_matrix;
+      }
+      kernel_matrix->values.push_back(value);
+    }
+  }
+
+  return kernel_matrix;
+}
+
+ConvolveMatrixFilterOperation* ResolveConvolveMatrix(
+    v8::Local<v8::Object> v8_filter_obj,
+    ScriptState* script_state,
+    ExceptionState& exception_state) {
+  KernelMatrix* kernel_matrix =
+      GetKernelMatrix(v8_filter_obj, script_state, exception_state);
+
+  if (!kernel_matrix)
+    return nullptr;
+
+  IntSize kernel_size(kernel_matrix->width, kernel_matrix->height);
+  double divisor = GetDoubleOr(1, v8_filter_obj, "divisor", script_state);
+  double bias = GetDoubleOr(0, v8_filter_obj, "bias", script_state);
+  IntPoint target_offset =
+      IntPoint(int(GetDoubleOr(kernel_matrix->width / 2, v8_filter_obj,
+                               "targetX", script_state)),
+               int(GetDoubleOr(kernel_matrix->height / 2, v8_filter_obj,
+                               "targetY", script_state)));
+  FEConvolveMatrix::EdgeModeType edge_mode =
+      FEConvolveMatrix::EDGEMODE_DUPLICATE;
+  String edge_mode_string =
+      GetStringValue(v8_filter_obj, "edgeMode", script_state);
+  if (edge_mode_string == "wrap")
+    edge_mode = FEConvolveMatrix::EDGEMODE_WRAP;
+  if (edge_mode_string == "none")
+    edge_mode = FEConvolveMatrix::EDGEMODE_NONE;
+  bool preserve_alpha =
+      GetBooleanOr(false, v8_filter_obj, "preserve_alpha", script_state);
+
+  return MakeGarbageCollected<ConvolveMatrixFilterOperation>(
+      kernel_size, divisor, bias, target_offset, edge_mode, preserve_alpha,
+      kernel_matrix->values);
+}
+}  // namespace
+
 FilterOperations CanvasFilterOperationResolver::CreateFilterOperations(
     ScriptState* script_state,
     HeapVector<Member<CanvasFilterDictionary>> filters,
@@ -142,12 +274,12 @@
             &v8_object)) {
       String type = GetStringValue(v8_object, "type", script_state);
       if (type == "hueRotate") {
-        double amount = GetDoubleValueOrZero(v8_object, "values", script_state);
+        double amount = GetDoubleOr(0, v8_object, "values", script_state);
         operations.Operations().push_back(
             MakeGarbageCollected<BasicColorMatrixFilterOperation>(
                 amount, FilterOperation::HUE_ROTATE));
       } else if (type == "saturate") {
-        double amount = GetDoubleValueOrZero(v8_object, "values", script_state);
+        double amount = GetDoubleOr(0, v8_object, "values", script_state);
         operations.Operations().push_back(
             MakeGarbageCollected<BasicColorMatrixFilterOperation>(
                 amount, FilterOperation::SATURATE));
@@ -160,6 +292,14 @@
         operations.Operations().push_back(color_matrix_operation);
       }
     }
+    if (filter->hasConvolveMatrix() &&
+        filter->convolveMatrix().V8Value()->ToObject(context).ToLocal(
+            &v8_object)) {
+      if (auto* convolve_operation =
+              ResolveConvolveMatrix(v8_object, script_state, exception_state)) {
+        operations.Operations().push_back(convolve_operation);
+      }
+    }
   }
 
   return operations;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index cf74ce4..2951941 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -116,6 +116,11 @@
       status: "stable",
     },
     {
+      name: "AccessibilityAriaTouchPassthrough",
+      status: "experimental",
+      origin_trial_feature_name: "AccessibilityAriaTouchPassthrough",
+    },
+    {
       name: "AccessibilityAriaVirtualContent",
       status: "experimental",
     },
diff --git a/third_party/blink/web_tests/android/ChromeWPTOverrideExpectations b/third_party/blink/web_tests/android/ChromeWPTOverrideExpectations
index 782577dd4..12fbcf7 100644
--- a/third_party/blink/web_tests/android/ChromeWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/ChromeWPTOverrideExpectations
@@ -130,14 +130,8 @@
 crbug.com/1198573 external/wpt/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html [ Failure ]
 crbug.com/1198573 external/wpt/html/webappapis/the-windoworworkerglobalscope-mixin/Worker_Self_Origin.html [ Failure ]
 
-# Flaky failure on a DCHECK in DevToolsAgent::ReportChildWorkersCallback
-crbug.com/1199769 external/wpt/background-fetch/abort.https.window.html [ Crash Pass ]
-crbug.com/1199769 external/wpt/background-fetch/fetch-uploads.https.window.html [ Crash Pass ]
-crbug.com/1199769 external/wpt/background-fetch/fetch.https.window.html [ Crash Pass ]
-crbug.com/1199769 external/wpt/background-fetch/match.https.window.html [ Crash Pass ]
-crbug.com/1199769 external/wpt/background-fetch/mixed-content-and-allowed-schemes.https.window.html [ Crash ]
-crbug.com/1199769 external/wpt/background-fetch/port-blocking.https.window.html [ Crash Pass ]
-crbug.com/1199769 external/wpt/background-fetch/update-ui.https.window.html [ Crash Pass ]
+# Failing background-fetch tests.
+crbug.com/882282 external/wpt/background-fetch/fetch.https.window.html [ Failure Pass Timeout ]
 
 # crbug.com/178097: navigator.registerProtocolHandler is not available on Android.
 crbug.com/1198573 external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/historical.https.window.html [ Failure ]
diff --git a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
index 3b57b6c..1a3f28fa 100644
--- a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
@@ -68,6 +68,7 @@
 crbug.com/1198063 external/wpt/scroll-animations/scroll-timeline-snapshotting.html [ Timeout ]
 
 # SharedWorker is not supported on Android (https://crbug.com/154571).
+crbug.com/1050754 external/wpt/background-fetch/idlharness.https.any.sharedworker.html [ Failure ]
 crbug.com/1190392 external/wpt/trusted-types/WorkerGlobalScope-eval.html [ Failure ]
 crbug.com/1190392 external/wpt/trusted-types/WorkerGlobalScope-importScripts.html [ Failure ]
 crbug.com/1190392 external/wpt/trusted-types/worker-constructor.https.html [ Failure ]
@@ -313,15 +314,8 @@
 crbug.com/1198089 external/wpt/webcodecs/videoFrame-texImage.any.html [ Failure Pass ]
 crbug.com/1198089 external/wpt/webcodecs/videoFrame-texImage.any.worker.html [ Failure Pass ]
 
-# Flaky failure on a DCHECK in DevToolsAgent::ReportChildWorkersCallback. For some reason, this is classified as a Failure here and
-# a Crash in Chrome.
-crbug.com/1199769 external/wpt/background-fetch/abort.https.window.html [ Failure Pass ]
-crbug.com/1199769 external/wpt/background-fetch/fetch-uploads.https.window.html [ Failure Pass ]
-crbug.com/1199769 external/wpt/background-fetch/fetch.https.window.html [ Failure Pass Timeout ]
-crbug.com/1199769 external/wpt/background-fetch/match.https.window.html [ Failure Pass ]
-crbug.com/1199769 external/wpt/background-fetch/mixed-content-and-allowed-schemes.https.window.html [ Failure Pass ]
-crbug.com/1199769 external/wpt/background-fetch/port-blocking.https.window.html [ Failure Pass ]
-crbug.com/1199769 external/wpt/background-fetch/update-ui.https.window.html [ Failure Pass ]
+# Failing background-fetch tests.
+crbug.com/882282 external/wpt/background-fetch/fetch.https.window.html [ Failure Pass Timeout ]
 
 # Test seems to be flaky on both Chrome and WebLayer.
 crbug.com/1177918 external/wpt/input-events/input-events-get-target-ranges.html [ Failure Pass ]
@@ -344,9 +338,6 @@
 crbug.com/1050754 external/wpt/animation-worklet/inactive-timeline.https.html [ Timeout ]
 crbug.com/1050754 external/wpt/animation-worklet/scroll-timeline-writing-modes.https.html [ Failure ]
 crbug.com/1050754 external/wpt/animation-worklet/worklet-animation-local-time-null-1.https.html [ Failure Pass ]
-crbug.com/1050754 external/wpt/background-fetch/get-ids.https.window.html [ Pass ]
-crbug.com/1050754 external/wpt/background-fetch/get.https.window.html [ Pass ]
-crbug.com/1050754 external/wpt/background-fetch/idlharness.https.any.sharedworker.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/adapter/adapter-absent-getAvailability.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/adapter/adapter-added-getAvailability.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/adapter/adapter-powered-off-getAvailability.https.window.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-011-ref.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-011-ref.html
index e1a968b..8c9d248 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-011-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-011-ref.html
@@ -1,18 +1,20 @@
 <!DOCTYPE html>
 <link rel="author" title="Sergio Villar Senin" href="mailto:svillar@igalia.com">
+<link href="support/flexbox.css" rel="stylesheet">
 <style>
-.item {
+.flex-item {
+    flex: 1 0 auto;
+    height: 100%;
     border: 1px solid blue;
 }
 </style>
 
-<p>Test PASS if there are two boxes with blue borders vertically stretched to fit their contents.</p>
-<div style="width: min-content">
-    <div>
-        <div class="item">
+<div class="flexbox">
+    <div class="flexbox column">
+        <div class="flex-item">
             <div>AAA</div>
         </div>
-        <div class="item">
+        <div class="flex-item">
             <div>BBB</div>
         </div>
     </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-011.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-011.html
index 78bbd5d8..9edb5dc9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-011.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-011.html
@@ -12,7 +12,6 @@
 }
 </style>
 
-<p>Test PASS if there are two boxes with blue borders vertically stretched to fit their contents.</p>
 <div class="flexbox">
     <div class="flexbox column">
         <div class="flex-item">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-012.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-012.html
new file mode 100644
index 0000000..b1addde
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-basis-012.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1199632">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="display: flex; flex-direction: column; width: 100px;">
+  <div style="display: flex; flex-direction: column; height: 100px; background: red;">
+    <div style="flex-basis: 100%; background: green;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/accent-color-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/accent-color-expected.txt
new file mode 100644
index 0000000..3c4e773
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/accent-color-expected.txt
@@ -0,0 +1,21 @@
+This is a testharness.js-based test.
+PASS Can set 'accent-color' to CSS-wide keywords
+PASS Can set 'accent-color' to var() references
+PASS Can set 'accent-color' to the 'currentcolor' keyword
+FAIL Can set 'accent-color' to the 'auto' keyword assert_class_string: expected "[object CSSStyleValue]" but got "[object CSSKeywordValue]"
+PASS Setting 'accent-color' to a length throws TypeError
+PASS Setting 'accent-color' to a percent throws TypeError
+PASS Setting 'accent-color' to a time throws TypeError
+PASS Setting 'accent-color' to an angle throws TypeError
+PASS Setting 'accent-color' to a flexible length throws TypeError
+PASS Setting 'accent-color' to a number throws TypeError
+PASS Setting 'accent-color' to a position throws TypeError
+PASS Setting 'accent-color' to a URL throws TypeError
+PASS Setting 'accent-color' to a transform throws TypeError
+PASS 'accent-color' does not supported 'red'
+PASS 'accent-color' does not supported '#bbff00'
+PASS 'accent-color' does not supported 'rgb(255, 255, 128)'
+PASS 'accent-color' does not supported 'hsl(50, 33%, 25%)'
+PASS 'accent-color' does not supported 'transparent'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/accent-color.html b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/accent-color.html
new file mode 100644
index 0000000..8cd0b98
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/accent-color.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>'accent-color' property</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-get">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymap-set">
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#property-stle-value-normalization">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/testhelper.js"></script>
+<script src="resources/testsuite.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+runPropertyTests('accent-color', [
+  {
+    syntax: 'currentcolor',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  },
+  {
+    syntax: 'auto',
+    // computes to a <color>, which is not supported in level 1
+    computed: (_, result) => assert_class_string(result, 'CSSStyleValue')
+  }
+]);
+
+// <color>s are not supported in level 1
+runUnsupportedPropertyTests('accent-color', [
+  'red', '#bbff00', 'rgb(255, 255, 128)', 'hsl(50, 33%, 25%)',
+  'transparent'
+]);
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-computed-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-computed-expected.txt
index 4908625..54594b62 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-computed-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-computed-expected.txt
@@ -1,7 +1,10 @@
 This is a testharness.js-based test.
-FAIL Property accent-color value 'initial' assert_equals: expected "auto" but got ""
-FAIL Property accent-color value 'inherit' assert_equals: expected "rgb(255, 0, 0)" but got ""
-FAIL Property accent-color value 'red' assert_equals: expected "rgb(255, 0, 0)" but got ""
-FAIL Property accent-color value 'blue' assert_equals: expected "rgb(0, 0, 255)" but got ""
+PASS Property accent-color value 'initial'
+PASS Property accent-color value 'inherit'
+PASS Property accent-color value 'red'
+PASS Property accent-color value 'blue'
+PASS Property accent-color value 'auto'
+FAIL Property accent-color value 'currentcolor' assert_equals: expected "currentcolor" but got "rgb(0, 0, 0)"
+PASS Property accent-color value '#fff'
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-computed.html b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-computed.html
index 896e4bb..10e08010 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-computed.html
@@ -9,9 +9,16 @@
 <div id="outer">
   <div id="target"></div>
 </div>
+<div id=noAccentColor></div>
 <script>
 test_computed_value('accent-color', 'initial', 'auto');
 test_computed_value('accent-color', 'inherit', 'rgb(255, 0, 0)');
 test_computed_value('accent-color', 'red', 'rgb(255, 0, 0)');
 test_computed_value('accent-color', 'blue', 'rgb(0, 0, 255)');
+test_computed_value('accent-color', 'auto', 'auto');
+test_computed_value('accent-color', 'currentcolor', 'currentcolor');
+test_computed_value('accent-color', '#fff', 'rgb(255, 255, 255)');
+
+// When accent-color isn't specified, it should return 'auto'
+assert_equals(getComputedStyle(noAccentColor).accentColor, 'auto');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-visited-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-visited-ref.html
new file mode 100644
index 0000000..3954a319
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-visited-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<a href="">
+  <input type=checkbox>
+</a>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-visited.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-visited.tentative.html
new file mode 100644
index 0000000..5aee0f1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-visited.tentative.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="match" href="accent-color-visited-ref.html">
+
+<!-- :visited shouldn't apply to accent-color. -->
+
+<style>
+:visited {
+  accent-color: red;
+}
+</style>
+<a href="">
+  <input type=checkbox>
+</a>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/animation/accent-color-interpolation.html b/third_party/blink/web_tests/external/wpt/css/css-ui/animation/accent-color-interpolation.html
new file mode 100644
index 0000000..20d87c4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/animation/accent-color-interpolation.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>accent-color interpolation</title>
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-ui-4/#widget-accent">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<style>
+.parent {
+  accent-color: blue;
+}
+.target {
+  display: inline-block;
+  font-size: 60pt;
+  accent-color: yellow;
+}
+.expected {
+  margin-right: 15px;
+}
+</style>
+
+<body contenteditable>
+  <template id="target-template">T</template>
+</body>
+
+<script>
+test_interpolation({
+  property: 'accent-color',
+  from: neutralKeyframe,
+  to: 'green',
+}, [
+  {at: -0.3, expect: 'rgb(255, 255, 0)'},
+  {at: 0, expect: 'rgb(255, 255, 0)'},
+  {at: 0.3, expect: 'rgb(179, 217, 0)'},
+  {at: 0.6, expect: 'rgb(102, 179, 0)'},
+  {at: 1, expect: 'rgb(0, 128, 0)'},
+  {at: 1.5, expect: 'rgb(0, 65, 0)'},
+]);
+
+test_no_interpolation({
+  property: 'accent-color',
+  from: 'initial',
+  to: 'green',
+});
+
+test_no_interpolation({
+  property: 'accent-color',
+  from: 'auto',
+  to: 'green',
+});
+
+test_interpolation({
+  property: 'accent-color',
+  from: 'currentColor',
+  to: 'green',
+}, [
+  {at: -0.3, expect: 'rgb(0, 0, 0)'},
+  {at: 0, expect: 'rgb(0, 0, 0)'},
+  {at: 0.3, expect: 'rgb(0, 38, 0)'},
+  {at: 0.6, expect: 'rgb(0, 77, 0)'},
+  {at: 1, expect: 'rgb(0, 128, 0)'},
+  {at: 1.5, expect: 'rgb(0, 192, 0)'},
+]);
+
+test_interpolation({
+  property: 'accent-color',
+  from: 'inherit',
+  to: 'green',
+}, [
+  {at: -0.3, expect: 'rgb(0, 0, 255)'},
+  {at: 0, expect: 'rgb(0, 0, 255)'},
+  {at: 0.3, expect: 'rgb(0, 38, 179)'},
+  {at: 0.6, expect: 'rgb(0, 77, 102)'},
+  {at: 1, expect: 'rgb(0, 128, 0)'},
+  {at: 1.5, expect: 'rgb(0, 192, 0)'},
+]);
+
+test_interpolation({
+  property: 'accent-color',
+  from: 'unset',
+  to: 'green',
+}, [
+  {at: -0.3, expect: 'rgb(0, 0, 255)'},
+  {at: 0, expect: 'rgb(0, 0, 255)'},
+  {at: 0.3, expect: 'rgb(0, 38, 179)'},
+  {at: 0.6, expect: 'rgb(0, 77, 102)'},
+  {at: 1, expect: 'rgb(0, 128, 0)'},
+  {at: 1.5, expect: 'rgb(0, 192, 0)'},
+]);
+
+test_interpolation({
+  property: 'accent-color',
+  from: 'black',
+  to: 'orange',
+}, [
+  {at: -0.3, expect: 'rgb(0, 0, 0)'},
+  {at: 0, expect: 'rgb(0, 0, 0)'},
+  {at: 0.3, expect: 'rgb(77, 50, 0)'},
+  {at: 0.6, expect: 'rgb(153, 99, 0)'},
+  {at: 1, expect: 'rgb(255, 165, 0)'},
+  {at: 1.5, expect: 'rgb(255, 248, 0)'},
+]);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/cssstyledeclaration-custom-properties-expected.txt b/third_party/blink/web_tests/external/wpt/css/cssom/cssstyledeclaration-custom-properties-expected.txt
index 7287e4f..fdc0f0c6 100644
--- a/third_party/blink/web_tests/external/wpt/css/cssom/cssstyledeclaration-custom-properties-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/cssstyledeclaration-custom-properties-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Custom properties are included in computed style assert_equals: Should show up in .length expected 342 but got 341
+FAIL Custom properties are included in computed style assert_equals: Should show up in .length expected 343 but got 342
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/getComputedStyle-detached-subtree-expected.txt b/third_party/blink/web_tests/external/wpt/css/cssom/getComputedStyle-detached-subtree-expected.txt
index 97d84ff..e1c2ab5 100644
--- a/third_party/blink/web_tests/external/wpt/css/cssom/getComputedStyle-detached-subtree-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/getComputedStyle-detached-subtree-expected.txt
@@ -1,9 +1,9 @@
 This is a testharness.js-based test.
 PASS getComputedStyle returns no style for detached element
-FAIL getComputedStyle returns no style for element in non-rendered iframe (display: none) assert_equals: expected 0 but got 341
-FAIL getComputedStyle returns no style for element in non-rendered iframe (display: none) from iframe's window assert_equals: expected 0 but got 341
-FAIL getComputedStyle returns no style for element outside the flat tree assert_equals: expected 0 but got 341
-FAIL getComputedStyle returns no style for descendant outside the flat tree assert_equals: expected 0 but got 341
+FAIL getComputedStyle returns no style for element in non-rendered iframe (display: none) assert_equals: expected 0 but got 342
+FAIL getComputedStyle returns no style for element in non-rendered iframe (display: none) from iframe's window assert_equals: expected 0 but got 342
+FAIL getComputedStyle returns no style for element outside the flat tree assert_equals: expected 0 but got 342
+FAIL getComputedStyle returns no style for descendant outside the flat tree assert_equals: expected 0 but got 342
 PASS getComputedStyle returns no style for shadow tree outside of flattened tree
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.html
new file mode 100644
index 0000000..dd7b57c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.filter.canvasFilterObject.convolveMatrix.exceptions</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>2d.filter.canvasFilterObject.convolveMatrix.exceptions</h1>
+<p class="desc">Test exceptions on CanvasFilter() convolveMatrix</p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("Test exceptions on CanvasFilter() convolveMatrix");
+_addTest(function(canvas, ctx) {
+
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: null}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {divisor: 2}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: null}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: 1}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0]]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, "a"], [0]]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], 0]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0, Infinity]]}}); });
+
+
+});
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/filters/canvas-filter-object-convolve-matrix-expected.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/filters/canvas-filter-object-convolve-matrix-expected.html
new file mode 100644
index 0000000..f8dba9bb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/filters/canvas-filter-object-convolve-matrix-expected.html
@@ -0,0 +1,38 @@
+<body>
+  <canvas id="canvas"></canvas>
+  <svg width="0" height="0">
+    <filter id="emboss">
+          <feConvolveMatrix
+              kernelMatrix="3 0 0
+                            0 0 0
+                            0 0 -3"/>
+    </filter>
+  </svg>
+</body>
+<script type="text/javascript">
+
+const canvas = document.getElementById("canvas");
+const ctx = canvas.getContext("2d");
+
+function draw() {
+  ctx.fillRect(20, 20, 100, 100);
+
+  ctx.beginPath();
+  ctx.arc(150, 70, 50, 0, 2*Math.PI);
+  ctx.fill();
+
+  ctx.beginPath();
+  ctx.moveTo(220, 20);
+  ctx.lineTo(170, 120);
+  ctx.lineTo(270, 120);
+  ctx.lineTo(220, 20);
+  ctx.fill();
+}
+
+ctx.fillStyle = "rgba(0,255,0,0.5)";
+draw();
+ctx.fillStyle = "rgba(255,0,255,0.5)";
+ctx.filter = "url('#emboss')";
+draw();
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/filters/canvas-filter-object-convolve-matrix.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/filters/canvas-filter-object-convolve-matrix.html
new file mode 100644
index 0000000..c565107
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/filters/canvas-filter-object-convolve-matrix.html
@@ -0,0 +1,38 @@
+<head>
+    <link rel="match" href="canvas-filter-object-convolve-matrix-expected.html">
+</head>
+<body>
+  <canvas id="canvas"></canvas>
+</body>
+<script>
+  const canvas = document.getElementById("canvas");
+  const ctx = canvas.getContext("2d");
+
+  const convolveFilter = new CanvasFilter({convolveMatrix: {
+    kernelMatrix: [
+      [3, 0, 0],
+      [0, 0, 0],
+      [0, 0, -3],
+    ],
+  }});
+
+  function draw() {
+    ctx.fillRect(20, 20, 100, 100);
+
+    ctx.beginPath();
+    ctx.arc(150, 70, 50, 0, 2*Math.PI);
+    ctx.fill();
+
+    ctx.beginPath();
+    ctx.moveTo(220, 20);
+    ctx.lineTo(170, 120);
+    ctx.lineTo(270, 120);
+    ctx.lineTo(220, 20);
+    ctx.fill();
+  }
+  ctx.fillStyle = "rgba(0,255,0,0.5)";
+  draw();
+  ctx.fillStyle = "rgba(255,0,255,0.5)";
+  ctx.filter = convolveFilter;
+  draw();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.colorMatrix.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.colorMatrix.html
index ed4fe91..e11013e2 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.colorMatrix.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.colorMatrix.html
@@ -64,5 +64,6 @@
 _assertPixelApprox(offscreenCanvas, 10,30, 0,255,0,255, "10,30", "0,255,0,255", 2);
 _assertPixelApprox(offscreenCanvas, 60,30, 0,255,0,255, "60,30", "0,255,0,255", 2);
 t.done();
+
 });
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.colorMatrix.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.colorMatrix.worker.js
index a2f73e09..3b4959d 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.colorMatrix.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.colorMatrix.worker.js
@@ -60,5 +60,6 @@
 _assertPixelApprox(offscreenCanvas, 10,30, 0,255,0,255, "10,30", "0,255,0,255", 2);
 _assertPixelApprox(offscreenCanvas, 60,30, 0,255,0,255, "60,30", "0,255,0,255", 2);
 t.done();
+
 });
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.html
new file mode 100644
index 0000000..e8838ff
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.filter.canvasFilterObject.convolveMatrix.exceptions</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.filter.canvasFilterObject.convolveMatrix.exceptions</h1>
+<p class="desc">Test exceptions on CanvasFilter() convolveMatrix</p>
+
+
+<script>
+var t = async_test("Test exceptions on CanvasFilter() convolveMatrix");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+var offscreenCanvas = new OffscreenCanvas(100, 50);
+var ctx = offscreenCanvas.getContext('2d');
+
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: null}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {divisor: 2}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: null}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: 1}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0]]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, "a"], [0]]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], 0]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0, Infinity]]}}); });
+t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.worker.js
new file mode 100644
index 0000000..de249c5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/filters/2d.filter.canvasFilterObject.convolveMatrix.exceptions.worker.js
@@ -0,0 +1,31 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.filter.canvasFilterObject.convolveMatrix.exceptions
+// Description:Test exceptions on CanvasFilter() convolveMatrix
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("Test exceptions on CanvasFilter() convolveMatrix");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+var offscreenCanvas = new OffscreenCanvas(100, 50);
+var ctx = offscreenCanvas.getContext('2d');
+
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: null}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {divisor: 2}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: null}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: 1}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0]]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, "a"], [0]]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], 0]}}); });
+assert_throws_js(TypeError, function() { new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0, Infinity]]}}); });
+t.done();
+
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/filters.yaml b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/filters.yaml
index c4dabab..11026f4 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/filters.yaml
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/filters.yaml
@@ -108,4 +108,18 @@
     @assert pixel 60,10 ==~ 0,255,0,255;
     @assert pixel 10,30 ==~ 0,255,0,255;
     @assert pixel 60,30 ==~ 0,255,0,255;
-  expected: green
\ No newline at end of file
+  expected: green
+
+- name: 2d.filter.canvasFilterObject.convolveMatrix.exceptions
+  desc: Test exceptions on CanvasFilter() convolveMatrix
+  code: |
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: null});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {divisor: 2}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: null}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: 1}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0]]}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, "a"], [0]]}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], 0]}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0, Infinity]]}});
+
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/filters.yaml b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/filters.yaml
index dd11080..1bcd5bb0 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/filters.yaml
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/filters.yaml
@@ -76,4 +76,18 @@
     @assert pixel 60,10 ==~ 0,255,0,255;
     @assert pixel 10,30 ==~ 0,255,0,255;
     @assert pixel 60,30 ==~ 0,255,0,255;
-    t.done();
\ No newline at end of file
+    t.done();
+
+- name: 2d.filter.canvasFilterObject.convolveMatrix.exceptions
+  desc: Test exceptions on CanvasFilter() convolveMatrix
+  code: |
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: null});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {divisor: 2}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: null}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: 1}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0]]}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, "a"], [0]]}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], 0]}});
+    @assert throws TypeError new CanvasFilter({convolveMatrix: {kernelMatrix: [[1, 0], [0, Infinity]]}});
+    t.done();
diff --git a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
index f78c331..f8f5f87 100644
--- a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
+++ b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-listing-expected.txt
@@ -50,6 +50,7 @@
 -webkit-user-drag: auto
 -webkit-user-modify: read-only
 -webkit-writing-mode: horizontal-tb
+accent-color: auto
 align-content: normal
 align-items: normal
 align-self: auto
diff --git a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
index cd8e69e..ee3b24f 100644
--- a/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
+++ b/third_party/blink/web_tests/fast/css/getComputedStyle/computed-style-without-renderer-listing-expected.txt
@@ -50,6 +50,7 @@
 -webkit-user-drag: auto
 -webkit-user-modify: read-only
 -webkit-writing-mode: horizontal-tb
+accent-color: auto
 align-content: normal
 align-items: normal
 align-self: auto
diff --git a/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt b/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
index cb25ba9..418e082 100644
--- a/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
+++ b/third_party/blink/web_tests/svg/css/getComputedStyle-listing-expected.txt
@@ -50,6 +50,7 @@
 -webkit-user-drag: auto
 -webkit-user-modify: read-only
 -webkit-writing-mode: horizontal-tb
+accent-color: auto
 align-content: normal
 align-items: normal
 align-self: auto
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index 2608fe3..cf4fd68 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -42,3 +42,5 @@
  - MemoryMap.SelfLargeMapFile, SelfBasic, SelfLargeFiles are disabled when
    BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) are defined. crbug.com/1163794
    (crashpad/util/BUILD.gn, crashpad/util/linux/memory_map_test.cc)
+ - CloseMultipleNowOrOnExec has been updated to invoke the new
+   base::subtle::ResetFDOwnership() API
diff --git a/third_party/crashpad/crashpad/util/posix/close_multiple.cc b/third_party/crashpad/crashpad/util/posix/close_multiple.cc
index 0c07832..a26ff94b 100644
--- a/third_party/crashpad/crashpad/util/posix/close_multiple.cc
+++ b/third_party/crashpad/crashpad/util/posix/close_multiple.cc
@@ -109,6 +109,12 @@
 }  // namespace
 
 void CloseMultipleNowOrOnExec(int fd, int preserve_fd) {
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+  // See comments on the ResetFDOwnership() declaration in
+  // base/files/scoped_file.h regarding why this is called here.
+  base::subtle::ResetFDOwnership();
+#endif
+
   if (CloseMultipleNowOrOnExecUsingFDDir(fd, preserve_fd)) {
     return;
   }
diff --git a/tools/android/checkstyle/checkstyle.py b/tools/android/checkstyle/checkstyle.py
index 21cf13b..987ba0b 100644
--- a/tools/android/checkstyle/checkstyle.py
+++ b/tools/android/checkstyle/checkstyle.py
@@ -52,7 +52,7 @@
                             'com.puppycrawl.tools.checkstyle.Main', '-c',
                             style_file, '-f', 'xml'] + java_files,
                             stdout=subprocess.PIPE)
-  stdout, _ = check.communicate()
+  stdout = check.communicate()[0].decode('utf-8')
 
   result_errors = []
   result_warnings = []
diff --git a/tools/licenses.py b/tools/licenses.py
index aaa520b..7c2e504 100755
--- a/tools/licenses.py
+++ b/tools/licenses.py
@@ -438,7 +438,7 @@
       raise LicenseError("missing README.chromium or licenses.py "
                          "SPECIAL_CASES entry in %s\n" % path)
 
-    for line in open(readme_path):
+    for line in codecs.open(readme_path, encoding='utf-8'):
       line = line.strip()
       if not line:
         break
@@ -500,7 +500,7 @@
     third_party_dirs."""
   additional_paths_file = os.path.join(root, dirname, ADDITIONAL_PATHS_FILENAME)
   if os.path.exists(additional_paths_file):
-    with open(additional_paths_file) as paths_file:
+    with codecs.open(additional_paths_file, encoding='utf-8') as paths_file:
       extra_paths = json.load(paths_file)
       third_party_dirs.update([os.path.join(dirname, p) for p in extra_paths])
 
@@ -673,7 +673,8 @@
     env = {
         'name': metadata['Name'],
         'url': metadata['URL'],
-        'license': open(metadata['License File']).read(),
+        'license': codecs.open(metadata['License File'],
+                               encoding='utf-8').read(),
     }
     return {
         'name': metadata['Name'],
@@ -701,7 +702,7 @@
                                        'about_ui', 'resources',
                                        'about_credits_entry.tmpl')
 
-  entry_template = open(entry_template_file).read()
+  entry_template = codecs.open(entry_template_file, encoding='utf-8').read()
   entries = []
   # Start from Chromium's LICENSE file
   chromium_license_metadata = {
@@ -744,7 +745,7 @@
     entry['content'] = entry['content'].replace('{{id}}', str(entry_id))
 
   entries_contents = '\n'.join([entry['content'] for entry in entries])
-  file_template = open(file_template_file).read()
+  file_template = codecs.open(file_template_file, encoding='utf-8').read()
   template_contents = "<!-- Generated by licenses.py; do not edit. -->"
   template_contents += EvaluateTemplate(
       file_template, {'entries': entries_contents}, escape=False)
@@ -752,13 +753,13 @@
   if output_file:
     changed = True
     try:
-      old_output = open(output_file, 'r').read()
+      old_output = codecs.open(output_file, 'r', encoding='utf-8').read()
       if old_output == template_contents:
         changed = False
     except:
       pass
     if changed:
-      with open(output_file, 'w') as output:
+      with codecs.open(output_file, 'w', encoding='utf-8') as output:
         output.write(template_contents)
   else:
     print(template_contents)
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 9467283..e5bd2a3 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -111,12 +111,15 @@
       self.args.expectations_dir = os.path.join(
           os.path.dirname(self.args.config_file), 'mb_config_expectations')
 
+    banned_from_rts_map = json.loads(
+        self.ReadFile(
+            self.PathJoin(self.chromium_src_dir, 'tools', 'mb',
+                          'rts_banned_suites.json')))
+    self.banned_from_rts.update(banned_from_rts_map.get('*', set()))
+
     if getattr(self.args, 'builder', None):
-      banned_from_rts_map = json.loads(
-          self.ReadFile(
-              self.PathJoin(self.chromium_src_dir, 'tools', 'mb',
-                            'rts_banned_suites.json')))
-      self.banned_from_rts = banned_from_rts_map.get(self.args.builder, set())
+      self.banned_from_rts.update(
+          banned_from_rts_map.get(self.args.builder, set()))
 
   def Main(self, args):
     self.ParseArgs(args)
diff --git a/tools/mb/rts_banned_suites.json b/tools/mb/rts_banned_suites.json
index e8eb8a63..5317924 100644
--- a/tools/mb/rts_banned_suites.json
+++ b/tools/mb/rts_banned_suites.json
@@ -1,66 +1,17 @@
 {
-  "linux-rel-rts": [
+
+  "*": [
     "xr_browser_tests",
-    "hardware_accelerated_feature_tests",
+    "telemetry_gpu_integration_test",
+    "metrics_python_tests",
+    "mojo_python_unittests",
+    "blink_python_tests",
+    "grit_python_unittests",
     "telemetry_perf_unittests",
-    "pixel_skia_gold_passthrough_test",
-    "gl_renderer_screenshot_sync_tests",
-    "mojo_python_unittests",
-    "blink_python_tests",
-    "info_collection_tests",
-    "telemetry_gpu_unittests",
-    "depth_capture_tests",
-    "metrics_python_tests",
-    "grit_python_unittests",
-    "webgl_conformance_tests",
-    "gpu_process_launch_tests",
-    "maps_pixel_passthrough_test",
-    "trace_test",
-    "context_lost_passthrough_tests",
-    "screenshot_sync_passthrough_tests"
-  ],
-  "win10_chromium_x64_rel_ng_rts": [
-    "trace_test",
-    "webgl_conformance_d3d11_passthrough_tests",
-    "xr_browser_tests",
-    "info_collection_tests",
-    "maps_pixel_passthrough_test",
-    "pixel_skia_gold_passthrough_test",
-    "metrics_python_tests",
-    "screenshot_sync_passthrough_tests",
-    "blink_python_tests",
-    "gpu_process_launch_tests",
-    "mojo_python_unittests",
-    "depth_capture_tests",
-    "grit_python_unittests",
-    "hardware_accelerated_feature_tests",
-    "telemetry_gpu_unittests",
-    "telemetry_desktop_minidump_unittests",
-    "context_lost_passthrough_tests"
+    "maps_tests",
+    "telemetry_gpu_unittests"
   ],
   "fuchsia_x64_rts": [
-    "maps_tests",
-    "hardware_accelerated_feature_tests",
-    "webgl_conformance_tests",
-    "trace_test",
-    "blink_web_tests",
-    "gpu_process_launch_tests",
-    "context_lost_validating_tests"
-  ],
-  "mac-rel-rts": [
-    "trace_test",
-    "maps_pixel_validating_test",
-    "screenshot_sync_validating_tests",
-    "hardware_accelerated_feature_tests",
-    "webgl_conformance_tests",
-    "pixel_skia_gold_validating_test",
-    "info_collection_tests",
-    "context_lost_validating_tests",
-    "depth_capture_tests",
-    "gpu_process_launch_tests"
-  ],
-  "chromeos-amd64-generic-rel-rts": [
-    "telemetry_perf_unittests",
-    "webgl_conformance_tests"
+    "blink_web_tests"
   ]
 }
diff --git a/tools/media_engagement_preload/PRESUBMIT.py b/tools/media_engagement_preload/PRESUBMIT.py
index df19c77f..97e25f9 100644
--- a/tools/media_engagement_preload/PRESUBMIT.py
+++ b/tools/media_engagement_preload/PRESUBMIT.py
@@ -5,6 +5,10 @@
 
 """Chromium presubmit script for src/tools/media_engagement_preload."""
 
+# This line is 'magic' in that git-cl looks for it to decide whether to
+# use Python3 instead of Python2 when running the code in this file.
+USE_PYTHON3 = True
+
 
 def _RunMakeDafsaTests(input_api, output_api):
   """Runs unittest for make_dafsa if any related file has been modified."""
@@ -12,16 +16,14 @@
            'tools/media_engagement_preload/make_dafsa_unittest.py')
   if not any(f in input_api.LocalPaths() for f in files):
     return []
-  test_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
-                                     'make_dafsa_unittest.py')
-  cmd_name = 'make_dafsa_unittest'
-  cmd = [input_api.python_executable, test_path]
-  test_cmd = input_api.Command(
-    name=cmd_name,
-    cmd=cmd,
-    kwargs={},
-    message=output_api.PresubmitPromptWarning)
-  return input_api.RunTests([test_cmd])
+
+  return input_api.RunTests(
+      input_api.canned_checks.RunUnitTestsInDirectory(
+          input_api,
+          output_api,
+          input_api.PresubmitLocalPath(),
+          files_to_check=['.*test\.py$'],
+          run_on_python2=False))
 
 
 def CheckChangeOnUpload(input_api, output_api):
diff --git a/tools/media_engagement_preload/make_dafsa.py b/tools/media_engagement_preload/make_dafsa.py
index 72cbf7d..db78c67 100755
--- a/tools/media_engagement_preload/make_dafsa.py
+++ b/tools/media_engagement_preload/make_dafsa.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2017 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.
@@ -9,7 +9,7 @@
 import json
 import sys
 import os
-import urlparse
+import urllib.parse
 
 SOURCE_ROOT = os.path.join(os.path.dirname(
     os.path.abspath(__file__)), os.pardir, os.pardir)
@@ -440,7 +440,7 @@
 def to_proto(data):
   """Generates protobuf from a list of encoded bytes."""
   message = media_engagement_preload_pb2.PreloadedData()
-  message.dafsa = array.array('B', data).tostring()
+  message.dafsa = array.array('B', data).tobytes()
   return message.SerializeToString()
 
 
@@ -458,7 +458,7 @@
     netlocs = {}
     for entry in json.loads(infile):
       # Parse the origin and reject any with an invalid protocol.
-      parsed = urlparse.urlparse(entry)
+      parsed = urllib.parse.urlparse(entry)
       if parsed.scheme != 'http' and parsed.scheme != 'https':
         raise InputError('Invalid protocol: %s' % entry)
 
@@ -471,7 +471,7 @@
 
     # Join the numerical values to the netlocs.
     output = []
-    for location, value in netlocs.iteritems():
+    for location, value in netlocs.items():
       output.append(location + str(value))
     return output
   except ValueError:
diff --git a/tools/media_engagement_preload/make_dafsa_unittest.py b/tools/media_engagement_preload/make_dafsa_unittest.py
index cdf7965..3575af58 100755
--- a/tools/media_engagement_preload/make_dafsa_unittest.py
+++ b/tools/media_engagement_preload/make_dafsa_unittest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2017 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.
@@ -6,6 +6,7 @@
 
 import sys
 import unittest
+
 import make_dafsa
 
 
@@ -678,14 +679,14 @@
   def testExample1(self):
     """Tests Example 1 from make_dafsa.py."""
     infile = '["https://www.example.com:8081", "http://www.example.org"]'
-    outfile = "\n\x1c\x81www.example\xae\x02\x89com:8081\x80org\x81"
+    outfile = b'\n\x1c\x81www.example\xae\x02\x84org\x81com:8081\x80'
     self.assertEqual(make_dafsa.words_to_proto(make_dafsa.parse_json(infile)),
                       outfile)
 
   def testExample2(self):
     """Tests Example 2 from make_dafsa.py."""
     infile = '["https://www.example.org", "http://www.google.com"]'
-    outfile = "\n\x1e\x81www\xae\x02\x8bgoogle.com\x81example.org\x80"
+    outfile = b'\n\x1e\x81www\xae\x02\x8bgoogle.com\x81example.org\x80'
     self.assertEqual(make_dafsa.words_to_proto(make_dafsa.parse_json(infile)),
                       outfile)
 
diff --git a/tools/media_engagement_preload/media_engagement_preload_pb2.py b/tools/media_engagement_preload/media_engagement_preload_pb2.py
index 0cde5091..bdb9bdc 100644
--- a/tools/media_engagement_preload/media_engagement_preload_pb2.py
+++ b/tools/media_engagement_preload/media_engagement_preload_pb2.py
@@ -1,23 +1,26 @@
-# Copyright 2017 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.
-
+# -*- coding: utf-8 -*-
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # source: media_engagement_preload.proto
 
 from google.protobuf import descriptor as _descriptor
 from google.protobuf import message as _message
 from google.protobuf import reflection as _reflection
-from google.protobuf import descriptor_pb2
+from google.protobuf import symbol_database as _symbol_database
 # @@protoc_insertion_point(imports)
 
+_sym_db = _symbol_database.Default()
+
 
 
 
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='media_engagement_preload.proto',
   package='chrome_browser_media',
-  serialized_pb='\n\x1emedia_engagement_preload.proto\x12\x14\x63hrome_browser_media\"\x1e\n\rPreloadedData\x12\r\n\x05\x64\x61\x66sa\x18\x01 \x01(\x0c\x42\x02H\x03')
+  syntax='proto2',
+  serialized_options=b'H\003',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x1emedia_engagement_preload.proto\x12\x14\x63hrome_browser_media\"\x1e\n\rPreloadedData\x12\r\n\x05\x64\x61\x66sa\x18\x01 \x01(\x0c\x42\x02H\x03'
+)
 
 
 
@@ -28,36 +31,41 @@
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
+  create_key=_descriptor._internal_create_key,
   fields=[
     _descriptor.FieldDescriptor(
       name='dafsa', full_name='chrome_browser_media.PreloadedData.dafsa', index=0,
       number=1, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value="",
+      has_default_value=False, default_value=b"",
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
-      options=None),
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
   ],
   extensions=[
   ],
   nested_types=[],
   enum_types=[
   ],
-  options=None,
+  serialized_options=None,
   is_extendable=False,
+  syntax='proto2',
   extension_ranges=[],
+  oneofs=[
+  ],
   serialized_start=56,
   serialized_end=86,
 )
 
 DESCRIPTOR.message_types_by_name['PreloadedData'] = _PRELOADEDDATA
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
 
-class PreloadedData(_message.Message):
-  __metaclass__ = _reflection.GeneratedProtocolMessageType
-  DESCRIPTOR = _PRELOADEDDATA
-
+PreloadedData = _reflection.GeneratedProtocolMessageType('PreloadedData', (_message.Message,), {
+  'DESCRIPTOR' : _PRELOADEDDATA,
+  '__module__' : 'media_engagement_preload_pb2'
   # @@protoc_insertion_point(class_scope:chrome_browser_media.PreloadedData)
+  })
+_sym_db.RegisterMessage(PreloadedData)
 
 
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003')
+DESCRIPTOR._options = None
 # @@protoc_insertion_point(module_scope)
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index cb9cf0d3..67d9ea7 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -25596,9 +25596,9 @@
   <int value="324" label="WINDOWS_ON_REMOVED"/>
   <int value="325" label="FILE_SYSTEM_PROVIDER_ON_EXECUTE_ACTION_REQUESTED"/>
   <int value="326" label="FILE_SYSTEM_PROVIDER_ON_GET_ACTIONS_REQUESTED"/>
-  <int value="327" label="LAUNCHER_SEARCH_PROVIDER_ON_QUERY_STARTED"/>
-  <int value="328" label="LAUNCHER_SEARCH_PROVIDER_ON_QUERY_ENDED"/>
-  <int value="329" label="LAUNCHER_SEARCH_PROVIDER_ON_OPEN_RESULT"/>
+  <int value="327" label="DELETED_LAUNCHER_SEARCH_PROVIDER_ON_QUERY_STARTED"/>
+  <int value="328" label="DELETED_LAUNCHER_SEARCH_PROVIDER_ON_QUERY_ENDED"/>
+  <int value="329" label="DELETED_LAUNCHER_SEARCH_PROVIDER_ON_OPEN_RESULT"/>
   <int value="330" label="CHROME_WEB_VIEW_INTERNAL_ON_CLICKED"/>
   <int value="331" label="WEB_VIEW_INTERNAL_CONTEXT_MENUS"/>
   <int value="332" label="CONTEXT_MENUS"/>
@@ -26809,7 +26809,7 @@
   <int value="992" label="DELETED_EASYUNLOCKPRIVATE_HIDEERRORBUBBLE"/>
   <int value="993" label="WEBVIEWINTERNAL_SETZOOMMODE"/>
   <int value="994" label="WEBVIEWINTERNAL_GETZOOMMODE"/>
-  <int value="995" label="LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS"/>
+  <int value="995" label="DELETED_LAUNCHERSEARCHPROVIDER_SETSEARCHRESULTS"/>
   <int value="996" label="DELETED_DATAREDUCTIONPROXY_CLEARDATASAVINGS"/>
   <int value="997" label="BLUETOOTHPRIVATE_SETDISCOVERYFILTER"/>
   <int value="998" label="FILESYSTEM_GETVOLUMELIST"/>
@@ -27881,7 +27881,7 @@
   <int value="92" label="kInput"/>
   <int value="93" label="kInputMethodPrivate"/>
   <int value="94" label="kDeleted_InterceptAllKeys"/>
-  <int value="95" label="kLauncherSearchProvider"/>
+  <int value="95" label="kDeleted_LauncherSearchProvider"/>
   <int value="96" label="kLocation"/>
   <int value="97" label="kDeleted_LogPrivate"/>
   <int value="98" label="kManagement"/>
@@ -32552,6 +32552,9 @@
   <int value="3915" label="ClientHintsPrefersColorScheme"/>
   <int value="3916" label="OverscrollBehaviorWillBeFixed"/>
   <int value="3917" label="ControlledWorkerWillBeUncontrolled"/>
+  <int value="3918" label="ARIATouchpassthroughAttribute"/>
+  <int value="3919" label="ARIAVirtualcontentAttribute"/>
+  <int value="3920" label="AccessibilityTouchPassthroughSet"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -38953,6 +38956,7 @@
   <int value="4" label="kScreenRecording"/>
   <int value="5" label="kArcDownload"/>
   <int value="6" label="kPrintedPdf"/>
+  <int value="7" label="kDiagnosticsLog"/>
 </enum>
 
 <enum name="HoldingSpacePodAction">
@@ -70084,6 +70088,7 @@
   <int value="7" label="MODEL_INVALID_VERSION_NUMBER"/>
   <int value="8" label="BAD_HASH_IDS"/>
   <int value="9" label="MODEL_NEVER_FETCHED"/>
+  <int value="10" label="FAILED_MAP_VISUAL_TFLITE_MODEL"/>
 </enum>
 
 <enum name="SBClientPhishingScorerCreationStatus">
diff --git a/tools/metrics/histograms/histograms_xml/apps/histograms.xml b/tools/metrics/histograms/histograms_xml/apps/histograms.xml
index e77a764..a6b9cda 100644
--- a/tools/metrics/histograms/histograms_xml/apps/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/apps/histograms.xml
@@ -1442,6 +1442,16 @@
   </summary>
 </histogram>
 
+<histogram name="Apps.ArcGhostWindowLaunch" enum="Boolean"
+    expires_after="2021-11-01">
+  <owner>nancylingwang@chromium.org</owner>
+  <owner>sstan@chromium.org</owner>
+  <summary>
+    Records whether the ARC ghost window is launched when the ARC app are
+    restored during the system startup phase.
+  </summary>
+</histogram>
+
 <histogram name="Apps.Bounced" enum="BooleanBounced" expires_after="2020-12-14">
   <owner>ajlinker@chromium.org</owner>
   <owner>dominickn@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/holding_space/histograms.xml b/tools/metrics/histograms/histograms_xml/holding_space/histograms.xml
index 76cf35c4..7b13f77 100644
--- a/tools/metrics/histograms/histograms_xml/holding_space/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/holding_space/histograms.xml
@@ -35,6 +35,8 @@
 <variants name="HoldingSpaceItemType">
   <variant name="All" summary="Includes all item types."/>
   <variant name="ArcDownload" summary="Items backed by an ARC download file."/>
+  <variant name="DiagnosticsLog"
+      summary="Items backed by a diagnostics log file."/>
   <variant name="Download" summary="Items backed by a download file."/>
   <variant name="NearbyShare" summary="Items backed by a nearby shared file."/>
   <variant name="PinnedFile" summary="Items pinned explicitly by the user."/>
diff --git a/tools/metrics/histograms/histograms_xml/local/histograms.xml b/tools/metrics/histograms/histograms_xml/local/histograms.xml
index cf229e11..854f5c4 100644
--- a/tools/metrics/histograms/histograms_xml/local/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/local/histograms.xml
@@ -268,6 +268,8 @@
   <summary>
     The renderer side cache hit rate metrics for new HTML5 LocalStorage DB
     opened.
+
+    Warning: this histogram was expired from M78 to M92; data may be missing.
   </summary>
 </histogram>
 
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 8f47c31..42c297c 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -101,7 +101,9 @@
 ]
 
 # This is an opt-in list for builders which uses dynamic sharding.
-DYNAMIC_SHARDING_TESTERS = ['android-pixel2-perf-fyi', 'linux-perf-calibration']
+DYNAMIC_SHARDING_TESTERS = [
+    'android-pixel2-perf', 'android-pixel2-perf-fyi', 'linux-perf-calibration'
+]
 
 CALIBRATION_BUILDERS = {
     'linux-perf-calibration': {
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 5d51f57..378573350 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,11 +6,11 @@
         },
         "mac": {
             "hash": "d60c6038003ec1b551e16068d49f4d55ee20656f",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/7d6375fd3e2f91b5880195a9c02de2334a3fa0d4/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/565fc165d0f867c9de129ead9b1766f42d2a1205/trace_processor_shell"
         },
         "linux": {
             "hash": "dd23313e9aabc34dcaed1743f3bb1938be1c63b2",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/9a1689eeaa281b21c5bcedcad5d54ddaeeb7889c/trace_processor_shell"
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/565fc165d0f867c9de129ead9b1766f42d2a1205/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/cross_device_test_config.py b/tools/perf/cross_device_test_config.py
index 49bf646..e0cb88e 100644
--- a/tools/perf/cross_device_test_config.py
+++ b/tools/perf/cross_device_test_config.py
@@ -32,12 +32,8 @@
         'system_health.memory_mobile': 3,
     },
     'android-pixel2-perf': {
-        'system_health.common_mobile': {
-            # timeToFirstContentfulPaint
-            'browse:media:googleplaystore:2019': 10,
-            'load:social:pinterest:2019': 10,
-            'browse:media:facebook_photos:2019': 10
-        }
+        'system_health.common_mobile': 3,
+        'system_health.memory_mobile': 3,
     },
     'android-go-perf': {
         'system_health.common_mobile': {
diff --git a/ui/base/clipboard/clipboard_android.cc b/ui/base/clipboard/clipboard_android.cc
index f8391c2..62e6ef7 100644
--- a/ui/base/clipboard/clipboard_android.cc
+++ b/ui/base/clipboard/clipboard_android.cc
@@ -480,13 +480,13 @@
     types->push_back(base::UTF8ToUTF16(kMimeTypeText));
   if (IsFormatAvailable(ClipboardFormatType::GetHtmlType(), buffer, data_dst))
     types->push_back(base::UTF8ToUTF16(kMimeTypeHTML));
+  if (IsFormatAvailable(ClipboardFormatType::GetBitmapType(), buffer, data_dst))
+    types->push_back(base::UTF8ToUTF16(kMimeTypePNG));
 
   // these formats aren't supported by the ClipboardMap currently, but might
   // be one day?
   if (IsFormatAvailable(ClipboardFormatType::GetRtfType(), buffer, data_dst))
     types->push_back(base::UTF8ToUTF16(kMimeTypeRTF));
-  if (IsFormatAvailable(ClipboardFormatType::GetBitmapType(), buffer, data_dst))
-    types->push_back(base::UTF8ToUTF16(kMimeTypePNG));
 }
 
 // |data_dst| is not used. It's only passed to be consistent with other
diff --git a/ui/file_manager/BUILD.gn b/ui/file_manager/BUILD.gn
index a842f79..469ef97a 100644
--- a/ui/file_manager/BUILD.gn
+++ b/ui/file_manager/BUILD.gn
@@ -193,7 +193,6 @@
     "file_manager/background/js/file_operation_util.m.js",
     "file_manager/background/js/import_history.m.js",
     "file_manager/background/js/launcher.m.js",
-    "file_manager/background/js/launcher_search.m.js",
     "file_manager/background/js/media_import_handler.m.js",
     "file_manager/background/js/media_scanner.m.js",
     "file_manager/background/js/metadata_proxy.m.js",
diff --git a/ui/file_manager/file_manager/background/js/BUILD.gn b/ui/file_manager/file_manager/background/js/BUILD.gn
index 4598a53..c6ef5950 100644
--- a/ui/file_manager/file_manager/background/js/BUILD.gn
+++ b/ui/file_manager/file_manager/background/js/BUILD.gn
@@ -54,7 +54,6 @@
     ":file_operation_util",
     ":import_history",
     ":launcher",
-    ":launcher_search",
     ":media_import_handler",
     ":media_scanner",
     ":metadata_proxy",
@@ -87,7 +86,6 @@
     ":file_operation_util.m",
     ":import_history.m",
     ":launcher.m",
-    ":launcher_search.m",
     ":media_import_handler.m",
     ":media_scanner.m",
     ":metadata_proxy.m",
@@ -163,7 +161,6 @@
     "//ui/file_manager/file_manager/externs/background_window.js",
     "//ui/file_manager/file_manager/externs/css_rule.js",
     "//ui/file_manager/file_manager/externs/drive_dialog_controller.js",
-    "//ui/file_manager/file_manager/externs/launcher_search_provider.js",
     "//ui/file_manager/file_manager/externs/platform.js",
     "//ui/file_manager/file_manager/externs/progress_center_panel.js",
     "//ui/file_manager/file_manager/externs/background/task_queue.js",
@@ -216,7 +213,6 @@
     ":file_operation_manager",
     ":import_history",
     ":launcher",
-    ":launcher_search",
     ":media_import_handler",
     ":mount_metrics",
     ":progress_center",
@@ -243,7 +239,6 @@
     ":file_operation_util.m",
     ":import_history.m",
     ":launcher.m",
-    ":launcher_search.m",
     ":media_import_handler.m",
     ":media_scanner.m",
     ":mount_metrics.m",
@@ -763,30 +758,6 @@
   extra_deps = [ ":modulize" ]
 }
 
-js_library("launcher_search") {
-  deps = [
-    ":launcher",
-    ":volume_manager_factory",
-    "//ui/file_manager/file_manager/common/js:file_type",
-    "//ui/file_manager/file_manager/common/js:util",
-  ]
-}
-
-js_library("launcher_search.m") {
-  sources = [ "$root_gen_dir/ui/file_manager/file_manager/background/js/launcher_search.m.js" ]
-  deps = [
-    ":launcher.m",
-    ":volume_manager_factory.m",
-    "//ui/file_manager/file_manager/common/js:file_type.m",
-    "//ui/file_manager/file_manager/common/js:util.m",
-  ]
-
-  externs_list =
-      [ "//ui/file_manager/file_manager/externs/launcher_search_provider.js" ]
-
-  extra_deps = [ ":modulize" ]
-}
-
 js_library("media_import_handler") {
   deps = [
     ":drive_sync_handler",
@@ -1366,7 +1337,6 @@
     "progress_center.js",
     "device_handler.js",
     "launcher.js",
-    "launcher_search.js",
     "background.js",
   ]
 
diff --git a/ui/file_manager/file_manager/background/js/background.js b/ui/file_manager/file_manager/background/js/background.js
index ddbd283..caee064 100644
--- a/ui/file_manager/file_manager/background/js/background.js
+++ b/ui/file_manager/file_manager/background/js/background.js
@@ -22,7 +22,6 @@
 // #import {FileOperationManagerImpl} from './file_operation_manager.m.js';
 // #import {VolumeManagerCommon} from '../../common/js/volume_manager_types.m.js';
 // #import {volumeManagerFactory} from './volume_manager_factory.m.js';
-// #import {LauncherSearch} from './launcher_search.m.js';
 // #import {MountMetrics} from './mount_metrics.m.js';
 // #import {CrostiniImpl} from './crostini.m.js';
 // #import {mediaImport} from './media_import_handler.m.js';
@@ -126,12 +125,6 @@
      */
     this.stringData = null;
 
-    /**
-     * Provides drive search to app launcher.
-     * @private {!LauncherSearch}
-     */
-    this.launcherSearch_ = new LauncherSearch();
-
     if (!window.isSWA) {
       // Initialize listener for importer.handlePhotosAppMessage messages to
       // the files app back-end. FIXME: Files SWA needs to support photos
diff --git a/ui/file_manager/file_manager/background/js/background_scripts.js b/ui/file_manager/file_manager/background/js/background_scripts.js
index 0888738b..68ced1039 100644
--- a/ui/file_manager/file_manager/background/js/background_scripts.js
+++ b/ui/file_manager/file_manager/background/js/background_scripts.js
@@ -22,7 +22,6 @@
 // <include src="file_operation_util.js">
 // <include src="import_history.js">
 // <include src="launcher.js">
-// <include src="launcher_search.js">
 // <include src="task_queue.js">
 // <include src="media_import_handler.js">
 // <include src="media_scanner.js">
diff --git a/ui/file_manager/file_manager/background/js/launcher_search.js b/ui/file_manager/file_manager/background/js/launcher_search.js
deleted file mode 100644
index a87059c4..0000000
--- a/ui/file_manager/file_manager/background/js/launcher_search.js
+++ /dev/null
@@ -1,350 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// #import {FileType} from '../../common/js/file_type.m.js';
-// #import {launcher, LaunchType} from './launcher.m.js';
-// #import {util} from '../../common/js/util.m.js';
-// #import {volumeManagerFactory} from './volume_manager_factory.m.js';
-
-/**
- * Provides drive search results to chrome launcher.
- */
-/* #export */ class LauncherSearch {
-  constructor() {
-    // Launcher search provider is restricted to dev channel at now.
-    if (!chrome.launcherSearchProvider) {
-      return;
-    }
-
-    /**
-     * Active query id. This value is set null when there is no active query.
-     * @private {?number}
-     */
-    this.queryId_ = null;
-
-    /**
-     * True if this feature is enabled.
-     * @private {boolean}
-     */
-    this.enabled_ = false;
-
-    /**
-     * @private {function(number, string, number)}
-     */
-    this.onQueryStartedBound_ = this.onQueryStarted_.bind(this);
-
-    /**
-     * @private {function(number)}
-     */
-    this.onQueryEndedBound_ = this.onQueryEnded_.bind(this);
-
-    /**
-     * @private {function(string)}
-     */
-    this.onOpenResultBound_ = this.onOpenResult_.bind(this);
-
-    // This feature is disabled when drive is disabled.
-    chrome.fileManagerPrivate.onPreferencesChanged.addListener(
-        this.onPreferencesChanged_.bind(this));
-    this.onPreferencesChanged_();
-  }
-
-  /**
-   * Handles onPreferencesChanged event.
-   */
-  onPreferencesChanged_() {
-    chrome.fileManagerPrivate.getPreferences(preferences => {
-      this.initializeEventListeners_(
-          preferences.driveEnabled, preferences.searchSuggestEnabled);
-    });
-  }
-
-  /**
-   * Initialize event listeners of chrome.launcherSearchProvider.
-   *
-   * When drive and search suggest are enabled, listen events of
-   * chrome.launcherSearchProvider and provide search resutls. When one of them
-   * is disabled, remove these event listeners and stop providing search
-   * results.
-   *
-   * @param {boolean} isDriveEnabled True if drive is enabled.
-   * @param {boolean} isSearchSuggestEnabled True if search suggest is enabled.
-   */
-  initializeEventListeners_(isDriveEnabled, isSearchSuggestEnabled) {
-    const launcherSearchEnabled = isDriveEnabled && isSearchSuggestEnabled;
-
-    // If this.enabled_ === launcherSearchEnabled, we don't need to change
-    // anything here.
-    if (this.enabled_ === launcherSearchEnabled) {
-      return;
-    }
-
-    // Remove event listeners if it's already enabled.
-    if (this.enabled_) {
-      chrome.launcherSearchProvider.onQueryStarted.removeListener(
-          this.onQueryStartedBound_);
-      chrome.launcherSearchProvider.onQueryEnded.removeListener(
-          this.onQueryEndedBound_);
-      chrome.launcherSearchProvider.onOpenResult.removeListener(
-          this.onOpenResultBound_);
-    }
-
-    // Set queryId_ to null to prevent that on-going search returns search
-    // results.
-    this.queryId_ = null;
-
-    // Add event listeners when launcher search of Drive is enabled.
-    if (launcherSearchEnabled) {
-      this.enabled_ = true;
-      chrome.launcherSearchProvider.onQueryStarted.addListener(
-          this.onQueryStartedBound_);
-      chrome.launcherSearchProvider.onQueryEnded.addListener(
-          this.onQueryEndedBound_);
-      chrome.launcherSearchProvider.onOpenResult.addListener(
-          this.onOpenResultBound_);
-    } else {
-      this.enabled_ = false;
-    }
-  }
-
-  /**
-   * Handles onQueryStarted event.
-   * @param {number} queryId
-   * @param {string} query
-   * @param {number} limit
-   */
-  onQueryStarted_(queryId, query, limit) {
-    this.queryId_ = queryId;
-
-    const startTime = Date.now();
-    // Request an instance of volume manager to ensure that all volumes are
-    // initialized. When user searches while background page of the Files app is
-    // not running, it happens that this method is executed before all volumes
-    // are initialized. In this method,
-    // chrome.fileManagerPrivate.searchDriveMetadata resolves url internally,
-    // and it fails if filesystem of the url is not initialized.
-    volumeManagerFactory.getInstance()
-        .then(() => {
-          return Promise.all([
-            this.queryDriveEntries_(queryId, query, limit, startTime),
-            this.queryLocalEntries_(query, limit, startTime),
-          ]);
-        })
-        .then((results) => {
-          const entries = results[0].concat(results[1]);
-          if (queryId !== this.queryId_ || entries.length === 0) {
-            return;
-          }
-
-          const resultEntries = this.chooseEntries_(entries, query, limit);
-          const searchResults = resultEntries.map(this.createSearchResult_);
-          chrome.launcherSearchProvider.setSearchResults(
-              queryId, searchResults);
-        });
-  }
-
-  /**
-   * Handles onQueryEnded event.
-   * @param {number} queryId
-   */
-  onQueryEnded_(queryId) {
-    this.queryId_ = null;
-  }
-
-  /**
-   * Handles onOpenResult event.
-   * @param {string} itemId
-   */
-  onOpenResult_(itemId) {
-    // Request an instance of volume manager to ensure that all volumes are
-    // initialized. webkitResolveLocalFileSystemURL in util.urlToEntry fails if
-    // filesystem of the url is not initialized.
-    volumeManagerFactory.getInstance().then(() => {
-      util.urlToEntry(itemId).then(entry => {
-        if (entry.isDirectory) {
-          // If it's directory, open the directory with file manager.
-          launcher.launchFileManager(
-              {currentDirectoryURL: entry.toURL()}, undefined, /* App ID */
-              LaunchType.FOCUS_SAME_OR_CREATE);
-        } else {
-          // getFileTasks does not support fake entries.
-          if (util.isFakeEntry(entry)) {
-            return;
-          }
-          // If the file is not directory, try to execute default task.
-          chrome.fileManagerPrivate.getFileTasks([entry], tasks => {
-            // Select default task.
-            let defaultTask = null;
-            for (let i = 0; i < tasks.length; i++) {
-              const task = tasks[i];
-              if (task.isDefault) {
-                defaultTask = task;
-                break;
-              }
-            }
-
-            // If we haven't picked a default task yet, then just pick the first
-            // one which is not generic file handler as default task.
-            // TODO(yawano) Share task execution logic with file_tasks.js.
-            if (!defaultTask) {
-              for (let i = 0; i < tasks.length; i++) {
-                const task = tasks[i];
-                if (!task.isGenericFileHandler) {
-                  defaultTask = task;
-                  break;
-                }
-              }
-            }
-
-            if (defaultTask) {
-              // Execute default task.
-              chrome.fileManagerPrivate.executeTask(
-                  defaultTask.taskId, [entry], result => {
-                    if (chrome.runtime.lastError) {
-                      console.warn(
-                          'Unable to execute task: ' +
-                          chrome.runtime.lastError.message);
-                    }
-                    if (result === 'opened' || result === 'message_sent') {
-                      return;
-                    }
-                    this.openFileManagerWithSelectionURL_(entry.toURL());
-                  });
-            } else {
-              // If there is no default task for the url, open a file manager
-              // with selecting it.
-              // TODO(yawano): Add fallback to view-in-browser as file_tasks.js
-              // do
-              this.openFileManagerWithSelectionURL_(entry.toURL());
-            }
-          });
-        }
-      });
-    });
-  }
-
-  /**
-   * Opens file manager with selecting a specified url.
-   * @param {string} selectionURL A url to be selected.
-   * @private
-   */
-  openFileManagerWithSelectionURL_(selectionURL) {
-    launcher.launchFileManager(
-        {selectionURL: selectionURL}, undefined, /* App ID */
-        LaunchType.FOCUS_SAME_OR_CREATE);
-  }
-
-  /**
-   * Queries entries which match the given query in Google Drive.
-   * @param {number} queryId
-   * @param {string} query
-   * @param {number} limit
-   * @param {number} startTime
-   * @return {!Promise<!Array<!Entry>>}
-   * @private
-   */
-  queryDriveEntries_(queryId, query, limit, startTime) {
-    const param = {
-      query: query,
-      types: chrome.fileManagerPrivate.SearchType.ALL,
-      maxResults: limit
-    };
-    return new Promise((resolve, reject) => {
-      chrome.fileManagerPrivate.searchDriveMetadata(param, results => {
-        chrome.fileManagerPrivate.getDriveConnectionState(connectionState => {
-          if (connectionState.type !==
-              chrome.fileManagerPrivate.DriveConnectionStateType.ONLINE) {
-            results = results.filter(
-                result => result.entry.isDirectory ||
-                    result.availableOffline !== false);
-          }
-          chrome.metricsPrivate.recordTime(
-              'FileBrowser.LauncherSearch.Drive', Date.now() - startTime);
-          resolve(results.map(result => result.entry));
-        });
-      });
-    });
-  }
-
-  /**
-   * Queries entries which match the given query in Downloads.
-   * @param {string} query
-   * @param {number} startTime
-   * @return {!Promise<!Array<!Entry>>}
-   * @private
-   */
-  queryLocalEntries_(query, limit, startTime) {
-    if (!query) {
-      return Promise.resolve([]);
-    }
-    return new Promise((resolve, reject) => {
-      chrome.fileManagerPrivate.searchFiles(
-          {
-            query: query,
-            types: chrome.fileManagerPrivate.SearchType.ALL,
-            maxResults: limit
-          },
-          results => {
-            chrome.metricsPrivate.recordTime(
-                'FileBrowser.LauncherSearch.Local', Date.now() - startTime);
-            resolve(results);
-          });
-    });
-  }
-
-  /**
-   * Chooses entries to show among the given entries.
-   * @param {!Array<!Entry>} entries
-   * @param {string} query
-   * @param {number} limit
-   * @return {!Array<!Entry>}
-   * @private
-   */
-  chooseEntries_(entries, query, limit) {
-    query = query.toLowerCase();
-    const scoreEntry = (entry) => {
-      // Prefer entry which has the query string as a prefix.
-      if (entry.name.toLowerCase().indexOf(query) === 0) {
-        return 1;
-      }
-      return 0;
-    };
-    const sortedEntries = entries.sort((a, b) => {
-      return scoreEntry(b) - scoreEntry(a);
-    });
-    return sortedEntries.slice(0, limit);
-  }
-
-  /**
-   * Creates a search result from entry to pass to launcherSearchProvider API.
-   * @param {!Entry} entry
-   * @return {!Object}
-   * @private
-   */
-  createSearchResult_(entry) {
-    // TODO(yawano): Use filetype_folder_shared.png for a shared
-    //     folder.
-    let icon = FileType.getIcon(entry);
-
-    if (icon === 'UNKNOWN') {
-      icon = 'generic';
-    }
-
-    // Hide extensions for hosted files.
-    const title = FileType.isHosted(entry) ?
-        entry.name.substr(
-            0, entry.name.length - FileType.getExtension(entry).length) :
-        entry.name;
-
-    return {
-      itemId: entry.toURL(),
-      title: title,
-      iconType: icon,
-      // Relevance is set as 2 for all results as a temporary
-      // implementation. 2 is the middle value.
-      // TODO(yawano): Implement practical relevance calculation.
-      relevance: 2
-    };
-  }
-}
diff --git a/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js b/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
index 7e76d6b..5ea65a80 100644
--- a/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
+++ b/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
@@ -992,22 +992,6 @@
 };
 
 /**
- * Opens the file URL. It emulates the interaction that Launcher search does
- * from a search result, it triggers the background page's event listener that
- * listens to evens from launcher_search_provider API.
- *
- * @param {string} fileURL File URL to open by Files app background dialog.
- * @suppress {accessControls|missingProperties} Closure disallow calling private
- * launcherSearch_, but here we just want to emulate the behaviour, so we don't
- * need to make this attribute public. Also the interface
- * "FileBrowserBackground" doesn't define the attributes "launcherSearch_" so we
- * need to suppress missingProperties.
- */
-test.util.sync.launcherSearchOpenResult = fileURL => {
-  window.background.launcherSearch_.onOpenResult_(fileURL);
-};
-
-/**
  * Gets file entries just under the volume.
  *
  * @param {VolumeManagerCommon.VolumeType} volumeType Volume type.
diff --git a/ui/file_manager/file_manager/externs/launcher_search_provider.js b/ui/file_manager/file_manager/externs/launcher_search_provider.js
deleted file mode 100644
index 6bb7f45..0000000
--- a/ui/file_manager/file_manager/externs/launcher_search_provider.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Namespace for chrome.launcherSearchProvider API. Since this API is under
- * development and idl change may happen, we put extern file under
- * ui/file_manager as temporary.
- */
-chrome.launcherSearchProvider = {};
-
-/**
- * @param {number} queryId
- * @param {Array<{itemId:string, title:string, iconUrl:?string,
- *     relevance:number}>} results
- */
-chrome.launcherSearchProvider.setSearchResults = function(queryId, results) {};
-
-/**
- * @type {!ChromeEvent}
- */
-chrome.launcherSearchProvider.onQueryStarted;
-
-/**
- * @type {!ChromeEvent}
- */
-chrome.launcherSearchProvider.onQueryEnded;
-
-/**
- * @type {!ChromeEvent}
- */
-chrome.launcherSearchProvider.onOpenResult;
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 52c1023..102f72f 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
@@ -58,7 +58,7 @@
        */
       this.menuEndGap_ = 0;  // padding on cr.menu + 2px.
 
-      /** @private {?EventTracker} */
+      /** @private {?cr.EventTracker} */
       this.showingEvents_ = null;
 
       /** TODO(adanilo) Annotate these for closure checking. */
@@ -99,7 +99,7 @@
 
     decorate() {
       // Event tracker for the sub-menu specific listeners.
-      this.showingEvents_ = new EventTracker();
+      this.showingEvents_ = new cr.EventTracker();
       this.currentMenu = this;
       this.menuEndGap_ = 18;  // padding on cr.menu + 2px
     }
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 50966e9..80289169 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
@@ -38,7 +38,7 @@
        */
       this.menuEndGap_ = 0;  // padding on cr.menu + 2px
 
-      /** @private {?EventTracker} */
+      /** @private {?cr.EventTracker} */
       this.showingEvents_ = null;
 
       /** @private {?cr.ui.Menu} */
@@ -115,7 +115,7 @@
 
       // An event tracker for events we only connect to while the menu is
       // displayed.
-      this.showingEvents_ = new EventTracker();
+      this.showingEvents_ = new cr.EventTracker();
     }
 
     /**
diff --git a/ui/file_manager/file_manager/manifest.json b/ui/file_manager/file_manager/manifest.json
index ce44f3d..70a377ac 100644
--- a/ui/file_manager/file_manager/manifest.json
+++ b/ui/file_manager/file_manager/manifest.json
@@ -35,7 +35,6 @@
     "https://docs.google.com/",
     "https://drive.google.com/",
     "https://www.google-analytics.com/",
-    "launcherSearchProvider",
     "metricsPrivate",
     "notifications",
     "power",
diff --git a/ui/file_manager/integration_tests/file_manager/BUILD.gn b/ui/file_manager/integration_tests/file_manager/BUILD.gn
index a69fa55..958ac37 100644
--- a/ui/file_manager/integration_tests/file_manager/BUILD.gn
+++ b/ui/file_manager/integration_tests/file_manager/BUILD.gn
@@ -28,7 +28,6 @@
     ":grid_view",
     ":install_linux_package_dialog",
     ":keyboard_operations",
-    ":launcher_search",
     ":metadata",
     ":metrics",
     ":my_files",
@@ -157,11 +156,6 @@
   deps = []
 }
 
-js_library("launcher_search") {
-  testonly = true
-  deps = []
-}
-
 js_library("metadata") {
   testonly = true
   deps = []
diff --git a/ui/file_manager/integration_tests/file_manager/launcher_search.js b/ui/file_manager/integration_tests/file_manager/launcher_search.js
deleted file mode 100644
index ea9e5f8..0000000
--- a/ui/file_manager/integration_tests/file_manager/launcher_search.js
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-/**
- * @fileoverview Tests for interface we expose to Launcher app's search feature.
- */
-
-'use strict';
-
-(() => {
-  /**
-   * Tests opening an image using the Launcher app's search feature.
-   */
-  testcase.launcherOpenSearchResult = async () => {
-    const imageName = ENTRIES.desktop.nameText;
-
-    await sendTestMessage({
-      name: 'expectFileTask',
-      fileNames: [ENTRIES.desktop.targetPath],
-      openType: 'launch'
-    });
-
-    // Create an image file in Drive.
-    await addEntries(['drive'], [ENTRIES.desktop]);
-
-    // Get the image file URL.
-    const fileURLs = await remoteCall.callRemoteTestUtil(
-        'getFilesUnderVolume', null, ['drive', [imageName]]);
-
-    // Request Files app to open the image URL.
-    // This emulates the Launcher interaction with Files app.
-    chrome.test.assertEq(1, fileURLs.length);
-    await remoteCall.callRemoteTestUtil(
-        'launcherSearchOpenResult', null, [fileURLs[0]]);
-
-    // Files app opens the MediaApp for images, so wait for it.
-    await waitForMediaApp();
-  };
-
-  const hostedDocument = Object.assign(
-      {}, ENTRIES.testDocument,
-      {nameText: 'testDocument.txt.gdoc', targetPath: 'testDocument.txt'});
-  const photos = Object.assign(
-      {}, ENTRIES.photos, {nameText: 'photos.txt', targetPath: 'photos.txt'});
-
-  /**
-   * Tests Local and Drive files show up in search results.
-   */
-  testcase.launcherSearch = async () => {
-    // Create a file in Downloads, and a pinned and unpinned file in Drive.
-    await Promise.all([
-      addEntries(['local'], [ENTRIES.tallText, photos]),
-      addEntries(
-          ['drive'], [ENTRIES.hello, ENTRIES.pinned, hostedDocument, photos]),
-    ]);
-    const appId = await openNewWindow(null, null);
-    chrome.test.assertTrue(!!appId, 'failed to open new window');
-
-    const result = JSON.parse(await sendTestMessage({
-      name: 'runLauncherSearch',
-      query: '.Txt',
-    }));
-    chrome.test.assertEq(
-        [
-          ENTRIES.hello.targetPath,
-          photos.targetPath,
-          photos.targetPath,
-          ENTRIES.pinned.targetPath,
-          ENTRIES.tallText.targetPath,
-          hostedDocument.targetPath,
-        ],
-        result);
-  };
-
-  /**
-   * Tests Local and Drive files show up in search results.
-   */
-  testcase.launcherSearchOffline = async () => {
-    // Create a file in Downloads, and a pinned and unpinned file in Drive.
-    await Promise.all([
-      addEntries(['local'], [ENTRIES.tallText, photos]),
-      addEntries(
-          ['drive'], [ENTRIES.hello, ENTRIES.pinned, hostedDocument, photos]),
-    ]);
-    const appId = await openNewWindow(null, null);
-    chrome.test.assertTrue(!!appId, 'failed to open new window');
-
-    const result = JSON.parse(await sendTestMessage({
-      name: 'runLauncherSearch',
-      query: '.txt',
-    }));
-    chrome.test.assertEq(
-        [
-          photos.targetPath,
-          photos.targetPath,
-          ENTRIES.pinned.targetPath,
-          ENTRIES.tallText.targetPath,
-        ],
-        result);
-  };
-})();
diff --git a/ui/file_manager/integration_tests/file_manager_test_manifest.json b/ui/file_manager/integration_tests/file_manager_test_manifest.json
index 194b7bb..d373a73 100644
--- a/ui/file_manager/integration_tests/file_manager_test_manifest.json
+++ b/ui/file_manager/integration_tests/file_manager_test_manifest.json
@@ -34,7 +34,6 @@
       "file_manager/install_linux_package_dialog.js",
       "file_manager/keyboard_operations.js",
       "file_manager/launch_files_app_swa.js",
-      "file_manager/launcher_search.js",
       "file_manager/metadata.js",
       "file_manager/metrics.js",
       "file_manager/my_files.js",
diff --git a/ui/views/controls/message_box_view.cc b/ui/views/controls/message_box_view.cc
index d94b81c0..bfd76ace 100644
--- a/ui/views/controls/message_box_view.cc
+++ b/ui/views/controls/message_box_view.cc
@@ -109,11 +109,11 @@
   } else {
     add_label(message, true, gfx::ALIGN_LEFT);
   }
-  auto scroll_view = std::make_unique<ScrollView>();
-  scroll_view->ClipHeightTo(0, provider->GetDistanceMetric(
-                                   DISTANCE_DIALOG_SCROLLABLE_AREA_MAX_HEIGHT));
-  scroll_view->SetContents(std::move(message_contents));
-  scroll_view_ = AddChildView(std::move(scroll_view));
+  scroll_view_ = AddChildView(std::make_unique<ScrollView>());
+  scroll_view_->ClipHeightTo(
+      0,
+      provider->GetDistanceMetric(DISTANCE_DIALOG_SCROLLABLE_AREA_MAX_HEIGHT));
+  scroll_view_->SetContents(std::move(message_contents));
   // Don't enable text selection if multiple labels are used, since text
   // selection can't span multiple labels.
   if (message_labels_.size() == 1u)
diff --git a/ui/views/controls/scroll_view.cc b/ui/views/controls/scroll_view.cc
index 8e7479a..df0bde7 100644
--- a/ui/views/controls/scroll_view.cc
+++ b/ui/views/controls/scroll_view.cc
@@ -159,13 +159,6 @@
     scroll_view_->ScrollContentsRegionToBeVisible(scroll_rect);
   }
 
-  // TODO(https://crbug.com/947053): this override should not be necessary, but
-  // there are some assumptions that this calls Layout().
-  void ChildPreferredSizeChanged(View* child) override {
-    if (parent())
-      parent()->Layout();
-  }
-
   void ViewHierarchyChanged(
       const ViewHierarchyChangedDetails& details) override {
     if (details.is_add && GetIsContentsViewport() && Contains(details.parent))
@@ -417,6 +410,9 @@
 }
 
 void ScrollView::ClipHeightTo(int min_height, int max_height) {
+  if (min_height != min_height_ || max_height != max_height_)
+    PreferredSizeChanged();
+
   min_height_ = min_height;
   max_height_ = max_height;
 }
@@ -467,12 +463,11 @@
 }
 
 gfx::Size ScrollView::CalculatePreferredSize() const {
-  if (!is_bounded())
-    return View::CalculatePreferredSize();
-
-  gfx::Size size = contents_->GetPreferredSize();
-  size.SetToMax(gfx::Size(size.width(), min_height_));
-  size.SetToMin(gfx::Size(size.width(), max_height_));
+  gfx::Size size = contents_ ? contents_->GetPreferredSize() : gfx::Size();
+  if (is_bounded()) {
+    size.SetToMax(gfx::Size(size.width(), min_height_));
+    size.SetToMin(gfx::Size(size.width(), max_height_));
+  }
   gfx::Insets insets = GetInsets();
   size.Enlarge(insets.width(), insets.height());
   return size;
@@ -484,7 +479,8 @@
 
   gfx::Insets insets = GetInsets();
   width = std::max(0, width - insets.width());
-  int height = contents_->GetHeightForWidth(width) + insets.height();
+  int height = contents_ ? contents_->GetHeightForWidth(width) + insets.height()
+                         : insets.height();
   return base::ClampToRange(height, min_height_, max_height_);
 }
 
@@ -900,10 +896,7 @@
     *member = parent->AddChildViewAt(std::move(new_view), 0);
   else
     *member = nullptr;
-  // TODO(https://crbug.com/947053): this should call InvalidateLayout(), but
-  // there are some assumptions that it call Layout(). These assumptions should
-  // be updated.
-  Layout();
+  InvalidateLayout();
 }
 
 void ScrollView::ScrollContentsRegionToBeVisible(const gfx::Rect& rect) {
diff --git a/ui/views/controls/scroll_view_unittest.cc b/ui/views/controls/scroll_view_unittest.cc
index 54cf561..6823b96 100644
--- a/ui/views/controls/scroll_view_unittest.cc
+++ b/ui/views/controls/scroll_view_unittest.cc
@@ -634,12 +634,14 @@
 }
 
 // Assertions around adding a header.
-TEST_F(ScrollViewTest, Header) {
-  auto* header = scroll_view_->SetHeader(std::make_unique<CustomView>());
+TEST_F(WidgetScrollViewTest, Header) {
+  auto contents_ptr = std::make_unique<View>();
+  auto* contents = contents_ptr.get();
+  ScrollView* scroll_view = AddScrollViewWithContents(std::move(contents_ptr));
+  auto* header = scroll_view->SetHeader(std::make_unique<CustomView>());
   View* header_parent = header->parent();
-  View* contents = InstallContents();
 
-  scroll_view_->Layout();
+  widget()->LayoutRootViewIfNecessary();
   // |header|s preferred size is empty, which should result in all space going
   // to contents.
   EXPECT_EQ("0,0 100x0", header->parent()->bounds().ToString());
@@ -657,8 +659,8 @@
 
   // Get the header a height of 20.
   header->SetPreferredSize(gfx::Size(10, 20));
-  EXPECT_TRUE(ViewTestApi(scroll_view_.get()).needs_layout());
-  scroll_view_->Layout();
+  EXPECT_TRUE(ViewTestApi(scroll_view).needs_layout());
+  widget()->LayoutRootViewIfNecessary();
   EXPECT_EQ("0,0 100x20", header->parent()->bounds().ToString());
   EXPECT_EQ("0,20 100x80", contents->parent()->bounds().ToString());
   if (contents->layer()) {
@@ -668,9 +670,10 @@
   EXPECT_EQ("0,0 0x0", contents->bounds().ToString());
 
   // Remove the header.
-  scroll_view_->SetHeader(nullptr);
+  scroll_view->SetHeader(nullptr);
   // SetHeader(nullptr) deletes header.
   header = nullptr;
+  widget()->LayoutRootViewIfNecessary();
   EXPECT_EQ("0,0 100x0", header_parent->bounds().ToString());
   EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
 }
@@ -1093,6 +1096,25 @@
   EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view_->size());
 }
 
+// Verifies ClipHeightTo() updates the ScrollView's preferred size.
+TEST_F(ScrollViewTest, ClipHeightToUpdatesPreferredSize) {
+  auto contents_view = std::make_unique<View>();
+  contents_view->SetPreferredSize({100, 100});
+  scroll_view_->SetContents(std::move(contents_view));
+  EXPECT_FALSE(scroll_view_->is_bounded());
+
+  constexpr int kMinHeight1 = 20;
+  constexpr int kMaxHeight1 = 80;
+  scroll_view_->ClipHeightTo(kMinHeight1, kMaxHeight1);
+  EXPECT_TRUE(scroll_view_->is_bounded());
+  EXPECT_EQ(scroll_view_->GetPreferredSize().height(), kMaxHeight1);
+
+  constexpr int kMinHeight2 = 200;
+  constexpr int kMaxHeight2 = 300;
+  scroll_view_->ClipHeightTo(kMinHeight2, kMaxHeight2);
+  EXPECT_EQ(scroll_view_->GetPreferredSize().height(), kMinHeight2);
+}
+
 TEST_F(ScrollViewTest, CornerViewVisibility) {
   View* contents = InstallContents();
   View* corner_view = ScrollViewTestApi(scroll_view_.get()).corner_view();
@@ -2270,6 +2292,21 @@
   EXPECT_EQ(gfx::ScrollOffset(10, 0), test_api.CurrentOffset());
 }
 
+TEST_F(WidgetScrollViewTest, UnboundedScrollViewUsesContentPreferredSize) {
+  auto contents = std::make_unique<View>();
+  constexpr gfx::Size kContentsPreferredSize(500, 500);
+  contents->SetPreferredSize(kContentsPreferredSize);
+  ScrollView* scroll_view =
+      AddScrollViewWithContents(std::move(contents), true);
+  EXPECT_EQ(kContentsPreferredSize, scroll_view->GetPreferredSize());
+
+  constexpr gfx::Insets kInsets(20);
+  scroll_view->SetBorder(CreateEmptyBorder(kInsets));
+  gfx::Size preferred_size_with_insets(kContentsPreferredSize);
+  preferred_size_with_insets.Enlarge(kInsets.width(), kInsets.height());
+  EXPECT_EQ(preferred_size_with_insets, scroll_view->GetPreferredSize());
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          WidgetScrollViewTestRTLAndLayers,
                          ::testing::Values(UiConfig::kLtr,
diff --git a/ui/views/examples/examples_with_content_main.cc b/ui/views/examples/examples_with_content_main.cc
index b8dd433..7fca765e 100644
--- a/ui/views/examples/examples_with_content_main.cc
+++ b/ui/views/examples/examples_with_content_main.cc
@@ -48,7 +48,7 @@
   // dlsym search path, which breaks (usually valid) assumptions made in
   // sandbox::InitLibcUrandomOverrides(). See http://crbug.com/374712.
   if (!browser_context) {
-    content::BrowserContext::SaveSessionState(nullptr);
+    browser_context->SaveSessionState();
     NOTREACHED();
   }
 }
diff --git a/ui/views/examples/native_theme_example.cc b/ui/views/examples/native_theme_example.cc
index bf0d7a86c..28eb339 100644
--- a/ui/views/examples/native_theme_example.cc
+++ b/ui/views/examples/native_theme_example.cc
@@ -39,11 +39,13 @@
 void InsertColorRow(GridLayout* layout,
                     base::StringPiece16 label_string,
                     ui::NativeTheme::ColorId color_id) {
-  auto label_view = std::make_unique<Label>(std::u16string(label_string));
+  layout->StartRow(GridLayout::kFixedSize, 0);
+  auto* label_view =
+      layout->AddView(std::make_unique<Label>(std::u16string(label_string)));
   label_view->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
   label_view->SetSelectable(true);
 
-  auto color_view = std::make_unique<Label>();
+  auto* color_view = layout->AddView(std::make_unique<Label>());
   color_view->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
   auto background_color =
       color_view->GetNativeTheme()->GetSystemColor(color_id);
@@ -57,16 +59,12 @@
   color_view->SetBackgroundColor(background_color);
   color_view->SetBackground(CreateSolidBackground(background_color));
   color_view->SetSelectable(true);
-
-  layout->StartRow(GridLayout::kFixedSize, 0);
-  layout->AddView(std::move(label_view));
-  layout->AddView(std::move(color_view));
 }
 
 // Returns a view of two columns where the first contains the identifier names
 // of ui::NativeTheme::ColorId and the second contains the color.
-std::unique_ptr<View> CreateAllColorsView() {
-  auto container = std::make_unique<View>();
+void CreateAllColorsView(ScrollView* scroll_view) {
+  auto* container = scroll_view->SetContents(std::make_unique<View>());
   auto* layout = container->SetLayoutManager(std::make_unique<GridLayout>());
   auto* column_set = layout->AddColumnSet(0);
   column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1.0,
@@ -163,9 +161,22 @@
   InsertColorRow(layout, COLOR_LABEL_ARGS(kColorId_DefaultIconColor));
   // Expands the view to allow for scrolling.
   container->SizeToPreferredSize();
-  return container;
 }
 
+class AllColorsScrollView : public ScrollView {
+ public:
+  AllColorsScrollView() {
+    constexpr int kMaxHeight = 300;
+    ClipHeightTo(0, kMaxHeight);
+  }
+
+ protected:
+  void OnThemeChanged() override {
+    ScrollView::OnThemeChanged();
+    CreateAllColorsView(this);
+  }
+};
+
 }  // namespace
 
 NativeThemeExample::NativeThemeExample()
@@ -175,9 +186,7 @@
 
 void NativeThemeExample::CreateExampleView(View* container) {
   container->SetLayoutManager(std::make_unique<FillLayout>());
-  auto scroll_view = std::make_unique<ScrollView>();
-  scroll_view->SetContents(CreateAllColorsView());
-  container->AddChildView(std::move(scroll_view));
+  container->AddChildView(std::make_unique<AllColorsScrollView>());
 }
 
 }  // namespace examples
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn b/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
index ea45ec53..677200e0 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
@@ -141,6 +141,7 @@
   deps = [
     ":base_page.m",
     ":cellular_setup_delegate.m",
+    "//ui/webui/resources/cr_elements/cr_lottie:cr_lottie.m",
     "//ui/webui/resources/js:assert.m",
   ]
   extra_deps = [ ":setup_loading_page_module" ]
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/button_bar.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/button_bar.html
index 65323d3..72beca8d 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/button_bar.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/button_bar.html
@@ -18,6 +18,10 @@
         padding: 10px 0;
       }
 
+      #forward:focus {
+        box-shadow: 0 0 0 2px var(--focus-shadow-color);
+      }
+
       #flex {
         flex: 1;
       }
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.html
index 916383a9..db0c29c3 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.html
@@ -30,9 +30,7 @@
     <iron-pages attr-for-selected="id"
         selected="[[selectedESimPageName_]]">
       <setup-loading-page id="profileLoadingPage"
-          delegate="[[delegate]]"
-          loading-message="[[i18n('eSimProfileDetectMessage')]]"
-          state="[[getLoadingPageState_(hasHadActiveCellularNetwork_)]]">
+          loading-message="[[getLoadingMessage_(hasHadActiveCellularNetwork_)]]">
       </setup-loading-page>
       <profile-discovery-list-page id="profileDiscoveryPage"
           pending-profiles="[[pendingProfiles_]]"
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.js
index ee35f8f..891470f 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/esim_flow_ui.js
@@ -671,14 +671,11 @@
           this.state_ === ESimUiState.PROFILE_SELECTION_INSTALLING;
     },
 
-    /**
-     * @param {boolean} hasActiveCellularNetwork
-     * @private
-     */
-    getLoadingPageState_(hasActiveCellularNetwork) {
-      return hasActiveCellularNetwork ?
-          LoadingPageState.CELLULAR_DISCONNECT_WARNING :
-          LoadingPageState.LOADING;
+    /** @private */
+    getLoadingMessage_() {
+      return this.hasHadActiveCellularNetwork_ ?
+          this.i18n('eSimProfileDetectDuringActiveCellularConnectionMessage') :
+          this.i18n('eSimProfileDetectMessage');
     },
 
     /**
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.html
index 0571520..2a1e88c 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.html
@@ -27,8 +27,9 @@
         selected="[[selectedPSimPageName_]]"
         selected-item="{{selectedPage_}}">
       <setup-loading-page id="simDetectPage"
-          delegate="[[delegate]]" state="[[getLoadingPageState_(state_)]]"
-          loading-message="[[i18n('establishNetworkConnectionMessage')]]">
+          loading-title="[[getLoadingTitle_(state_)]]"
+          loading-message="[[getLoadingMessage_(state_)]]"
+          is-sim-detect-error="[[isSimDetectError_(state_)]]">
       </setup-loading-page>
       <provisioning-page id="provisioningPage"
           delegate="[[delegate]]" show-error="{{showError_}}"
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.js
index ff00949..860748a7 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/psim_flow_ui.js
@@ -576,17 +576,28 @@
                               PSimUIState.ACTIVATION_FAILURE;
     },
 
-    /**
-     * @return {LoadingPageState}
-     * @private
-     */
-    getLoadingPageState_() {
+    /** @return {string} */
+    getLoadingMessage_() {
       if (this.state_ === PSimUIState.TIMEOUT_START_ACTIVATION) {
-        return LoadingPageState.SIM_DETECT_ERROR;
+        return this.i18n('simDetectPageErrorMessage');
       } else if (this.state_ === PSimUIState.FINAL_TIMEOUT_START_ACTIVATION) {
-        return LoadingPageState.FINAL_SIM_DETECT_ERROR;
+        return this.i18n('simDetectPageFinalErrorMessage');
       }
-      return LoadingPageState.LOADING;
+      return this.i18n('establishNetworkConnectionMessage');
+    },
+
+    /** @return {boolean} */
+    isSimDetectError_() {
+      return this.state_ === PSimUIState.TIMEOUT_START_ACTIVATION ||
+          this.state_ === PSimUIState.FINAL_TIMEOUT_START_ACTIVATION;
+    },
+
+    /** @return {string} */
+    getLoadingTitle_() {
+      if (this.delegate.shouldShowPageTitle() && this.isSimDetectError_()) {
+        return this.i18n('simDetectPageErrorTitle');
+      }
+      return '';
     },
   });
 
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html
index 43968642..407731f 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html
@@ -6,15 +6,17 @@
 <link rel="import" href="../../../cr_elements/hidden_style_css.html">
 <link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_lottie/cr_lottie.html">
 
 <dom-module id="setup-loading-page">
   <template>
     <style include="iron-flex cr-hidden-style">
-      paper-spinner-lite {
-        height: 32px;
-        margin-bottom: 8px;
-        width: 32px;
+      #animationContainer {
+        display: flex;
+        height: 216px;
+        justify-content: center;
+        margin-bottom: 30px;
+        margin-top: 24px;
       }
 
       #simDetectError {
@@ -26,20 +28,20 @@
         width: 100%;
       }
 
-      #networkConnectionMessage {
-        text-align: center;
+      #pageBody {
+        height: 222px;
       }
     </style>
-    <base-page title="[[getTitle_(state)]]"
-        message="[[getMessage_(state)]]"
-        message-icon="[[getMessageIcon_(state)]]">
-      <div slot="page-body" class="layout vertical center-center">
-        <paper-spinner-lite active hidden$="[[shouldShowSimDetectError_(state)]]">
-        </paper-spinner-lite>
-        <div id="networkConnectionMessage" hidden$="[[shouldShowSimDetectError_(state)]]" aria-live="polite">
-          [[loadingMessage]]
+    <base-page title="[[loadingTitle]]" message="[[loadingMessage]]">
+      <div slot="page-body" id="pageBody" class="layout vertical center-center">
+        <template is="dom-if" if="[[!isSimDetectError]]" restamp>
+          <div id="animationContainer">
+            <cr-lottie id="spinner" animation-url="spinner.json" autoplay>
+            </cr-lottie>
+          </div>
+        </template>
+        <div id="simDetectError" hidden$="[[!isSimDetectError]]">
         </div>
-        <div id="simDetectError" hidden$="[[!shouldShowSimDetectError_(state)]]"></div>
       </div>
     </base-page>
   </template>
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.js
index 060d033..c2c7772 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.js
@@ -2,19 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @enum {number} */
-/* #export */ const LoadingPageState = {
-  LOADING: 1,
-  SIM_DETECT_ERROR: 2,
-  FINAL_SIM_DETECT_ERROR: 3,
-  CELLULAR_DISCONNECT_WARNING: 4,
-};
-
 /**
- * Loading subpage in Cellular Setup flow. This element contains image
- * asset and description to indicate that a SIM detection or eSIM profiles
- * loading is in progress. It can show a 'detecting sim' error or a 'cellular
- * disconnection' warning depending on its state.
+ * Loading subpage in Cellular Setup flow that shows an in progress operation or
+ * an error. This element contains error image asset and loading animation.
  */
 Polymer({
   is: 'setup-loading-page',
@@ -22,15 +12,6 @@
   behaviors: [I18nBehavior],
 
   properties: {
-    /** @type {!cellular_setup.CellularSetupDelegate} */
-    delegate: Object,
-
-    /** @type {!LoadingPageState} */
-    state: {
-      type: Object,
-      value: LoadingPageState.LOADING,
-    },
-
     /**
      * Message displayed with spinner when in LOADING state.
      */
@@ -38,61 +19,22 @@
       type: String,
       value: '',
     },
-  },
 
-  /**
-   * @param {LoadingPageState} state
-   * @return {?string}
-   * @private
-   */
-  getTitle_(state) {
-    if (this.delegate.shouldShowPageTitle() &&
-        state === LoadingPageState.SIM_DETECT_ERROR) {
-      return this.i18n('simDetectPageErrorTitle');
-    }
-    return null;
-  },
+    /**
+     * Title for page if needed.
+     * @type {?string}
+     */
+    loadingTitle: {
+      type: Object,
+      value: '',
+    },
 
-  /**
-   * @param {LoadingPageState} state
-   * @return {string}
-   * @private
-   */
-  getMessage_(state) {
-    switch (state) {
-      case LoadingPageState.SIM_DETECT_ERROR:
-        return this.i18n('simDetectPageErrorMessage');
-      case LoadingPageState.FINAL_SIM_DETECT_ERROR:
-        return this.i18n('simDetectPageFinalErrorMessage');
-      case LoadingPageState.CELLULAR_DISCONNECT_WARNING:
-        return this.i18n('eSimConnectionWarning');
-      case LoadingPageState.LOADING:
-        return '';
-      default:
-        assertNotReached();
-    }
-  },
-
-  /**
-   * @param {LoadingPageState} state
-   * @return {string}
-   * @private
-   */
-  getMessageIcon_(state) {
-    if (state === LoadingPageState.CELLULAR_DISCONNECT_WARNING) {
-      return 'info';
-    }
-
-    return '';
-  },
-
-  /**
-   * @param {LoadingPageState} state
-   * @return {boolean}
-   * @private
-   */
-  shouldShowSimDetectError_(state) {
-    return state === LoadingPageState.SIM_DETECT_ERROR ||
-        state === LoadingPageState.FINAL_SIM_DETECT_ERROR;
+    /**
+     * Displays a sim detect error graphic if true.
+     */
+    isSimDetectError: {
+      type: Boolean,
+      value: false,
+    },
   },
 });
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 42499fd4f..7de1bdec 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
@@ -163,7 +163,9 @@
           </cr-policy-indicator>
         </template>
         <!-- This can only be shown if isUpdatedCellularUiEnabled_ is enabled. -->
-        <template is="dom-if" if="[[isPSimPendingActivationNetwork_]]" restamp>
+        <template is="dom-if"
+            if="[[shouldShowActivateButton_(isPSimPendingActivationNetwork_,
+              showButtons)]]" restamp>
           <cr-button id="activateButton"
               aria-label$="[[getActivateBtnA11yLabel_(item)]]"
               on-click="onActivateButtonClick_"
@@ -177,12 +179,12 @@
           <paper-spinner-lite id="activatingPSimSpinner" active>
           </paper-spinner-lite>
           [[i18n('networkListItemActivating')]]
-          <div class="separator"></div>
+          <div class="separator" hidden$="[[!showButtons]]"></div>
         </template>
         <template is="dom-if"
             if="[[isSubpageButtonVisible_(networkState, showButtons, disabled_,
               networkState.typeState.cellular.simLocked,
-              isPSimPendingActivationNetwork_)]]" restamp>
+              isPSimPendingActivationNetwork_, isPSimActivatingNetwork_)]]" restamp>
           <div>
             <cr-icon-button class="subpage-arrow"
                 id="subpageButton"
@@ -195,7 +197,7 @@
           </div>
         </template>
         <template is="dom-if" if="[[shouldShowUnlockButton_(networkState,
-          networkState.typeState.cellular.simLocked)]]" restamp>
+          networkState.typeState.cellular.simLocked, showButtons)]]" restamp>
           <cr-button id="unlockButton"
               aria-label$="[[getUnlockBtnA11yLabel_(item)]]"
               on-click="onUnlockButtonClick_"
@@ -209,7 +211,8 @@
               device-state="[[deviceState]]">
           </sim-lock-dialogs>
         </template>
-        <template is="dom-if" if="[[isESimPendingProfile_]]" restamp>
+        <template is="dom-if" if="[[shouldShowInstallButton_(
+            isESimPendingProfile_, showButtons)]]" restamp>
           <cr-button id="installButton"
               aria-label$="[[getInstallBtnA11yLabel_(item)]]"
               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 20eaf25b..325b883 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
@@ -618,11 +618,13 @@
    * @private
    */
   isSubpageButtonVisible_(networkState, showButtons, disabled_) {
+    if (!this.showButtons) {
+      return false;
+    }
     if (this.isPSimPendingActivationNetwork_ || this.isPSimActivatingNetwork_) {
       return true;
     }
-    return !!networkState && showButtons && !disabled_ &&
-        !this.shouldShowUnlockButton_();
+    return !!networkState && !disabled_ && !this.shouldShowUnlockButton_();
   },
 
   /**
@@ -673,15 +675,17 @@
             this.networkState, this.showButtons, this.disabled_) &&
         this.$$('#subpageButton') === this.shadowRoot.activeElement) {
       this.fireShowDetails_(event);
-    } else if (this.isESimPendingProfile_) {
+    } else if (this.shouldShowInstallButton_()) {
       this.onInstallButtonClick_(event);
     } else if (this.shouldShowUnlockButton_()) {
       this.onUnlockButtonClick_();
     } else if (this.item && this.item.hasOwnProperty('customItemName')) {
       this.fire('custom-item-selected', this.item);
+    } else if (this.shouldShowActivateButton_()) {
+      this.fireShowDetails_(event);
     } else if (
-        this.isPSimPendingActivationNetwork_ ||
-        this.isPSimUnavailableNetwork_ || this.isPSimActivatingNetwork_) {
+        this.showButtons &&
+        (this.isPSimUnavailableNetwork_ || this.isPSimActivatingNetwork_)) {
       this.fireShowDetails_(event);
     } else {
       this.fire('selected', this.item);
@@ -819,6 +823,17 @@
   },
 
   /**
+   * @return {boolean}
+   * @private
+   */
+  shouldShowActivateButton_() {
+    if (!this.showButtons) {
+      return false;
+    }
+    return this.isPSimPendingActivationNetwork_;
+  },
+
+  /**
    * @return {string}
    * @private
    */
@@ -902,6 +917,9 @@
    * @private
    */
   shouldShowUnlockButton_() {
+    if (!this.showButtons) {
+      return false;
+    }
     if (!this.networkState || !this.networkState.typeState.cellular ||
         !this.isUpdatedCellularUiEnabled_) {
       return false;
@@ -918,6 +936,17 @@
   },
 
   /**
+   * @return {boolean}
+   * @private
+   */
+  shouldShowInstallButton_() {
+    if (!this.showButtons) {
+      return false;
+    }
+    return this.isESimPendingProfile_;
+  },
+
+  /**
    * @return {string}
    * @private
    */
diff --git a/ui/webui/resources/cr_components/chromeos/os_cr_components.gni b/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
index 6e0f162..09b92ec 100644
--- a/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
+++ b/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
@@ -49,7 +49,6 @@
   "ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.html|setCellularSetupRemoteForTesting,getCellularSetupRemote,setESimManagerRemoteForTesting,getESimManagerRemote,observeESimManager",
   "ui/webui/resources/cr_components/chromeos/cellular_setup/esim_manager_utils.html|getPendingESimProfiles,getNonPendingESimProfiles,getNumESimProfiles,getEuicc,getESimProfile,getESimProfileProperties",
   "ui/webui/resources/cr_components/chromeos/cellular_setup/esim_manager_listener_behavior.html|ESimManagerListenerBehavior",
-  "ui/webui/resources/cr_components/chromeos/cellular_setup/setup_loading_page.html|LoadingPageState",
   "ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_browser_proxy.html|BrowserProxy,BrowserProxyImpl",
   "ui/webui/resources/cr_components/chromeos/multidevice_setup/mojo_api.html|MojoInterfaceProvider,MojoInterfaceProviderImpl",
   "ui/webui/resources/cr_components/chromeos/multidevice_setup/multidevice_setup_delegate.html|MultiDeviceSetupDelegate",
diff --git a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
index 115f054..5b5bdfe3 100644
--- a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
+++ b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
@@ -58,7 +58,7 @@
     /** @private {Array<!CrRadioButtonElement>} */
     buttons_: null,
 
-    /** @private {EventTracker} */
+    /** @private {cr.EventTracker} */
     buttonEventTracker_: null,
 
     /** @private {Map<string, number>} */
@@ -84,7 +84,7 @@
         ['PageDown', 1],
         ['PageUp', -1],
       ]);
-      this.buttonEventTracker_ = new EventTracker();
+      this.buttonEventTracker_ = new cr.EventTracker();
 
       this.populateBound_ = () => this.populate_();
       // Needed for when the radio buttons change when using dom-repeat or
diff --git a/ui/webui/resources/cr_elements/cr_slider/cr_slider.js b/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
index 9ce4c4a..064e8456 100644
--- a/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
+++ b/ui/webui/resources/cr_elements/cr_slider/cr_slider.js
@@ -187,7 +187,7 @@
     /** @private {Map<string, number>} */
     deltaKeyMap_: null,
 
-    /** @private {EventTracker} */
+    /** @private {cr.EventTracker} */
     draggingEventTracker_: null,
 
     /** @override */
@@ -201,7 +201,7 @@
         ['ArrowLeft', this.isRtl_ ? 1 : -1],
         ['ArrowRight', this.isRtl_ ? -1 : 1],
       ]);
-      this.draggingEventTracker_ = new EventTracker();
+      this.draggingEventTracker_ = new cr.EventTracker();
     },
 
     /**
diff --git a/ui/webui/resources/js/BUILD.gn b/ui/webui/resources/js/BUILD.gn
index e1e8a220..a049d0e 100644
--- a/ui/webui/resources/js/BUILD.gn
+++ b/ui/webui/resources/js/BUILD.gn
@@ -173,6 +173,7 @@
 }
 
 js_library("event_tracker") {
+  deps = [ ":cr" ]
 }
 
 js_library("icon") {
diff --git a/ui/webui/resources/js/cr/ui/context_menu_handler.js b/ui/webui/resources/js/cr/ui/context_menu_handler.js
index 801d4bdd..3a6f1fd 100644
--- a/ui/webui/resources/js/cr/ui/context_menu_handler.js
+++ b/ui/webui/resources/js/cr/ui/context_menu_handler.js
@@ -26,8 +26,8 @@
   class ContextMenuHandler extends cr.EventTarget {
     constructor() {
       super();
-      /** @private {!EventTracker} */
-      this.showingEvents_ = new EventTracker();
+      /** @private {!cr.EventTracker} */
+      this.showingEvents_ = new cr.EventTracker();
 
       /**
        * The menu that we are currently showing.
diff --git a/ui/webui/resources/js/cr/ui/focus_row.js b/ui/webui/resources/js/cr/ui/focus_row.js
index c297608..1a22300f 100644
--- a/ui/webui/resources/js/cr/ui/focus_row.js
+++ b/ui/webui/resources/js/cr/ui/focus_row.js
@@ -40,8 +40,8 @@
       /** @type {cr.ui.FocusRowDelegate|undefined} */
       this.delegate = delegate;
 
-      /** @protected {!EventTracker} */
-      this.eventTracker = new EventTracker;
+      /** @protected {!cr.EventTracker} */
+      this.eventTracker = new cr.EventTracker;
     }
 
     /**
diff --git a/ui/webui/resources/js/cr/ui/menu_button.js b/ui/webui/resources/js/cr/ui/menu_button.js
index 0cfd92b6..00ec20a 100644
--- a/ui/webui/resources/js/cr/ui/menu_button.js
+++ b/ui/webui/resources/js/cr/ui/menu_button.js
@@ -63,7 +63,7 @@
 
       // An event tracker for events we only connect to while the menu is
       // displayed.
-      this.showingEvents_ = new EventTracker();
+      this.showingEvents_ = new cr.EventTracker();
 
       this.anchorType = cr.ui.AnchorType.BELOW;
       this.invertLeftRight = false;
diff --git a/ui/webui/resources/js/event_tracker.js b/ui/webui/resources/js/event_tracker.js
index fd0634b..58bf1df 100644
--- a/ui/webui/resources/js/event_tracker.js
+++ b/ui/webui/resources/js/event_tracker.js
@@ -11,84 +11,86 @@
  * calling Function.bind.
  */
 
-// eslint-disable-next-line no-var
-/* #export */ var EventTracker = class {
-  /**
-   * Create an EventTracker to track a set of events.
-   * EventTracker instances are typically tied 1:1 with other objects or
-   * DOM elements whose listeners should be removed when the object is disposed
-   * or the corresponding elements are removed from the DOM.
-   */
-  constructor() {
+cr.define('cr', function() {
+  /* #export */ class EventTracker {
     /**
-     * @type {Array<EventTrackerEntry>}
-     * @private
+     * Create an EventTracker to track a set of events.
+     * EventTracker instances are typically tied 1:1 with other objects or
+     * DOM elements whose listeners should be removed when the object is
+     * disposed or the corresponding elements are removed from the DOM.
      */
-    this.listeners_ = [];
+    constructor() {
+      /**
+       * @type {Array<EventTrackerEntry>}
+       * @private
+       */
+      this.listeners_ = [];
+    }
+
+    /**
+     * Add an event listener - replacement for EventTarget.addEventListener.
+     * @param {!EventTarget} target The DOM target to add a listener to.
+     * @param {string} eventType The type of event to subscribe to.
+     * @param {EventListener|Function} listener The listener to add.
+     * @param {boolean=} opt_capture Whether to invoke during the capture phase.
+     */
+    add(target, eventType, listener, opt_capture) {
+      const capture = !!opt_capture;
+      const h = {
+        target: target,
+        eventType: eventType,
+        listener: listener,
+        capture: capture,
+      };
+      this.listeners_.push(h);
+      target.addEventListener(eventType, listener, capture);
+    }
+
+    /**
+     * Remove any specified event listeners added with this EventTracker.
+     * @param {!EventTarget} target The DOM target to remove a listener from.
+     * @param {?string} eventType The type of event to remove.
+     */
+    remove(target, eventType) {
+      this.listeners_ = this.listeners_.filter(listener => {
+        if (listener.target === target &&
+            (!eventType || (listener.eventType === eventType))) {
+          EventTracker.removeEventListener(listener);
+          return false;
+        }
+        return true;
+      });
+    }
+
+    /** Remove all event listeners added with this EventTracker. */
+    removeAll() {
+      this.listeners_.forEach(
+          listener => EventTracker.removeEventListener(listener));
+      this.listeners_ = [];
+    }
+
+    /**
+     * Remove a single event listener given it's tracking entry. It's up to the
+     * caller to ensure the entry is removed from listeners_.
+     * @param {EventTrackerEntry} entry The entry describing the listener to
+     * remove.
+     */
+    static removeEventListener(entry) {
+      entry.target.removeEventListener(
+          entry.eventType, entry.listener, entry.capture);
+    }
   }
 
   /**
-   * Add an event listener - replacement for EventTarget.addEventListener.
-   * @param {!EventTarget} target The DOM target to add a listener to.
-   * @param {string} eventType The type of event to subscribe to.
-   * @param {EventListener|Function} listener The listener to add.
-   * @param {boolean=} opt_capture Whether to invoke during the capture phase.
+   * The type of the internal tracking entry.
+   * @typedef {{target: !EventTarget,
+   *            eventType: string,
+   *            listener: (EventListener|Function),
+   *            capture: boolean}}
    */
-  add(target, eventType, listener, opt_capture) {
-    const capture = !!opt_capture;
-    const h = {
-      target: target,
-      eventType: eventType,
-      listener: listener,
-      capture: capture,
-    };
-    this.listeners_.push(h);
-    target.addEventListener(eventType, listener, capture);
-  }
+  let EventTrackerEntry;
 
-  /**
-   * Remove any specified event listeners added with this EventTracker.
-   * @param {!EventTarget} target The DOM target to remove a listener from.
-   * @param {?string} eventType The type of event to remove.
-   */
-  remove(target, eventType) {
-    this.listeners_ = this.listeners_.filter(listener => {
-      if (listener.target === target &&
-          (!eventType || (listener.eventType === eventType))) {
-        EventTracker.removeEventListener(listener);
-        return false;
-      }
-      return true;
-    });
-  }
-
-  /** Remove all event listeners added with this EventTracker. */
-  removeAll() {
-    this.listeners_.forEach(
-        listener => EventTracker.removeEventListener(listener));
-    this.listeners_ = [];
-  }
-
-  /**
-   * Remove a single event listener given it's tracking entry. It's up to the
-   * caller to ensure the entry is removed from listeners_.
-   * @param {EventTrackerEntry} entry The entry describing the listener to
-   * remove.
-   */
-  static removeEventListener(entry) {
-    entry.target.removeEventListener(
-        entry.eventType, entry.listener, entry.capture);
-  }
-};
-
-/**
- * The type of the internal tracking entry.
- * @typedef {{target: !EventTarget,
- *            eventType: string,
- *            listener: (EventListener|Function),
- *            capture: boolean}}
- */
-// eslint-disable-next-line no-var
-var EventTrackerEntry;
-
-/* #ignore */ console.warn('crbug/1173575, non-JS module files deprecated.');
+  // #cr_define_end
+  console.warn('crbug/1173575, non-JS module files deprecated.');
+  return {EventTracker};
+});
diff --git a/ui/webui/resources/tools/js_modulizer.gni b/ui/webui/resources/tools/js_modulizer.gni
index 452ed5ce..d4ca2084b 100644
--- a/ui/webui/resources/tools/js_modulizer.gni
+++ b/ui/webui/resources/tools/js_modulizer.gni
@@ -3,10 +3,10 @@
 # found in the LICENSE file.
 
 common_namespace_rewrites = [
-  "cr_slider.SliderTick|SliderTick",
   "cr.addSingletonGetter|addSingletonGetter",
   "cr.addWebUIListener|addWebUIListener",
   "cr.dispatchSimpleEvent|dispatchSimpleEvent",
+  "cr.EventTracker|EventTracker",
   "cr.icon.getFavicon|getFavicon",
   "cr.icon.getImage|getImage",
   "cr.isAndroid|isAndroid",
@@ -19,6 +19,7 @@
   "cr.png.convertImageSequenceToPng|convertImageSequenceToPng",
   "cr.removeWebUIListener|removeWebUIListener",
   "cr.sendWithPromise|sendWithPromise",
+  "cr_slider.SliderTick|SliderTick",
   "cr.toastManager.getToastManager|getToastManager",
   "cr.ui.FocusOutlineManager|FocusOutlineManager",
   "cr.ui.FocusRowBehavior|FocusRowBehavior",
diff --git a/weblayer/browser/browser_context_impl.cc b/weblayer/browser/browser_context_impl.cc
index 431ae2b..4a933a9 100644
--- a/weblayer/browser/browser_context_impl.cc
+++ b/weblayer/browser/browser_context_impl.cc
@@ -124,7 +124,7 @@
 }
 
 BrowserContextImpl::~BrowserContextImpl() {
-  NotifyWillBeDestroyed(this);
+  NotifyWillBeDestroyed();
 
   BrowserContextDependencyManager::GetInstance()->DestroyBrowserContextServices(
       this);