diff --git a/DEPS b/DEPS
index 9dbc2a0..2e4ad48 100644
--- a/DEPS
+++ b/DEPS
@@ -280,15 +280,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '1716e84b8f5da34319d3370d307ad97c9d19ff3d',
+  'skia_revision': 'fb1fe33fc2f602988fb35d947bfd3ac49036baf2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '5397eefc0cee7e1b6ea4a794853ca772222a1dfe',
+  'v8_revision': '84fda7aefc3c95545250ab9614da753f5cd8a752',
   # 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': '94bbb40a42cdbe7700739f733df9df2835ba453e',
+  'angle_revision': 'bf52137bc90b8778354c8bcbb253caf228164ce2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -331,7 +331,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '7c151abb6903ddb9c9ed5a1c7b962819c1b2d36f',
+  'freetype_revision': '3414fef74f9dd1858aca521f93ebadca9ab36f96',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -351,7 +351,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'd7cbf23b7d50ba1069dada756ad736fd523ee015',
+  'catapult_revision': 'be7e8caaea166110c3b050b47804c00925194b20',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -359,7 +359,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '1aa36584d580ed5aa2caf7a8533f2c89b16ab66b',
+  'devtools_frontend_revision': 'dec5d619fec1569276d748b246c6eb78339d5fd7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -395,7 +395,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '342931ae08a24f58f19e05e13e39dd275c687bfe',
+  'dawn_revision': '2468978f0b9944b756455a697f1097c69a0ff670',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -929,7 +929,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'jh10aKp2in7ld755-cUy8ZafrE-iVdH__bQjHMhmTA4C',
+          'version': 's85b63CjV4De8612Y-6JytJjowIAUUym2RzGH6ct7jsC',
       },
     ],
     'condition': 'checkout_android',
@@ -1564,7 +1564,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f56e4abd18a85f6719dcff6fcdc305673d0f3f4e',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '9bad4b45b023dcf07fed418a0b62ef6cf18f44ae',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1734,7 +1734,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'a65914cd9634244099dc6105de5f2b33f2c1c823',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '401158ba9a267ed2fb986bc4b89e84f490aae4cc',
+    Var('webrtc_git') + '/src.git' + '@' + 'dd4884dfb2998769255a9d8858989fb6ec21361e',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1807,7 +1807,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@748ff790b2e1de7806d4b90865223e936cdf5376',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0a784d375175d0bd1aa32489d96df5c21d23794b',
     'condition': 'checkout_src_internal',
   },
 
@@ -1859,7 +1859,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'gNSSAdVa5JtNaO15xW6x6KoDOldWgGNOCIOEBgVNNb8C',
+        'version': 'jNStO58za5CBk027NGwO_ZnrHIDSaKjMdpywbEtxfdEC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/devui/ComponentsListFragmentTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/devui/ComponentsListFragmentTest.java
index ea871b17..5fb320c 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/devui/ComponentsListFragmentTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/devui/ComponentsListFragmentTest.java
@@ -90,6 +90,14 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
+    public void testHasPublicNoArgsConstructor() throws Throwable {
+        ComponentsListFragment fragment = new ComponentsListFragment();
+        Assert.assertNotNull(fragment);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
     public void testComponentsDownloadDirectory_isEmpty() throws Throwable {
         sComponentsDownloadDir.mkdirs();
         CallbackHelper helper = getComponentInfoLoadedListener();
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/devui/FlagsFragmentTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/devui/FlagsFragmentTest.java
index 26103f3..9436bec 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/devui/FlagsFragmentTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/devui/FlagsFragmentTest.java
@@ -47,6 +47,7 @@
 import androidx.test.espresso.DataInteraction;
 import androidx.test.espresso.Espresso;
 import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -69,6 +70,7 @@
 import org.chromium.android_webview.test.AwJUnit4ClassRunner;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.test.BaseActivityTestRule;
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
@@ -79,11 +81,12 @@
 
 /**
  * UI tests for {@link FlagsFragment}.
- * <p>
- * These tests should not be batched to make sure that the DeveloperUiService is killed
- * after each test, leaving a clean state.
+ *
+ * <p>These tests should not be batched to make sure that the DeveloperUiService is killed after
+ * each test, leaving a clean state.
  */
 @RunWith(AwJUnit4ClassRunner.class)
+@Batch(Batch.PER_CLASS)
 @DisabledTest(message = "Flaky test: https://crbug.com/1312662")
 public class FlagsFragmentTest {
     @Rule
@@ -238,6 +241,14 @@
     }
 
     @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testHasPublicNoArgsConstructor() throws Throwable {
+        FlagsFragment fragment = new FlagsFragment();
+        Assert.assertNotNull(fragment);
+    }
+
+    @Test
     @MediumTest
     @Feature({"AndroidWebView"})
     public void testSearchEmptyByDefault() throws Throwable {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java
index b22fbf9..165b461 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java
@@ -36,13 +36,16 @@
 import androidx.test.espresso.intent.Intents;
 import androidx.test.espresso.intent.matcher.IntentMatchers;
 import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.android_webview.devui.HomeFragment;
 import org.chromium.android_webview.devui.MainActivity;
 import org.chromium.android_webview.devui.R;
 import org.chromium.android_webview.devui.WebViewPackageError;
@@ -94,6 +97,14 @@
     }
 
     @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testHasPublicNoArgsConstructor() throws Throwable {
+        HomeFragment fragment = new HomeFragment();
+        Assert.assertNotNull(fragment);
+    }
+
+    @Test
     @MediumTest
     @Feature({"AndroidWebView"})
     // Test when the system WebView provider is the same package from which the developer UI is
diff --git a/android_webview/nonembedded/java/src/org/chromium/android_webview/devui/FlagsFragment.java b/android_webview/nonembedded/java/src/org/chromium/android_webview/devui/FlagsFragment.java
index 68b0b03fb..e1391dd7 100644
--- a/android_webview/nonembedded/java/src/org/chromium/android_webview/devui/FlagsFragment.java
+++ b/android_webview/nonembedded/java/src/org/chromium/android_webview/devui/FlagsFragment.java
@@ -80,7 +80,7 @@
             STATE_ENABLED,
     };
 
-    private final boolean mEnabled;
+    private boolean mEnabled;
 
     private Map<String, Boolean> mOverriddenFlags = new HashMap<>();
     private FlagsListAdapter mListAdapter;
@@ -97,6 +97,11 @@
         mEnabled = enabled;
     }
 
+    public FlagsFragment() {
+        // All fragments must have a public no-args constructor
+        // https://developer.android.com/reference/android/app/Fragment
+    }
+
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
diff --git a/ash/display/mouse_cursor_event_filter.cc b/ash/display/mouse_cursor_event_filter.cc
index 0782bf4c..04ab12de 100644
--- a/ash/display/mouse_cursor_event_filter.cc
+++ b/ash/display/mouse_cursor_event_filter.cc
@@ -46,10 +46,6 @@
   if (event->flags() & ui::EF_IS_SYNTHESIZED)
     return;
 
-  // Don't warp if the event specifically requests us not to.
-  if (event->flags() & ui::EF_NOT_SUITABLE_FOR_MOUSE_WARPING)
-    return;
-
   // Handle both MOVED and DRAGGED events here because when the mouse pointer
   // enters the other root window while dragging, the underlying window system
   // (at least X11) stops generating a ui::ET_MOUSE_MOVED event.
@@ -58,11 +54,15 @@
     return;
   }
 
+  bool mouse_warp_enabled =
+      mouse_warp_enabled_ &&
+      (event->flags() & ui::EF_NOT_SUITABLE_FOR_MOUSE_WARPING) == 0;
+
   Shell::Get()
       ->window_tree_host_manager()
       ->cursor_window_controller()
       ->UpdateLocation();
-  mouse_warp_controller_->SetEnabled(mouse_warp_enabled_);
+  mouse_warp_controller_->SetEnabled(mouse_warp_enabled);
 
   if (mouse_warp_controller_->WarpMouseCursor(event))
     event->StopPropagation();
diff --git a/ash/strings/ash_strings_es.xtb b/ash/strings/ash_strings_es.xtb
index ec7ffe1..85258a72 100644
--- a/ash/strings/ash_strings_es.xtb
+++ b/ash/strings/ash_strings_es.xtb
@@ -160,7 +160,7 @@
 <translation id="1836215606488044471">Asistente (cargando...)</translation>
 <translation id="1838895407229022812">Luz nocturna desactivada.</translation>
 <translation id="1864454756846565995">Dispositivo USB-C (puerto trasero)</translation>
-<translation id="1879018240766558464">Las ventanas de incógnito no se admiten actualmente. Se guardarán las demás aplicaciones.</translation>
+<translation id="1879018240766558464">Las ventanas de Incógnito no se admiten actualmente. Se guardarán las demás aplicaciones.</translation>
 <translation id="1882814835921407042">Sin red móvil</translation>
 <translation id="1882897271359938046">Proyectando en <ph name="DISPLAY_NAME" /></translation>
 <translation id="1885785240814121742">Desbloquear con huella digital</translation>
@@ -765,7 +765,7 @@
 <translation id="5400461572260843123">Ajustes rápidos. Pulsa la tecla de búsqueda y la flecha izquierda para acceder al centro de notificaciones.</translation>
 <translation id="5426063383988017631">Menú Ajustes cerrado</translation>
 <translation id="5428899915242071344">Empezar a seleccionar</translation>
-<translation id="5429993543155113935">Las ventanas de incógnito no se admiten actualmente</translation>
+<translation id="5429993543155113935">Las ventanas de Incógnito no se admiten actualmente</translation>
 <translation id="5430931332414098647">Conexión compartida instantánea</translation>
 <translation id="5431318178759467895">Color</translation>
 <translation id="5433020815079095860">Entrada de audio</translation>
@@ -926,7 +926,7 @@
 <translation id="6431865393913628856">Grabar pantalla</translation>
 <translation id="6445835306623867477"><ph name="ROUTE_TITLE" /> en <ph name="RECEIVER_NAME" /></translation>
 <translation id="6447111710783417522"><ph name="DATE" />, eventos: <ph name="NUMBER" /></translation>
-<translation id="6449483711453944360">Las aplicaciones Linux y las ventanas de incógnito no se admiten actualmente</translation>
+<translation id="6449483711453944360">Las aplicaciones Linux y las ventanas de Incógnito no se admiten actualmente</translation>
 <translation id="6452181791372256707">Rechazar</translation>
 <translation id="6453179446719226835">Se ha cambiado el idioma</translation>
 <translation id="6459472438155181876">Extendiendo pantalla a <ph name="DISPLAY_NAME" /></translation>
@@ -1110,7 +1110,7 @@
 <translation id="7515998400212163428">Android</translation>
 <translation id="7526573455193969409">Es posible que la red esté supervisada</translation>
 <translation id="7536035074519304529">Dirección IP: <ph name="ADDRESS" /></translation>
-<translation id="7543399541175347147">Las aplicaciones Linux y las ventanas de incógnito no se admiten actualmente. Se guardarán las demás aplicaciones.</translation>
+<translation id="7543399541175347147">Las aplicaciones Linux y las ventanas de Incógnito no se admiten actualmente. Se guardarán las demás aplicaciones.</translation>
 <translation id="7544300628205093162">La retroiluminación del teclado está activada</translation>
 <translation id="7548434653388805669">Es hora de acostarse</translation>
 <translation id="7551643184018910560">Fijar en la estantería</translation>
diff --git a/ash/webui/camera_app_ui/document_scanner_installer.cc b/ash/webui/camera_app_ui/document_scanner_installer.cc
index 6c043c2..1500f09 100644
--- a/ash/webui/camera_app_ui/document_scanner_installer.cc
+++ b/ash/webui/camera_app_ui/document_scanner_installer.cc
@@ -24,14 +24,19 @@
 
 DocumentScannerInstaller::~DocumentScannerInstaller() = default;
 
-void DocumentScannerInstaller::GetLibraryPath(
-    OnGetLibraryPathCallback callback) {
+void DocumentScannerInstaller::RegisterLibraryPathCallback(
+    LibraryPathCallback callback) {
   base::AutoLock auto_lock(library_path_lock_);
   if (library_path_.empty()) {
-    get_library_path_callbacks_.push_back(std::move(callback));
-    return;
+    if (!installing_) {
+      ui_task_runner_->PostTask(
+          FROM_HERE, base::BindOnce(&DocumentScannerInstaller::TriggerInstall,
+                                    base::Unretained(this)));
+    }
+    library_path_callbacks_.push_back(std::move(callback));
+  } else {
+    std::move(callback).Run(library_path_);
   }
-  std::move(callback).Run(library_path_);
 }
 
 void DocumentScannerInstaller::TriggerInstall() {
@@ -41,6 +46,12 @@
     return;
   }
 
+  base::AutoLock auto_lock(library_path_lock_);
+  if (installing_) {
+    return;
+  }
+  installing_ = true;
+
   dlcservice::InstallRequest install_request;
   install_request.set_id(kDocumentScannerDlcId);
   chromeos::DlcserviceClient::Get()->Install(
@@ -58,13 +69,15 @@
   base::AutoLock auto_lock(library_path_lock_);
   if (install_result.error == dlcservice::kErrorNone) {
     library_path_ = install_result.root_path;
-    for (auto& callback : get_library_path_callbacks_) {
-      std::move(callback).Run(library_path_);
-    }
-    get_library_path_callbacks_.clear();
   } else {
-    LOG(ERROR) << "Failed to install document scanner DLC";
+    LOG(ERROR) << "Failed to install document scanner DLC: "
+               << install_result.error;
   }
+  for (auto& callback : library_path_callbacks_) {
+    std::move(callback).Run(library_path_);
+  }
+  library_path_callbacks_.clear();
+  installing_ = false;
 }
 
 }  // namespace ash
diff --git a/ash/webui/camera_app_ui/document_scanner_installer.h b/ash/webui/camera_app_ui/document_scanner_installer.h
index c6e974e..43324688 100644
--- a/ash/webui/camera_app_ui/document_scanner_installer.h
+++ b/ash/webui/camera_app_ui/document_scanner_installer.h
@@ -22,7 +22,7 @@
 // Singleton installer for document scanner library via DLC service.
 class DocumentScannerInstaller {
  public:
-  using OnGetLibraryPathCallback =
+  using LibraryPathCallback =
       base::OnceCallback<void(const std::string& library_path)>;
 
   static DocumentScannerInstaller* GetInstance();
@@ -31,7 +31,7 @@
   DocumentScannerInstaller& operator=(const DocumentScannerInstaller&) = delete;
   ~DocumentScannerInstaller();
 
-  void GetLibraryPath(OnGetLibraryPathCallback callback);
+  void RegisterLibraryPathCallback(LibraryPathCallback callback);
 
   // It should only be called on the UI thread.
   void TriggerInstall();
@@ -46,9 +46,11 @@
 
   std::string library_path_ GUARDED_BY(library_path_lock_);
 
-  std::vector<OnGetLibraryPathCallback> get_library_path_callbacks_
+  std::vector<LibraryPathCallback> library_path_callbacks_
       GUARDED_BY(library_path_lock_);
 
+  bool installing_ GUARDED_BY(library_path_lock_) = false;
+
   scoped_refptr<base::SingleThreadTaskRunner> original_task_runner_ = nullptr;
 
   scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_ = nullptr;
diff --git a/ash/webui/camera_app_ui/document_scanner_service_client.cc b/ash/webui/camera_app_ui/document_scanner_service_client.cc
index 1fc962e..af18616 100644
--- a/ash/webui/camera_app_ui/document_scanner_service_client.cc
+++ b/ash/webui/camera_app_ui/document_scanner_service_client.cc
@@ -155,15 +155,22 @@
   if (IsEnabledOnRootfs()) {
     LoadDocumentScanner(kLibDocumentScannerDefaultDir);
   } else if (IsEnabledOnDlc()) {
-    DocumentScannerInstaller::GetInstance()->GetLibraryPath(base::BindPostTask(
-        base::SequencedTaskRunnerHandle::Get(),
-        base::BindOnce(&DocumentScannerServiceClient::LoadDocumentScanner,
-                       weak_ptr_factory_.GetWeakPtr())));
+    DocumentScannerInstaller::GetInstance()->RegisterLibraryPathCallback(
+        base::BindPostTask(
+            base::SequencedTaskRunnerHandle::Get(),
+            base::BindOnce(&DocumentScannerServiceClient::LoadDocumentScanner,
+                           weak_ptr_factory_.GetWeakPtr())));
   }
 }
 
 void DocumentScannerServiceClient::LoadDocumentScanner(
     const std::string& lib_path) {
+  if (lib_path.empty()) {
+    OnLoadedDocumentScanner(chromeos::machine_learning::mojom::LoadModelResult::
+                                FEATURE_NOT_SUPPORTED_ERROR);
+    return;
+  }
+
   auto config = chromeos::machine_learning::mojom::DocumentScannerConfig::New();
   config->library_dlc_path = base::FilePath(lib_path);
 
diff --git a/ash/webui/camera_app_ui/resources/utils/cca.py b/ash/webui/camera_app_ui/resources/utils/cca.py
index bb52736..894bd326 100755
--- a/ash/webui/camera_app_ui/resources/utils/cca.py
+++ b/ash/webui/camera_app_ui/resources/utils/cca.py
@@ -65,14 +65,6 @@
         subprocess.check_call(cmd)
 
 
-def gen_files_are_hard_links(gen_dir):
-    cca_root = os.getcwd()
-
-    util_js = os.path.join(cca_root, 'js/util.ts')
-    util_js_in_gen = os.path.join(gen_dir, 'js/util.ts')
-    return os.stat(util_js).st_ino == os.stat(util_js_in_gen).st_ino
-
-
 CCA_OVERRIDE_PATH = '/etc/camera/cca'
 CCA_OVERRIDE_FEATURE = 'CCALocalOverride'
 CHROME_DEV_CONF_PATH = '/etc/chrome_dev.conf'
@@ -108,33 +100,59 @@
     run(['ssh', device, '--', 'restart', 'ui'])
 
 
+def get_tsc_paths(board):
+    root_dir = get_chromium_root()
+    target_gen_dir = os.path.join(root_dir, f'out_{board}/Release/gen')
+
+    cca_root = os.getcwd()
+    src_relative_dir = os.path.relpath(cca_root, root_dir)
+
+    webui_dir = os.path.join(target_gen_dir, src_relative_dir,
+                             'js/mojom-webui/*')
+    resources_dir = os.path.join(target_gen_dir,
+                                 'ui/webui/resources/preprocessed/*')
+
+    return {
+        '/mojom-webui/*': [os.path.relpath(webui_dir)],
+        '//resources/*': [os.path.relpath(resources_dir)],
+        'chrome://resources/*': [os.path.relpath(resources_dir)],
+    }
+
+
+def generate_tsconfig(board):
+    cca_root = os.getcwd()
+
+    with open(os.path.join(cca_root, 'tsconfig_base.json')) as f:
+        tsconfig = json.load(f)
+
+    tsconfig['files'] = glob.glob('js/**/*.ts', recursive=True)
+    tsconfig['compilerOptions']['noEmit'] = True
+    tsconfig['compilerOptions']['paths'] = get_tsc_paths(board)
+
+    with open(os.path.join(cca_root, 'tsconfig.json'), 'w') as f:
+        json.dump(tsconfig, f)
+
+
+# Use a fixed temporary output folder for deploy, so incremental compilation
+# works and deploy is faster.
+DEPLOY_OUTPUT_TEMP_DIR = '/tmp/cca-deploy-out'
+
+
 def deploy(args):
     root_dir = get_chromium_root()
     cca_root = os.getcwd()
-    target_dir = os.path.join(get_chromium_root(), f'out_{args.board}/Release')
 
-    src_relative_dir = os.path.relpath(cca_root, root_dir)
-    gen_dir = os.path.join(target_dir, 'gen', src_relative_dir)
-    tsc_dir = os.path.join(gen_dir, 'js/tsc/js')
+    os.makedirs(DEPLOY_OUTPUT_TEMP_DIR, exist_ok=True)
+    js_out_dir = os.path.join(DEPLOY_OUTPUT_TEMP_DIR, 'js')
 
-    # Since CCA copy source to gen directory and place it together with other
-    # generated files for TypeScript compilation, and GN use hard links when
-    # possible to copy files from source to gen directory, we do a check here
-    # that the file in gen directory is indeed hard linked to the source file
-    # (which should be the case when the two directory are in the same file
-    # system), so we don't need to emulate what GN does here and skip copying
-    # the files, and just call tsc on the gen directory.
-    # TODO(pihsun): Support this case if there's some common scenario that
-    # would cause this.
-    assert gen_files_are_hard_links(gen_dir), (
-        'The generated files are not hard linked, compile Chrome first?')
-
-    build_preload_images_js(os.path.join(gen_dir, 'js'))
+    generate_tsconfig(args.board)
 
     run_node([
         'typescript/bin/tsc',
-        '--project',
-        os.path.join(gen_dir, 'js/tsconfig_build_ts.json'),
+        '--outDir',
+        js_out_dir,
+        # Makes compilation faster
+        '--incremental',
         # For better debugging experience on DUT.
         '--inlineSourceMap',
         '--inlineSources',
@@ -145,12 +163,15 @@
         'false',
     ])
 
+    build_preload_images_js(js_out_dir)
+
     deploy_new_tsc_files = [
         'rsync',
         '--recursive',
         '--inplace',
         '--delete',
         '--mkpath',
+        '--exclude=tsconfig.tsbuildinfo',
         # rsync by default use source file permission masked by target file
         # system umask while transferring new files, and since workstation
         # defaults to have file not readable by others, this makes deployed
@@ -161,7 +182,7 @@
         # will have their permission fixed.
         '--perms',
         '--chmod=a+rX',
-        f'{tsc_dir}/',
+        f'{js_out_dir}/',
         f'{args.device}:{CCA_OVERRIDE_PATH}/js/',
     ]
     run(deploy_new_tsc_files)
@@ -222,37 +243,8 @@
         print('ESLint check failed, return code =', e.returncode)
 
 
-def get_tsc_paths(board):
-    root_dir = get_chromium_root()
-    target_gen_dir = os.path.join(root_dir, f'out_{board}/Release/gen')
-
-    cca_root = os.getcwd()
-    src_relative_dir = os.path.relpath(cca_root, root_dir)
-
-    webui_dir = os.path.join(target_gen_dir, src_relative_dir,
-                             'js/mojom-webui/*')
-    resources_dir = os.path.join(target_gen_dir,
-                                 'ui/webui/resources/preprocessed/*')
-
-    return {
-        '/mojom-webui/*': [os.path.relpath(webui_dir)],
-        '//resources/*': [os.path.relpath(resources_dir)],
-        'chrome://resources/*': [os.path.relpath(resources_dir)],
-    }
-
-
 def tsc(args):
-    cca_root = os.getcwd()
-
-    with open(os.path.join(cca_root, 'tsconfig_base.json')) as f:
-        tsconfig = json.load(f)
-
-    tsconfig['files'] = glob.glob('js/**/*.ts', recursive=True)
-    tsconfig['compilerOptions']['noEmit'] = True
-    tsconfig['compilerOptions']['paths'] = get_tsc_paths(args.board)
-
-    with open(os.path.join(cca_root, 'tsconfig.json'), 'w') as f:
-        json.dump(tsconfig, f)
+    generate_tsconfig(args.board)
 
     try:
         run_node(['typescript/bin/tsc'])
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index f0a9a086..54b0ac4 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -2343,8 +2343,7 @@
       cflags += [ "-g2" ]
     }
 
-    if (!is_nacl && is_clang && !is_tsan && !is_asan &&
-        !(is_ios && enable_ios_bitcode)) {
+    if (!is_nacl && is_clang && !is_tsan && !is_asan) {
       # gcc generates dwarf-aranges by default on -g1 and -g2. On clang it has
       # to be manually enabled.
       #
@@ -2468,8 +2467,7 @@
       cflags += [ "-g1" ]
     }
 
-    if (!is_nacl && is_clang && !is_tsan && !is_asan &&
-        !(is_ios && enable_ios_bitcode)) {
+    if (!is_nacl && is_clang && !is_tsan && !is_asan) {
       # See comment for -gdwarf-aranges in config("symbols").
       cflags += [ "-gdwarf-aranges" ]
     }
diff --git a/build/config/ios/BUILD.gn b/build/config/ios/BUILD.gn
index cfe4a0f..358c336d 100644
--- a/build/config/ios/BUILD.gn
+++ b/build/config/ios/BUILD.gn
@@ -2,11 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# TODO(crbug.com/1259122): Remove //build/config/sanitizers/sanitizers.gni when
-# Chromium Clang ship with a catalyst version of the compiler runtime.
-
 import("//build/config/ios/ios_sdk.gni")
-import("//build/config/sanitizers/sanitizers.gni")
 import("//build/toolchain/goma.gni")
 import("//build/toolchain/rbe.gni")
 import("//build/toolchain/toolchain.gni")
@@ -116,14 +112,6 @@
     ]
   }
 
-  if (enable_ios_bitcode) {
-    if (is_debug) {
-      common_flags += [ "-fembed-bitcode-marker" ]
-    } else {
-      common_flags += [ "-fembed-bitcode" ]
-    }
-  }
-
   asmflags = common_flags
   cflags = common_flags
   ldflags = common_flags
diff --git a/build/config/ios/ios_sdk.gni b/build/config/ios/ios_sdk.gni
index a4838ef..af1b4c6 100644
--- a/build/config/ios/ios_sdk.gni
+++ b/build/config/ios/ios_sdk.gni
@@ -69,16 +69,6 @@
 
   # Set to true if all test apps should use the same bundle id.
   ios_use_shared_bundle_id_for_test_apps = true
-
-  # Enabling this option makes clang compile to an intermediate
-  # representation ("bitcode"), and not to native code. This is preferred
-  # when including WebRTC in the apps that will be sent to Apple's App Store
-  # and mandatory for the apps that run on watchOS or tvOS.
-  #
-  # Mimicking how Xcode handles it, the production builds (is_debug = false)
-  # get real bitcode sections added, while the debug builds (is_debug = true)
-  # only get bitcode-section "markers" added in them.
-  enable_ios_bitcode = false
 }
 
 declare_args() {
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 378490b..3c31d7ad 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-8.20220708.0.1
+8.20220708.1.1
diff --git a/chrome/VERSION b/chrome/VERSION
index c472a2c..8175f17 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=105
 MINOR=0
-BUILD=5168
+BUILD=5169
 PATCH=0
diff --git a/chrome/android/expectations/lint-suppressions.xml b/chrome/android/expectations/lint-suppressions.xml
index 5eb084f..7fb1fda 100644
--- a/chrome/android/expectations/lint-suppressions.xml
+++ b/chrome/android/expectations/lint-suppressions.xml
@@ -290,6 +290,15 @@
     <!-- Temporarily suppressed until impelmentation is ready, see: https://crbug.com/1330631 -->
     <ignore regexp="The resource `R.string.price_drop_spotted_iph` appears to be unused"/>
 
+    <!-- 7: TODO(crbug.com/1330900): figure out why this is triggering a lint check (the resources are used). -->
+    <ignore regexp="The resource `R.string.parent_website_approval_title` appears to be unused"/>
+    <ignore regexp="The resource `R.string.parent_website_approval_all_of_domain` appears to be unused"/>
+    <ignore regexp="The resource `R.string.parent_website_approval_approve_button` appears to be unused"/>
+    <ignore regexp="The resource `R.string.parent_website_approval_deny_button` appears to be unused"/>
+    <ignore regexp="The resource `R.string.parent_website_approval_content_description` appears to be unused"/>
+    <ignore regexp="The resource `R.string.parent_website_approval_full_height` appears to be unused"/>
+    <ignore regexp="The resource `R.string.parent_website_approval_closed` appears to be unused"/>
+
     <!-- Endnote: Please specify number of suppressions when adding more -->
   </issue>
   <issue id="VectorPath" severity="ignore"/>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java
index dac60e43..0fe9e6ac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java
@@ -521,6 +521,7 @@
             getPrefService().setBoolean(Pref.CREDENTIALS_ENABLE_SERVICE, (boolean) newValue);
             RecordHistogram.recordBooleanHistogram(
                     "PasswordManager.Settings.ToggleOfferToSavePasswords", (boolean) newValue);
+            if ((boolean) newValue) PasswordManagerHelper.resetUpmUnenrollment();
             return true;
         });
         mSavePasswordsSwitch.setManagedPreferenceDelegate(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/RuntimePermissionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/RuntimePermissionTest.java
index 35347a6..55ec938 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/RuntimePermissionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/RuntimePermissionTest.java
@@ -6,7 +6,6 @@
 
 import android.Manifest;
 
-import android.os.Build.VERSION_CODES;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Assert;
@@ -16,7 +15,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.download.DownloadItem;
 import org.chromium.chrome.browser.download.DownloadManagerService;
@@ -149,12 +147,9 @@
     }
 
     @Test
-    @DisableIf.Build(sdk_is_less_than = VERSION_CODES.Q,
-            message = "Test has been very flaky crbug.com/1179099")
     @MediumTest
     @Feature({"RuntimePermissions", "Downloads"})
-    public void
-    testDenyRuntimeDownload() throws Exception {
+    public void testDenyRuntimeDownload() throws Exception {
         DownloadObserver observer = new DownloadObserver() {
             @Override
             public void onAllDownloadsRetrieved(
@@ -206,11 +201,8 @@
     @Test
     @MediumTest
     @Feature({"RuntimePermissions", "MediaPermissions"})
-    @DisableIf.Build(message = "Failing on Android P, see crbug.com/1251332.",
-            sdk_is_greater_than = VERSION_CODES.O_MR1)
     @CommandLineFlags.Add(ContentSwitches.USE_FAKE_DEVICE_FOR_MEDIA_STREAM)
-    public void
-    testDenyAndNeverAskMicrophone() throws Exception {
+    public void testDenyAndNeverAskMicrophone() throws Exception {
         // First ask for mic and reply with "deny and never ask again";
         String[] requestablePermission = new String[] {Manifest.permission.RECORD_AUDIO};
         mTestAndroidPermissionDelegate = new TestAndroidPermissionDelegate(
@@ -235,11 +227,8 @@
     @Test
     @MediumTest
     @Feature({"RuntimePermissions", "MediaPermissions"})
-    @DisableIf.Build(message = "Failing on Android P, see crbug.com/1251332.",
-            sdk_is_greater_than = VERSION_CODES.O_MR1)
     @CommandLineFlags.Add(ContentSwitches.USE_FAKE_DEVICE_FOR_MEDIA_STREAM)
-    public void
-    testDenyAndNeverAskCamera() throws Exception {
+    public void testDenyAndNeverAskCamera() throws Exception {
         // First ask for camera and reply with "deny and never ask again";
         String[] requestablePermission = new String[] {Manifest.permission.CAMERA};
         mTestAndroidPermissionDelegate = new TestAndroidPermissionDelegate(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/RuntimePermissionTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/RuntimePermissionTestUtils.java
index 326de4e1..3cbc1457 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/RuntimePermissionTestUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/RuntimePermissionTestUtils.java
@@ -139,9 +139,8 @@
      * @param testUrl The URL of the test page to load in order to run the text.
      * @param expectPermissionAllowed Whether to expect that the permissions is granted by the end
      *         of the test.
-     * @param permissionPromptAllow Whether to respond with "allow" on the Chrome permission prompt
+     * @param permissionPromptAllow Whether to "allow" or "reject" on the Chrome permission prompt
      *         (`null` means skip waiting for a permission prompt at all).
-     * @param runtimePromptResponse How to respond to the runtime prompt.
      * @param waitForMissingPermissionPrompt Whether to wait for a Chrome dialog informing the user
      *         that the Android permission is missing.
      * @param waitForUpdater Whether to wait for the test page to update the window title to confirm
@@ -150,7 +149,6 @@
      *         skip).
      * @param missingPermissionPromptTextId The resource string id that matches the text of the
      *         missing permission prompt dialog (0 if not applicable).
-     * @param requestablePermission The Android permission(s) that will be requested by this test.
      * @throws Exception
      */
     public static void runTest(final PermissionTestRule permissionTestRule,
@@ -170,22 +168,36 @@
         permissionTestRule.setUpUrl(testUrl);
 
         if (javascriptToExecute != null && !javascriptToExecute.isEmpty()) {
-            permissionTestRule.runJavaScriptCodeInCurrentTab(javascriptToExecute);
+            permissionTestRule.runJavaScriptCodeInCurrentTabWithGesture(javascriptToExecute);
         }
 
         if (permissionPromptAllow != null) {
-            // Wait for chrome permission dialog and accept it.
+            // A permission prompt dialog is expected. Wait for chrome to display and accept or
+            // deny.
             PermissionTestRule.waitForDialog(activity);
             PermissionTestRule.replyToDialog(permissionPromptAllow, activity);
+
+            if (waitForMissingPermissionPrompt) {
+                // Wait for Chrome to inform user that a permission is missing --> different dialog
+                final ModalDialogManager manager = TestThreadUtils.runOnUiThreadBlockingNoException(
+                        activity::getModalDialogManager);
+                waitUntilDifferentDialogIsShowing(
+                        permissionTestRule, manager.getCurrentDialogForTest());
+            }
         }
 
         if (waitForMissingPermissionPrompt) {
-            // Wait for missing permission dialog and dismiss it.
             final ModalDialogManager manager = TestThreadUtils.runOnUiThreadBlockingNoException(
                     activity::getModalDialogManager);
-            waitUntilDifferentDialogIsShowing(
-                    permissionTestRule, manager.getCurrentDialogForTest());
 
+            // Wait for the dialog that informs the user permissions are missing, when the initial 
+            // prompt is rejected or expected to not be shown.
+            if (!Boolean.TRUE.equals(permissionPromptAllow)) {
+                waitUntilDifferentDialogIsShowing(
+                        permissionTestRule, manager.getCurrentDialogForTest());
+            }
+
+            // Verify the correct missing permission string resource is displayed.
             final View dialogText = manager.getCurrentDialogForTest()
                                             .get(ModalDialogProperties.CUSTOM_VIEW)
                                             .findViewById(R.id.text);
diff --git a/chrome/app/resources/chromium_strings_es.xtb b/chrome/app/resources/chromium_strings_es.xtb
index f6bbc84..b63c9e76 100644
--- a/chrome/app/resources/chromium_strings_es.xtb
+++ b/chrome/app/resources/chromium_strings_es.xtb
@@ -17,7 +17,7 @@
 <translation id="1414495520565016063">Has iniciado sesión en Chromium</translation>
 <translation id="1524282610922162960">Compartir una pestaña de Chromium</translation>
 <translation id="1553461853655228091">Para crear un mapa 3D de tu entorno, Chromium necesita permiso para acceder a tu cámara</translation>
-<translation id="1607715478322902680">{COUNT,plural, =0{Tu administrador pide que reinicies Chromium para aplicar una actualización}=1{Tu administrador pide que reinicies Chromium para aplicar una actualización. La ventana de incógnito no se volverá a abrir.}other{Tu administrador pide que reinicies Chromium para aplicar una actualización. Las # ventanas de incógnito no se volverán a abrir.}}</translation>
+<translation id="1607715478322902680">{COUNT,plural, =0{Tu administrador pide que reinicies Chromium para aplicar una actualización}=1{Tu administrador pide que reinicies Chromium para aplicar una actualización. La ventana de Incógnito no se volverá a abrir.}other{Tu administrador pide que reinicies Chromium para aplicar una actualización. Las # ventanas de Incógnito no se volverán a abrir.}}</translation>
 <translation id="1625909126243026060">Revisa los controles principales de privacidad y de seguridad en Chromium</translation>
 <translation id="1632539827495546968">Si quieres usar esta cuenta solo una vez, puedes usar el <ph name="GUEST_LINK_BEGIN" />Modo Invitado<ph name="GUEST_LINK_END" /> en el navegador Chromium. Si quieres añadir una cuenta para otra persona, <ph name="LINK_BEGIN" />añade una persona<ph name="LINK_END" /> a tu <ph name="DEVICE_TYPE" />.
 
@@ -108,11 +108,11 @@
 <translation id="3575459661164320785">Se ha detectado software dañino en tu ordenador. Chromium puede eliminarlo, restaurar tu configuración y desactivar las extensiones para que el navegador vuelva a funcionar con normalidad.</translation>
 <translation id="3593091352817399191">Ya hay otra cuenta con la sesión iniciada. Para mantener tu información de navegación aparte, Chromium puede crearte un perfil propio.</translation>
 <translation id="3639635944603682591">Los datos de navegación de este usuario se eliminarán del dispositivo. Para recuperar tus datos, inicia sesión en Chromium como <ph name="USER_EMAIL" />.</translation>
-<translation id="364817392622123556">{COUNT,plural, =0{Hay una nueva actualización de Chromium disponible que se aplicará cuando reinicies el navegador.}=1{Hay una nueva actualización de Chromium disponible que se aplicará cuando reinicies el navegador. La ventana de incógnito no se volverá a abrir.}other{Hay una nueva actualización de Chromium disponible que se aplicará cuando reinicies el navegador. Las # ventanas de incógnito no se volverán a abrir.}}</translation>
+<translation id="364817392622123556">{COUNT,plural, =0{Hay una nueva actualización de Chromium disponible que se aplicará cuando reinicies el navegador.}=1{Hay una nueva actualización de Chromium disponible que se aplicará cuando reinicies el navegador. La ventana de Incógnito no se volverá a abrir.}other{Hay una nueva actualización de Chromium disponible que se aplicará cuando reinicies el navegador. Las # ventanas de Incógnito no se volverán a abrir.}}</translation>
 <translation id="3651803019964686660">Para enviar un número desde <ph name="ORIGIN" /> a tu teléfono Android, inicia sesión en Chromium con ambos dispositivos.</translation>
 <translation id="3685209450716071127">Chromium no puede comprobar tus contraseñas. Revisa tu conexión a Internet.</translation>
 <translation id="3702352323269013324">Más información sobre la personalización de anuncios en Chromium</translation>
-<translation id="370962675267501463">{COUNT,plural, =0{Tu administrador pide que reinicies Chromium para aplicar esta actualización}=1{Tu administrador pide que reinicies Chromium para aplicar esta actualización La ventana de incógnito no se volverá a abrir.}other{Tu administrador pide que reinicies Chromium para aplicar esta actualización Las # ventanas de incógnito no se volverán a abrir.}}</translation>
+<translation id="370962675267501463">{COUNT,plural, =0{Tu administrador pide que reinicies Chromium para aplicar esta actualización}=1{Tu administrador pide que reinicies Chromium para aplicar esta actualización La ventana de Incógnito no se volverá a abrir.}other{Tu administrador pide que reinicies Chromium para aplicar esta actualización Las # ventanas de Incógnito no se volverán a abrir.}}</translation>
 <translation id="3713809861844741608">Abrir enlace en una pes&amp;taña nueva de Chromium</translation>
 <translation id="3728336900324680424">Chromium accederá a tu Drive para mostrarte sugerencias en la barra de direcciones</translation>
 <translation id="378917192836375108">Chromium te permite hacer clic en un número de teléfono en la Web y llamar mediante Skype.</translation>
diff --git a/chrome/app/resources/generated_resources_de.xtb b/chrome/app/resources/generated_resources_de.xtb
index c470322..6ab58e7 100644
--- a/chrome/app/resources/generated_resources_de.xtb
+++ b/chrome/app/resources/generated_resources_de.xtb
@@ -5746,7 +5746,7 @@
 <translation id="6556903358015358733">Design und Hintergrund</translation>
 <translation id="6557290421156335491">Meine Verknüpfungen</translation>
 <translation id="6560151649238390891">Vorschlag eingefügt</translation>
-<translation id="6561560012278703671">Ein Symbol in der Adressleiste einblenden (störende Aufforderungen zum Annehmen von Benachrichtigungen werden blockiert)</translation>
+<translation id="6561560012278703671">Unaufdringlichere Benachrichtigungen verwenden (störende Aufforderungen zum Annehmen von Benachrichtigungen werden blockiert)</translation>
 <translation id="6561726789132298588">Eingabe</translation>
 <translation id="6562117348069327379">Systemprotokolle im Downloadverzeichnis speichern.</translation>
 <translation id="656293578423618167">Der Dateipfad oder Dateiname ist zu lang. Verwende bitte einen kürzeren Namen oder einen anderen Speicherort.</translation>
@@ -8170,7 +8170,7 @@
 <translation id="8934732568177537184">Weiter</translation>
 <translation id="8938800817013097409">USB-C-Gerät (rechter Port hinten)</translation>
 <translation id="8940081510938872932">Dein Computer erledigt momentan zu viele Aufgaben gleichzeitig. Bitte versuche es später erneut.</translation>
-<translation id="8940381019874223173">Von deinen Google Fotos</translation>
+<translation id="8940381019874223173">Aus deinen Google Fotos</translation>
 <translation id="8941173171815156065">Berechtigung "<ph name="PERMISSION" />" aufheben</translation>
 <translation id="894360074127026135">Netscape International Step-up</translation>
 <translation id="8944099748578356325">Den Akku schneller verbrauchen (aktueller Stand: <ph name="BATTERY_PERCENTAGE" /> %)</translation>
diff --git a/chrome/app/resources/generated_resources_es-419.xtb b/chrome/app/resources/generated_resources_es-419.xtb
index 3446095..6abb083f 100644
--- a/chrome/app/resources/generated_resources_es-419.xtb
+++ b/chrome/app/resources/generated_resources_es-419.xtb
@@ -5260,7 +5260,7 @@
 <translation id="6077189836672154517">Sugerencias y actualizaciones para <ph name="DEVICE_TYPE" /></translation>
 <translation id="6077476112742402730">Hablar para escribir</translation>
 <translation id="6078121669093215958">{0,plural, =1{Invitado}other{# ventanas de invitado abiertas}}</translation>
-<translation id="6078323886959318429">Agregar un acceso directo</translation>
+<translation id="6078323886959318429">Agregar acceso directo</translation>
 <translation id="6078752646384677957">Comprueba el micrófono y los niveles de audio.</translation>
 <translation id="6078769373519310690">"<ph name="CHROME_EXTENSION_NAME" />" quiere conectarse a un dispositivo HID</translation>
 <translation id="608029822688206592">No se encontraron redes. Inserta tu tarjeta SIM y vuelve a intentarlo.</translation>
diff --git a/chrome/app/resources/generated_resources_es.xtb b/chrome/app/resources/generated_resources_es.xtb
index 70039d51..ef2db41 100644
--- a/chrome/app/resources/generated_resources_es.xtb
+++ b/chrome/app/resources/generated_resources_es.xtb
@@ -721,7 +721,7 @@
 <translation id="1668979692599483141">Información sobre sugerencias</translation>
 <translation id="1670399744444387456">Básico</translation>
 <translation id="1673137583248014546"><ph name="URL" /> quiere ver la marca y el modelo de tu llave de seguridad</translation>
-<translation id="1674073353928166410">Abrir todas (<ph name="URL_COUNT" />) en una ventana de incógnito</translation>
+<translation id="1674073353928166410">Abrir todas (<ph name="URL_COUNT" />) en una ventana de Incógnito</translation>
 <translation id="1676902103953506022">Detalles de las credenciales de <ph name="USERNAME" /> en <ph name="DOMAIN" /></translation>
 <translation id="1677306805708094828">No se ha podido añadir <ph name="EXTENSION_TYPE_PARAMETER" /></translation>
 <translation id="1677472565718498478">Queda: <ph name="TIME" /></translation>
@@ -732,7 +732,7 @@
 <translation id="1680841347983561661">Intenta iniciar Google Play de nuevo dentro de unos minutos.</translation>
 <translation id="1680849702532889074">No se ha podido instalar la aplicación de Linux.</translation>
 <translation id="16815041330799488">No permitir que los sitios vean el texto y las imágenes que se hayan copiado en el portapapeles</translation>
-<translation id="1682548588986054654">Nueva ventana de incógnito</translation>
+<translation id="1682548588986054654">Nueva ventana de Incógnito</translation>
 <translation id="1682696837763999627">Cursor del ratón grande</translation>
 <translation id="1682867089915960590">¿Activar navegación por cursor de texto?</translation>
 <translation id="1686550358074589746">Habilitar la escritura deslizando el dedo</translation>
@@ -883,7 +883,7 @@
 <translation id="1817871734039893258">Recuperación de archivos de Microsoft</translation>
 <translation id="1818007989243628752">Eliminar la contraseña de <ph name="USERNAME" /></translation>
 <translation id="1818913467757368489">Se está subiendo el registro.</translation>
-<translation id="1819443852740954262">Abrir todas en una ventana de incógnito</translation>
+<translation id="1819443852740954262">Abrir todas en una ventana de Incógnito</translation>
 <translation id="1819721979226826163">Toca Notificaciones de aplicaciones &gt; Servicios de Google Play.</translation>
 <translation id="1820028137326691631">Introduce la contraseña proporcionada por el administrador</translation>
 <translation id="1822140782238030981">¿Ya utilizas Chrome? Inicia sesión</translation>
@@ -1130,7 +1130,7 @@
 <translation id="2059913712424898428">Zona horaria</translation>
 <translation id="2060375639911876205">Quitar perfil de eSIM</translation>
 <translation id="2061366302742593739">Nada que mostrar</translation>
-<translation id="2062354623176996748">Navega por Internet sin guardar tu historial de navegación con una ventana de incógnito</translation>
+<translation id="2062354623176996748">Navega por Internet sin guardar tu historial de navegación con una ventana de Incógnito</translation>
 <translation id="2065405795449409761">Un software automatizado de pruebas está controlando Chrome.</translation>
 <translation id="2071393345806050157">No hay ningún archivo de registro local.</translation>
 <translation id="2071692954027939183">Se han bloqueado automáticamente las notificaciones porque no las sueles permitir.</translation>
@@ -1340,7 +1340,7 @@
 <translation id="2249605167705922988">p. ej. 1-5, 8, 11-13</translation>
 <translation id="2251218783371366160">Abrir con visor del sistema</translation>
 <translation id="225163402930830576">Actualizar redes</translation>
-<translation id="2251809247798634662">Nueva ventana de incógnito</translation>
+<translation id="2251809247798634662">Nueva ventana de Incógnito</translation>
 <translation id="225240747099314620">Permitir identificadores para contenido protegido (es posible que sea necesario reiniciar el ordenador)</translation>
 <translation id="2255077166240162850">Este dispositivo ha sido bloqueado en un dominio o modo diferente.</translation>
 <translation id="2255317897038918278">Impresión de fecha de Microsoft</translation>
@@ -2054,7 +2054,7 @@
 <translation id="2893180576842394309">Es posible que Google utilice tu historial para personalizar la Búsqueda y otros servicios de Google</translation>
 <translation id="2894757982205307093">Nueva pestaña en grupo</translation>
 <translation id="289695669188700754">ID de clave: <ph name="KEY_ID" /></translation>
-<translation id="2897713966423243833">Este ajuste personalizado se quitará cuando cierres todas tus ventanas de incógnito</translation>
+<translation id="2897713966423243833">Este ajuste personalizado se quitará cuando cierres todas tus ventanas de Incógnito</translation>
 <translation id="2897878306272793870">¿Seguro que quieres abrir <ph name="TAB_COUNT" /> pestañas?</translation>
 <translation id="290105521672621980">El archivo utiliza funciones no admitidas</translation>
 <translation id="2901348420151309559">Fotos y aplicaciones recientes</translation>
@@ -2723,7 +2723,7 @@
 <translation id="3569407787324516067">Salvapantallas</translation>
 <translation id="3569682580018832495"><ph name="ORIGIN" /> puede ver los siguientes archivos y carpetas:</translation>
 <translation id="3571734092741541777">Configurar</translation>
-<translation id="3575121482199441727">Permitir para este sitio web</translation>
+<translation id="3575121482199441727">Permitir en este sitio web</translation>
 <translation id="3577745545227000795">Recogida de datos de hardware de <ph name="DEVICE_OS" /></translation>
 <translation id="3578594933904494462">Se está compartiendo el contenido de esta pestaña.</translation>
 <translation id="3578874072190212775">Ayuda a los sitios a luchar contra el fraude y a distinguir a los bots de las personas</translation>
@@ -3056,7 +3056,7 @@
 <translation id="3888586133700543064">Esta información nos ayudará a entender mejor tu problema con el Asistente. Se almacena durante un máximo de 90 días y solo podrán acceder a estos datos los equipos correspondientes de ingeniería y sugerencias.</translation>
 <translation id="3890064827463908288">Activa la Sincronización de Chrome para usar la Sincronización Wi‑Fi</translation>
 <translation id="389313931326656921">Asignar interruptor para "Siguiente"</translation>
-<translation id="3893295674388762059">Para borrar los datos, cierra todas las ventanas de incógnito</translation>
+<translation id="3893295674388762059">Para borrar los datos, cierra todas las ventanas de Incógnito</translation>
 <translation id="3893536212201235195">Leer y cambiar tu configuración de accesibilidad</translation>
 <translation id="3893630138897523026">ChromeVox (conversión de texto a voz)</translation>
 <translation id="3893764153531140319"><ph name="DOWNLOADED_SIZE" />/<ph name="DOWNLOAD_SIZE" /></translation>
@@ -4367,7 +4367,7 @@
 <translation id="5191251636205085390">Más información sobre las nuevas tecnologías que aspiran a reemplazar las cookies de terceros y cómo controlarlas</translation>
 <translation id="5192062846343383368">Abre la aplicación Family Link para ver la configuración de supervisión</translation>
 <translation id="5193988420012215838">Se ha copiado al portapapeles</translation>
-<translation id="5194256020863090856">Esto solo afecta a las ventanas de incógnito</translation>
+<translation id="5194256020863090856">Esto solo afecta a las ventanas de Incógnito</translation>
 <translation id="5195074424945754995">Las URLs que se correspondan con estas reglas no activarán ningún cambio de navegador y podrán abrirse en <ph name="BROWSER_NAME" /> o en <ph name="ALTERNATIVE_BROWSER_NAME" />.</translation>
 <translation id="5195863934285556588"><ph name="BEGIN_PARAGRAPH1" />El servicio de ubicación de Google usa fuentes como redes Wi‑Fi o móviles y sensores para determinar la ubicación de este dispositivo.<ph name="END_PARAGRAPH1" />
     <ph name="BEGIN_PARAGRAPH2" />Puedes desactivar la ubicación de Android en este dispositivo cuando quieras si vas a Ajustes &gt; Aplicaciones &gt; Google Play Store &gt; Gestionar preferencias de Android &gt; Seguridad y ubicación &gt; Ubicación. También puedes desactivar el uso de redes Wi‑Fi o móviles y sensores para determinar la ubicación de Android si desactivas Precisión de la ubicación de Google en el mismo menú.<ph name="END_PARAGRAPH2" /></translation>
@@ -4513,7 +4513,7 @@
 <translation id="5336688142483283574">Esta página también se eliminará del historial y de la actividad de <ph name="SEARCH_ENGINE" />.</translation>
 <translation id="5337771866151525739">Instalada por una aplicación externa</translation>
 <translation id="5337926771328966926">El nombre actual del dispositivo es <ph name="DEVICE_NAME" /></translation>
-<translation id="5338338064218053691">Puedes navegar en privado con una ventana de incógnito</translation>
+<translation id="5338338064218053691">Puedes navegar en privado con una ventana de Incógnito</translation>
 <translation id="5338503421962489998">Almacenamiento local</translation>
 <translation id="5339031667684712858">Sitios que has eliminado</translation>
 <translation id="5340638867532133571">Permitir a los sitios instalar controladores de pago (recomendado)</translation>
@@ -5751,7 +5751,7 @@
 <translation id="6556903358015358733">Tema y fondo de pantalla</translation>
 <translation id="6557290421156335491">Mis accesos directos</translation>
 <translation id="6560151649238390891">Sugerencia insertada</translation>
-<translation id="6561560012278703671">Usar notificaciones más discretas (bloquea las notificaciones emergentes para evitar interrupciones)</translation>
+<translation id="6561560012278703671">Usar notificaciones más discretas (bloquea las notificaciones emergentes para que no te interrumpan)</translation>
 <translation id="6561726789132298588">intro</translation>
 <translation id="6562117348069327379">Almacena registros del sistema en el directorio Descargas.</translation>
 <translation id="656293578423618167">La ruta o el nombre del archivo es demasiado largo. Especifica un nombre más corto o guarda el archivo en otra ubicación.</translation>
@@ -5996,7 +5996,7 @@
 <translation id="6808039367995747522">Para seguir, inserta y toca tu llave de seguridad</translation>
 <translation id="6808166974213191158">Escritor de imágenes del sistema de ChromeOS Flex</translation>
 <translation id="6808193438228982088">Zorro</translation>
-<translation id="6809470175540814047">Abrir en una ventana de incógnito</translation>
+<translation id="6809470175540814047">Abrir en una ventana de Incógnito</translation>
 <translation id="6809656734323672573">Si aceptas, el Asistente de Google permanecerá en modo inactivo para detectar el comando "Hey Google" y podrá reconocer tu voz con Voice Match.
     <ph name="BR" />
     Con Voice Match, el Asistente puede reconocer tu voz y diferenciarla de otras. Lo consigue utilizando grabaciones de tu voz para crear un modelo de voz único que solo se almacena en tu dispositivo. Puede que tu modelo de voz se envíe temporalmente a Google para identificar mejor tu voz.
@@ -6185,7 +6185,7 @@
 <translation id="6979158407327259162">Google Drive</translation>
 <translation id="6979440798594660689">Silenciar (predeterminado)</translation>
 <translation id="6979737339423435258">Desde siempre</translation>
-<translation id="6981553172137913845">Para navegar en privado, haz clic en el icono de puntos y abre una ventana de incógnito</translation>
+<translation id="6981553172137913845">Para navegar en privado, haz clic en el icono de puntos y abre una ventana de Incógnito</translation>
 <translation id="6981761993313539853">Comprueba que tu dispositivo Bluetooth esté cerca y en modo Emparejamiento. Emparéjalo solo con dispositivos de confianza. <ph name="BEGIN_LINK_LEARN_MORE" />Más información<ph name="END_LINK_LEARN_MORE" /></translation>
 <translation id="6981982820502123353">Accesibilidad</translation>
 <translation id="6983507711977005608">Desconectar red de conexión compartida instantánea</translation>
@@ -6269,7 +6269,7 @@
 <translation id="7053983685419859001">Bloquear</translation>
 <translation id="7055152154916055070">Redirección bloqueada:</translation>
 <translation id="7055451306017383754">No se ha podido dejar de compartir esta carpeta porque una aplicación está usándola. La carpeta dejará de compartirse la próxima vez que se apague Parallels Desktop.</translation>
-<translation id="7056418393177503237">{0,plural, =1{Incógnito}other{# ventanas de incógnito abiertas}}</translation>
+<translation id="7056418393177503237">{0,plural, =1{Incógnito}other{# ventanas de Incógnito abiertas}}</translation>
 <translation id="7056526158851679338">&amp;Inspeccionar dispositivos</translation>
 <translation id="7057184853669165321">{NUM_MINS,plural, =1{La comprobación de seguridad se ha realizado hace 1 minuto}other{La comprobación de seguridad se ha realizado hace {NUM_MINS} minutos}}</translation>
 <translation id="7057767408836081338">No se han podido obtener datos de la aplicación. Intentando ejecutarla de todos modos...</translation>
@@ -6753,7 +6753,7 @@
 <translation id="7561982940498449837">Cerrar menú</translation>
 <translation id="756445078718366910">Abrir ventana del navegador</translation>
 <translation id="7564847347806291057">Finalizar proceso</translation>
-<translation id="756503097602602175">Puedes gestionar las cuentas de Google con las que se haya iniciado sesión en <ph name="LINK_BEGIN" />Configuración<ph name="LINK_END" />. Es posible que los permisos que hayas dado a sitios web y aplicaciones se apliquen a todas las cuentas. Si no quieres que los sitios ni las aplicaciones accedan a la información de tu cuenta, puedes iniciar sesión en tu <ph name="DEVICE_TYPE" /> como invitado o navegar por Internet en una <ph name="LINK_2_BEGIN" />ventana de incógnito<ph name="LINK_2_END" />.</translation>
+<translation id="756503097602602175">Puedes gestionar las cuentas de Google con las que se haya iniciado sesión en <ph name="LINK_BEGIN" />Configuración<ph name="LINK_END" />. Es posible que los permisos que hayas dado a sitios web y aplicaciones se apliquen a todas las cuentas. Si no quieres que los sitios ni las aplicaciones accedan a la información de tu cuenta, puedes iniciar sesión en tu <ph name="DEVICE_TYPE" /> como invitado o navegar por Internet en una <ph name="LINK_2_BEGIN" />ventana de Incógnito<ph name="LINK_2_END" />.</translation>
 <translation id="7566118625369982896">Gestionar enlaces de aplicaciones de Play</translation>
 <translation id="756809126120519699">Datos de Chrome borrados</translation>
 <translation id="756876171895853918">Personalizar avatar</translation>
@@ -7510,7 +7510,7 @@
 <translation id="8264024885325823677">Este ajuste lo gestiona tu administrador.</translation>
 <translation id="8264718194193514834"><ph name="EXTENSION_NAME" /> ha activado el modo de pantalla completa.</translation>
 <translation id="826511437356419340">Se ha cambiado al modo de vista general de ventanas. Desliza el dedo para desplazarte o pulsa el tabulador si estás utilizando un teclado.</translation>
-<translation id="8265671588726449108">{COUNT,plural, =1{1 ventana de incógnito no se abrirá después de reiniciar}other{{COUNT} ventanas de incógnito no se volverán a abrir después de reiniciar}}</translation>
+<translation id="8265671588726449108">{COUNT,plural, =1{1 ventana de Incógnito no se abrirá después de reiniciar}other{{COUNT} ventanas de Incógnito no se volverán a abrir después de reiniciar}}</translation>
 <translation id="8266947622852630193">Todos los métodos de introducción de texto</translation>
 <translation id="8267539814046467575">Añadir impresora</translation>
 <translation id="8267961145111171918"><ph name="BEGIN_PARAGRAPH1" />Estos datos incluyen información general sobre tu dispositivo y sobre cómo lo usas (como el nivel de batería, la actividad de las aplicaciones y del sistema, y los errores). Sirven para mejorar Android, y parte de los datos recogidos también ayudan a las aplicaciones y partners de Google, como los desarrolladores de Android, a mejorar sus productos y aplicaciones.<ph name="END_PARAGRAPH1" />
@@ -8100,7 +8100,7 @@
 <translation id="8863753581171631212">Abrir enlace en una ventana nueva de <ph name="APP" /></translation>
 <translation id="8864055848767439877">Compartiendo <ph name="TAB_NAME" /> con <ph name="APP_NAME" /></translation>
 <translation id="8864458770072227512">Se ha eliminado <ph name="EMAIL" /> del dispositivo</translation>
-<translation id="8865112428068029930">¿Usas un ordenador compartido? Prueba a abrir una ventana de incógnito.</translation>
+<translation id="8865112428068029930">¿Usas un ordenador compartido? Prueba a abrir una ventana de Incógnito.</translation>
 <translation id="8867102760244540173">Buscar pestañas...</translation>
 <translation id="8867228703146808825">Copiar detalles de compilación al portapapeles</translation>
 <translation id="8868333925931032127">Iniciando modo Demo</translation>
@@ -8337,7 +8337,7 @@
 <translation id="9094859731829297286">¿Seguro que quieres reservar un disco de tamaño fijo para Linux?</translation>
 <translation id="9094982973264386462">Quitar</translation>
 <translation id="9095253524804455615">Quitar</translation>
-<translation id="909554839118732438">Cerrar ventanas de incógnito</translation>
+<translation id="909554839118732438">Cerrar ventanas de Incógnito</translation>
 <translation id="9100416672768993722">Para cambiar al último método de introducción de texto usado, pulsa <ph name="BEGIN_SHORTCUT" /><ph name="BEGIN_CTRL" />Ctrl<ph name="END_CTRL" /><ph name="SEPARATOR" /><ph name="BEGIN_SPACE" />Espacio<ph name="END_SPACE" /><ph name="END_SHORTCUT" /></translation>
 <translation id="9100765901046053179">Configuración avanzada</translation>
 <translation id="9101691533782776290">Iniciar aplicación</translation>
diff --git a/chrome/app/resources/generated_resources_it.xtb b/chrome/app/resources/generated_resources_it.xtb
index 8fd48d7..e0547d323 100644
--- a/chrome/app/resources/generated_resources_it.xtb
+++ b/chrome/app/resources/generated_resources_it.xtb
@@ -6664,7 +6664,7 @@
 <translation id="7484645889979462775">Mai per questo sito</translation>
 <translation id="7487141338393529395">Attiva il controllo ortografico avanzato</translation>
 <translation id="7487969577036436319">Nessun componente installato</translation>
-<translation id="7488682689406685343">Il sito potrebbe tentare di ingannarti per indurti a consentire notifiche invasive.</translation>
+<translation id="7488682689406685343">Questo sito potrebbe tentare di ingannarti per indurti a consentire notifiche invasive.</translation>
 <translation id="7489761397368794366">Chiama dal tuo dispositivo</translation>
 <translation id="749028671485790643">Persona <ph name="VALUE" /></translation>
 <translation id="7490683549040131791">Controlla le password rimanenti</translation>
diff --git a/chrome/app/resources/generated_resources_pt-BR.xtb b/chrome/app/resources/generated_resources_pt-BR.xtb
index b4793ae..a2fa598 100644
--- a/chrome/app/resources/generated_resources_pt-BR.xtb
+++ b/chrome/app/resources/generated_resources_pt-BR.xtb
@@ -6973,7 +6973,7 @@
 <translation id="7746739418892731373">Você está vendo destaques de fotos e vídeos da sua biblioteca do Google Fotos. É possível controlar o que aparece nos seus destaques em <ph name="BEGIN_LINK" />photos.google.com/settings<ph name="END_LINK" />.
         <ph name="BREAK" />
         <ph name="BREAK" />
-        Você pode gerenciar as configurações desse card no menu dele ou ver mais opções em "Personalizar o Chrome".</translation>
+        Você pode gerenciar as configurações no menu desse card ou ver mais opções em "Personalizar o Chrome".</translation>
 <translation id="7750228210027921155">Picture-in-picture</translation>
 <translation id="7751260505918304024">Exibir todos</translation>
 <translation id="7751619076382363711">Você não removeu nenhum site</translation>
diff --git a/chrome/app/resources/generated_resources_ru.xtb b/chrome/app/resources/generated_resources_ru.xtb
index 613fe7a..ca71f42 100644
--- a/chrome/app/resources/generated_resources_ru.xtb
+++ b/chrome/app/resources/generated_resources_ru.xtb
@@ -5264,7 +5264,7 @@
 <translation id="6077189836672154517">Обновления и полезные советы по работе с <ph name="DEVICE_TYPE" /></translation>
 <translation id="6077476112742402730">Голосовой ввод</translation>
 <translation id="6078121669093215958">{0,plural, =1{Гость}one{# открытое окно в гостевом режиме}few{# открытых окна в гостевом режиме}many{# открытых окон в гостевом режиме}other{# открытого окна в гостевом режиме}}</translation>
-<translation id="6078323886959318429">Добавить ярлык</translation>
+<translation id="6078323886959318429">Новый ярлык</translation>
 <translation id="6078752646384677957">Проверьте микрофон и уровни громкости.</translation>
 <translation id="6078769373519310690">Расширение "<ph name="CHROME_EXTENSION_NAME" />" запрашивает разрешение на подключение к HID-устройству</translation>
 <translation id="608029822688206592">Сеть не найдена. Установите SIM-карту и повторите попытку.</translation>
@@ -6959,7 +6959,7 @@
 <translation id="7746739418892731373">Это фото и видео с лучшими моментами из вашей библиотеки Google Фото. Управлять ими можно на странице <ph name="BEGIN_LINK" />photos.google.com/settings<ph name="END_LINK" />.
         <ph name="BREAK" />
         <ph name="BREAK" />
-        Вы можете управлять настройками в меню карточки. Чтобы посмотреть дополнительные параметры, выберите "Настроить Chrome".</translation>
+        Вы можете задать нужные настройки в меню карточки. Чтобы посмотреть дополнительные параметры, выберите "Настроить Chrome".</translation>
 <translation id="7750228210027921155">Картинка в картинке</translation>
 <translation id="7751260505918304024">Показать все</translation>
 <translation id="7751619076382363711">Вы не удаляли сайты.</translation>
@@ -7418,7 +7418,7 @@
 <translation id="8168071266284693455">Закладки, пароли, история и другие данные теперь синхронизируются на всех ваших устройствах.</translation>
 <translation id="8168435359814927499">Контент</translation>
 <translation id="8169165065843881617">{NUM_TABS,plural, =1{Добавить вкладку в список для чтения}one{Добавить вкладки в список для чтения}few{Добавить вкладки в список для чтения}many{Добавить вкладки в список для чтения}other{Добавить вкладки в список для чтения}}</translation>
-<translation id="8171334254070436367">Скрыть все подсказки</translation>
+<translation id="8171334254070436367">Скрыть все карточки</translation>
 <translation id="8174047975335711832">Сведения об устройстве</translation>
 <translation id="8174876712881364124">Резервное копирование на Google Диск. Позволяет в любой момент восстановить данные (в том числе данные приложений) или перенести их на новое устройство. Резервные копии загружаются в Google и шифруются с помощью пароля аккаунта вашего ребенка. <ph name="BEGIN_LINK1" />Подробнее…<ph name="END_LINK1" /></translation>
 <translation id="8176332201990304395">Розовый с белым</translation>
diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/resources/generated_resources_zh-CN.xtb
index 0f105ab0..3f77953 100644
--- a/chrome/app/resources/generated_resources_zh-CN.xtb
+++ b/chrome/app/resources/generated_resources_zh-CN.xtb
@@ -3299,7 +3299,7 @@
 <translation id="4116704186509653070">重新打开</translation>
 <translation id="4117714603282104018">触控板触感反馈</translation>
 <translation id="4118579674665737931">请重新启动此设备,然后重试。</translation>
-<translation id="412022815379960229">登录后即可开始浏览您在 Google 相册中的“回忆”集锦。</translation>
+<translation id="412022815379960229">登录后即可开始浏览您在 Google 相册中的回忆集锦。</translation>
 <translation id="4120388883569225797">无法重置此安全密钥</translation>
 <translation id="4120817667028078560">路径过长</translation>
 <translation id="4124823734405044952">您的安全密钥已重置完毕</translation>
@@ -4594,7 +4594,7 @@
 <translation id="5435779377906857208">始终允许 <ph name="HOST" /> 使用您的位置信息</translation>
 <translation id="5436492226391861498">正在等待代理隧道的响应...</translation>
 <translation id="5436510242972373446">用 <ph name="SITE_NAME" /> 搜索:</translation>
-<translation id="5436575196282187764">Google 相册中的“回忆”集锦</translation>
+<translation id="5436575196282187764">Google 相册中的回忆集锦</translation>
 <translation id="5439680044267106777">跳过此步骤并设置新的个人资料</translation>
 <translation id="544083962418256601">创建快捷方式...</translation>
 <translation id="5441133529460183413">从 Chrome 浏览器安装的 Web 应用</translation>
@@ -4912,7 +4912,7 @@
 <translation id="5747552184818312860">到期时间</translation>
 <translation id="5747785204778348146">开发者版本 - 不稳定</translation>
 <translation id="5747809636523347288">粘贴并转到 <ph name="URL" /></translation>
-<translation id="5755022574660047665">Google 相册中的“回忆”集锦</translation>
+<translation id="5755022574660047665">Google 相册中的回忆集锦</translation>
 <translation id="5756163054456765343">帮助中心(&amp;E)</translation>
 <translation id="5757375109985023827">选择一个标签页即可预览</translation>
 <translation id="5758631781033351321">您的阅读清单会显示在此处</translation>
@@ -5481,7 +5481,7 @@
 <translation id="6308493641021088955">登录服务提供方:<ph name="EXTENSION_NAME" /></translation>
 <translation id="6308937455967653460">链接另存为(&amp;K)...</translation>
 <translation id="6309443618838462258">您的管理员不允许使用此输入法</translation>
-<translation id="630948338437014525">“回忆”集锦</translation>
+<translation id="630948338437014525">回忆集锦</translation>
 <translation id="6309510305002439352">麦克风已关闭</translation>
 <translation id="6310141306111263820">无法安装 eSIM 卡配置文件。如需帮助,请与您的运营商联系。</translation>
 <translation id="6311220991371174222">打开您的个人资料时出了点问题,无法启动 Chrome。请尝试重新启动 Chrome。</translation>
@@ -6598,7 +6598,7 @@
 <translation id="7423513079490750513">移除<ph name="INPUT_METHOD_NAME" /></translation>
 <translation id="7423807071740419372"><ph name="APP_NAME" /> 需要获得相应权限才能运行</translation>
 <translation id="7424818322350938336">已添加网络</translation>
-<translation id="7427315069950454694">您今天的“回忆”集锦</translation>
+<translation id="7427315069950454694">今天的回忆集锦</translation>
 <translation id="7427348830195639090">后台网页:<ph name="BACKGROUND_PAGE_URL" /></translation>
 <translation id="7427798576651127129">通过<ph name="DEVICE_NAME" />拨打电话</translation>
 <translation id="7431719494109538750">找不到任何 HID 设备</translation>
@@ -6617,7 +6617,7 @@
 <translation id="744341768939279100">创建新的个人资料</translation>
 <translation id="744366959743242014">正在加载数据,这最多可能需要几秒钟的时间。</translation>
 <translation id="7443806024147773267">只要您登录自己的 Google 帐号,即可获取您的密码</translation>
-<translation id="7444970023873202833">在 Google 相册中发掘您的更多美好回忆</translation>
+<translation id="7444970023873202833">在 Google 相册中找寻更多美好回忆</translation>
 <translation id="7444983668544353857">停用<ph name="NETWORKDEVICE" /></translation>
 <translation id="7448430327655618736">自动安装应用</translation>
 <translation id="7449381494541372002">正在启动…</translation>
diff --git a/chrome/app/resources/google_chrome_strings_es.xtb b/chrome/app/resources/google_chrome_strings_es.xtb
index e08c14b..9a799340 100644
--- a/chrome/app/resources/google_chrome_strings_es.xtb
+++ b/chrome/app/resources/google_chrome_strings_es.xtb
@@ -144,7 +144,7 @@
 <translation id="3541482654983822893">Chrome no puede comprobar tus contraseñas. Vuelve a intentarlo en 24 horas.</translation>
 <translation id="3576528680708590453">El administrador del sistema ha configurado Google Chrome para que abra otro navegador al acceder a <ph name="TARGET_URL_HOSTNAME" />.</translation>
 <translation id="3582972582564653026">Sincroniza y personaliza Chrome en todos tus dispositivos</translation>
-<translation id="3596080736082218006">{COUNT,plural, =0{Tu administrador pide que reinicies Chrome para aplicar una actualización}=1{Tu administrador pide que reinicies Chrome para aplicar una actualización. La ventana de incógnito no se volverá a abrir.}other{Tu administrador pide que reinicies Chrome para aplicar una actualización. Las # ventanas de incógnito no se volverán a abrir.}}</translation>
+<translation id="3596080736082218006">{COUNT,plural, =0{Tu administrador pide que reinicies Chrome para aplicar una actualización}=1{Tu administrador pide que reinicies Chrome para aplicar una actualización. La ventana de Incógnito no se volverá a abrir.}other{Tu administrador pide que reinicies Chrome para aplicar una actualización. Las # ventanas de Incógnito no se volverán a abrir.}}</translation>
 <translation id="3622797965165704966">Ahora es más fácil utilizar Chrome con tu cuenta de Google y en ordenadores compartidos.</translation>
 <translation id="3673813398384385993">Chrome ha detectado que "<ph name="EXTENSION_NAME" />" contiene software malicioso</translation>
 <translation id="3703994572283698466">ChromeOS se ha creado gracias a <ph name="BEGIN_LINK_CROS_OSS" />software libre<ph name="END_LINK_CROS_OSS" /> adicional, como el <ph name="BEGIN_LINK_LINUX_OSS" />entorno de desarrollo de Linux<ph name="END_LINK_LINUX_OSS" />.</translation>
@@ -236,7 +236,7 @@
 <translation id="556024056938947818">Google Chrome está intentando mostrar contraseñas.</translation>
 <translation id="5566025111015594046">Google Chrome (tráfico mDNS entrante)</translation>
 <translation id="565744775970812598">Es posible que <ph name="FILE_NAME" /> sea peligroso, por lo que Chrome lo ha bloqueado.</translation>
-<translation id="5678190148303298925">{COUNT,plural, =0{Tu administrador pide que reinicies Chrome para aplicar esta actualización}=1{Tu administrador pide que reinicies Chrome para aplicar esta actualización. La ventana de incógnito no se volverá a abrir.}other{Tu administrador pide que reinicies Chrome para aplicar esta actualización. Las # ventanas de incógnito no se volverán a abrir.}}</translation>
+<translation id="5678190148303298925">{COUNT,plural, =0{Tu administrador pide que reinicies Chrome para aplicar esta actualización}=1{Tu administrador pide que reinicies Chrome para aplicar esta actualización. La ventana de Incógnito no se volverá a abrir.}other{Tu administrador pide que reinicies Chrome para aplicar esta actualización. Las # ventanas de Incógnito no se volverán a abrir.}}</translation>
 <translation id="5686916850681061684">Personaliza y controla Google Chrome. Se requiere tu intervención. Haz clic aquí para obtener más información.</translation>
 <translation id="5690427481109656848">Google LLC</translation>
 <translation id="5715063361988620182">{SECONDS,plural, =1{Google Chrome se reiniciará en 1 segundo}other{Google Chrome se reiniciará en # segundos}}</translation>
@@ -300,7 +300,7 @@
 <translation id="7098166902387133879">Google Chrome está utilizando el micrófono.</translation>
 <translation id="7099479769133613710">Reiniciar para actualizar &amp;ChromeOS</translation>
 <translation id="7106741999175697885">Administrador de tareas de Google Chrome</translation>
-<translation id="7140653346177713799">{COUNT,plural, =0{Hay una nueva actualización de Chrome disponible que se aplicará cuando reinicies el navegador.}=1{Hay una nueva actualización de Chrome disponible que se aplicará cuando reinicies el navegador. La ventana de incógnito no se volverá a abrir.}other{Hay una nueva actualización de Chrome disponible que se aplicará cuando reinicies el navegador. Las # ventanas de incógnito no se volverán a abrir.}}</translation>
+<translation id="7140653346177713799">{COUNT,plural, =0{Hay una nueva actualización de Chrome disponible que se aplicará cuando reinicies el navegador.}=1{Hay una nueva actualización de Chrome disponible que se aplicará cuando reinicies el navegador. La ventana de Incógnito no se volverá a abrir.}other{Hay una nueva actualización de Chrome disponible que se aplicará cuando reinicies el navegador. Las # ventanas de Incógnito no se volverán a abrir.}}</translation>
 <translation id="7155997830309522122">Si es así, edita la contraseña guardada en Chrome para que coincida con la nueva.</translation>
 <translation id="7161904924553537242">Bienvenido a Google Chrome</translation>
 <translation id="7177959540995930968">Puedes obtener más información sobre estas funciones en la configuración de Chrome.</translation>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 4bf78ef1..910dd0e2 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5945,6 +5945,11 @@
      flag_descriptions::kEnableCssSelectorFragmentAnchorDescription, kOsAll,
      FEATURE_VALUE_TYPE(blink::features::kCssSelectorFragmentAnchor)},
 
+    {"drop-input-events-before-first-paint",
+     flag_descriptions::kDropInputEventsBeforeFirstPaintName,
+     flag_descriptions::kDropInputEventsBeforeFirstPaintDescription, kOsAll,
+     FEATURE_VALUE_TYPE(blink::features::kDropInputEventsBeforeFirstPaint)},
+
     {"enable-resampling-input-events",
      flag_descriptions::kEnableResamplingInputEventsName,
      flag_descriptions::kEnableResamplingInputEventsDescription, kOsAll,
diff --git a/chrome/browser/apps/app_service/app_service_proxy_unittest.cc b/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
index dedc4dd..b74d7a9 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/features.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
@@ -53,6 +54,11 @@
     CallOnApps(known_app_ids_, /*uninstall=*/false);
   }
 
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override {}
+
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override {}
 
diff --git a/chrome/browser/apps/app_service/publishers/app_publisher.h b/chrome/browser/apps/app_service/publishers/app_publisher.h
index dea0def..0652ab3 100644
--- a/chrome/browser/apps/app_service/publishers/app_publisher.h
+++ b/chrome/browser/apps/app_service/publishers/app_publisher.h
@@ -13,6 +13,7 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
 #include "chrome/browser/apps/app_service/launch_result_type.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/cpp/intent.h"
@@ -74,6 +75,16 @@
                         bool allow_placeholder_icon,
                         LoadIconCallback callback) = 0;
 
+  // Launches an app identified by `app_id`. `event_flags` contains launch
+  // options (e.g. window disposition). `launch_source` contains the source
+  // of the launch. When provided, `window_info` contains the expected window
+  // bounds, etc. that are requested for the placement of the launched app
+  // window.
+  virtual void Launch(const std::string& app_id,
+                      int32_t event_flags,
+                      LaunchSource launch_source,
+                      WindowInfoPtr window_info) = 0;
+
   // Launches an app with |params|.
   //
   // Publishers implementing this method should:
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.h b/chrome/browser/apps/app_service/publishers/arc_apps.h
index ef69756..ca5aeaa9 100644
--- a/chrome/browser/apps/app_service/publishers/arc_apps.h
+++ b/chrome/browser/apps/app_service/publishers/arc_apps.h
@@ -108,7 +108,7 @@
   void Launch(const std::string& app_id,
               int32_t event_flags,
               LaunchSource launch_source,
-              WindowInfoPtr window_info);
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
   void LaunchShortcut(const std::string& app_id,
diff --git a/chrome/browser/apps/app_service/publishers/borealis_apps.cc b/chrome/browser/apps/app_service/publishers/borealis_apps.cc
index 50936639..0bbf1015 100644
--- a/chrome/browser/apps/app_service/publishers/borealis_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/borealis_apps.cc
@@ -317,6 +317,14 @@
                        std::move(callback));
 }
 
+void BorealisApps::Launch(const std::string& app_id,
+                          int32_t event_flags,
+                          LaunchSource launch_source,
+                          WindowInfoPtr window_info) {
+  borealis::BorealisService::GetForProfile(profile_)->AppLauncher().Launch(
+      app_id, base::DoNothing());
+}
+
 void BorealisApps::LaunchAppWithParams(AppLaunchParams&& params,
                                        LaunchCallback callback) {
   Launch(params.app_id, ui::EF_NONE, apps::mojom::LaunchSource::kUnknown,
diff --git a/chrome/browser/apps/app_service/publishers/borealis_apps.h b/chrome/browser/apps/app_service/publishers/borealis_apps.h
index 4f0392de..ffb769b8 100644
--- a/chrome/browser/apps/app_service/publishers/borealis_apps.h
+++ b/chrome/browser/apps/app_service/publishers/borealis_apps.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/ash/borealis/borealis_window_manager.h"
 #include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
 #include "components/prefs/pref_change_registrar.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
 #include "components/services/app_service/public/mojom/app_service.mojom.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
@@ -76,6 +77,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
 
diff --git a/chrome/browser/apps/app_service/publishers/built_in_chromeos_apps.cc b/chrome/browser/apps/app_service/publishers/built_in_chromeos_apps.cc
index a677e511..c2524331 100644
--- a/chrome/browser/apps/app_service/publishers/built_in_chromeos_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/built_in_chromeos_apps.cc
@@ -145,6 +145,15 @@
   std::move(callback).Run(std::make_unique<IconValue>());
 }
 
+void BuiltInChromeOsApps::Launch(const std::string& app_id,
+                                 int32_t event_flags,
+                                 LaunchSource launch_source,
+                                 WindowInfoPtr window_info) {
+  if (app_id == ash::kInternalAppIdKeyboardShortcutViewer) {
+    ash::ToggleKeyboardShortcutViewer();
+  }
+}
+
 void BuiltInChromeOsApps::LaunchAppWithParams(AppLaunchParams&& params,
                                               LaunchCallback callback) {
   Launch(params.app_id, ui::EF_NONE, apps::mojom::LaunchSource::kUnknown,
diff --git a/chrome/browser/apps/app_service/publishers/built_in_chromeos_apps.h b/chrome/browser/apps/app_service/publishers/built_in_chromeos_apps.h
index d0e178b..f1c6e8f9 100644
--- a/chrome/browser/apps/app_service/publishers/built_in_chromeos_apps.h
+++ b/chrome/browser/apps/app_service/publishers/built_in_chromeos_apps.h
@@ -11,6 +11,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
 #include "chrome/browser/apps/app_service/launch_result_type.h"
 #include "chrome/browser/apps/app_service/publishers/app_publisher.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
 #include "components/services/app_service/public/mojom/app_service.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -51,6 +52,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
 
diff --git a/chrome/browser/apps/app_service/publishers/crostini_apps.cc b/chrome/browser/apps/app_service/publishers/crostini_apps.cc
index 8e168c6c..a9a65363 100644
--- a/chrome/browser/apps/app_service/publishers/crostini_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/crostini_apps.cc
@@ -109,6 +109,15 @@
                       std::move(callback));
 }
 
+void CrostiniApps::Launch(const std::string& app_id,
+                          int32_t event_flags,
+                          LaunchSource launch_source,
+                          WindowInfoPtr window_info) {
+  crostini::LaunchCrostiniApp(
+      profile_, app_id,
+      window_info ? window_info->display_id : display::kInvalidDisplayId);
+}
+
 void CrostiniApps::LaunchAppWithParams(AppLaunchParams&& params,
                                        LaunchCallback callback) {
   auto event_flags = apps::GetEventFlags(params.disposition,
diff --git a/chrome/browser/apps/app_service/publishers/crostini_apps.h b/chrome/browser/apps/app_service/publishers/crostini_apps.h
index cca49ae..0bd7451 100644
--- a/chrome/browser/apps/app_service/publishers/crostini_apps.h
+++ b/chrome/browser/apps/app_service/publishers/crostini_apps.h
@@ -17,6 +17,7 @@
 #include "chrome/browser/apps/app_service/publishers/app_publisher.h"
 #include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
 #include "components/services/app_service/public/mojom/app_service.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -61,6 +62,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
 
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_base.cc b/chrome/browser/apps/app_service/publishers/extension_apps_base.cc
index 8d7acc0..21b17375 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_base.cc
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_base.cc
@@ -423,6 +423,13 @@
                         std::move(callback));
 }
 
+void ExtensionAppsBase::Launch(const std::string& app_id,
+                               int32_t event_flags,
+                               LaunchSource launch_source,
+                               WindowInfoPtr window_info) {
+  // TODO(crbug.com/1253250): Add the implementation.
+}
+
 void ExtensionAppsBase::LaunchAppWithParams(AppLaunchParams&& params,
                                             LaunchCallback callback) {
   auto app_id = params.app_id;
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_base.h b/chrome/browser/apps/app_service/publishers/extension_apps_base.h
index ccac0d7a..0aaf8c6 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_base.h
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_base.h
@@ -17,6 +17,7 @@
 #include "chrome/browser/apps/app_service/app_icon/icon_key_util.h"
 #include "chrome/browser/apps/app_service/launch_result_type.h"
 #include "chrome/browser/apps/app_service/publishers/app_publisher.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
 #include "components/services/app_service/public/mojom/app_service.mojom.h"
@@ -159,6 +160,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
 
diff --git a/chrome/browser/apps/app_service/publishers/plugin_vm_apps.cc b/chrome/browser/apps/app_service/publishers/plugin_vm_apps.cc
index 33098b4..02d5a899 100644
--- a/chrome/browser/apps/app_service/publishers/plugin_vm_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/plugin_vm_apps.cc
@@ -259,6 +259,19 @@
                       std::move(callback));
 }
 
+void PluginVmApps::Launch(const std::string& app_id,
+                          int32_t event_flags,
+                          LaunchSource launch_source,
+                          WindowInfoPtr window_info) {
+  DCHECK_EQ(plugin_vm::kPluginVmShelfAppId, app_id);
+  if (plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile_)) {
+    plugin_vm::PluginVmManagerFactory::GetForProfile(profile_)->LaunchPluginVm(
+        base::DoNothing());
+  } else {
+    plugin_vm::ShowPluginVmInstallerView(profile_);
+  }
+}
+
 void PluginVmApps::LaunchAppWithParams(AppLaunchParams&& params,
                                        LaunchCallback callback) {
   Launch(params.app_id, ui::EF_NONE, apps::mojom::LaunchSource::kUnknown,
diff --git a/chrome/browser/apps/app_service/publishers/plugin_vm_apps.h b/chrome/browser/apps/app_service/publishers/plugin_vm_apps.h
index 52a21db..244b9d1 100644
--- a/chrome/browser/apps/app_service/publishers/plugin_vm_apps.h
+++ b/chrome/browser/apps/app_service/publishers/plugin_vm_apps.h
@@ -15,6 +15,7 @@
 #include "chrome/browser/ash/plugin_vm/plugin_vm_manager.h"
 #include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
 #include "components/prefs/pref_change_registrar.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
 #include "components/services/app_service/public/mojom/app_service.mojom.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -59,6 +60,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
 
diff --git a/chrome/browser/apps/app_service/publishers/remote_apps.cc b/chrome/browser/apps/app_service/publishers/remote_apps.cc
index 5909e9b..fbea8e6 100644
--- a/chrome/browser/apps/app_service/publishers/remote_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/remote_apps.cc
@@ -129,6 +129,13 @@
                    std::move(callback));
 }
 
+void RemoteApps::Launch(const std::string& app_id,
+                        int32_t event_flags,
+                        LaunchSource launch_source,
+                        WindowInfoPtr window_info) {
+  delegate_->LaunchApp(app_id);
+}
+
 void RemoteApps::LaunchAppWithParams(AppLaunchParams&& params,
                                      LaunchCallback callback) {
   Launch(params.app_id, ui::EF_NONE, apps::mojom::LaunchSource::kUnknown,
diff --git a/chrome/browser/apps/app_service/publishers/remote_apps.h b/chrome/browser/apps/app_service/publishers/remote_apps.h
index 5285d2d..443701c 100644
--- a/chrome/browser/apps/app_service/publishers/remote_apps.h
+++ b/chrome/browser/apps/app_service/publishers/remote_apps.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/apps/app_service/launch_result_type.h"
 #include "chrome/browser/apps/app_service/publishers/app_publisher.h"
 #include "chrome/browser/ash/remote_apps/remote_apps_model.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
 #include "components/services/app_service/public/mojom/app_service.mojom.h"
@@ -88,6 +89,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
 
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 def5cc72a..c2bdc309 100644
--- a/chrome/browser/apps/app_service/publishers/standalone_browser_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/standalone_browser_apps.cc
@@ -158,6 +158,15 @@
                        std::move(callback));
 }
 
+void StandaloneBrowserApps::Launch(const std::string& app_id,
+                                   int32_t event_flags,
+                                   LaunchSource launch_source,
+                                   WindowInfoPtr window_info) {
+  DCHECK_EQ(app_constants::kLacrosAppId, app_id);
+  crosapi::BrowserManager::Get()->NewTab(
+      /*should_trigger_session_restore=*/true);
+}
+
 void StandaloneBrowserApps::LaunchAppWithParams(AppLaunchParams&& params,
                                                 LaunchCallback callback) {
   Launch(params.app_id, ui::EF_NONE, apps::mojom::LaunchSource::kUnknown,
diff --git a/chrome/browser/apps/app_service/publishers/standalone_browser_apps.h b/chrome/browser/apps/app_service/publishers/standalone_browser_apps.h
index 1016a852..36b8e99 100644
--- a/chrome/browser/apps/app_service/publishers/standalone_browser_apps.h
+++ b/chrome/browser/apps/app_service/publishers/standalone_browser_apps.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/apps/app_service/publishers/app_publisher.h"
 #include "chrome/browser/ash/crosapi/browser_manager.h"
 #include "chrome/browser/ash/crosapi/browser_manager_observer.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
 #include "components/services/app_service/public/mojom/app_service.mojom-forward.h"
@@ -67,6 +68,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
 
diff --git a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.cc b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.cc
index ee54fab4..b62f434 100644
--- a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.cc
@@ -125,6 +125,13 @@
                      std::move(callback)));
 }
 
+void StandaloneBrowserExtensionApps::Launch(const std::string& app_id,
+                                            int32_t event_flags,
+                                            LaunchSource launch_source,
+                                            WindowInfoPtr window_info) {
+  // TODO(crbug.com/1253250): Add the implementation.
+}
+
 void StandaloneBrowserExtensionApps::LaunchAppWithParams(
     AppLaunchParams&& params,
     LaunchCallback callback) {
diff --git a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
index a9cf818..1d6ec29 100644
--- a/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
+++ b/chrome/browser/apps/app_service/publishers/standalone_browser_extension_apps.h
@@ -17,6 +17,7 @@
 #include "chromeos/crosapi/mojom/app_service.mojom.h"
 #include "chromeos/login/login_state/login_state.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
@@ -83,6 +84,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
 
diff --git a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
index 6635975..5182beb7 100644
--- a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
+++ b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
@@ -90,6 +90,13 @@
                      std::move(callback)));
 }
 
+void WebAppsCrosapi::Launch(const std::string& app_id,
+                            int32_t event_flags,
+                            LaunchSource launch_source,
+                            WindowInfoPtr window_info) {
+  // TODO(crbug.com/1253250): Add the implementation.
+}
+
 void WebAppsCrosapi::LaunchAppWithParams(AppLaunchParams&& params,
                                          LaunchCallback callback) {
   if (!LogIfNotConnected(FROM_HERE)) {
diff --git a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
index a4961104..a963a56 100644
--- a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
+++ b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/apps/app_service/publishers/app_publisher.h"
 #include "chromeos/crosapi/mojom/app_service.mojom.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
@@ -78,6 +79,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              LaunchSource launch_source,
+              WindowInfoPtr window_info) override;
   void LaunchAppWithParams(AppLaunchParams&& params,
                            LaunchCallback callback) override;
   void LaunchShortcut(const std::string& app_id,
diff --git a/chrome/browser/ash/crosapi/crosapi_util.cc b/chrome/browser/ash/crosapi/crosapi_util.cc
index 2c94103..c0f9900 100644
--- a/chrome/browser/ash/crosapi/crosapi_util.cc
+++ b/chrome/browser/ash/crosapi/crosapi_util.cc
@@ -170,6 +170,10 @@
   return policy::CopyComponentPolicyMap(map);
 }
 
+bool GetIsCurrentUserOwner() {
+  return user_manager::UserManager::Get()->IsCurrentUserOwner();
+}
+
 bool GetUseCupsForPrinting() {
 #if defined(USE_CUPS)
   return true;
@@ -532,6 +536,7 @@
   params->ash_chrome_version = version_info::GetVersionNumber();
   params->use_cups_for_printing = GetUseCupsForPrinting();
   params->use_floss_bluetooth = floss::features::IsFlossEnabled();
+  params->is_current_user_device_owner = GetIsCurrentUserOwner();
 
   return params;
 }
diff --git a/chrome/browser/ash/crosapi/device_settings_ash.cc b/chrome/browser/ash/crosapi/device_settings_ash.cc
index e2c361a..9550d980 100644
--- a/chrome/browser/ash/crosapi/device_settings_ash.cc
+++ b/chrome/browser/ash/crosapi/device_settings_ash.cc
@@ -56,10 +56,8 @@
       ProfileManager::GetActiveUserProfile());
   client->EnableUserPolicies(false);
   DeviceCloudPolicyStatusProviderChromeOS provider(connector);
-  base::DictionaryValue status;
-  provider.GetStatus(&status);
-  std::move(callback).Run(client->GetChromePolicies(),
-                          std::move(status.GetDict()));
+  base::Value::Dict status = provider.GetStatus();
+  std::move(callback).Run(client->GetChromePolicies(), std::move(status));
 }
 
 void DeviceSettingsAsh::GetDevicePolicyDeprecated(
@@ -75,10 +73,9 @@
       ProfileManager::GetActiveUserProfile());
   client->EnableUserPolicies(false);
   DeviceCloudPolicyStatusProviderChromeOS provider(connector);
-  base::DictionaryValue status;
-  provider.GetStatus(&status);
+  base::Value::Dict status = provider.GetStatus();
   std::move(callback).Run(base::Value(client->GetChromePolicies()),
-                          std::move(status));
+                          base::Value(std::move(status)));
 }
 
 }  // namespace crosapi
diff --git a/chrome/browser/ash/login/reporting/login_logout_reporter_browsertest.cc b/chrome/browser/ash/login/reporting/login_logout_reporter_browsertest.cc
new file mode 100644
index 0000000..f8c83ea
--- /dev/null
+++ b/chrome/browser/ash/login/reporting/login_logout_reporter_browsertest.cc
@@ -0,0 +1,143 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "ash/components/login/auth/auth_status_consumer.h"
+#include "ash/components/login/auth/key.h"
+#include "ash/components/login/auth/stub_authenticator_builder.h"
+#include "ash/components/login/auth/user_context.h"
+#include "ash/components/settings/cros_settings_names.h"
+#include "ash/public/cpp/login_screen_test_api.h"
+#include "base/run_loop.h"
+#include "chrome/browser/ash/login/session/user_session_manager_test_api.h"
+#include "chrome/browser/ash/login/test/fake_gaia_mixin.h"
+#include "chrome/browser/ash/login/test/login_manager_mixin.h"
+#include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
+#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/policy/messaging_layer/proto/synced/login_logout_event.pb.h"
+#include "chromeos/dbus/missive/missive_client.h"
+#include "chromeos/dbus/missive/missive_client_test_observer.h"
+#include "components/account_id/account_id.h"
+#include "components/reporting/proto/synced/record.pb.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using chromeos::MissiveClient;
+using chromeos::MissiveClientTestObserver;
+using reporting::Destination;
+using reporting::Priority;
+using reporting::Record;
+using testing::Eq;
+using testing::SizeIs;
+
+namespace ash::reporting {
+
+class LoginLogoutReporterBrowserTest
+    : public policy::DevicePolicyCrosBrowserTest {
+ public:
+  LoginLogoutReporterBrowserTest() = default;
+
+  LoginLogoutReporterBrowserTest(const LoginLogoutReporterBrowserTest&) =
+      delete;
+  LoginLogoutReporterBrowserTest& operator=(
+      const LoginLogoutReporterBrowserTest&) = delete;
+
+  void SetUpStubAuthenticatorAndAttemptLogin(
+      AuthFailure::FailureReason failure_reason = AuthFailure::NONE) {
+    const UserContext user_context =
+        LoginManagerMixin::CreateDefaultUserContext(test_user_);
+
+    auto authenticator_builder =
+        std::make_unique<StubAuthenticatorBuilder>(user_context);
+    if (failure_reason != AuthFailure::NONE) {
+      authenticator_builder->SetUpAuthFailure(failure_reason);
+    }
+
+    test::UserSessionManagerTestApi(UserSessionManager::GetInstance())
+        .InjectAuthenticatorBuilder(std::move(authenticator_builder));
+
+    const std::string& password = user_context.GetKey()->GetSecret();
+    LoginScreenTestApi::SubmitPassword(test_user_.account_id, password,
+                                       /*check_if_submittable=*/true);
+  }
+
+ protected:
+  void SetIsReportLoginLogoutPolicyEnabled(bool enabled) {
+    policy_helper()
+        ->device_policy()
+        ->payload()
+        .mutable_device_reporting()
+        ->set_report_login_logout(enabled);
+    policy_helper()->RefreshPolicyAndWaitUntilDeviceSettingsUpdated(
+        {ash::kReportDeviceLoginLogout});
+  }
+
+  const LoginManagerMixin::TestUserInfo test_user_{
+      AccountId::FromUserEmailGaiaId(FakeGaiaMixin::kFakeUserEmail,
+                                     FakeGaiaMixin::kFakeUserGaiaId)};
+
+  LoginManagerMixin login_manager_{&mixin_host_, {test_user_}};
+};
+
+IN_PROC_BROWSER_TEST_F(LoginLogoutReporterBrowserTest, LoginSuccessful) {
+  SetIsReportLoginLogoutPolicyEnabled(true);
+
+  MissiveClientTestObserver observer(Destination::LOGIN_LOGOUT_EVENTS);
+  SetUpStubAuthenticatorAndAttemptLogin();
+  test::WaitForPrimaryUserSessionStart();
+  base::RunLoop().RunUntilIdle();
+
+  std::tuple<Priority, Record> enqueued_record =
+      observer.GetNextEnqueuedRecord();
+  Priority priority = std::get<0>(enqueued_record);
+  Record record = std::get<1>(enqueued_record);
+
+  EXPECT_FALSE(observer.HasNewEnqueuedRecords());
+  EXPECT_THAT(priority, Eq(Priority::SECURITY));
+  LoginLogoutRecord record_data;
+  ASSERT_TRUE(record_data.ParseFromString(record.data()));
+  EXPECT_THAT(record_data.session_type(),
+              Eq(LoginLogoutSessionType::REGULAR_USER_SESSION));
+  EXPECT_FALSE(record_data.has_affiliated_user());
+  ASSERT_TRUE(record_data.has_login_event());
+  EXPECT_FALSE(record_data.login_event().has_failure());
+}
+
+IN_PROC_BROWSER_TEST_F(LoginLogoutReporterBrowserTest, LoginFailed) {
+  SetIsReportLoginLogoutPolicyEnabled(true);
+
+  MissiveClientTestObserver observer(Destination::LOGIN_LOGOUT_EVENTS);
+  SetUpStubAuthenticatorAndAttemptLogin(
+      AuthFailure::COULD_NOT_MOUNT_CRYPTOHOME);
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(MissiveClient::Get());
+  MissiveClient::TestInterface* const fake_missive =
+      MissiveClient::Get()->GetTestInterface();
+  ASSERT_TRUE(fake_missive);
+
+  std::tuple<Priority, Record> enqueued_record =
+      observer.GetNextEnqueuedRecord();
+  Priority priority = std::get<0>(enqueued_record);
+  Record record = std::get<1>(enqueued_record);
+
+  EXPECT_FALSE(observer.HasNewEnqueuedRecords());
+  EXPECT_THAT(priority, Eq(Priority::SECURITY));
+  LoginLogoutRecord record_data;
+  ASSERT_TRUE(record_data.ParseFromString(record.data()));
+  EXPECT_THAT(record_data.session_type(),
+              Eq(LoginLogoutSessionType::REGULAR_USER_SESSION));
+  EXPECT_FALSE(record_data.has_affiliated_user());
+  ASSERT_TRUE(record_data.has_login_event());
+  ASSERT_TRUE(record_data.login_event().has_failure());
+  EXPECT_THAT(record_data.login_event().failure().reason(),
+              LoginFailureReason::AUTHENTICATION_ERROR);
+}
+
+}  // namespace ash::reporting
diff --git a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
index 1558fad..2c02b2e 100644
--- a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
+++ b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
@@ -5,15 +5,21 @@
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h"
 
 #include <stdint.h>
+#include <memory>
+#include <string>
 
 #include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
 #include "base/guid.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
-#include "chrome/browser/ash/login/enrollment/auto_enrollment_controller.h"
+#include "base/time/time.h"
+#include "base/values.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_state_message_processor.h"
 #include "chrome/browser/ash/policy/enrollment/private_membership/psm_rlwe_dmserver_client.h"
 #include "chrome/browser/ash/policy/server_backed_state/server_backed_device_state.h"
@@ -64,9 +70,10 @@
     dict->RemoveKey(pref_path);
 }
 
-}  // namespace
-
-class AutoEnrollmentClientImpl::DeviceIdentifierProviderFRE {
+// Provides device identifier for Forced Re-Enrollment (FRE), where the
+// server-backed state key is used. It will set the identifier for the
+// DeviceAutoEnrollmentRequest.
+class DeviceIdentifierProviderFRE {
  public:
   explicit DeviceIdentifierProviderFRE(
       const std::string& server_backed_state_key) {
@@ -75,23 +82,23 @@
         crypto::SHA256HashString(server_backed_state_key);
   }
 
-  // Disallow copy constructor and assignment operator.
   DeviceIdentifierProviderFRE(const DeviceIdentifierProviderFRE&) = delete;
   DeviceIdentifierProviderFRE& operator=(const DeviceIdentifierProviderFRE&) =
       delete;
 
   ~DeviceIdentifierProviderFRE() = default;
 
-  // Should return the EnrollmentCheckType to be used in the
+  // Should return the `EnrollmentCheckType` to be used in the
   // DeviceAutoEnrollmentRequest. This specifies the identifier set used on
   // the server.
-  EnrollmentCheckType GetEnrollmentCheckType() const {
+  em::DeviceAutoEnrollmentRequest::EnrollmentCheckType GetEnrollmentCheckType()
+      const {
     return em::DeviceAutoEnrollmentRequest::ENROLLMENT_CHECK_TYPE_FRE;
   }
 
   // Should return the hash of this device's identifier. The
   // DeviceAutoEnrollmentRequest exchange will check if this hash is in the
-  // server-side identifier set specified by |GetEnrollmentCheckType()|
+  // server-side identifier set specified by `GetEnrollmentCheckType()`
   const std::string& GetIdHash() const { return server_backed_state_key_hash_; }
 
  private:
@@ -99,6 +106,478 @@
   std::string server_backed_state_key_hash_;
 };
 
+}  // namespace
+
+enum class AutoEnrollmentClientImpl::ServerStateAvailabilityResult {
+  // Indicates that request has been successful and server state availability is
+  // known.
+  kSuccess = 0,
+  // Indicates a connection error during request.
+  kConnectionError = 1,
+  // Indicates an invalid response from server.
+  kServerError = 2,
+  // Special case for server state availability result via auto enrollment
+  // request.
+  // Indicates that request shall be immediately retried.
+  kAutoEnrollmentRetriableError = 3,
+  // Special case for server state availability result via PSM.
+  // Indicates an internal non-recoverable error.
+  kPsmInternalError = 4,
+};
+
+// Base class to handle server state availability requests.
+class AutoEnrollmentClientImpl::ServerStateAvailabilityRequester {
+ public:
+  using CompletionCallback =
+      base::OnceCallback<void(ServerStateAvailabilityResult)>;
+
+  virtual ~ServerStateAvailabilityRequester() = default;
+
+  // Initiates request and reports back with `callback` once request is
+  // finished.
+  virtual void Start(CompletionCallback callback) = 0;
+
+  // Returns:
+  // * nullopt if server state is not obtained yet,
+  // * false if server state has been obtained and the answer is: it is not
+  // available.
+  // * true if server state has been obtained and the answer is: it is
+  // available.
+  virtual absl::optional<bool> GetServerStateIfObtained() const = 0;
+};
+
+// Responsible for resolving server state availability status via auto
+// enrollment requests for force re-enrollment.
+class AutoEnrollmentClientImpl::FREServerStateAvailabilityRequester
+    : public ServerStateAvailabilityRequester {
+ public:
+  static void RegisterPrefs(PrefRegistrySimple* registry) {
+    registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
+    registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
+  }
+
+  FREServerStateAvailabilityRequester(
+      DeviceManagementService* device_management_service,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      PrefService* local_state,
+      const std::string& device_id,
+      const std::string& uma_suffix,
+      int current_power,
+      int power_limit,
+      const std::string& server_backed_state_key)
+      : device_management_service_(device_management_service),
+        url_loader_factory_(url_loader_factory),
+        local_state_(local_state),
+        device_id_(device_id),
+        uma_suffix_(uma_suffix),
+        current_power_(current_power),
+        power_limit_(power_limit),
+        device_identifier_provider_fre_(server_backed_state_key) {
+    DCHECK_LE(current_power_, power_limit_);
+  }
+
+  FREServerStateAvailabilityRequester(
+      const FREServerStateAvailabilityRequester&) = delete;
+  FREServerStateAvailabilityRequester& operator=(
+      const FREServerStateAvailabilityRequester&) = delete;
+
+  void Start(CompletionCallback callback) override {
+    StartImpl(std::move(callback));
+  }
+
+  absl::optional<bool> GetServerStateIfObtained() const override {
+    const PrefService::Preference* has_server_state_pref =
+        local_state_->FindPreference(prefs::kShouldAutoEnroll);
+    const PrefService::Preference* previous_limit_pref =
+        local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit);
+
+    if (!has_server_state_pref || has_server_state_pref->IsDefaultValue() ||
+        !previous_limit_pref || previous_limit_pref->IsDefaultValue()) {
+      return absl::nullopt;
+    }
+
+    DCHECK(has_server_state_pref->GetValue()->is_bool());
+    DCHECK(previous_limit_pref->GetValue()->is_int());
+
+    if (power_limit_ > previous_limit_pref->GetValue()->GetInt()) {
+      return absl::nullopt;
+    }
+
+    return has_server_state_pref->GetValue()->GetBool();
+  }
+
+ private:
+  void StartImpl(CompletionCallback callback) {
+    DCHECK(!request_job_);
+    DCHECK(callback);
+    DCHECK(!completion_callback_);
+
+    completion_callback_ = std::move(callback);
+
+    // Start the Hash dance timer during the first attempt.
+    if (hash_dance_time_start_.is_null())
+      hash_dance_time_start_ = base::TimeTicks::Now();
+
+    std::string id_hash = device_identifier_provider_fre_.GetIdHash();
+    // Currently AutoEnrollmentClientImpl supports working with hashes that are
+    // at least 8 bytes long. If this is reduced, the computation of the
+    // remainder must also be adapted to handle the case of a shorter hash
+    // gracefully.
+    DCHECK_GE(id_hash.size(), 8u);
+
+    uint64_t remainder = 0;
+    const size_t last_byte_index = id_hash.size() - 1;
+    for (int i = 0; 8 * i < current_power_; ++i) {
+      uint64_t byte = id_hash[last_byte_index - i] & 0xff;
+      remainder = remainder | (byte << (8 * i));
+    }
+    remainder = remainder & ((UINT64_C(1) << current_power_) - 1);
+
+    // Record the time when the bucket download request is started. Note that
+    // the time may be set multiple times. This is fine, only the last request
+    // is the one where the hash bucket is actually downloaded.
+    time_start_bucket_download_ = base::TimeTicks::Now();
+
+    // TODO(crbug.com/1271134): Logging as "WARNING" to make sure it's preserved
+    // in the logs.
+    LOG(WARNING) << "Request bucket #" << remainder;
+
+    std::unique_ptr<DMServerJobConfiguration> config =
+        std::make_unique<DMServerJobConfiguration>(
+            device_management_service_,
+            DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
+            device_id_,
+            /*critical=*/false, DMAuth::NoAuth(),
+            /*oauth_token=*/absl::nullopt, url_loader_factory_,
+            base::BindOnce(
+                &FREServerStateAvailabilityRequester::HandleRequestCompletion,
+                base::Unretained(this)));
+
+    em::DeviceAutoEnrollmentRequest* request =
+        config->request()->mutable_auto_enrollment_request();
+    request->set_remainder(remainder);
+    request->set_modulus(INT64_C(1) << current_power_);
+    request->set_enrollment_check_type(
+        device_identifier_provider_fre_.GetEnrollmentCheckType());
+
+    request_job_ = device_management_service_->CreateJob(std::move(config));
+  }
+
+  void HandleRequestCompletion(DeviceManagementService::Job* job,
+                               DeviceManagementStatus status,
+                               int net_error,
+                               const em::DeviceManagementResponse& response) {
+    DCHECK(request_job_);
+    DCHECK(completion_callback_);
+
+    request_job_.reset();
+
+    base::UmaHistogramSparse(kUMAHashDanceRequestStatus + uma_suffix_, status);
+    // TODO(crbug.com/1312919): Check `status` for specific errors.
+    if (status != DM_STATUS_SUCCESS) {
+      LOG(ERROR) << "Auto enrollment error: " << status;
+      if (status == DM_STATUS_REQUEST_FAILED)
+        base::UmaHistogramSparse(kUMAHashDanceNetworkErrorCode + uma_suffix_,
+                                 -net_error);
+      RunCallback(status == DM_STATUS_REQUEST_FAILED
+                      ? ServerStateAvailabilityResult::kConnectionError
+                      : ServerStateAvailabilityResult::kServerError);
+      return;
+    }
+
+    ServerStateAvailabilityResult result =
+        ServerStateAvailabilityResult::kSuccess;
+    const em::DeviceAutoEnrollmentResponse& enrollment_response =
+        response.auto_enrollment_response();
+    if (!response.has_auto_enrollment_response()) {
+      LOG(ERROR) << "Server failed to provide auto-enrollment response.";
+      result = ServerStateAvailabilityResult::kServerError;
+    } else if (enrollment_response.has_expected_modulus()) {
+      // Server is asking us to retry with a different modulus.
+      modulus_updates_received_++;
+
+      int64_t modulus = enrollment_response.expected_modulus();
+      int power = NextPowerOf2(modulus);
+      if ((INT64_C(1) << power) != modulus) {
+        LOG(ERROR) << "Auto enrollment: the server didn't ask for a power-of-2 "
+                   << "modulus. Using the closest power-of-2 instead "
+                   << "(" << modulus << " vs 2^" << power << ")";
+        result = ServerStateAvailabilityResult::kServerError;
+      }
+      if (modulus_updates_received_ >= 2) {
+        LOG(ERROR) << "Auto enrollment error: already retried with an updated "
+                   << "modulus but the server asked for a new one again: "
+                   << power;
+        result = ServerStateAvailabilityResult::kServerError;
+      } else if (power > power_limit_) {
+        LOG(ERROR) << "Auto enrollment error: the server asked for a larger "
+                   << "modulus than the client accepts (" << power << " vs "
+                   << power_limit_ << ").";
+        result = ServerStateAvailabilityResult::kServerError;
+      } else {
+        // Retry at most once with the modulus that the server requested.
+        if (power <= current_power_) {
+          LOG(WARNING) << "Auto enrollment: the server asked to use a modulus ("
+                       << power << ") that isn't larger than the first used ("
+                       << current_power_ << "). Retrying anyway.";
+        }
+        // Remember this value, so that eventual retries start with the correct
+        // modulus.
+        current_power_ = power;
+        DCHECK(!GetServerStateIfObtained());
+        RunCallback(
+            ServerStateAvailabilityResult::kAutoEnrollmentRetriableError);
+        return;
+      }
+    } else {
+      // Server should have sent down a list of hashes to try.
+      const bool has_server_state =
+          IsIdHashInProtobuf(enrollment_response.hashes());
+      // Cache the current decision in local_state, so that it is reused in case
+      // the device reboots before enrolling.
+      local_state_->SetBoolean(prefs::kShouldAutoEnroll, has_server_state);
+      local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
+      local_state_->CommitPendingWrite();
+
+      // TODO(crbug.com/1271134): Logging as "WARNING" to make sure it's
+      // preserved in the logs.
+      LOG(WARNING) << "Received has_state=" << has_server_state;
+
+      result = ServerStateAvailabilityResult::kSuccess;
+      RecordHashDanceSuccessTimeHistogram();
+    }
+
+    const bool succeeded_with_result =
+        result == ServerStateAvailabilityResult::kSuccess &&
+        GetServerStateIfObtained();
+    const bool failed_without_result =
+        result != ServerStateAvailabilityResult::kSuccess &&
+        !GetServerStateIfObtained();
+    DCHECK(succeeded_with_result || failed_without_result);
+
+    // Bucket download done, update UMA.
+    UpdateBucketDownloadTimingHistograms();
+    RunCallback(result);
+  }
+
+  void RunCallback(ServerStateAvailabilityResult result) {
+    DCHECK(completion_callback_);
+    std::move(completion_callback_).Run(result);
+  }
+
+  bool IsIdHashInProtobuf(
+      const google::protobuf::RepeatedPtrField<std::string>& hashes) const {
+    const std::string id_hash = device_identifier_provider_fre_.GetIdHash();
+    for (int i = 0; i < hashes.size(); ++i) {
+      if (hashes.Get(i) == id_hash)
+        return true;
+    }
+    return false;
+  }
+
+  void UpdateBucketDownloadTimingHistograms() const {
+    // These values determine bucketing of the histogram, they should not be
+    // changed.
+    // The minimum time can't be 0, must be at least 1.
+    static const base::TimeDelta kMin = base::Milliseconds(1);
+    static const base::TimeDelta kMax = base::Minutes(5);
+    static const int kBuckets = 50;
+
+    base::TimeTicks now = base::TimeTicks::Now();
+    if (!hash_dance_time_start_.is_null()) {
+      base::TimeDelta delta = now - hash_dance_time_start_;
+      base::UmaHistogramCustomTimes(kUMAHashDanceProtocolTime + uma_suffix_,
+                                    delta, kMin, kMax, kBuckets);
+    }
+    if (!time_start_bucket_download_.is_null()) {
+      base::TimeDelta delta = now - time_start_bucket_download_;
+      base::UmaHistogramCustomTimes(
+          kUMAHashDanceBucketDownloadTime + uma_suffix_, delta, kMin, kMax,
+          kBuckets);
+    }
+  }
+
+  void RecordHashDanceSuccessTimeHistogram() const {
+    // These values determine bucketing of the histogram, they should not be
+    // changed.
+    static const base::TimeDelta kMin = base::Milliseconds(1);
+    static const base::TimeDelta kMax = base::Seconds(25);
+    static const int kBuckets = 50;
+
+    base::TimeTicks now = base::TimeTicks::Now();
+    if (!hash_dance_time_start_.is_null()) {
+      base::TimeDelta delta = now - hash_dance_time_start_;
+      base::UmaHistogramCustomTimes(kUMAHashDanceSuccessTime + uma_suffix_,
+                                    delta, kMin, kMax, kBuckets);
+    }
+  }
+
+  DeviceManagementService* device_management_service_;
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+  PrefService* local_state_;
+  const std::string device_id_;
+  const std::string uma_suffix_;
+
+  // Power-of-2 modulus to try next.
+  int current_power_;
+
+  // Power of the maximum power-of-2 modulus that this client will accept from
+  // a retry response from the server.
+  const int power_limit_;
+
+  // Number of requests for a different modulus received from the server.
+  // Used to determine if the server keeps asking for different moduli.
+  int modulus_updates_received_ = 0;
+
+  // Times used to determine the duration of the protocol, and the extra time
+  // needed to complete after the signin was complete.
+  // If `hash_dance_time_start_` is not null, the protocol is still running.
+  base::TimeTicks hash_dance_time_start_;
+
+  // The time when the bucket download part of the protocol started.
+  base::TimeTicks time_start_bucket_download_;
+
+  std::unique_ptr<DeviceManagementService::Job> request_job_;
+
+  const DeviceIdentifierProviderFRE device_identifier_provider_fre_;
+
+  CompletionCallback completion_callback_;
+};
+
+// Responsible for resolving server state availability status via private
+// membership check requests for initial enrollment.
+class AutoEnrollmentClientImpl::InitialServerStateAvailabilityRequester
+    : public ServerStateAvailabilityRequester {
+ public:
+  static void RegisterPrefs(PrefRegistrySimple* registry) {
+    registry->RegisterBooleanPref(prefs::kShouldRetrieveDeviceState, false);
+    registry->RegisterIntegerPref(prefs::kEnrollmentPsmResult, -1);
+    registry->RegisterTimePref(prefs::kEnrollmentPsmDeterminationTime,
+                               base::Time());
+  }
+
+  explicit InitialServerStateAvailabilityRequester(
+      std::unique_ptr<PsmRlweDmserverClient> psm_rlwe_dmserver_client,
+      PrefService* local_state)
+      : psm_rlwe_dmserver_client_(std::move(psm_rlwe_dmserver_client)),
+        local_state_(local_state) {}
+
+  InitialServerStateAvailabilityRequester(
+      const InitialServerStateAvailabilityRequester&) = delete;
+  InitialServerStateAvailabilityRequester& operator=(
+      const InitialServerStateAvailabilityRequester&) = delete;
+
+  void Start(CompletionCallback callback) override {
+    StartImpl(std::move(callback));
+  }
+
+  absl::optional<bool> GetServerStateIfObtained() const override {
+    const PrefService::Preference* has_psm_server_state_pref =
+        local_state_->FindPreference(prefs::kShouldRetrieveDeviceState);
+
+    if (!has_psm_server_state_pref ||
+        has_psm_server_state_pref->IsDefaultValue()) {
+      return absl::nullopt;
+    }
+
+    DCHECK(has_psm_server_state_pref->GetValue()->is_bool());
+
+    return has_psm_server_state_pref->GetValue()->GetBool();
+  }
+
+ private:
+  void StartImpl(CompletionCallback callback) {
+    DCHECK(callback);
+    DCHECK(!completion_callback_);
+    DCHECK(!psm_rlwe_dmserver_client_->IsCheckMembershipInProgress());
+
+    PrepareLocalState();
+
+    completion_callback_ = std::move(callback);
+
+    psm_rlwe_dmserver_client_->CheckMembership(base::BindOnce(
+        &InitialServerStateAvailabilityRequester::HandlePsmCompletion,
+        base::Unretained(this)));
+  }
+
+  void HandlePsmCompletion(
+      PsmRlweDmserverClient::ResultHolder psm_result_holder) {
+    UpdateLocalState(psm_result_holder);
+
+    switch (psm_result_holder.psm_result) {
+      case PsmResult::kConnectionError:
+        RunCallback(ServerStateAvailabilityResult::kConnectionError);
+        break;
+      case PsmResult::kServerError:
+        RunCallback(ServerStateAvailabilityResult::kServerError);
+        break;
+
+      case PsmResult::kSuccessfulDetermination:
+        DCHECK(GetServerStateIfObtained());
+        RunCallback(ServerStateAvailabilityResult::kSuccess);
+        break;
+
+      // At the moment, `AutoEnrollmentClientImpl` will not distinguish
+      // between any of the PSM errors (except for connection error, and server
+      // error) and will report final progress with given server state even if
+      // it's not available.
+      // TODO(crbug.com/1249792): Handle internal PSM Errors.
+      case PsmResult::kCreateRlweClientLibraryError:
+      case PsmResult::kCreateOprfRequestLibraryError:
+      case PsmResult::kCreateQueryRequestLibraryError:
+      case PsmResult::kProcessingQueryResponseLibraryError:
+      case PsmResult::kEmptyOprfResponseError:
+      case PsmResult::kEmptyQueryResponseError:
+      case PsmResult::kTimeout:
+        DCHECK(!GetServerStateIfObtained());
+        RunCallback(ServerStateAvailabilityResult::kPsmInternalError);
+        break;
+    }
+  }
+
+  void RunCallback(ServerStateAvailabilityResult result) {
+    DCHECK(completion_callback_);
+    std::move(completion_callback_).Run(result);
+  }
+
+  void PrepareLocalState() {
+    // Set the initial PSM execution result as unknown until it finishes
+    // successfully or due to an error.
+    // Also, clear the PSM determination timestamp.
+    local_state_->SetInteger(prefs::kEnrollmentPsmResult,
+                             em::DeviceRegisterRequest::PSM_RESULT_UNKNOWN);
+    local_state_->ClearPref(prefs::kEnrollmentPsmDeterminationTime);
+  }
+
+  void UpdateLocalState(
+      const PsmRlweDmserverClient::ResultHolder& psm_result_holder) {
+    if (psm_result_holder.IsError()) {
+      local_state_->SetInteger(prefs::kEnrollmentPsmResult,
+                               em::DeviceRegisterRequest::PSM_RESULT_ERROR);
+      return;
+    }
+
+    local_state_->SetBoolean(prefs::kShouldRetrieveDeviceState,
+                             psm_result_holder.membership_result.value());
+    local_state_->SetTime(
+        prefs::kEnrollmentPsmDeterminationTime,
+        psm_result_holder.membership_determination_time.value());
+    local_state_->SetInteger(
+        prefs::kEnrollmentPsmResult,
+        psm_result_holder.membership_result.value()
+            ? em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE
+            : em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITHOUT_STATE);
+  }
+
+  // Obtains the device state using PSM protocol. Handles all communications
+  // related to PSM protocol with DMServer.
+  std::unique_ptr<PsmRlweDmserverClient> psm_rlwe_dmserver_client_;
+
+  PrefService* local_state_;
+
+  CompletionCallback completion_callback_;
+};
+
 enum class AutoEnrollmentClientImpl::ServerStateRetrievalResult {
   // Indicates that request has been successful and server state is available.
   kSuccess = 0,
@@ -157,25 +636,93 @@
     }
   }
 
-  // TODO(crbug.com/1294843): Cleanup once refactoring is done.
-  void Reset() {
-    request_job_.reset();
-    completion_callback_.Reset();
-    device_state_available_ = false;
+ private:
+  void StartImpl(CompletionCallback callback) {
+    DCHECK(!request_job_);
+    DCHECK(callback);
+    DCHECK(!completion_callback_);
+    DCHECK(!device_state_available_);
+
+    completion_callback_ = std::move(callback);
+
+    std::unique_ptr<DMServerJobConfiguration> config =
+        std::make_unique<DMServerJobConfiguration>(
+            device_management_service_,
+            state_download_message_processor_->GetJobType(), device_id_,
+            /*critical=*/false, DMAuth::NoAuth(),
+            /*oauth_token=*/absl::nullopt, url_loader_factory_,
+            base::BindRepeating(&ServerStateRetriever::HandleRequestCompletion,
+                                base::Unretained(this)));
+
+    state_download_message_processor_->FillRequest(config->request());
+    request_job_ = device_management_service_->CreateJob(std::move(config));
   }
 
-  // TODO(crbug.com/1294843): Cleanup once refactoring is done.
-  bool IsInProgress() const { return request_job_ != nullptr; }
-
- private:
-  // TODO(crbug.com/1294843): Move the definition here.
-  void StartImpl(CompletionCallback callback);
-
-  // TODO(crbug.com/1294843): Move the definition here.
   void HandleRequestCompletion(DeviceManagementService::Job* job,
                                DeviceManagementStatus status,
                                int net_error,
-                               const em::DeviceManagementResponse& response);
+                               const em::DeviceManagementResponse& response) {
+    DCHECK(request_job_);
+    DCHECK(completion_callback_);
+
+    request_job_.reset();
+
+    base::UmaHistogramSparse(kUMAHashDanceRequestStatus + uma_suffix_, status);
+    // TODO(crbug.com/1312919): Check `status` for specific errors.
+    if (status != DM_STATUS_SUCCESS) {
+      LOG(ERROR) << "Auto enrollment error: " << status;
+      if (status == DM_STATUS_REQUEST_FAILED)
+        base::UmaHistogramSparse(kUMAHashDanceNetworkErrorCode + uma_suffix_,
+                                 -net_error);
+      RunCallback(status == DM_STATUS_REQUEST_FAILED
+                      ? ServerStateRetrievalResult::kConnectionError
+                      : ServerStateRetrievalResult::kServerError);
+      return;
+    }
+
+    absl::optional<AutoEnrollmentStateMessageProcessor::ParsedResponse>
+        parsed_response_result =
+            state_download_message_processor_->ParseResponse(response);
+    if (!parsed_response_result) {
+      RunCallback(ServerStateRetrievalResult::kServerError);
+      return;
+    }
+
+    AutoEnrollmentStateMessageProcessor::ParsedResponse parsed_response =
+        std::move(parsed_response_result.value());
+    {
+      DictionaryPrefUpdate dict(local_state_, prefs::kServerBackedDeviceState);
+      UpdateDict(
+          dict.Get(), kDeviceStateManagementDomain,
+          parsed_response.management_domain.has_value(),
+          std::make_unique<base::Value>(
+              parsed_response.management_domain.value_or(std::string())));
+
+      UpdateDict(dict.Get(), kDeviceStateMode,
+                 !parsed_response.restore_mode.empty(),
+                 std::make_unique<base::Value>(parsed_response.restore_mode));
+
+      UpdateDict(dict.Get(), kDeviceStateDisabledMessage,
+                 parsed_response.disabled_message.has_value(),
+                 std::make_unique<base::Value>(
+                     parsed_response.disabled_message.value_or(std::string())));
+
+      UpdateDict(
+          dict.Get(), kDeviceStatePackagedLicense,
+          parsed_response.is_license_packaged_with_device.has_value(),
+          std::make_unique<base::Value>(
+              parsed_response.is_license_packaged_with_device.value_or(false)));
+
+      UpdateDict(dict.Get(), kDeviceStateLicenseType,
+                 parsed_response.license_type.has_value(),
+                 std::make_unique<base::Value>(
+                     parsed_response.license_type.value_or(std::string())));
+    }
+    local_state_->CommitPendingWrite();
+
+    device_state_available_ = true;
+    RunCallback(ServerStateRetrievalResult::kSuccess);
+  }
 
   void RunCallback(ServerStateRetrievalResult result) {
     DCHECK(completion_callback_);
@@ -200,8 +747,8 @@
   CompletionCallback completion_callback_;
 };
 
-AutoEnrollmentClientImpl::FactoryImpl::FactoryImpl() {}
-AutoEnrollmentClientImpl::FactoryImpl::~FactoryImpl() {}
+AutoEnrollmentClientImpl::FactoryImpl::FactoryImpl() = default;
+AutoEnrollmentClientImpl::FactoryImpl::~FactoryImpl() = default;
 
 std::unique_ptr<AutoEnrollmentClient>
 AutoEnrollmentClientImpl::FactoryImpl::CreateForFRE(
@@ -214,16 +761,15 @@
     int power_limit) {
   const std::string device_id = base::GenerateGUID();
   return base::WrapUnique(new AutoEnrollmentClientImpl(
-      progress_callback, device_management_service, local_state,
-      url_loader_factory,
-      std::make_unique<DeviceIdentifierProviderFRE>(server_backed_state_key),
+      progress_callback,
+      std::make_unique<FREServerStateAvailabilityRequester>(
+          device_management_service, url_loader_factory, local_state, device_id,
+          kUMASuffixFRE, power_initial, power_limit, server_backed_state_key),
       std::make_unique<ServerStateRetriever>(
           device_management_service, url_loader_factory, local_state, device_id,
           kUMASuffixFRE,
           AutoEnrollmentStateMessageProcessor::CreateForFRE(
-              server_backed_state_key)),
-      device_id, power_initial, power_limit, kUMASuffixFRE,
-      /*psm_rlwe_dmserver_client=*/nullptr));
+              server_backed_state_key))));
 }
 
 std::unique_ptr<AutoEnrollmentClient>
@@ -237,323 +783,149 @@
     int power_initial,
     int power_limit,
     std::unique_ptr<PsmRlweDmserverClient> psm_rlwe_dmserver_client) {
-  const std::string device_id = base::GenerateGUID();
   return base::WrapUnique(new AutoEnrollmentClientImpl(
-      progress_callback, device_management_service, local_state,
-      url_loader_factory,
-      /*device_identifier_provider_fre=*/nullptr,
+      progress_callback,
+      std::make_unique<InitialServerStateAvailabilityRequester>(
+          std::move(psm_rlwe_dmserver_client), local_state),
       std::make_unique<ServerStateRetriever>(
-          device_management_service, url_loader_factory, local_state, device_id,
-          kUMASuffixInitialEnrollment,
+          device_management_service, url_loader_factory, local_state,
+          /*device_id=*/base::GenerateGUID(), kUMASuffixInitialEnrollment,
           AutoEnrollmentStateMessageProcessor::CreateForInitialEnrollment(
-              device_serial_number, device_brand_code)),
-      device_id, power_initial, power_limit, kUMASuffixInitialEnrollment,
-      std::move(psm_rlwe_dmserver_client)));
-}
-
-AutoEnrollmentClientImpl::~AutoEnrollmentClientImpl() {
-  content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
+              device_serial_number, device_brand_code))));
 }
 
 // static
 void AutoEnrollmentClientImpl::RegisterPrefs(PrefRegistrySimple* registry) {
-  registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
-  registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
-  registry->RegisterBooleanPref(prefs::kShouldRetrieveDeviceState, false);
-  registry->RegisterIntegerPref(prefs::kEnrollmentPsmResult, -1);
-  registry->RegisterTimePref(prefs::kEnrollmentPsmDeterminationTime,
-                             base::Time());
+  FREServerStateAvailabilityRequester::RegisterPrefs(registry);
+  InitialServerStateAvailabilityRequester::RegisterPrefs(registry);
 }
 
+AutoEnrollmentClientImpl::AutoEnrollmentClientImpl(
+    ProgressCallback callback,
+    std::unique_ptr<ServerStateAvailabilityRequester>
+        server_state_avalability_requester,
+    std::unique_ptr<ServerStateRetriever> server_state_retriever)
+    : progress_callback_(std::move(callback)),
+      server_state_avalability_requester_(
+          std::move(server_state_avalability_requester)),
+      server_state_retriever_(std::move(server_state_retriever)) {
+  DCHECK(progress_callback_);
+}
+
+AutoEnrollmentClientImpl::~AutoEnrollmentClientImpl() = default;
+
 void AutoEnrollmentClientImpl::Start() {
-  // (Re-)register the network change observer.
-  content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
-  content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
+  DCHECK_EQ(state_, State::kIdle);
+  DCHECK(!server_state_retriever_->GetAutoEnrollmentStateIfObtained());
 
-  // Drop the previous job and reset state.
-  request_job_.reset();
-  hash_dance_time_start_ = base::TimeTicks();
-  state_ = AUTO_ENROLLMENT_STATE_PENDING;
-  modulus_updates_received_ = 0;
-  has_server_state_ = false;
-  server_state_retriever_->Reset();
+  network_connection_observer_.Observe(content::GetNetworkConnectionTracker());
 
-  NextStep();
+  RequestServerStateAvailability();
 }
 
 void AutoEnrollmentClientImpl::Retry() {
-  RetryStep();
+  switch (state_) {
+    case State::kIdle:
+      Start();
+      break;
+
+    // Request in progress, nothing to do.
+    case State::kRequestingServerStateAvailability:
+    case State::kRequestingStateRetrieval:
+      break;
+
+    case State::kRequestServerStateAvailabilityConnectionError:
+    case State::kRequestServerStateAvailabilityServerError:
+      RequestServerStateAvailability();
+      break;
+
+    case State::kRequestStateRetrievalConnectionError:
+    case State::kRequestStateRetrievalServerError:
+      RequestStateRetrieval();
+      break;
+
+    // All possible requests are done and the final device state has been
+    // reported. Nothing to to do.
+    case State::kFinished:
+      break;
+    case State::kRequestServerStateAvailabilitySuccess:
+      NOTREACHED() << "kRequestServerStateAvailabilitySuccess supposed to "
+                      "immediately resolve to kRequestingStateRetrieval.";
+      break;
+  }
 }
 
 void AutoEnrollmentClientImpl::OnConnectionChanged(
     network::mojom::ConnectionType type) {
   if (type != network::mojom::ConnectionType::CONNECTION_NONE) {
-    RetryStep();
+    Retry();
   }
 }
 
-AutoEnrollmentClientImpl::AutoEnrollmentClientImpl(
-    const ProgressCallback& callback,
-    DeviceManagementService* service,
-    PrefService* local_state,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    std::unique_ptr<DeviceIdentifierProviderFRE> device_identifier_provider_fre,
-    std::unique_ptr<ServerStateRetriever> server_state_retriever,
-    const std::string& device_id,
-    int power_initial,
-    int power_limit,
-    std::string uma_suffix,
-    std::unique_ptr<PsmRlweDmserverClient> psm_rlwe_dmserver_client)
-    : progress_callback_(callback),
-      state_(AUTO_ENROLLMENT_STATE_IDLE),
-      has_server_state_(false),
-      device_id_(device_id),
-      current_power_(power_initial),
-      power_limit_(power_limit),
-      modulus_updates_received_(0),
-      device_management_service_(service),
-      local_state_(local_state),
-      url_loader_factory_(url_loader_factory),
-      device_identifier_provider_fre_(
-          std::move(device_identifier_provider_fre)),
-      server_state_retriever_(std::move(server_state_retriever)),
-      psm_rlwe_dmserver_client_(std::move(psm_rlwe_dmserver_client)),
-      uma_suffix_(uma_suffix) {
-  DCHECK_LE(current_power_, power_limit_);
-  DCHECK(!progress_callback_.is_null());
-}
+void AutoEnrollmentClientImpl::RequestServerStateAvailability() {
+  DCHECK(state_ == State::kIdle ||
+         state_ == State::kRequestServerStateAvailabilityConnectionError ||
+         state_ == State::kRequestServerStateAvailabilityServerError);
+  state_ = State::kRequestingServerStateAvailability;
 
-bool AutoEnrollmentClientImpl::GetCachedDecision() {
-  const PrefService::Preference* has_server_state_pref =
-      local_state_->FindPreference(prefs::kShouldAutoEnroll);
-  const PrefService::Preference* previous_limit_pref =
-      local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit);
-
-  if (!has_server_state_pref || has_server_state_pref->IsDefaultValue() ||
-      !has_server_state_pref->GetValue()->is_bool() || !previous_limit_pref ||
-      previous_limit_pref->IsDefaultValue() ||
-      !previous_limit_pref->GetValue()->is_int() ||
-      power_limit_ > previous_limit_pref->GetValue()->GetInt()) {
-    return false;
-  }
-
-  has_server_state_ = has_server_state_pref->GetValue()->GetBool();
-  return true;
-}
-
-bool AutoEnrollmentClientImpl::RetrievePsmCachedDecision() {
-  // PSM protocol has to be enabled whenever this function is called.
-  DCHECK(psm_rlwe_dmserver_client_);
-
-  const PrefService::Preference* has_psm_server_state_pref =
-      local_state_->FindPreference(prefs::kShouldRetrieveDeviceState);
-
-  if (!has_psm_server_state_pref ||
-      has_psm_server_state_pref->IsDefaultValue()) {
-    // Verify the pref is registered as a boolean.
-    DCHECK(has_psm_server_state_pref->GetValue()->is_bool());
-
-    return false;
-  }
-
-  has_server_state_ = has_psm_server_state_pref->GetValue()->GetBool();
-  return true;
-}
-
-bool AutoEnrollmentClientImpl::IsClientForInitialEnrollment() const {
-  return psm_rlwe_dmserver_client_ != nullptr;
-}
-
-bool AutoEnrollmentClientImpl::ShouldSendDeviceStateRequest() const {
-  return has_server_state_.has_value() && has_server_state_.value() &&
-         !server_state_retriever_->GetAutoEnrollmentStateIfObtained();
-}
-
-bool AutoEnrollmentClientImpl::RetryStep() {
-  // If there is a pending request job, let it finish.
-  if (request_job_ || server_state_retriever_->IsInProgress())
-    return true;
-
-  if (IsClientForInitialEnrollment()) {
-    if (PsmRetryStep())
-      return true;
-  } else {
-    // Send DeviceAutoEnrollmentRequest (i.e. Hash dance protocol) if Hash dance
-    // decision has not been retrieved before.
-    if (!GetCachedDecision()) {
-      SendBucketDownloadRequest();
-      return true;
-    }
-  }
-
-  // Send DeviceStateRetrievalRequest if it has a server-backed state
-  // (determined by either PSM or Hash dance protocol).
-  if (ShouldSendDeviceStateRequest()) {
-    SendDeviceStateRequest();
-    return true;
-  }
-
-  return false;
-}
-
-bool AutoEnrollmentClientImpl::PsmRetryStep() {
-  // PSM protocol has to be enabled whenever this function is called.
-  DCHECK(psm_rlwe_dmserver_client_);
-
-  // Don't retry if the protocol had an error.
-  if (psm_result_holder_ && psm_result_holder_->IsError())
-    return false;
-
-  // If the PSM protocol is in progress, signal to the caller
-  // that nothing else needs to be done.
-  if (psm_rlwe_dmserver_client_->IsCheckMembershipInProgress())
-    return true;
-
-  if (RetrievePsmCachedDecision()) {
-    LOG(WARNING) << "PSM Cached: psm_server_state="
-                 << has_server_state_.value();
-    return false;
-  } else {
-    // Set the initial PSM execution result as unknown until it finishes
-    // successfully or due to an error.
-    // Also, clear the PSM determination timestamp.
-    local_state_->SetInteger(prefs::kEnrollmentPsmResult,
-                             em::DeviceRegisterRequest::PSM_RESULT_UNKNOWN);
-    local_state_->ClearPref(prefs::kEnrollmentPsmDeterminationTime);
-
-    psm_rlwe_dmserver_client_->CheckMembership(
-        base::BindOnce(&AutoEnrollmentClientImpl::HandlePsmCompletion,
-                       base::Unretained(this)));
-    return true;
-  }
-}
-
-void AutoEnrollmentClientImpl::HandlePsmCompletion(
-    PsmRlweDmserverClient::ResultHolder psm_result_holder) {
-  psm_result_holder_ = std::move(psm_result_holder);
-
-  // Update `local_state_` PSM's prefs with their corresponding result values.
-  if (psm_result_holder_->psm_result != PsmResult::kSuccessfulDetermination) {
-    local_state_->SetInteger(prefs::kEnrollmentPsmResult,
-                             em::DeviceRegisterRequest::PSM_RESULT_ERROR);
-  } else {
-    local_state_->SetBoolean(prefs::kShouldRetrieveDeviceState,
-                             psm_result_holder_->membership_result.value());
-    local_state_->SetTime(
-        prefs::kEnrollmentPsmDeterminationTime,
-        psm_result_holder_->membership_determination_time.value());
-    local_state_->SetInteger(
-        prefs::kEnrollmentPsmResult,
-        psm_result_holder_->membership_result.value()
-            ? em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE
-            : em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITHOUT_STATE);
-  }
-
-  switch (psm_result_holder_->psm_result) {
-    case PsmResult::kConnectionError:
-      ReportProgress(AUTO_ENROLLMENT_STATE_CONNECTION_ERROR);
-      break;
-    case PsmResult::kServerError:
-      ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR);
-      break;
-
-    // At the moment, AutoEnrollmentClientImpl will not distinguish between
-    // any of the PSM errors and will perform `NextStep`, except for
-    // connection error, and server error. These are the ones that will be
-    // reported directly as an error.
-    // TODO(crbug.com/1249792): Call `NextStep` only when PSM executed
-    // successfully (i.e. PsmResult has value kSuccessfulDetermination).
-    case PsmResult::kSuccessfulDetermination:
-    case PsmResult::kCreateRlweClientLibraryError:
-    case PsmResult::kCreateOprfRequestLibraryError:
-    case PsmResult::kCreateQueryRequestLibraryError:
-    case PsmResult::kProcessingQueryResponseLibraryError:
-    case PsmResult::kEmptyOprfResponseError:
-    case PsmResult::kEmptyQueryResponseError:
-    case PsmResult::kTimeout:
-      NextStep();
-      break;
-  }
-}
-
-void AutoEnrollmentClientImpl::ReportProgress(AutoEnrollmentState state) {
-  state_ = state;
-  progress_callback_.Run(state_);
-}
-
-void AutoEnrollmentClientImpl::NextStep() {
-  if (RetryStep())
+  if (server_state_avalability_requester_->GetServerStateIfObtained()) {
+    OnServerStateAvailabilityCompleted(ServerStateAvailabilityResult::kSuccess);
     return;
-
-  // Protocol finished successfully, report result.
-  const auto auto_enrollment_state_result =
-      server_state_retriever_->GetAutoEnrollmentStateIfObtained();
-  if (auto_enrollment_state_result) {
-    ReportProgress(auto_enrollment_state_result.value());
-  } else {
-    DCHECK(!ShouldSendDeviceStateRequest());
-    ReportProgress(AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
   }
-}
-
-void AutoEnrollmentClientImpl::SendBucketDownloadRequest() {
-  // This method should only be called when the client has been created for FRE
-  // use case.
-  DCHECK(!IsClientForInitialEnrollment());
-  DCHECK(device_identifier_provider_fre_);
-
-  // Start the Hash dance timer during the first attempt.
-  if (hash_dance_time_start_.is_null())
-    hash_dance_time_start_ = base::TimeTicks::Now();
-
-  std::string id_hash = device_identifier_provider_fre_->GetIdHash();
-  // Currently AutoEnrollmentClientImpl supports working with hashes that are at
-  // least 8 bytes long. If this is reduced, the computation of the remainder
-  // must also be adapted to handle the case of a shorter hash gracefully.
-  DCHECK_GE(id_hash.size(), 8u);
-
-  uint64_t remainder = 0;
-  const size_t last_byte_index = id_hash.size() - 1;
-  for (int i = 0; 8 * i < current_power_; ++i) {
-    uint64_t byte = id_hash[last_byte_index - i] & 0xff;
-    remainder = remainder | (byte << (8 * i));
-  }
-  remainder = remainder & ((UINT64_C(1) << current_power_) - 1);
 
   ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
 
-  // Record the time when the bucket download request is started. Note that the
-  // time may be set multiple times. This is fine, only the last request is the
-  // one where the hash bucket is actually downloaded.
-  time_start_bucket_download_ = base::TimeTicks::Now();
-
-  // TODO(crbug.com/1271134): Logging as "WARNING" to make sure it's preserved
-  // in the logs.
-  LOG(WARNING) << "Request bucket #" << remainder;
-
-  std::unique_ptr<DMServerJobConfiguration> config =
-      std::make_unique<DMServerJobConfiguration>(
-          device_management_service_,
-          DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
-          device_id_,
-          /*critical=*/false, DMAuth::NoAuth(),
-          /*oauth_token=*/absl::nullopt, url_loader_factory_,
-          base::BindOnce(
-              &AutoEnrollmentClientImpl::HandleRequestCompletion,
-              base::Unretained(this),
-              &AutoEnrollmentClientImpl::OnBucketDownloadRequestCompletion));
-
-  em::DeviceAutoEnrollmentRequest* request =
-      config->request()->mutable_auto_enrollment_request();
-  request->set_remainder(remainder);
-  request->set_modulus(INT64_C(1) << current_power_);
-  request->set_enrollment_check_type(
-      device_identifier_provider_fre_->GetEnrollmentCheckType());
-
-  request_job_ = device_management_service_->CreateJob(std::move(config));
+  server_state_avalability_requester_->Start(base::BindOnce(
+      &AutoEnrollmentClientImpl::OnServerStateAvailabilityCompleted,
+      base::Unretained(this)));
 }
 
-void AutoEnrollmentClientImpl::SendDeviceStateRequest() {
+void AutoEnrollmentClientImpl::OnServerStateAvailabilityCompleted(
+    ServerStateAvailabilityResult result) {
+  DCHECK(state_ == State::kRequestingServerStateAvailability);
+
+  switch (result) {
+    case ServerStateAvailabilityResult::kSuccess:
+      DCHECK(server_state_avalability_requester_->GetServerStateIfObtained());
+      if (server_state_avalability_requester_->GetServerStateIfObtained()
+              .value()) {
+        state_ = State::kRequestServerStateAvailabilitySuccess;
+        RequestStateRetrieval();
+      } else {
+        state_ = State::kFinished;
+        ReportFinished();
+      }
+      break;
+    case ServerStateAvailabilityResult::kConnectionError:
+      state_ = State::kRequestServerStateAvailabilityConnectionError;
+      ReportProgress(AUTO_ENROLLMENT_STATE_CONNECTION_ERROR);
+      break;
+    case ServerStateAvailabilityResult::kServerError:
+      state_ = State::kRequestServerStateAvailabilityServerError;
+      ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR);
+      break;
+    case ServerStateAvailabilityResult::kAutoEnrollmentRetriableError:
+      state_ = State::kRequestServerStateAvailabilityServerError;
+      Retry();
+      break;
+    case ServerStateAvailabilityResult::kPsmInternalError:
+      DCHECK(!server_state_avalability_requester_->GetServerStateIfObtained());
+      state_ = State::kFinished;
+      ReportFinished();
+      break;
+  }
+}
+
+void AutoEnrollmentClientImpl::RequestStateRetrieval() {
+  DCHECK(state_ == State::kRequestServerStateAvailabilitySuccess ||
+         state_ == State::kRequestStateRetrievalConnectionError ||
+         state_ == State::kRequestStateRetrievalServerError);
+  DCHECK(server_state_avalability_requester_->GetServerStateIfObtained());
+  DCHECK(
+      server_state_avalability_requester_->GetServerStateIfObtained().value());
+  DCHECK(!server_state_retriever_->GetAutoEnrollmentStateIfObtained());
+  state_ = State::kRequestingStateRetrieval;
+
   ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
 
   server_state_retriever_->Start(
@@ -561,262 +933,41 @@
                      base::Unretained(this)));
 }
 
-void AutoEnrollmentClientImpl::ServerStateRetriever::StartImpl(
-    CompletionCallback callback) {
-  DCHECK(!request_job_);
-  DCHECK(callback);
-  DCHECK(!completion_callback_);
-  DCHECK(!device_state_available_);
-
-  completion_callback_ = std::move(callback);
-
-  std::unique_ptr<DMServerJobConfiguration> config =
-      std::make_unique<DMServerJobConfiguration>(
-          device_management_service_,
-          state_download_message_processor_->GetJobType(), device_id_,
-          /*critical=*/false, DMAuth::NoAuth(),
-          /*oauth_token=*/absl::nullopt, url_loader_factory_,
-          base::BindRepeating(&ServerStateRetriever::HandleRequestCompletion,
-                              base::Unretained(this)));
-
-  state_download_message_processor_->FillRequest(config->request());
-  request_job_ = device_management_service_->CreateJob(std::move(config));
-}
-
-void AutoEnrollmentClientImpl::HandleRequestCompletion(
-    RequestCompletionHandler handler,
-    DeviceManagementService::Job* job,
-    DeviceManagementStatus status,
-    int net_error,
-    const em::DeviceManagementResponse& response) {
-  base::UmaHistogramSparse(kUMAHashDanceRequestStatus + uma_suffix_, status);
-  if (status != DM_STATUS_SUCCESS) {
-    LOG(ERROR) << "Auto enrollment error: " << status;
-    if (status == DM_STATUS_REQUEST_FAILED)
-      base::UmaHistogramSparse(kUMAHashDanceNetworkErrorCode + uma_suffix_,
-                               -net_error);
-    request_job_.reset();
-
-    ReportProgress(status == DM_STATUS_REQUEST_FAILED
-                       ? AUTO_ENROLLMENT_STATE_CONNECTION_ERROR
-                       : AUTO_ENROLLMENT_STATE_SERVER_ERROR);
-    return;
-  }
-
-  bool progress =
-      (this->*handler)(request_job_.get(), status, net_error, response);
-  request_job_.reset();
-  if (progress)
-    NextStep();
-  else
-    ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR);
-}
-
-bool AutoEnrollmentClientImpl::OnBucketDownloadRequestCompletion(
-    DeviceManagementService::Job* job,
-    DeviceManagementStatus status,
-    int net_error,
-    const em::DeviceManagementResponse& response) {
-  // This method should only be called when the client has been created for FRE
-  // use case.
-  DCHECK(!IsClientForInitialEnrollment());
-
-  bool progress = false;
-  const em::DeviceAutoEnrollmentResponse& enrollment_response =
-      response.auto_enrollment_response();
-  if (!response.has_auto_enrollment_response()) {
-    LOG(ERROR) << "Server failed to provide auto-enrollment response.";
-  } else if (enrollment_response.has_expected_modulus()) {
-    // Server is asking us to retry with a different modulus.
-    modulus_updates_received_++;
-
-    int64_t modulus = enrollment_response.expected_modulus();
-    int power = NextPowerOf2(modulus);
-    if ((INT64_C(1) << power) != modulus) {
-      LOG(ERROR) << "Auto enrollment: the server didn't ask for a power-of-2 "
-                 << "modulus. Using the closest power-of-2 instead "
-                 << "(" << modulus << " vs 2^" << power << ")";
-    }
-    if (modulus_updates_received_ >= 2) {
-      LOG(ERROR) << "Auto enrollment error: already retried with an updated "
-                 << "modulus but the server asked for a new one again: "
-                 << power;
-    } else if (power > power_limit_) {
-      LOG(ERROR) << "Auto enrollment error: the server asked for a larger "
-                 << "modulus than the client accepts (" << power << " vs "
-                 << power_limit_ << ").";
-    } else {
-      // Retry at most once with the modulus that the server requested.
-      if (power <= current_power_) {
-        LOG(WARNING) << "Auto enrollment: the server asked to use a modulus ("
-                     << power << ") that isn't larger than the first used ("
-                     << current_power_ << "). Retrying anyway.";
-      }
-      // Remember this value, so that eventual retries start with the correct
-      // modulus.
-      current_power_ = power;
-      return true;
-    }
-  } else {
-    // Server should have sent down a list of hashes to try.
-    has_server_state_ = IsIdHashInProtobuf(enrollment_response.hashes());
-    // Cache the current decision in local_state, so that it is reused in case
-    // the device reboots before enrolling.
-    local_state_->SetBoolean(prefs::kShouldAutoEnroll,
-                             has_server_state_.value());
-    local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
-    local_state_->CommitPendingWrite();
-
-    // TODO(crbug.com/1271134): Logging as "WARNING" to make sure it's preserved
-    // in the logs.
-    LOG(WARNING) << "Received has_state=" << has_server_state_.value();
-
-    progress = true;
-    RecordHashDanceSuccessTimeHistogram();
-  }
-
-  // Bucket download done, update UMA.
-  UpdateBucketDownloadTimingHistograms();
-  return progress;
-}
-
 void AutoEnrollmentClientImpl::OnStateRetrievalCompleted(
     ServerStateRetrievalResult result) {
+  DCHECK(state_ == State::kRequestingStateRetrieval);
+
   switch (result) {
     case ServerStateRetrievalResult::kSuccess:
-      NextStep();
+      DCHECK(server_state_retriever_->GetAutoEnrollmentStateIfObtained());
+      state_ = State::kFinished;
+      ReportFinished();
       break;
     case ServerStateRetrievalResult::kConnectionError:
+      state_ = State::kRequestStateRetrievalConnectionError;
       ReportProgress(AUTO_ENROLLMENT_STATE_CONNECTION_ERROR);
       break;
     case ServerStateRetrievalResult::kServerError:
+      state_ = State::kRequestStateRetrievalServerError;
       ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR);
       break;
   }
 }
 
-void AutoEnrollmentClientImpl::ServerStateRetriever::HandleRequestCompletion(
-    DeviceManagementService::Job* job,
-    DeviceManagementStatus status,
-    int net_error,
-    const em::DeviceManagementResponse& response) {
-  DCHECK(request_job_);
-  DCHECK(completion_callback_);
-
-  request_job_.reset();
-
-  base::UmaHistogramSparse(kUMAHashDanceRequestStatus + uma_suffix_, status);
-  // TODO(crbug.com/1312919): Check `status` for specific errors.
-  if (status != DM_STATUS_SUCCESS) {
-    LOG(ERROR) << "Auto enrollment error: " << status;
-    if (status == DM_STATUS_REQUEST_FAILED)
-      base::UmaHistogramSparse(kUMAHashDanceNetworkErrorCode + uma_suffix_,
-                               -net_error);
-    RunCallback(status == DM_STATUS_REQUEST_FAILED
-                    ? ServerStateRetrievalResult::kConnectionError
-                    : ServerStateRetrievalResult::kServerError);
-    return;
-  }
-
-  absl::optional<AutoEnrollmentStateMessageProcessor::ParsedResponse>
-      parsed_response_opt =
-          state_download_message_processor_->ParseResponse(response);
-  if (!parsed_response_opt) {
-    RunCallback(ServerStateRetrievalResult::kServerError);
-    return;
-  }
-
-  AutoEnrollmentStateMessageProcessor::ParsedResponse parsed_response =
-      std::move(parsed_response_opt.value());
-  {
-    DictionaryPrefUpdate dict(local_state_, prefs::kServerBackedDeviceState);
-    UpdateDict(dict.Get(), kDeviceStateManagementDomain,
-               parsed_response.management_domain.has_value(),
-               std::make_unique<base::Value>(
-                   parsed_response.management_domain.value_or(std::string())));
-
-    UpdateDict(dict.Get(), kDeviceStateMode,
-               !parsed_response.restore_mode.empty(),
-               std::make_unique<base::Value>(parsed_response.restore_mode));
-
-    UpdateDict(dict.Get(), kDeviceStateDisabledMessage,
-               parsed_response.disabled_message.has_value(),
-               std::make_unique<base::Value>(
-                   parsed_response.disabled_message.value_or(std::string())));
-
-    UpdateDict(
-        dict.Get(), kDeviceStatePackagedLicense,
-        parsed_response.is_license_packaged_with_device.has_value(),
-        std::make_unique<base::Value>(
-            parsed_response.is_license_packaged_with_device.value_or(false)));
-
-    UpdateDict(dict.Get(), kDeviceStateLicenseType,
-               parsed_response.license_type.has_value(),
-               std::make_unique<base::Value>(
-                   parsed_response.license_type.value_or(std::string())));
-  }
-  local_state_->CommitPendingWrite();
-
-  device_state_available_ = true;
-  RunCallback(ServerStateRetrievalResult::kSuccess);
+void AutoEnrollmentClientImpl::ReportProgress(AutoEnrollmentState state) const {
+  DCHECK(progress_callback_);
+  progress_callback_.Run(state);
 }
 
-bool AutoEnrollmentClientImpl::IsIdHashInProtobuf(
-    const google::protobuf::RepeatedPtrField<std::string>& hashes) {
-  // This method should only be called when the client has been created for FRE
-  // use case.
-  DCHECK(!IsClientForInitialEnrollment());
-  DCHECK(device_identifier_provider_fre_);
+void AutoEnrollmentClientImpl::ReportFinished() const {
+  DCHECK_EQ(state_, State::kFinished);
 
-  std::string id_hash = device_identifier_provider_fre_->GetIdHash();
-  for (int i = 0; i < hashes.size(); ++i) {
-    if (hashes.Get(i) == id_hash)
-      return true;
-  }
-  return false;
-}
-
-void AutoEnrollmentClientImpl::UpdateBucketDownloadTimingHistograms() {
-  // This method should only be called when the client has been created for FRE
-  // use case.
-  DCHECK(!IsClientForInitialEnrollment());
-
-  // These values determine bucketing of the histogram, they should not be
-  // changed.
-  // The minimum time can't be 0, must be at least 1.
-  static const base::TimeDelta kMin = base::Milliseconds(1);
-  static const base::TimeDelta kMax = base::Minutes(5);
-  static const int kBuckets = 50;
-
-  base::TimeTicks now = base::TimeTicks::Now();
-  if (!hash_dance_time_start_.is_null()) {
-    base::TimeDelta delta = now - hash_dance_time_start_;
-    base::UmaHistogramCustomTimes(kUMAHashDanceProtocolTime + uma_suffix_,
-                                  delta, kMin, kMax, kBuckets);
-  }
-  if (!time_start_bucket_download_.is_null()) {
-    base::TimeDelta delta = now - time_start_bucket_download_;
-    base::UmaHistogramCustomTimes(kUMAHashDanceBucketDownloadTime + uma_suffix_,
-                                  delta, kMin, kMax, kBuckets);
-  }
-}
-
-void AutoEnrollmentClientImpl::RecordHashDanceSuccessTimeHistogram() {
-  // This method should only be called when the client has been created for FRE
-  // use case.
-  DCHECK(!IsClientForInitialEnrollment());
-
-  // These values determine bucketing of the histogram, they should not be
-  // changed.
-  static const base::TimeDelta kMin = base::Milliseconds(1);
-  static const base::TimeDelta kMax = base::Seconds(25);
-  static const int kBuckets = 50;
-
-  base::TimeTicks now = base::TimeTicks::Now();
-  if (!hash_dance_time_start_.is_null()) {
-    base::TimeDelta delta = now - hash_dance_time_start_;
-    base::UmaHistogramCustomTimes(kUMAHashDanceSuccessTime + uma_suffix_, delta,
-                                  kMin, kMax, kBuckets);
+  const auto auto_enrollment_state_result =
+      server_state_retriever_->GetAutoEnrollmentStateIfObtained();
+  if (auto_enrollment_state_result) {
+    ReportProgress(auto_enrollment_state_result.value());
+  } else {
+    ReportProgress(AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
   }
 }
 
diff --git a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h
index e1b089b..820844f 100644
--- a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h
+++ b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h
@@ -8,38 +8,30 @@
 #include <memory>
 #include <string>
 
-#include "base/callback.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/time/time.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_client.h"
-#include "chrome/browser/ash/policy/enrollment/private_membership/psm_rlwe_dmserver_client.h"
-#include "components/policy/core/common/cloud/cloud_policy_constants.h"
-#include "components/policy/core/common/cloud/device_management_service.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
 
 class PrefRegistrySimple;
 class PrefService;
 
-namespace enterprise_management {
-class DeviceManagementResponse;
+namespace network {
+class SharedURLLoaderFactory;
 }
 
 namespace policy {
 
+class DeviceManagementService;
+class PsmRlweDmserverClient;
+
 // Interacts with the device management service and determines whether this
 // machine should automatically enter the Enterprise Enrollment screen during
 // OOBE.
-class AutoEnrollmentClientImpl
+class AutoEnrollmentClientImpl final
     : public AutoEnrollmentClient,
       public network::NetworkConnectionTracker::NetworkConnectionObserver {
  public:
-  // Provides device identifier for Forced Re-Enrollment (FRE), where the
-  // server-backed state key is used. It will set the identifier for the
-  // DeviceAutoEnrollmentRequest.
-  class DeviceIdentifierProviderFRE;
-
   class FactoryImpl : public Factory {
    public:
     FactoryImpl();
@@ -71,14 +63,15 @@
         override;
   };
 
+  // Registers preferences in local state.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
   AutoEnrollmentClientImpl(const AutoEnrollmentClientImpl&) = delete;
   AutoEnrollmentClientImpl& operator=(const AutoEnrollmentClientImpl&) = delete;
 
   ~AutoEnrollmentClientImpl() override;
 
-  // Registers preferences in local state.
-  static void RegisterPrefs(PrefRegistrySimple* registry);
-
+  // policy::AutoEnrollmentClient:
   void Start() override;
   void Retry() override;
 
@@ -86,173 +79,127 @@
   void OnConnectionChanged(network::mojom::ConnectionType type) override;
 
  private:
-  typedef bool (AutoEnrollmentClientImpl::*RequestCompletionHandler)(
-      DeviceManagementService::Job*,
-      DeviceManagementStatus,
-      int,
-      const enterprise_management::DeviceManagementResponse&);
+  // Base class to handle server state availability requests.
+  class ServerStateAvailabilityRequester;
+
+  // Responsible for resolving server state availability status via auto
+  // enrollment requests for force re-enrollment.
+  class FREServerStateAvailabilityRequester;
+
+  // Responsible for resolving server state availability status via private
+  // membership check requests for initial enrollment.
+  class InitialServerStateAvailabilityRequester;
+  enum class ServerStateAvailabilityResult;
 
   // Responsible for resolving server state status for both Forced Re-Enrollment
   // (FRE) and Initial Enrollment.
   class ServerStateRetriever;
   enum class ServerStateRetrievalResult;
 
+  enum class State {
+    // Initial state until `Start` or `Retry` are called. Resolves into
+    // `kRequestingServerStateAvailability`.
+    kIdle,
+    // Indicates server state availability request is in progress.
+    // Reached from:
+    // * `kIdle` after `Start`.
+    // * `kRequestServerStateAvailabilityConnectionError` on `Retry`.
+    // * `kRequestServerStateAvailabilityServerError` on `Retry`.
+    // Resolves into:
+    // * `kRequestServerStateAvailabilitySuccess` if valid response.
+    // * `kRequestServerStateAvailabilityConnectionError` if request fails due
+    //    to connection error.
+    // * `kRequestServerStateAvailabilityServerError` if response is invalid.
+    // * `kFinished` if response is valid and server state is not available.
+    kRequestingServerStateAvailability,
+    // Indicate connection or server errors during server state availability
+    // request.
+    // Reached from:
+    // * `kRequestingServerStateAvailability` if request fails.
+    // Resolves into:
+    // * `kRequestingServerStateAvailability` on `Retry`.
+    kRequestServerStateAvailabilityConnectionError,
+    kRequestServerStateAvailabilityServerError,
+    // Indicates success of state availability request.
+    // Reached from:
+    // * `kRequestingServerStateAvailability` if request is successful and
+    //   server state is available.
+    // Resolves into:
+    // * `kRequestingStateRetrieval` unconditionally.
+    kRequestServerStateAvailabilitySuccess,
+    // Indicates server state retrieval request is in progress.
+    // Reached from:
+    // * `kRequestServerStateAvailabilitySuccess` after server state
+    // availability request succeeded the state is available.
+    // * `kRequestStateRetrievalConnectionError` on `Retry`.
+    // * `kRequestStateRetrievalServerError` on `Retry`.
+    // Resolves into:
+    // * `kRequestStateRetrievalConnectionError` if request fails due to
+    // connection error.
+    // * `kRequestStateRetrievalServerError` if response is invalid.
+    // * `kFinished` if response is valid and state is retrieved.
+    kRequestingStateRetrieval,
+    // Indicate connection or server errors during state retrieval request.
+    // Reached from:
+    // * `kRequestingStateRetrieval` if request fails.
+    // Resolves into:
+    // * `kRequestingStateRetrieval` on `Retry`.
+    kRequestStateRetrievalConnectionError,
+    kRequestStateRetrievalServerError,
+    // Indicates the client has finished its requests and has the answer for
+    // final `AutoEnrollmentState` status.
+    // Reached from:
+    // * `kRequestingServerStateAvailability`
+    // * `kRequestingStateRetrieval`
+    // Resolves into nothing. It's the final state.
+    kFinished,
+  };
+
   AutoEnrollmentClientImpl(
-      const ProgressCallback& progress_callback,
-      DeviceManagementService* device_management_service,
-      PrefService* local_state,
-      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      std::unique_ptr<DeviceIdentifierProviderFRE>
-          device_identifier_provider_fre,
-      std::unique_ptr<ServerStateRetriever> server_state_retriever,
-      const std::string& device_id,
-      int power_initial,
-      int power_limit,
-      std::string uma_suffix,
-      std::unique_ptr<PsmRlweDmserverClient> psm_rlwe_dmserver_client);
+      ProgressCallback progress_callback,
+      std::unique_ptr<ServerStateAvailabilityRequester>
+          server_state_avalability_requester,
+      std::unique_ptr<ServerStateRetriever> server_state_retriever);
 
-  // Tries to load the result of a previous execution of the protocol from
-  // local state. Returns true if that decision has been made and is valid.
-  bool GetCachedDecision();
+  // Sends an auto-enrollment check or psm membership check request to the
+  // device management service.
+  void RequestServerStateAvailability();
 
-  // Returns true if PSM has a cached decision, then store its value locally
-  // (i.e. store it in |has_server_state_|). Otherwise, false.
-  bool RetrievePsmCachedDecision();
-
-  // Returns true if the current client got created for initial enrollment use
-  // case. Otherwise, false.
-  bool IsClientForInitialEnrollment() const;
-
-  // Returns true if the device has a server-backed state and its state hasn't
-  // been retrieved yet. Otherwise, false.
-  bool ShouldSendDeviceStateRequest() const;
-
-  // For detailed design, see go/psm-source-of-truth-initial-enrollment.
-  // Kicks protocol processing, restarting the current step if applicable.
-  // Returns true if progress has been made, false if the protocol is done.
-  bool RetryStep();
-
-  // Retries running PSM protocol, if it is possible to start it.
-  // Returns true if the protocol is in progress, false if the protocol is done
-  // or had an error.
-  // Note that the PSM protocol is only performed once per OOBE flow.
-  bool PsmRetryStep();
-
-  // Calls `NextStep` in case of successful execution of PSM protocol.
-  // Otherwise, reports the failure reason of PSM protocol execution.
-  void HandlePsmCompletion(
-      PsmRlweDmserverClient::ResultHolder psm_result_holder);
-
-  // Cleans up and invokes |progress_callback_|.
-  void ReportProgress(AutoEnrollmentState state);
-
-  // Calls RetryStep() to make progress or determine that all is done. In the
-  // latter case, calls ReportProgress().
-  void NextStep();
-
-  // Sends an auto-enrollment check request to the device management service.
-  void SendBucketDownloadRequest();
+  // Handles result of server state availability request. Proceeds to the next
+  // step on success. Reports failure otherwise.
+  void OnServerStateAvailabilityCompleted(ServerStateAvailabilityResult result);
 
   // Sends a device state download request to the device management service.
-  void SendDeviceStateRequest();
-
-  // Runs the response handler for device management requests and calls
-  // NextStep().
-  void HandleRequestCompletion(
-      RequestCompletionHandler handler,
-      DeviceManagementService::Job* job,
-      DeviceManagementStatus status,
-      int net_error,
-      const enterprise_management::DeviceManagementResponse& response);
-
-  // Parses the server response to a bucket download request.
-  bool OnBucketDownloadRequestCompletion(
-      DeviceManagementService::Job* job,
-      DeviceManagementStatus status,
-      int net_error,
-      const enterprise_management::DeviceManagementResponse& response);
+  void RequestStateRetrieval();
 
   // Handles result of server state retrieval request. Proceeds to the next
   // step on success. Reports failure otherwise.
   void OnStateRetrievalCompleted(ServerStateRetrievalResult result);
 
-  // Returns true if the identifier hash provided by
-  // |device_identifier_provider_fre_| is contained in |hashes|.
-  bool IsIdHashInProtobuf(
-      const google::protobuf::RepeatedPtrField<std::string>& hashes);
+  void ReportProgress(AutoEnrollmentState auto_enrollment_state) const;
 
-  // Updates UMA histograms for bucket download timings.
-  void UpdateBucketDownloadTimingHistograms();
+  void ReportFinished() const;
 
-  // Updates the UMA histogram for successful hash dance.
-  void RecordHashDanceSuccessTimeHistogram();
+  State state_ = State::kIdle;
+
+  base::ScopedObservation<
+      network::NetworkConnectionTracker,
+      network::NetworkConnectionTracker::NetworkConnectionObserver,
+      &network::NetworkConnectionTracker::AddNetworkConnectionObserver,
+      &network::NetworkConnectionTracker::RemoveNetworkConnectionObserver>
+      network_connection_observer_{this};
 
   // Callback to invoke when the protocol generates a relevant event. This can
   // be either successful completion or an error that requires external action.
   ProgressCallback progress_callback_;
 
-  // Current state.
-  AutoEnrollmentState state_;
-
-  // Indicates whether the device has a server-backed state or not, regardless
-  // of which protocol (i.e. PSM or Hash dance) collected that information.
-  // Note that if it doesn't have an associated value after starting the auto
-  // enrollment client, then the used protocol failed to collect that
-  // information.
-  absl::optional<bool> has_server_state_;
-
-  // Holds the cached PSM execution result.
-  absl::optional<PsmRlweDmserverClient::ResultHolder> psm_result_holder_;
-
-  // Randomly generated device id for the auto-enrollment requests.
-  std::string device_id_;
-
-  // Power-of-2 modulus to try next.
-  int current_power_;
-
-  // Power of the maximum power-of-2 modulus that this client will accept from
-  // a retry response from the server.
-  int power_limit_;
-
-  // Number of requests for a different modulus received from the server.
-  // Used to determine if the server keeps asking for different moduli.
-  int modulus_updates_received_;
-
-  // Used to communicate with the device management service.
-  DeviceManagementService* device_management_service_;
-  // Indicates whether Hash dance i.e. DeviceAutoEnrollmentRequest or
-  // DeviceStateRetrievalRequest is in progress. Note that is not affected by
-  // PSM protocol, whether it's in progress or not.
-  std::unique_ptr<DeviceManagementService::Job> request_job_;
-
-  // PrefService where the protocol's results are cached.
-  PrefService* local_state_;
-
-  // The loader factory to use to perform the auto enrollment request.
-  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
-
-  // Specifies the device identifier for FRE and its corresponding hash.
-  std::unique_ptr<DeviceIdentifierProviderFRE> device_identifier_provider_fre_;
+  // Sends server state availability request and parses response. Reports
+  // results.
+  std::unique_ptr<ServerStateAvailabilityRequester>
+      server_state_avalability_requester_;
 
   // Sends server state retrieval request and parses response. Reports results.
   std::unique_ptr<ServerStateRetriever> server_state_retriever_;
-
-  // Obtains the device state using PSM protocol. Handles all communications
-  // related to PSM protocol with DMServer.
-  std::unique_ptr<PsmRlweDmserverClient> psm_rlwe_dmserver_client_;
-
-  // Times used to determine the duration of the protocol, and the extra time
-  // needed to complete after the signin was complete.
-  // If |hash_dance_time_start_| is not null, the protocol is still running.
-  base::TimeTicks hash_dance_time_start_;
-
-  // The time when the bucket download part of the protocol started.
-  base::TimeTicks time_start_bucket_download_;
-
-  // The UMA histogram suffix. Will be ".ForcedReenrollment" for an
-  // |AutoEnrollmentClient| used for FRE and ".InitialEnrollment" for an
-  // |AutoEnrollmentclient| used for initial enrollment.
-  const std::string uma_suffix_;
 };
 
 }  // namespace policy
diff --git a/chrome/browser/ash/preferences_unittest.cc b/chrome/browser/ash/preferences_unittest.cc
index e958663..f5e302f 100644
--- a/chrome/browser/ash/preferences_unittest.cc
+++ b/chrome/browser/ash/preferences_unittest.cc
@@ -65,9 +65,7 @@
   json.Serialize(value);
   sync_pb::EntitySpecifics specifics;
   sync_pb::PreferenceSpecifics* pref =
-      features::IsSyncSettingsCategorizationEnabled()
-          ? specifics.mutable_os_preference()->mutable_preference()
-          : specifics.mutable_preference();
+      specifics.mutable_os_preference()->mutable_preference();
   pref->set_name(name);
   pref->set_value(serialized);
   return syncer::SyncData::CreateRemoteData(
@@ -280,18 +278,9 @@
   EXPECT_EQ(1, fake_update_engine_client_->is_feature_enabled_count());
 }
 
-class InputMethodPreferencesTest : public PreferencesTest,
-                                   public ::testing::WithParamInterface<bool> {
+class InputMethodPreferencesTest : public PreferencesTest {
  public:
-  InputMethodPreferencesTest() {
-    if (GetParam()) {
-      feature_list_.InitAndEnableFeature(features::kSyncSettingsCategorization);
-    } else {
-      feature_list_.InitAndDisableFeature(
-          features::kSyncSettingsCategorization);
-    }
-  }
-
+  InputMethodPreferencesTest() = default;
   InputMethodPreferencesTest(const InputMethodPreferencesTest&) = delete;
   InputMethodPreferencesTest& operator=(const InputMethodPreferencesTest&) =
       delete;
@@ -444,13 +433,9 @@
   // Simulates the initial sync of preferences.
   syncer::SyncableService* SyncPreferences(
       const syncer::SyncDataList& sync_data_list) {
-    // SyncSettingsCategorization moves IME prefs to be OS prefs.
-    syncer::ModelType model_type =
-        features::IsSyncSettingsCategorizationEnabled() ? syncer::OS_PREFERENCES
-                                                        : syncer::PREFERENCES;
     syncer::SyncableService* sync =
-        pref_service_->GetSyncableService(model_type);
-    sync->MergeDataAndStartSyncing(model_type, sync_data_list,
+        pref_service_->GetSyncableService(syncer::OS_PREFERENCES);
+    sync->MergeDataAndStartSyncing(syncer::OS_PREFERENCES, sync_data_list,
                                    std::unique_ptr<syncer::SyncChangeProcessor>(
                                        new syncer::FakeSyncChangeProcessor),
                                    std::unique_ptr<syncer::SyncErrorFactory>(
@@ -468,7 +453,7 @@
 };
 
 // Tests that the server values are added to the values chosen at OOBE.
-TEST_P(InputMethodPreferencesTest, TestOobeAndSync) {
+TEST_F(InputMethodPreferencesTest, TestOobeAndSync) {
   // Choose options at OOBE.
   pref_service_->SetBoolean(
       prefs::kLanguageShouldMergeInputMethods, true);
@@ -546,7 +531,7 @@
 }
 
 // Tests that logging in after sync has completed changes nothing.
-TEST_P(InputMethodPreferencesTest, TestLogIn) {
+TEST_F(InputMethodPreferencesTest, TestLogIn) {
   // Set up existing preference values.
   std::string languages("es");
   std::string preload_engines(ToInputMethodIds("xkb:es::spa"));
@@ -585,7 +570,7 @@
 
 // Tests that logging in with preferences from before a) XKB component
 // extensions and b) the IME syncing logic doesn't overwrite settings.
-TEST_P(InputMethodPreferencesTest, TestLogInLegacy) {
+TEST_F(InputMethodPreferencesTest, TestLogInLegacy) {
   // Simulate existing local preferences from M-36.
   SetLocalValues("es", "xkb:es::spa", kIdentityIMEID);
   InitPreferences();
@@ -619,7 +604,7 @@
 }
 
 // Tests some edge cases: empty strings, lots of values, duplicates.
-TEST_P(InputMethodPreferencesTest, MergeStressTest) {
+TEST_F(InputMethodPreferencesTest, MergeStressTest) {
   SetLocalValues("hr,lv,lt,es-419,he,el,da,ca,es,cs,bg",
                  ToInputMethodIds("xkb:es::spa,xkb:us::eng"),
                  std::string());
@@ -669,7 +654,7 @@
 }
 
 // Tests non-existent IDs.
-TEST_P(InputMethodPreferencesTest, MergeInvalidValues) {
+TEST_F(InputMethodPreferencesTest, MergeInvalidValues) {
   SetLocalValues("es",
                  ToInputMethodIds("xkb:es::spa,xkb:us::eng"),
                  kIdentityIMEID);
@@ -704,7 +689,7 @@
 
 // Tests that we merge input methods even if syncing has started before
 // initialization of Preferences.
-TEST_P(InputMethodPreferencesTest, MergeAfterSyncing) {
+TEST_F(InputMethodPreferencesTest, MergeAfterSyncing) {
   SetLocalValues("es",
                  ToInputMethodIds("xkb:es::spa,xkb:us::eng"),
                  kIdentityIMEID);
@@ -746,6 +731,4 @@
   }
 }
 
-INSTANTIATE_TEST_SUITE_P(All, InputMethodPreferencesTest, testing::Bool());
-
 }  // namespace ash
diff --git a/chrome/browser/ash/system_logs/debug_daemon_log_source.cc b/chrome/browser/ash/system_logs/debug_daemon_log_source.cc
index 6c95910..de892f9 100644
--- a/chrome/browser/ash/system_logs/debug_daemon_log_source.cc
+++ b/chrome/browser/ash/system_logs/debug_daemon_log_source.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/containers/contains.h"
+#include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
@@ -44,9 +45,14 @@
 
   // The log file's path relative to the user's profile directory.
   const char* log_file_relative_path;
+
+  // If true, |log_file_relative_path| is a pattern for which files to match.
+  // This works like shell globbing. If there are multiple matches files, it
+  // chooses the newest file, with the latest modified time.
+  bool pattern = false;
 } kUserLogs[] = {
     {"chrome_user_log", "log/chrome"},
-    {"chrome_user_log.PREVIOUS", "log/chrome.PREVIOUS"},
+    {"chrome_user_log.PREVIOUS", "log/chrome_??????-??????", true},
     {"libassistant_user_log", "google-assistant-library/log/libassistant.log"},
     {"login-times", "login-times"},
     {"logout-times", "logout-times"},
@@ -68,28 +74,57 @@
 
 }  // namespace
 
+std::string ReadUserLogFile(const base::FilePath& log_file_path) {
+  std::string value;
+  const bool read_success =
+      feedback_util::ReadEndOfFile(log_file_path, kMaxLogSize, &value);
+
+  if (read_success && value.length() == kMaxLogSize) {
+    value.replace(0, strlen(kLogTruncated), kLogTruncated);
+
+    LOG(WARNING) << "Large log file was likely truncated: " << log_file_path;
+  }
+  return (read_success && !value.empty()) ? value : std::string(kNotAvailable);
+}
+
+std::string ReadUserLogFilePattern(
+    const base::FilePath& log_file_path_pattern) {
+  base::FilePath log_file_dir = log_file_path_pattern.DirName();
+  LOG(ERROR) << log_file_dir.value();
+  base::FileEnumerator file_enumerator(
+      log_file_dir, /*recursive=*/false, base::FileEnumerator::FILES,
+      log_file_path_pattern.BaseName().value());
+
+  base::Time newest_file_mtime;
+  base::FilePath newest_file_path;
+  for (base::FilePath path = file_enumerator.Next(); !path.empty();
+       path = file_enumerator.Next()) {
+    const base::FileEnumerator::FileInfo info = file_enumerator.GetInfo();
+    if (newest_file_mtime.is_null() ||
+        info.GetLastModifiedTime() >= newest_file_mtime) {
+      newest_file_mtime = info.GetLastModifiedTime();
+      newest_file_path = path;
+    }
+  }
+
+  return newest_file_mtime.is_null() ? std::string(kNotAvailable)
+                                     : ReadUserLogFile(newest_file_path);
+}
+
 // Reads the contents of the user log files listed in |kUserLogs| and adds them
 // to the |response| parameter.
 void ReadUserLogFiles(const std::vector<base::FilePath>& profile_dirs,
                       SystemLogsResponse* response) {
   for (size_t i = 0; i < profile_dirs.size(); ++i) {
     std::string profile_prefix = "Profile[" + base::NumberToString(i) + "] ";
+    const base::FilePath profile_dir = profile_dirs[i];
     for (const auto& log : kUserLogs) {
-      std::string value;
-      const bool read_success = feedback_util::ReadEndOfFile(
-          profile_dirs[i].Append(log.log_file_relative_path), kMaxLogSize,
-          &value);
-
-      if (read_success && value.length() == kMaxLogSize) {
-        value.replace(0, strlen(kLogTruncated), kLogTruncated);
-
-        LOG(WARNING) << "Large log file was likely truncated: "
-                     << log.log_file_relative_path;
-      }
-
-      response->emplace(
-          profile_prefix + log.log_key,
-          (read_success && !value.empty()) ? std::move(value) : kNotAvailable);
+      const base::FilePath log_file_path_or_pattern =
+          profile_dir.AppendASCII(log.log_file_relative_path);
+      const std::string content =
+          log.pattern ? ReadUserLogFilePattern(log_file_path_or_pattern)
+                      : ReadUserLogFile(log_file_path_or_pattern);
+      response->emplace(profile_prefix + log.log_key, content);
     }
   }
 }
diff --git a/chrome/browser/ash/system_logs/debug_daemon_log_source.h b/chrome/browser/ash/system_logs/debug_daemon_log_source.h
index 6a5c038..0f766f2 100644
--- a/chrome/browser/ash/system_logs/debug_daemon_log_source.h
+++ b/chrome/browser/ash/system_logs/debug_daemon_log_source.h
@@ -26,6 +26,12 @@
                    std::string* contents,
                    size_t max_size);
 
+// Exposes the utility methods only for unittests.
+#if defined(UNIT_TEST)
+std::string ReadUserLogFile(const base::FilePath& log_file_path);
+std::string ReadUserLogFilePattern(const base::FilePath& log_file_path_pattern);
+#endif  // defined(UNIT_TEST)
+
 // Gathers log data from Debug Daemon.
 class DebugDaemonLogSource : public SystemLogsSource {
  public:
diff --git a/chrome/browser/ash/system_logs/debug_daemon_log_source_unittest.cc b/chrome/browser/ash/system_logs/debug_daemon_log_source_unittest.cc
new file mode 100644
index 0000000..2389283
--- /dev/null
+++ b/chrome/browser/ash/system_logs/debug_daemon_log_source_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/system_logs/debug_daemon_log_source.h"
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace system_logs {
+
+constexpr char kNotAvailable[] = "<not available>";
+
+class DebugDaemonLogSourceTest : public ::testing::Test {};
+
+TEST_F(DebugDaemonLogSourceTest, ReadUserLogFile) {
+  constexpr char kLogContent[] = "logloglog\n";
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  const base::FilePath temp_dir_path = temp_dir.GetPath();
+  ASSERT_TRUE(CreateDirectory(temp_dir_path.AppendASCII("dir")));
+
+  // Prepare a directory and log file in it.
+  base::FilePath log_file("dir/log.txt");
+  base::FilePath log_path = temp_dir_path.Append(log_file);
+  ASSERT_TRUE(base::WriteFile(log_path, kLogContent));
+
+  auto result = ReadUserLogFile(log_path);
+  EXPECT_EQ(kLogContent, result);
+}
+
+TEST_F(DebugDaemonLogSourceTest, ReadUserLogFile_FileNotExisting) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  // Prepare a directory but no log file in it.
+  const base::FilePath temp_dir_path = temp_dir.GetPath();
+  ASSERT_TRUE(CreateDirectory(temp_dir_path.AppendASCII("dir")));
+
+  base::FilePath log_file("dir/log.txt");
+  base::FilePath log_path = temp_dir_path.Append(log_file);
+  ASSERT_FALSE(base::PathExists(log_path));
+
+  auto result = ReadUserLogFile(log_path);
+  EXPECT_EQ(kNotAvailable, result);
+}
+
+TEST_F(DebugDaemonLogSourceTest, ReadUserLogFile_DirectoryNotExisting) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  // Prepare neither directory nor log file.
+  base::FilePath log_file("dir/log.txt");
+  const base::FilePath temp_dir_path = temp_dir.GetPath();
+  base::FilePath log_path = temp_dir_path.Append(log_file);
+  ASSERT_FALSE(base::PathExists(log_path));
+
+  auto result = ReadUserLogFile(log_path);
+  EXPECT_EQ(kNotAvailable, result);
+}
+
+TEST_F(DebugDaemonLogSourceTest, ReadUserLogFilePattern) {
+  constexpr char kLog1Content[] = "logloglog #1\n";
+  constexpr char kLog2Content[] = "logloglog #2\n";
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  // Prepare a directory.
+  const base::FilePath temp_dir_path = temp_dir.GetPath();
+  ASSERT_TRUE(CreateDirectory(temp_dir_path.AppendASCII("dir")));
+
+  // Prepare an older log file with mtime of yesterday.
+  base::FilePath log_path_not_latest =
+      temp_dir_path.AppendASCII("dir/log1.txt");
+  ASSERT_TRUE(base::WriteFile(log_path_not_latest, kLog1Content));
+  base::Time yesterday = base::Time::Now() - base::Days(1);
+  ASSERT_TRUE(base::File(log_path_not_latest,
+                         base::File::FLAG_OPEN | base::File::FLAG_READ)
+                  .SetTimes(yesterday, yesterday));
+
+  // Prepare a newer log file with mtime of now.
+  base::FilePath log_path_latest = temp_dir_path.AppendASCII("dir/log2.txt");
+  ASSERT_TRUE(base::WriteFile(log_path_latest, kLog2Content));
+
+  base::FilePath log_path_pattern("dir/log?.txt");
+  auto result = ReadUserLogFilePattern(temp_dir_path.Append(log_path_pattern));
+
+  // Confirm that the newer log file is detected.
+  EXPECT_EQ(kLog2Content, result);
+}
+
+TEST_F(DebugDaemonLogSourceTest, ReadUserLogFilePattern_FileNotExists) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  // Prepare a directory but no log file in it.
+  const base::FilePath temp_dir_path = temp_dir.GetPath();
+  ASSERT_TRUE(CreateDirectory(temp_dir_path.AppendASCII("dir")));
+
+  base::FilePath log_path_pattern("dir/log?.txt");
+  auto result = ReadUserLogFilePattern(temp_dir_path.Append(log_path_pattern));
+  EXPECT_EQ(kNotAvailable, result);
+}
+
+TEST_F(DebugDaemonLogSourceTest, ReadUserLogFilePattern_DirectoryNotExists) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  // Prepare neither directory nor log file.
+  base::FilePath log_path_pattern("dir/log?.txt");
+  const base::FilePath temp_dir_path = temp_dir.GetPath();
+  auto result = ReadUserLogFilePattern(temp_dir_path.Append(log_path_pattern));
+  EXPECT_EQ(kNotAvailable, result);
+}
+
+}  // namespace system_logs
diff --git a/chrome/browser/autofill_assistant/password_change/apc_onboarding_coordinator_impl.cc b/chrome/browser/autofill_assistant/password_change/apc_onboarding_coordinator_impl.cc
index b723b82d..099faffb 100644
--- a/chrome/browser/autofill_assistant/password_change/apc_onboarding_coordinator_impl.cc
+++ b/chrome/browser/autofill_assistant/password_change/apc_onboarding_coordinator_impl.cc
@@ -40,7 +40,7 @@
   // automatically on finishing the navigation. To avoid this, we check whether
   // such a navigation is ongoing and delay opening the dialog until completes.
   content::NavigationEntry* entry =
-      web_contents_->GetController().GetActiveEntry();
+      web_contents_->GetController().GetPendingEntry();
   if (entry &&
       !net::registry_controlled_domains::SameDomainOrHost(
           web_contents_->GetLastCommittedURL(), entry->GetURL(),
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index dc1a9d3..696ff80 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -3432,6 +3432,7 @@
     "../ash/system/user_removal_manager_unittest.cc",
     "../ash/system_logs/connected_input_devices_log_source_unittest.cc",
     "../ash/system_logs/crosapi_system_log_source_unittest.cc",
+    "../ash/system_logs/debug_daemon_log_source_unittest.cc",
     "../ash/system_logs/reven_log_source_unittest.cc",
     "../ash/system_logs/shill_log_source_unittest.cc",
     "../ash/system_logs/single_debug_daemon_log_source_unittest.cc",
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index 2e3dad0..925a8dc 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -275,6 +275,33 @@
   }
 }
 
+// A helper function that returns true when the legacy GREASE implementation is
+// seen. It relies on the old algorithm having only 3 possible permutations due
+// to a very limited set of allowed special characters. This may be removed once
+// the legacy algorithm is no longer supported for emergency situations.
+bool SawOldGrease(const std::string& ua_ch_result) {
+  bool seen_legacy = false;
+  // The legacy GREASE algorithm had only semicolon and space, and thus had one
+  // of these three permutations.
+  const std::string old_grease_permutations[]{";Not A Brand", " Not;A Brand",
+                                              " Not A;Brand"};
+  for (auto i : old_grease_permutations) {
+    seen_legacy = seen_legacy || (ua_ch_result.find(i) != std::string::npos);
+  }
+  return seen_legacy;
+}
+
+// A helper function to determine whether the GREASE algorithm per the spec:
+// https://wicg.github.io/ua-client-hints/#create-arbitrary-brands-section
+// was observed in the client hint user agent header.
+bool SawUpdatedGrease(const std::string& ua_ch_result) {
+  // The updated GREASE algorithm would contain at least two of these
+  // characters.
+  static constexpr char kUpdatedGreaseRegex[] =
+      "Not[ ()\\-.\\/:;=?_]A[ ()\\-.\\/:;=?_]Brand";
+  return re2::RE2::PartialMatch(ua_ch_result, kUpdatedGreaseRegex);
+}
+
 enum class UserAgentOriginTrialTestType {
   UAReduction,
   UADeprecation,
@@ -5270,16 +5297,8 @@
   const GURL gurl = accept_ch_url();
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
   std::string ua_ch_result = main_frame_ua_observed();
-  // The updated GREASE algorithm would contain at least one of these
-  // characters. The equal sign, space, and semicolon are not present as they
-  // exist in the old algorithm.
-  std::vector<char> updated_grease_chars = {'(', ':', '-', '.',
-                                            '/', ')', '?', '_'};
-  bool seen_updated = false;
-  for (auto c : updated_grease_chars) {
-    seen_updated = seen_updated || (ua_ch_result.find(c) != std::string::npos);
-  }
-  ASSERT_TRUE(seen_updated);
+
+  ASSERT_TRUE(SawUpdatedGrease(ua_ch_result) && !SawOldGrease(ua_ch_result));
 }
 
 class GreaseFeatureParamOptOutTest : public ClientHintsBrowserTest {
@@ -5301,14 +5320,8 @@
   const GURL gurl = accept_ch_url();
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
   std::string ua_ch_result = main_frame_ua_observed();
-  // The updated GREASE algorithm would contain at least one of these
-  // characters. The equal sign, space, and semicolon are not present as they
-  // exist in the old algorithm.
-  std::vector<char> updated_grease_chars = {'(', ':', '-', '.',
-                                            '/', ')', '?', '_'};
-  for (auto i : updated_grease_chars) {
-    ASSERT_TRUE(ua_ch_result.find(i) == std::string::npos);
-  }
+
+  ASSERT_TRUE(SawOldGrease(ua_ch_result));
 }
 
 class GreaseEnterprisePolicyTest : public ClientHintsBrowserTest {
@@ -5326,14 +5339,8 @@
   const GURL gurl = accept_ch_url();
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
   std::string ua_ch_result = main_frame_ua_observed();
-  // The updated GREASE algorithm would contain at least one of these
-  // characters. The equal sign, space, and semicolon are not present as they
-  // exist in the old algorithm.
-  std::vector<char> updated_grease_chars = {'(', ':', '-', '.',
-                                            '/', ')', '?', '_'};
-  for (auto i : updated_grease_chars) {
-    ASSERT_TRUE(ua_ch_result.find(i) == std::string::npos);
-  }
+
+  ASSERT_TRUE(SawOldGrease(ua_ch_result));
 }
 IN_PROC_BROWSER_TEST_F(GreaseEnterprisePolicyTest,
                        GreaseEnterprisePolicyDynamicRefreshTest) {
@@ -5347,13 +5354,8 @@
   provider_.UpdateChromePolicy(policies);
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
   std::string ua_ch_result = main_frame_ua_observed();
-  bool seen_updated = false;
-  std::vector<char> updated_grease_chars = {'(', ':', '-', '.',
-                                            '/', ')', '?', '_'};
-  for (auto c : updated_grease_chars) {
-    seen_updated = seen_updated || (ua_ch_result.find(c) != std::string::npos);
-  }
-  ASSERT_TRUE(seen_updated);
+
+  ASSERT_TRUE(SawUpdatedGrease(ua_ch_result) && !SawOldGrease(ua_ch_result));
 }
 
 // Tests that the Sec-CH-UA-Reduced client hint gets cleared on a redirect if
diff --git a/chrome/browser/device_reauth/android/biometric_authenticator_android.cc b/chrome/browser/device_reauth/android/biometric_authenticator_android.cc
index e7de6eb..f15930c 100644
--- a/chrome/browser/device_reauth/android/biometric_authenticator_android.cc
+++ b/chrome/browser/device_reauth/android/biometric_authenticator_android.cc
@@ -117,7 +117,8 @@
 
 void BiometricAuthenticatorAndroid::Authenticate(
     device_reauth::BiometricAuthRequester requester,
-    AuthenticateCallback callback) {
+    AuthenticateCallback callback,
+    bool use_last_valid_auth) {
   // Previous authentication is not yet completed, so return.
   if (callback_ || requester_.has_value())
     return;
@@ -127,7 +128,7 @@
 
   LogAuthRequester(requester);
 
-  if (last_good_auth_timestamp_.has_value() &&
+  if (use_last_valid_auth && last_good_auth_timestamp_.has_value() &&
       base::TimeTicks::Now() - last_good_auth_timestamp_.value() <
           base::Seconds(kAuthValidSeconds)) {
     LogAuthResult(requester, BiometricAuthFinalResult::kAuthStillValid);
diff --git a/chrome/browser/device_reauth/android/biometric_authenticator_android.h b/chrome/browser/device_reauth/android/biometric_authenticator_android.h
index 322598b..889fd0f2 100644
--- a/chrome/browser/device_reauth/android/biometric_authenticator_android.h
+++ b/chrome/browser/device_reauth/android/biometric_authenticator_android.h
@@ -56,8 +56,12 @@
   // Trigges an authentication flow based on biometrics, with the
   // screen lock as fallback. Note: this only supports one authentication
   // request at a time.
+  // |use_last_valid_auth| if set to false, ignores the grace 60 seconds
+  // period between the last valid authentication and the current
+  // authentication, and re-invokes system authentication.
   void Authenticate(device_reauth::BiometricAuthRequester requester,
-                    AuthenticateCallback callback) override;
+                    AuthenticateCallback callback,
+                    bool use_last_valid_auth) override;
 
   // Trigges an authentication flow based on biometrics, with the
   // screen lock as fallback. Displays `message` in the authentication UI.
diff --git a/chrome/browser/device_reauth/android/biometric_authenticator_android_unittest.cc b/chrome/browser/device_reauth/android/biometric_authenticator_android_unittest.cc
index 5091a49d..7448907e 100644
--- a/chrome/browser/device_reauth/android/biometric_authenticator_android_unittest.cc
+++ b/chrome/browser/device_reauth/android/biometric_authenticator_android_unittest.cc
@@ -102,7 +102,8 @@
   base::HistogramTester histogram_tester;
 
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                base::DoNothing());
+                                base::DoNothing(),
+                                /*use_last_valid_auth=*/true);
   histogram_tester.ExpectUniqueSample(
       "PasswordManager.BiometricAuthPwdFill.AuthRequester",
       BiometricAuthRequester::kAllPasswordsList, 1);
@@ -119,7 +120,8 @@
       .WillOnce(
           RunOnceCallback<0>(BiometricAuthUIResult::kSuccessWithBiometrics));
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                base::DoNothing());
+                                base::DoNothing(),
+                                /*use_last_valid_auth=*/true);
 
   // The next call to `Authenticate()` should not re-trigger an authentication.
   EXPECT_CALL(bridge(), Authenticate(_)).Times(0);
@@ -127,7 +129,8 @@
       result_callback;
   EXPECT_CALL(result_callback, Run(/*auth_succeeded=*/true));
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                result_callback.Get());
+                                result_callback.Get(),
+                                /*use_last_valid_auth=*/true);
   EXPECT_THAT(
       histogram_tester.GetAllSamples(
           "PasswordManager.BiometricAuthPwdFill.AuthResult"),
@@ -146,7 +149,8 @@
       .WillOnce(
           RunOnceCallback<0>(BiometricAuthUIResult::kSuccessWithBiometrics));
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                base::DoNothing());
+                                base::DoNothing(),
+                                /*use_last_valid_auth=*/true);
 
   task_environment().FastForwardBy(base::Seconds(60));
 
@@ -157,7 +161,40 @@
       result_callback;
   EXPECT_CALL(result_callback, Run(/*auth_succeeded=*/false));
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                result_callback.Get());
+                                result_callback.Get(),
+                                /*use_last_valid_auth=*/true);
+
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "PasswordManager.BiometricAuthPwdFill.AuthResult"),
+      ElementsAre(
+          Bucket(static_cast<int>(
+                     BiometricAuthFinalResult::kSuccessWithBiometrics),
+                 1),
+          Bucket(static_cast<int>(BiometricAuthFinalResult::kFailed), 1)));
+}
+
+TEST_F(BiometricAuthenticatorAndroidTest,
+       TriggersAuthIfWithin60Seconds_AndUseLastValidAuthIsFalse) {
+  base::HistogramTester histogram_tester;
+  // Simulate a previous successful authentication
+  EXPECT_CALL(bridge(), Authenticate)
+      .WillOnce(
+          RunOnceCallback<0>(BiometricAuthUIResult::kSuccessWithBiometrics));
+  authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
+                                base::DoNothing(),
+                                /*use_last_valid_auth=*/true);
+
+  // The next call to `Authenticate()` should re-trigger an authentication
+  // as |use_last_valid_auth| is set to false.
+  EXPECT_CALL(bridge(), Authenticate(_))
+      .WillOnce(RunOnceCallback<0>(BiometricAuthUIResult::kFailed));
+  base::MockCallback<BiometricAuthenticator::AuthenticateCallback>
+      result_callback;
+  EXPECT_CALL(result_callback, Run(/*auth_succeeded=*/false));
+  authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
+                                result_callback.Get(),
+                                /*use_last_valid_auth=*/false);
 
   EXPECT_THAT(
       histogram_tester.GetAllSamples(
@@ -175,7 +212,8 @@
   EXPECT_CALL(bridge(), Authenticate)
       .WillOnce(RunOnceCallback<0>(BiometricAuthUIResult::kFailed));
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                base::DoNothing());
+                                base::DoNothing(),
+                                /*use_last_valid_auth=*/true);
 
   // The next call to `Authenticate()` should re-trigger an authentication.
   EXPECT_CALL(bridge(), Authenticate(_))
@@ -185,7 +223,8 @@
       result_callback;
   EXPECT_CALL(result_callback, Run(/*auth_succeeded=*/true));
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                result_callback.Get());
+                                result_callback.Get(),
+                                /*use_last_valid_auth=*/true);
 
   EXPECT_THAT(
       histogram_tester.GetAllSamples(
@@ -200,7 +239,8 @@
 TEST_F(BiometricAuthenticatorAndroidTest, CancelsOngoingAuthIfSameRequester) {
   EXPECT_CALL(bridge(), Authenticate);
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                base::DoNothing());
+                                base::DoNothing(),
+                                /*use_last_valid_auth=*/true);
   EXPECT_CALL(bridge(), Cancel);
   authenticator()->Cancel(BiometricAuthRequester::kAllPasswordsList);
 }
@@ -208,7 +248,8 @@
 TEST_F(BiometricAuthenticatorAndroidTest, DoesntCancelAuthIfNotSameRequester) {
   EXPECT_CALL(bridge(), Authenticate);
   authenticator()->Authenticate(BiometricAuthRequester::kAllPasswordsList,
-                                base::DoNothing());
+                                base::DoNothing(),
+                                /*use_last_valid_auth=*/true);
   EXPECT_CALL(bridge(), Cancel).Times(0);
   authenticator()->Cancel(BiometricAuthRequester::kAccountChooserDialog);
 }
diff --git a/chrome/browser/device_reauth/android/java/src/org/chromium/chrome/browser/device_reauth/ReauthenticatorBridge.java b/chrome/browser/device_reauth/android/java/src/org/chromium/chrome/browser/device_reauth/ReauthenticatorBridge.java
index ba61b7a..d343914 100644
--- a/chrome/browser/device_reauth/android/java/src/org/chromium/chrome/browser/device_reauth/ReauthenticatorBridge.java
+++ b/chrome/browser/device_reauth/android/java/src/org/chromium/chrome/browser/device_reauth/ReauthenticatorBridge.java
@@ -34,11 +34,14 @@
      * Starts reauthentication.
      *
      * @param callback Callback that will be executed once request is done.
+     * @param useLastValidAuth A boolean value indicating whether to consider the last but "recent"
+     *         validated auth for passing the current authentication request.
      */
-    public void reauthenticate(Callback<Boolean> callback) {
+    public void reauthenticate(Callback<Boolean> callback, boolean useLastValidAuth) {
         assert mAuthResultCallback == null;
         mAuthResultCallback = callback;
-        ReauthenticatorBridgeJni.get().reauthenticate(mNativeReauthenticatorBridge);
+        ReauthenticatorBridgeJni.get().reauthenticate(
+                mNativeReauthenticatorBridge, useLastValidAuth);
     }
 
     @CalledByNative
@@ -52,6 +55,6 @@
     interface Natives {
         long create(ReauthenticatorBridge reauthenticatorBridge, int requester);
         boolean canUseAuthentication(long nativeReauthenticatorBridge);
-        void reauthenticate(long nativeReauthenticatorBridge);
+        void reauthenticate(long nativeReauthenticatorBridge, boolean useLastValidAuth);
     }
 }
diff --git a/chrome/browser/device_reauth/android/reauthenticator_bridge.cc b/chrome/browser/device_reauth/android/reauthenticator_bridge.cc
index 597f076..442446c 100644
--- a/chrome/browser/device_reauth/android/reauthenticator_bridge.cc
+++ b/chrome/browser/device_reauth/android/reauthenticator_bridge.cc
@@ -40,7 +40,8 @@
   return authenticator_ && authenticator_->CanAuthenticate(requester_);
 }
 
-void ReauthenticatorBridge::Reauthenticate(JNIEnv* env) {
+void ReauthenticatorBridge::Reauthenticate(JNIEnv* env,
+                                           bool use_last_valid_auth) {
   if (!authenticator_) {
     return;
   }
@@ -51,7 +52,8 @@
   authenticator_->Authenticate(
       requester_,
       base::BindOnce(&ReauthenticatorBridge::OnReauthenticationCompleted,
-                     base::Unretained(this)));
+                     base::Unretained(this)),
+      use_last_valid_auth);
 }
 
 void ReauthenticatorBridge::OnReauthenticationCompleted(bool auth_succeeded) {
diff --git a/chrome/browser/device_reauth/android/reauthenticator_bridge.h b/chrome/browser/device_reauth/android/reauthenticator_bridge.h
index dec83d5..d1c5629 100644
--- a/chrome/browser/device_reauth/android/reauthenticator_bridge.h
+++ b/chrome/browser/device_reauth/android/reauthenticator_bridge.h
@@ -26,7 +26,7 @@
   bool CanUseAuthentication(JNIEnv* env);
 
   // Called by Java to start authentication.
-  void Reauthenticate(JNIEnv* env);
+  void Reauthenticate(JNIEnv* env, bool use_last_valid_auth);
 
   // Called when reauthentication is completed.
   void OnReauthenticationCompleted(bool auth_succeeded);
diff --git a/chrome/browser/enterprise/connectors/analysis/analysis_service_settings_unittest.cc b/chrome/browser/enterprise/connectors/analysis/analysis_service_settings_unittest.cc
index 9a805ce..4e39e23 100644
--- a/chrome/browser/enterprise/connectors/analysis/analysis_service_settings_unittest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/analysis_service_settings_unittest.cc
@@ -6,7 +6,9 @@
 
 #include "base/json/json_reader.h"
 #include "base/no_destructor.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/enterprise/connectors/analysis/analysis_settings.h"
 #include "chrome/browser/enterprise/connectors/connectors_service.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -30,7 +32,7 @@
 };
 
 constexpr char kNormalSettings[] = R"({
-  "service_provider": "google",
+  "service_provider": "%s",
   "enable": [
     {"url_list": ["*"], "tags": ["dlp", "malware"]},
   ],
@@ -48,7 +50,7 @@
 })";
 
 constexpr char kOnlyEnabledPatternsSettings[] = R"({
-  "service_provider": "google",
+  "service_provider": "%s",
   "enable": [
     {"url_list": ["scan1.com", "scan2.com"], "tags": ["dlp"]},
   ],
@@ -72,7 +74,7 @@
 })";
 
 constexpr char kNoEnabledPatternsSettings[] = R"({
-  "service_provider": "google",
+  "service_provider": "%s",
   "disable": [
     {"url_list": ["no.dlp.com", "no.dlp.or.malware.ca"], "tags": ["dlp"]},
     {"url_list": ["no.malware.com", "no.dlp.or.malware.ca"],
@@ -86,7 +88,7 @@
 })";
 
 constexpr char kNormalSettingsWithCustomMessage[] = R"({
-  "service_provider": "google",
+  "service_provider": "%s",
   "enable": [
     {"url_list": ["*"], "tags": ["dlp", "malware"]},
   ],
@@ -116,7 +118,7 @@
 })";
 
 constexpr char kNormalSettingsDlpRequiresBypassJustification[] = R"({
-  "service_provider": "google",
+  "service_provider": "%s",
   "enable": [
     {"url_list": ["*"], "tags": ["dlp", "malware"]},
   ],
@@ -238,10 +240,13 @@
 class AnalysisServiceSettingsTest : public testing::TestWithParam<TestParam> {
  public:
   GURL url() const { return GURL(GetParam().url); }
-  const char* settings_value() const { return GetParam().settings_value; }
+  std::string settings_value() const {
+    return base::StringPrintf(GetParam().settings_value,
+                              is_cloud_ ? "google" : "local_test");
+  }
   AnalysisSettings* expected_settings() const {
     // Set the GURL field dynamically to avoid static initialization issues.
-    if (GetParam().expected_settings != NoSettings() &&
+    if (GetParam().expected_settings != NoSettings() && is_cloud_ &&
         !GetParam()
              .expected_settings->cloud_or_local_settings.analysis_url()
              .is_valid()) {
@@ -249,15 +254,35 @@
           GetParam().expected_settings->cloud_or_local_settings)
           .analysis_url =
           GURL("https://safebrowsing.google.com/safebrowsing/uploads/scan");
+      CloudAnalysisSettings cloud_settings;
+      cloud_settings.analysis_url =
+          GURL("https://safebrowsing.google.com/safebrowsing/uploads/scan");
+      GetParam().expected_settings->cloud_or_local_settings =
+          CloudOrLocalAnalysisSettings(std::move(cloud_settings));
     }
+    if (GetParam().expected_settings != NoSettings() && !is_cloud_) {
+      LocalAnalysisSettings local_settings;
+      local_settings.local_path = "test_path";
+      GetParam().expected_settings->cloud_or_local_settings =
+          CloudOrLocalAnalysisSettings(std::move(local_settings));
+
+      // The "local_test" analysis provider only supports the "dlp" tag, so
+      // it is expected that the malware tag is absent from final settings even
+      // when it is included in the policy.
+      GetParam().expected_settings->tags.erase("malware");
+      if (GetParam().expected_settings->tags.empty())
+        return NoSettings();
+    }
+
     return GetParam().expected_settings;
   }
 
- private:
+ protected:
+  bool is_cloud_ = true;
   content::BrowserTaskEnvironment task_environment_;
 };
 
-TEST_P(AnalysisServiceSettingsTest, Test) {
+TEST_P(AnalysisServiceSettingsTest, CloudTest) {
   auto settings = base::JSONReader::Read(settings_value(),
                                          base::JSON_ALLOW_TRAILING_COMMAS);
   ASSERT_TRUE(settings.has_value());
@@ -303,6 +328,53 @@
   }
 }
 
+TEST_P(AnalysisServiceSettingsTest, LocalTest) {
+  is_cloud_ = false;
+  auto settings = base::JSONReader::Read(settings_value(),
+                                         base::JSON_ALLOW_TRAILING_COMMAS);
+  ASSERT_TRUE(settings.has_value());
+
+  AnalysisServiceSettings service_settings(settings.value(),
+                                           *GetServiceProviderConfig());
+
+  auto analysis_settings = service_settings.GetAnalysisSettings(url());
+  ASSERT_EQ((expected_settings() != nullptr), analysis_settings.has_value());
+  if (analysis_settings.has_value()) {
+    ASSERT_EQ(analysis_settings.value().block_until_verdict,
+              expected_settings()->block_until_verdict);
+    ASSERT_EQ(analysis_settings.value().block_password_protected_files,
+              expected_settings()->block_password_protected_files);
+    ASSERT_EQ(analysis_settings.value().block_large_files,
+              expected_settings()->block_large_files);
+    ASSERT_EQ(analysis_settings.value().block_unsupported_file_types,
+              expected_settings()->block_unsupported_file_types);
+    ASSERT_TRUE(
+        analysis_settings.value().cloud_or_local_settings.is_local_analysis());
+    ASSERT_EQ(analysis_settings.value().cloud_or_local_settings.local_path(),
+              expected_settings()->cloud_or_local_settings.local_path());
+    ASSERT_EQ(analysis_settings.value().minimum_data_size,
+              expected_settings()->minimum_data_size);
+    for (const auto& entry : expected_settings()->tags) {
+      const std::string& tag = entry.first;
+      ASSERT_TRUE(analysis_settings.value().tags.count(entry.first));
+      ASSERT_EQ(analysis_settings.value().tags[tag].custom_message.message,
+                entry.second.custom_message.message);
+      if (!analysis_settings.value()
+               .tags[tag]
+               .custom_message.learn_more_url.is_empty()) {
+        ASSERT_EQ(kExpectedLearnMoreUrlSpecs.at(tag),
+                  analysis_settings.value()
+                      .tags[tag]
+                      .custom_message.learn_more_url.spec());
+        ASSERT_EQ(kExpectedLearnMoreUrlSpecs.at(tag),
+                  service_settings.GetLearnMoreUrl(tag).value().spec());
+      }
+      ASSERT_EQ(analysis_settings.value().tags[tag].requires_justification,
+                entry.second.requires_justification);
+    }
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(
     ,
     AnalysisServiceSettingsTest,
diff --git a/chrome/browser/enterprise/connectors/connectors_manager_unittest.cc b/chrome/browser/enterprise/connectors/connectors_manager_unittest.cc
index 41d64f6c..ffc5c57 100644
--- a/chrome/browser/enterprise/connectors/connectors_manager_unittest.cc
+++ b/chrome/browser/enterprise/connectors/connectors_manager_unittest.cc
@@ -42,7 +42,7 @@
 
 constexpr char kEmptySettingsPref[] = "[]";
 
-constexpr char kNormalAnalysisSettingsPref[] = R"([
+constexpr char kNormalCloudAnalysisSettingsPref[] = R"([
   {
     "service_provider": "google",
     "enable": [
@@ -60,6 +60,22 @@
   },
 ])";
 
+constexpr char kNormalLocalAnalysisSettingsPref[] = R"([
+  {
+    "service_provider": "local_test",
+    "enable": [
+      {"url_list": ["*"], "tags": ["dlp"]},
+    ],
+    "disable": [
+      {"url_list": ["no.dlp.com", "no.dlp.or.malware.ca"], "tags": ["dlp"]},
+    ],
+    "block_until_verdict": 1,
+    "block_password_protected": true,
+    "block_large_files": true,
+    "block_unsupported_file_types": true,
+  },
+])";
+
 constexpr char kNormalReportingSettingsPref[] = R"([
   {
     "service_provider": "google"
@@ -179,7 +195,7 @@
 class ConnectorsManagerConnectorPoliciesTest
     : public ConnectorsManagerTest,
       public testing::WithParamInterface<
-          std::tuple<AnalysisConnector, const char*>> {
+          std::tuple<AnalysisConnector, const char*, const char*>> {
  public:
   ConnectorsManagerConnectorPoliciesTest() = default;
 
@@ -187,6 +203,8 @@
 
   const char* url() const { return std::get<1>(GetParam()); }
 
+  const char* pref_value() const { return std::get<2>(GetParam()); }
+
   const char* pref() const { return ConnectorPref(connector()); }
 
   void SetUpExpectedAnalysisSettings(const char* pref) {
@@ -224,6 +242,13 @@
     else if (url == kOnlyMalwareUrl)
       settings.tags = {{"malware", TagSettings()}};
 
+    // The "local_test" service provider doesn't support the "malware" tag, so
+    // remove it from expectations.
+    if (pref == kNormalLocalAnalysisSettingsPref)
+      settings.tags.erase("malware");
+    if (settings.tags.empty())
+      return absl::nullopt;
+
     return settings;
   }
 
@@ -233,9 +258,8 @@
 TEST_P(ConnectorsManagerConnectorPoliciesTest, NormalPref) {
   ConnectorsManager manager(pref_service(), GetServiceProviderConfig());
   ASSERT_TRUE(manager.GetAnalysisConnectorsSettingsForTesting().empty());
-  ScopedConnectorPref scoped_pref(pref_service(), pref(),
-                                  kNormalAnalysisSettingsPref);
-  SetUpExpectedAnalysisSettings(kNormalAnalysisSettingsPref);
+  ScopedConnectorPref scoped_pref(pref_service(), pref(), pref_value());
+  SetUpExpectedAnalysisSettings(pref_value());
 
   // Verify that the expected settings are returned normally.
   auto settings_from_manager =
@@ -278,11 +302,14 @@
                      testing::Values(kDlpAndMalwareUrl,
                                      kOnlyDlpUrl,
                                      kOnlyMalwareUrl,
-                                     kNoTagsUrl)));
+                                     kNoTagsUrl),
+                     testing::Values(kNormalCloudAnalysisSettingsPref,
+                                     kNormalLocalAnalysisSettingsPref)));
 
 class ConnectorsManagerAnalysisConnectorsTest
     : public ConnectorsManagerTest,
-      public testing::WithParamInterface<AnalysisConnector> {
+      public testing::WithParamInterface<
+          std::tuple<AnalysisConnector, const char*>> {
  public:
   explicit ConnectorsManagerAnalysisConnectorsTest(bool enable = true) {
     if (enable) {
@@ -292,7 +319,9 @@
     }
   }
 
-  AnalysisConnector connector() const { return GetParam(); }
+  AnalysisConnector connector() const { return std::get<0>(GetParam()); }
+
+  const char* pref_value() const { return std::get<1>(GetParam()); }
 
   const char* pref() const { return ConnectorPref(connector()); }
 };
@@ -305,8 +334,7 @@
   // Once the pref is updated, the settings should be cached, and analysis
   // settings can be obtained.
   {
-    ScopedConnectorPref scoped_pref(pref_service(), pref(),
-                                    kNormalAnalysisSettingsPref);
+    ScopedConnectorPref scoped_pref(pref_service(), pref(), pref_value());
 
     const auto& cached_settings =
         manager.GetAnalysisConnectorsSettingsForTesting();
@@ -322,7 +350,14 @@
     expected_block_password_protected_files_ = true;
     expected_block_large_files_ = true;
     expected_block_unsupported_file_types_ = true;
-    expected_tags_ = {{"dlp", TagSettings()}, {"malware", TagSettings()}};
+
+    // The "local_test" service provider doesn't support the "malware" tag, so
+    // remove it from expectations.
+    if (pref_value() == kNormalCloudAnalysisSettingsPref)
+      expected_tags_ = {{"dlp", TagSettings()}, {"malware", TagSettings()}};
+    else
+      expected_tags_ = {{"dlp", TagSettings()}};
+
     ValidateSettings(settings.value());
   }
 
@@ -330,9 +365,12 @@
   ASSERT_TRUE(manager.GetAnalysisConnectorsSettingsForTesting().empty());
 }
 
-INSTANTIATE_TEST_SUITE_P(ConnectorsManagerAnalysisConnectorsTest,
-                         ConnectorsManagerAnalysisConnectorsTest,
-                         testing::ValuesIn(kAllAnalysisConnectors));
+INSTANTIATE_TEST_SUITE_P(
+    ConnectorsManagerAnalysisConnectorsTest,
+    ConnectorsManagerAnalysisConnectorsTest,
+    testing::Combine(testing::ValuesIn(kAllAnalysisConnectors),
+                     testing::Values(kNormalCloudAnalysisSettingsPref,
+                                     kNormalLocalAnalysisSettingsPref)));
 
 class ConnectorsManagerReportingTest
     : public ConnectorsManagerTest,
diff --git a/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc b/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
index d7e6e9b..f7bfa2a7 100644
--- a/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/connectors_service_browsertest.cc
@@ -49,7 +49,7 @@
 
 namespace {
 
-constexpr char kNormalAnalysisSettingsPref[] = R"([
+constexpr char kNormalCloudAnalysisSettingsPref[] = R"([
   {
     "service_provider": "google",
     "enable": [
@@ -58,6 +58,15 @@
   }
 ])";
 
+constexpr char kNormalLocalAnalysisSettingsPref[] = R"([
+  {
+    "service_provider": "local_test",
+    "enable": [
+      {"url_list": ["*"], "tags": ["dlp"]}
+    ]
+  }
+])";
+
 constexpr char kNormalReportingSettingsPref[] = R"([
   {
     "service_provider": "google"
@@ -342,11 +351,12 @@
 class ConnectorsServiceAnalysisProfileBrowserTest
     : public ConnectorsServiceProfileBrowserTest,
       public testing::WithParamInterface<
-          std::tuple<AnalysisConnector, ManagementStatus>> {
+          std::tuple<AnalysisConnector, ManagementStatus, const char*>> {
  public:
   ConnectorsServiceAnalysisProfileBrowserTest()
       : ConnectorsServiceProfileBrowserTest(std::get<1>(GetParam())) {}
   AnalysisConnector connector() { return std::get<0>(GetParam()); }
+  const char* settings_value() { return std::get<2>(GetParam()); }
 
   // Returns the Value the "normal" reporting workflow uses to validate that it
   // is in sync with the information sent through analysis-reporting.
@@ -473,28 +483,38 @@
         testing::Values(FILE_ATTACHED, FILE_DOWNLOADED, BULK_DATA_ENTRY, PRINT),
         testing::Values(ManagementStatus::AFFILIATED,
                         ManagementStatus::UNAFFILIATED,
-                        ManagementStatus::UNMANAGED)));
+                        ManagementStatus::UNMANAGED),
+        testing::Values(kNormalCloudAnalysisSettingsPref,
+                        kNormalLocalAnalysisSettingsPref)));
 
 IN_PROC_BROWSER_TEST_P(ConnectorsServiceAnalysisProfileBrowserTest,
                        DeviceReporting) {
   SetPrefs(ConnectorPref(connector()), ConnectorScopePref(connector()),
-           kNormalAnalysisSettingsPref, /*profile_scope*/ false);
+           settings_value(), /*profile_scope*/ false);
   SetPrefs(ConnectorPref(ReportingConnector::SECURITY_EVENT),
            ConnectorScopePref(ReportingConnector::SECURITY_EVENT),
-           kNormalReportingSettingsPref, /*profile_scope*/ false);
+           settings_value(), /*profile_scope*/ false);
   auto settings =
       ConnectorsServiceFactory::GetForBrowserContext(browser()->profile())
           ->GetAnalysisSettings(GURL(kTestUrl), connector());
   if (management_status() == ManagementStatus::UNMANAGED) {
-    ASSERT_FALSE(settings.has_value());
+    if (settings_value() == kNormalLocalAnalysisSettingsPref) {
+      ASSERT_TRUE(settings.has_value());
+      ASSERT_TRUE(settings.value().cloud_or_local_settings.is_local_analysis());
+      ASSERT_EQ("test_path",
+                settings.value().cloud_or_local_settings.local_path());
+    } else {
+      ASSERT_FALSE(settings.has_value());
+    }
   } else {
     ASSERT_TRUE(settings.has_value());
-    ASSERT_TRUE(settings.value().cloud_or_local_settings.is_cloud_analysis());
-    ASSERT_EQ(kFakeBrowserDMToken,
-              settings.value().cloud_or_local_settings.dm_token());
+    if (settings.value().cloud_or_local_settings.is_cloud_analysis()) {
+      ASSERT_EQ(kFakeBrowserDMToken,
+                settings.value().cloud_or_local_settings.dm_token());
+      ValidateClientMetadata(*settings.value().client_metadata,
+                             /*profile_reporting*/ false);
+    }
     ASSERT_FALSE(settings.value().per_profile);
-    ValidateClientMetadata(*settings.value().client_metadata,
-                           /*profile_reporting*/ false);
   }
 
 #if !BUILDFLAG(IS_CHROMEOS)
@@ -508,7 +528,7 @@
 IN_PROC_BROWSER_TEST_P(ConnectorsServiceAnalysisProfileBrowserTest,
                        ProfileReporting) {
   SetPrefs(ConnectorPref(connector()), ConnectorScopePref(connector()),
-           kNormalAnalysisSettingsPref);
+           settings_value());
   SetPrefs(ConnectorPref(ReportingConnector::SECURITY_EVENT),
            ConnectorScopePref(ReportingConnector::SECURITY_EVENT),
            kNormalReportingSettingsPref);
@@ -518,15 +538,21 @@
 
 #if BUILDFLAG(IS_CHROMEOS)
   if (management_status() == ManagementStatus::UNMANAGED) {
-    ASSERT_FALSE(settings.has_value());
+    if (settings_value() == kNormalLocalAnalysisSettingsPref) {
+      ASSERT_TRUE(settings.has_value());
+      // TODO(b/238216275): Verify the metadata has the expected values.
+    } else {
+      ASSERT_FALSE(settings.has_value());
+    }
   } else {
     ASSERT_TRUE(settings.has_value());
-    ASSERT_TRUE(settings.value().cloud_or_local_settings.is_cloud_analysis());
-    ASSERT_EQ(kFakeBrowserDMToken,
-              settings.value().cloud_or_local_settings.dm_token());
+    if (settings.value().cloud_or_local_settings.is_cloud_analysis()) {
+      ASSERT_EQ(kFakeBrowserDMToken,
+                settings.value().cloud_or_local_settings.dm_token());
+      ValidateClientMetadata(*settings.value().client_metadata,
+                             /*profile_reporting*/ false);
+    }
     ASSERT_FALSE(settings.value().per_profile);
-    ValidateClientMetadata(*settings.value().client_metadata,
-                           /*profile_reporting*/ false);
   }
 #else
   std::string management_domain =
@@ -534,28 +560,40 @@
           ->GetManagementDomain();
   switch (management_status()) {
     case ManagementStatus::UNAFFILIATED:
-      EXPECT_FALSE(settings.has_value());
+      if (settings_value() == kNormalLocalAnalysisSettingsPref) {
+        ASSERT_TRUE(settings.has_value());
+        ASSERT_TRUE(
+            settings.value().cloud_or_local_settings.is_local_analysis());
+        ASSERT_EQ("test_path",
+                  settings.value().cloud_or_local_settings.local_path());
+      } else {
+        ASSERT_FALSE(settings.has_value());
+      }
       ASSERT_TRUE(management_domain.empty());
       break;
     case ManagementStatus::AFFILIATED:
       EXPECT_TRUE(settings.has_value());
-      EXPECT_TRUE(settings.value().cloud_or_local_settings.is_cloud_analysis());
-      ASSERT_EQ(kFakeProfileDMToken,
-                settings.value().cloud_or_local_settings.dm_token());
+      if (settings.value().cloud_or_local_settings.is_cloud_analysis()) {
+        ASSERT_EQ(kFakeProfileDMToken,
+                  settings.value().cloud_or_local_settings.dm_token());
+        ValidateClientMetadata(*settings.value().client_metadata,
+                               /*profile_reporting*/ true);
+      }
       ASSERT_TRUE(settings.value().per_profile);
-      ValidateClientMetadata(*settings.value().client_metadata,
-                             /*profile_reporting*/ true);
       ASSERT_EQ(kDomain1, management_domain);
       break;
     case ManagementStatus::UNMANAGED:
       EXPECT_TRUE(settings.has_value());
-      EXPECT_TRUE(settings.value().cloud_or_local_settings.is_cloud_analysis());
-      ASSERT_EQ(kFakeProfileDMToken,
-                settings.value().cloud_or_local_settings.dm_token());
-      ASSERT_TRUE(settings.value().per_profile);
       ASSERT_TRUE(settings.value().client_metadata);
-      ValidateClientMetadata(*settings.value().client_metadata,
-                             /*profile_reporting*/ true);
+      if (settings.value().cloud_or_local_settings.is_cloud_analysis()) {
+        ASSERT_EQ(kFakeProfileDMToken,
+                  settings.value().cloud_or_local_settings.dm_token());
+        ValidateClientMetadata(*settings.value().client_metadata,
+                               /*profile_reporting*/ true);
+      } else {
+        // TODO(b/238216275): Verify the metadata has the expected values.
+      }
+      ASSERT_TRUE(settings.value().per_profile);
       ASSERT_EQ(kDomain1, management_domain);
       break;
   }
@@ -565,21 +603,30 @@
 IN_PROC_BROWSER_TEST_P(ConnectorsServiceAnalysisProfileBrowserTest,
                        NoReporting) {
   SetPrefs(ConnectorPref(connector()), ConnectorScopePref(connector()),
-           kNormalAnalysisSettingsPref);
+           settings_value());
   auto settings =
       ConnectorsServiceFactory::GetForBrowserContext(browser()->profile())
           ->GetAnalysisSettings(GURL(kTestUrl), connector());
 
 #if BUILDFLAG(IS_CHROMEOS)
   if (management_status() == ManagementStatus::UNMANAGED) {
-    ASSERT_FALSE(settings.has_value());
+    if (settings_value() == kNormalLocalAnalysisSettingsPref) {
+      ASSERT_TRUE(settings.has_value());
+      // TODO(b/238216275): Verify the metadata has the expected values.
+    } else {
+      ASSERT_FALSE(settings.has_value());
+    }
   } else {
     ASSERT_TRUE(settings.has_value());
-    ASSERT_TRUE(settings.value().cloud_or_local_settings.is_cloud_analysis());
-    ASSERT_EQ(kFakeBrowserDMToken,
-              settings.value().cloud_or_local_settings.dm_token());
+    if (settings.value().cloud_or_local_settings.is_cloud_analysis()) {
+      ASSERT_EQ(kFakeBrowserDMToken,
+                settings.value().cloud_or_local_settings.dm_token());
+      ASSERT_FALSE(settings.value().client_metadata);
+    } else {
+      ASSERT_TRUE(settings.value().client_metadata);
+      // TODO(b/238216275): Verify the metadata has the expected values.
+    }
     ASSERT_FALSE(settings.value().per_profile);
-    ASSERT_FALSE(settings.value().client_metadata);
   }
 #else
   std::string management_domain =
@@ -587,25 +634,41 @@
           ->GetManagementDomain();
   switch (management_status()) {
     case ManagementStatus::UNAFFILIATED:
-      EXPECT_FALSE(settings.has_value());
+      if (settings_value() == kNormalLocalAnalysisSettingsPref) {
+        ASSERT_TRUE(settings.has_value());
+        ASSERT_TRUE(
+            settings.value().cloud_or_local_settings.is_local_analysis());
+        ASSERT_EQ("test_path",
+                  settings.value().cloud_or_local_settings.local_path());
+      } else {
+        ASSERT_FALSE(settings.has_value());
+      }
       ASSERT_TRUE(management_domain.empty());
       break;
     case ManagementStatus::AFFILIATED:
       EXPECT_TRUE(settings.has_value());
-      EXPECT_TRUE(settings.value().cloud_or_local_settings.is_cloud_analysis());
-      ASSERT_EQ(kFakeProfileDMToken,
-                settings.value().cloud_or_local_settings.dm_token());
+      if (settings.value().cloud_or_local_settings.is_cloud_analysis()) {
+        ASSERT_EQ(kFakeProfileDMToken,
+                  settings.value().cloud_or_local_settings.dm_token());
+        ASSERT_FALSE(settings.value().client_metadata);
+      } else {
+        ASSERT_TRUE(settings.value().client_metadata);
+        // TODO(b/238216275): Verify the metadata has the expected values.
+      }
       ASSERT_TRUE(settings.value().per_profile);
-      ASSERT_FALSE(settings.value().client_metadata);
       ASSERT_EQ(kDomain1, management_domain);
       break;
     case ManagementStatus::UNMANAGED:
       EXPECT_TRUE(settings.has_value());
-      EXPECT_TRUE(settings.value().cloud_or_local_settings.is_cloud_analysis());
-      ASSERT_EQ(kFakeProfileDMToken,
-                settings.value().cloud_or_local_settings.dm_token());
+      if (settings.value().cloud_or_local_settings.is_cloud_analysis()) {
+        ASSERT_EQ(kFakeProfileDMToken,
+                  settings.value().cloud_or_local_settings.dm_token());
+        ASSERT_FALSE(settings.value().client_metadata);
+      } else {
+        ASSERT_TRUE(settings.value().client_metadata);
+        // TODO(b/238216275): Verify the metadata has the expected values.
+      }
       ASSERT_TRUE(settings.value().per_profile);
-      ASSERT_FALSE(settings.value().client_metadata);
       ASSERT_EQ(kDomain1, management_domain);
       break;
   }
diff --git a/chrome/browser/enterprise/connectors/connectors_service_unittest.cc b/chrome/browser/enterprise/connectors/connectors_service_unittest.cc
index 081f31e2..579c7ef 100644
--- a/chrome/browser/enterprise/connectors/connectors_service_unittest.cc
+++ b/chrome/browser/enterprise/connectors/connectors_service_unittest.cc
@@ -30,7 +30,7 @@
 
 constexpr char kEmptySettingsPref[] = "[]";
 
-constexpr char kNormalAnalysisSettingsPref[] = R"([
+constexpr char kNormalCloudAnalysisSettingsPref[] = R"([
   {
     "service_provider": "google",
     "enable": [
@@ -48,6 +48,24 @@
   }
 ])";
 
+constexpr char kNormalLocalAnalysisSettingsPref[] = R"([
+  {
+    "service_provider": "local_test",
+    "enable": [
+      {"url_list": ["*"], "tags": ["dlp", "malware"]}
+    ],
+    "disable": [
+      {"url_list": ["no.dlp.com", "no.dlp.or.malware.ca"], "tags": ["dlp"]},
+      {"url_list": ["no.malware.com", "no.dlp.or.malware.ca"],
+           "tags": ["malware"]}
+    ],
+    "block_until_verdict": 1,
+    "block_password_protected": true,
+    "block_large_files": true,
+    "block_unsupported_file_types": true
+  }
+])";
+
 constexpr char kWildcardAnalysisSettingsPref[] = R"([
   {
     "service_provider": "google",
@@ -112,19 +130,20 @@
 
 class ConnectorsServiceAnalysisNoFeatureTest
     : public ConnectorsServiceTest,
-      public testing::WithParamInterface<AnalysisConnector> {
+      public testing::WithParamInterface<
+          std::tuple<const char*, AnalysisConnector>> {
  public:
   ConnectorsServiceAnalysisNoFeatureTest() {
     scoped_feature_list_.InitWithFeatures({}, {kEnterpriseConnectorsEnabled});
   }
 
-  AnalysisConnector connector() { return GetParam(); }
+  std::string pref_value() { return std::get<0>(GetParam()); }
+  AnalysisConnector connector() { return std::get<1>(GetParam()); }
 };
 
 TEST_P(ConnectorsServiceAnalysisNoFeatureTest, AnalysisConnectors) {
-  profile_->GetPrefs()->Set(
-      ConnectorPref(connector()),
-      *base::JSONReader::Read(kNormalAnalysisSettingsPref));
+  profile_->GetPrefs()->Set(ConnectorPref(connector()),
+                            *base::JSONReader::Read(pref_value()));
   auto* service = ConnectorsServiceFactory::GetForBrowserContext(profile_);
   for (const char* url :
        {kDlpAndMalwareUrl, kOnlyDlpUrl, kOnlyMalwareUrl, kNoTagsUrl}) {
@@ -143,7 +162,12 @@
 INSTANTIATE_TEST_SUITE_P(
     ,
     ConnectorsServiceAnalysisNoFeatureTest,
-    testing::Values(FILE_ATTACHED, FILE_DOWNLOADED, BULK_DATA_ENTRY, PRINT));
+    testing::Combine(testing::Values(kNormalCloudAnalysisSettingsPref,
+                                     kNormalLocalAnalysisSettingsPref),
+                     testing::Values(FILE_ATTACHED,
+                                     FILE_DOWNLOADED,
+                                     BULK_DATA_ENTRY,
+                                     PRINT)));
 
 // Tests to make sure getting reporting settings work with both the feature flag
 // and the OnSecurityEventEnterpriseConnector policy. The parameter for these
diff --git a/chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.cc b/chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.cc
index 27666ce..64e5975 100644
--- a/chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.cc
+++ b/chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.cc
@@ -65,9 +65,7 @@
 namespace {
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-const char kActiveDirectoryPolicyClientDescription[] = "an Active Directory";
 const char kPolicyClientDescription[] = "any";
-const char kUserPolicyClientDescription[] = "a user";
 #else
 const char kChromeBrowserCloudManagementClientDescription[] =
     "a machine-level user";
@@ -211,38 +209,31 @@
   }
 
   policy::CloudPolicyClient* client = nullptr;
+  std::string client_id;
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+  Profile* profile = nullptr;
   const user_manager::User* user = GetChromeOSUser();
   if (user) {
-    Profile* profile = ash::ProfileHelper::Get()->GetProfileByUser(user);
+    profile = ash::ProfileHelper::Get()->GetProfileByUser(user);
     // If primary user profile is not finalized, use the current profile.
     if (!profile)
       profile = Profile::FromBrowserContext(context_);
-    DCHECK(profile);
-    if (user->IsActiveDirectoryUser()) {
-      // TODO(crbug.com/1012048): Handle AD, likely through crbug.com/1012170.
-      policy_client_desc = kActiveDirectoryPolicyClientDescription;
-    } else {
-      policy_client_desc = kUserPolicyClientDescription;
-      policy::UserCloudPolicyManagerAsh* policy_manager =
-          profile->GetUserCloudPolicyManagerAsh();
-      if (policy_manager)
-        client = policy_manager->core()->client();
-    }
   } else {
     LOG(ERROR) << "Could not determine who the user is.";
+    profile = Profile::FromBrowserContext(context_);
   }
-#else
-  std::string client_id =
-      policy::BrowserDMTokenStorage::Get()->RetrieveClientId();
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  DCHECK(profile);
+  client_id = reporting::GetUserClientId(profile).value_or("");
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
   Profile* main_profile = enterprise_connectors::GetMainProfileLacros();
   if (main_profile) {
     // Prefer the user client id if available.
     client_id = reporting::GetUserClientId(main_profile).value_or(client_id);
   }
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+#else
+  client_id = policy::BrowserDMTokenStorage::Get()->RetrieveClientId();
+#endif
 
   // Make sure DeviceManagementService has been initialized.
   device_management_service->ScheduleInitialization(0);
@@ -261,7 +252,6 @@
         dm_token, client_id,
         /*user_affiliation_ids=*/std::vector<std::string>());
   }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   return {policy_client_desc, client};
 }
@@ -299,17 +289,32 @@
 void RealtimeReportingClient::OnCloudPolicyClientAvailable(
     const std::string& policy_client_desc,
     policy::CloudPolicyClient* client) {
-  if (policy_client_desc == kProfilePolicyClientDescription)
-    profile_client_ = client;
-  else
-    browser_client_ = client;
-
   if (client == nullptr) {
     LOG(ERROR) << "Could not obtain " << policy_client_desc
                << " for safe browsing real-time event reporting.";
     return;
   }
 
+  if (policy_client_desc == kProfilePolicyClientDescription) {
+    DCHECK_NE(profile_client_, client);
+    if (profile_client_ == client)
+      return;
+
+    if (profile_client_ == client)
+      profile_client_->RemoveObserver(this);
+
+    profile_client_ = client;
+  } else {
+    DCHECK_NE(browser_client_, client);
+    if (browser_client_ == client)
+      return;
+
+    if (browser_client_)
+      browser_client_->RemoveObserver(this);
+
+    browser_client_ = client;
+  }
+
   client->AddObserver(this);
 
   VLOG(1) << "Ready for safe browsing real-time event reporting.";
diff --git a/chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h b/chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h
index 8bd87c28..37ddf27 100644
--- a/chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h
+++ b/chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h
@@ -15,9 +15,6 @@
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/enterprise/connectors/common.h"
-// #include
-// "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
-// #include "components/download/public/common/download_danger_type.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/cloud_policy_core.h"
diff --git a/chrome/browser/extensions/api/offscreen/offscreen_apitest.cc b/chrome/browser/extensions/api/offscreen/offscreen_apitest.cc
index 75f466e..7724a693 100644
--- a/chrome/browser/extensions/api/offscreen/offscreen_apitest.cc
+++ b/chrome/browser/extensions/api/offscreen/offscreen_apitest.cc
@@ -6,19 +6,66 @@
 
 #include <algorithm>
 
+#include "base/run_loop.h"
+#include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_util.h"
 #include "components/version_info/channel.h"
 #include "content/public/test/browser_test.h"
+#include "extensions/browser/api/offscreen/offscreen_document_manager.h"
+#include "extensions/browser/background_script_executor.h"
+#include "extensions/browser/extension_util.h"
+#include "extensions/browser/service_worker_task_queue.h"
+#include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_features.h"
 #include "extensions/common/features/feature_channel.h"
+#include "extensions/test/extension_background_page_waiter.h"
 #include "extensions/test/result_catcher.h"
 #include "extensions/test/test_extension_dir.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace extensions {
 
+namespace {
+
+// Sets the extension to be enabled in incognito mode.
+scoped_refptr<const Extension> SetExtensionIncognitoEnabled(
+    const Extension& extension,
+    Profile& profile) {
+  // Enabling the extension in incognito results in an extension reload; wait
+  // for that to finish and return the new extension pointer.
+  TestExtensionRegistryObserver registry_observer(
+      ExtensionRegistry::Get(&profile), extension.id());
+  util::SetIsIncognitoEnabled(extension.id(), &profile, true);
+  scoped_refptr<const Extension> reloaded_extension =
+      registry_observer.WaitForExtensionLoaded();
+
+  if (!reloaded_extension) {
+    ADD_FAILURE() << "Failed to properly reload extension.";
+    return nullptr;
+  }
+
+  EXPECT_TRUE(util::IsIncognitoEnabled(reloaded_extension->id(), &profile));
+  return reloaded_extension;
+}
+
+// Wakes up the service worker for the `extension` in the given `profile`.
+void WakeUpServiceWorker(const Extension& extension, Profile& profile) {
+  base::RunLoop run_loop;
+  auto quit_loop_adapter =
+      [&run_loop](std::unique_ptr<LazyContextTaskQueue::ContextInfo>) {
+        run_loop.QuitWhenIdle();
+      };
+  ServiceWorkerTaskQueue::Get(&profile)->AddPendingTask(
+      LazyContextId(&profile, extension.id(), extension.url()),
+      base::BindLambdaForTesting(quit_loop_adapter));
+  run_loop.Run();
+}
+
+}  // namespace
+
 class OffscreenApiTest : public ExtensionApiTest {
  public:
   OffscreenApiTest() {
@@ -27,6 +74,74 @@
   }
   ~OffscreenApiTest() override = default;
 
+  // Creates a new offscreen document through an API call, expecting success.
+  void ProgrammaticallyCreateOffscreenDocument(const Extension& extension,
+                                               Profile& profile) {
+    static constexpr char kScript[] =
+        R"((async () => {
+             let message;
+             try {
+               await chrome.offscreen.createDocument(
+                   {
+                     url: 'offscreen.html',
+                     reasons: ['TESTING'],
+                     justification: 'testing'
+                   });
+               message = 'success';
+             } catch (e) {
+               message = 'Error: ' + e.toString();
+             }
+             chrome.test.sendScriptResult(message);
+           })();)";
+    base::Value result = BackgroundScriptExecutor::ExecuteScript(
+        &profile, extension.id(), kScript,
+        BackgroundScriptExecutor::ResultCapture::kSendScriptResult);
+    ASSERT_TRUE(result.is_string());
+    EXPECT_EQ("success", result.GetString());
+  }
+
+  // Closes an offscreen document through an API call, expecting success.
+  void ProgrammaticallyCloseOffscreenDocument(const Extension& extension,
+                                              Profile& profile) {
+    static constexpr char kScript[] =
+        R"((async () => {
+             let message;
+             try {
+               await chrome.offscreen.closeDocument();
+               message = 'success';
+             } catch (e) {
+               message = 'Error: ' + e.toString();
+             }
+             chrome.test.sendScriptResult(message);
+           })();)";
+    base::Value result = BackgroundScriptExecutor::ExecuteScript(
+        &profile, extension.id(), kScript,
+        BackgroundScriptExecutor::ResultCapture::kSendScriptResult);
+    ASSERT_TRUE(result.is_string());
+    EXPECT_EQ("success", result.GetString());
+  }
+
+  // Returns the result of an API call to `offscreen.hasDocument()`. Expects the
+  // call to not throw an error, independent of whether a document exists.
+  bool ProgrammaticallyCheckIfHasOffscreenDocument(const Extension& extension,
+                                                   Profile& profile) {
+    static constexpr char kScript[] =
+        R"((async () => {
+             let result;
+             try {
+               result = await chrome.offscreen.hasDocument();
+             } catch (e) {
+               result = 'Error: ' + e.toString();
+             }
+             chrome.test.sendScriptResult(result);
+           })();)";
+    base::Value result = BackgroundScriptExecutor::ExecuteScript(
+        &profile, extension.id(), kScript,
+        BackgroundScriptExecutor::ResultCapture::kSendScriptResult);
+    EXPECT_TRUE(result.is_bool()) << result;
+    return result.is_bool() && result.GetBool();
+  }
+
  private:
   // The `offscreen` API is currently behind both a feature and a channel
   // restriction.
@@ -41,6 +156,140 @@
       << message_;
 }
 
+// Tests creating, querying, and closing offscreen documents in an incognito
+// split mode extension.
+IN_PROC_BROWSER_TEST_F(OffscreenApiTest, IncognitoModeHandling_SplitMode) {
+  // `split` incognito mode is required in order to allow the extension to
+  // have a separate process in incognito.
+  static constexpr char kManifest[] =
+      R"({
+           "name": "Offscreen Document Test",
+           "manifest_version": 3,
+           "version": "0.1",
+           "background": {"service_worker": "background.js"},
+           "permissions": ["offscreen"],
+           "incognito": "split"
+         })";
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "// Blank.");
+  test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
+                     "<html>offscreen</html>");
+
+  scoped_refptr<const Extension> extension =
+      LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  extension = SetExtensionIncognitoEnabled(*extension, *profile());
+  ASSERT_TRUE(extension);
+
+  Browser* incognito_browser = CreateIncognitoBrowser();
+  ASSERT_TRUE(incognito_browser);
+  Profile* incognito_profile = incognito_browser->profile();
+
+  // We're going to be executing scripts in the service worker context, so
+  // ensure the service worker is active.
+  // TODO(devlin): Should BackgroundScriptExecutor handle that for us? (Perhaps
+  // optionally?)
+  WakeUpServiceWorker(*extension, *profile());
+  WakeUpServiceWorker(*extension, *incognito_profile);
+
+  auto has_offscreen_document = [this, extension](Profile& profile) {
+    bool programmatic =
+        ProgrammaticallyCheckIfHasOffscreenDocument(*extension, profile);
+    bool in_manager =
+        OffscreenDocumentManager::Get(&profile)
+            ->GetOffscreenDocumentForExtension(*extension) != nullptr;
+    EXPECT_EQ(programmatic, in_manager) << "Mismatch between manager and API.";
+    return programmatic && in_manager;
+  };
+
+  // Create an offscreen document in the on-the-record profile. Only it should
+  // have a document; the off-the-record profile is considered distinct.
+  ProgrammaticallyCreateOffscreenDocument(*extension, *profile());
+  EXPECT_TRUE(has_offscreen_document(*profile()));
+  EXPECT_FALSE(has_offscreen_document(*incognito_profile));
+
+  // Now, create a new document in the off-the-record profile.
+  ProgrammaticallyCreateOffscreenDocument(*extension,
+                                          *incognito_browser->profile());
+  EXPECT_TRUE(has_offscreen_document(*profile()));
+  EXPECT_TRUE(has_offscreen_document(*incognito_profile));
+
+  // Close the off-the-record profile - the on-the-record profile's offscreen
+  // document should remain open.
+  ProgrammaticallyCloseOffscreenDocument(*extension, *incognito_profile);
+  EXPECT_TRUE(has_offscreen_document(*profile()));
+  EXPECT_FALSE(has_offscreen_document(*incognito_profile));
+
+  // Finally, close the on-the-record profile's document.
+  ProgrammaticallyCloseOffscreenDocument(*extension, *profile());
+  EXPECT_FALSE(has_offscreen_document(*profile()));
+  EXPECT_FALSE(has_offscreen_document(*incognito_profile));
+}
+
+// Tests creating, querying, and closing offscreen documents in an incognito
+// spanning mode extension.
+IN_PROC_BROWSER_TEST_F(OffscreenApiTest, IncognitoModeHandling_SpanningMode) {
+  static constexpr char kManifest[] =
+      R"({
+           "name": "Offscreen Document Test",
+           "manifest_version": 3,
+           "version": "0.1",
+           "background": {"service_worker": "background.js"},
+           "permissions": ["offscreen"],
+           "incognito": "spanning"
+         })";
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "// Blank.");
+  test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
+                     "<html>offscreen</html>");
+
+  scoped_refptr<const Extension> extension =
+      LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  extension = SetExtensionIncognitoEnabled(*extension, *profile());
+  ASSERT_TRUE(extension);
+
+  Browser* incognito_browser = CreateIncognitoBrowser();
+  ASSERT_TRUE(incognito_browser);
+  Profile* incognito_profile = incognito_browser->profile();
+
+  // Wake up the on-the-record service worker (the only one we have, as a
+  // spanning mode extension).
+  WakeUpServiceWorker(*extension, *profile());
+
+  auto has_offscreen_document = [this, extension](Profile& profile) {
+    bool programmatic =
+        ProgrammaticallyCheckIfHasOffscreenDocument(*extension, profile);
+    bool in_manager =
+        OffscreenDocumentManager::Get(&profile)
+            ->GetOffscreenDocumentForExtension(*extension) != nullptr;
+    EXPECT_EQ(programmatic, in_manager) << "Mismatch between manager and API.";
+    return programmatic && in_manager;
+  };
+
+  // There's less to do in a spanning mode extension - by definition, we can't
+  // call any methods from an incognito profile, so we just have to verify that
+  // the incognito profile is unaffected.
+  ProgrammaticallyCreateOffscreenDocument(*extension, *profile());
+  EXPECT_TRUE(has_offscreen_document(*profile()));
+  // Don't use `has_offscreen_document()` since we can't actually check the
+  // programmatic status, which requires executing script in an incognito
+  // process.
+  OffscreenDocumentManager* incognito_manager =
+      OffscreenDocumentManager::Get(incognito_profile);
+  EXPECT_EQ(nullptr,
+            incognito_manager->GetOffscreenDocumentForExtension(*extension));
+
+  ProgrammaticallyCloseOffscreenDocument(*extension, *profile());
+  EXPECT_FALSE(has_offscreen_document(*profile()));
+  EXPECT_EQ(nullptr,
+            incognito_manager->GetOffscreenDocumentForExtension(*extension));
+}
+
 class OffscreenApiTestWithoutFeature : public ExtensionApiTest {
  public:
   OffscreenApiTestWithoutFeature() = default;
diff --git a/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc b/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
index 77762d4..b78e9a2 100644
--- a/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
+++ b/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
@@ -344,7 +344,16 @@
 
   CredentialUIEntry credential_to_edit = *entry;
   credential_to_edit.password = base::UTF8ToUTF16(new_password);
-  return saved_passwords_presenter_->EditSavedCredentials(credential_to_edit);
+  switch (
+      saved_passwords_presenter_->EditSavedCredentials(credential_to_edit)) {
+    case password_manager::SavedPasswordsPresenter::EditResult::kSuccess:
+    case password_manager::SavedPasswordsPresenter::EditResult::kNothingChanged:
+      return true;
+    case password_manager::SavedPasswordsPresenter::EditResult::kNotFound:
+    case password_manager::SavedPasswordsPresenter::EditResult::kAlreadyExisits:
+    case password_manager::SavedPasswordsPresenter::EditResult::kEmptyPassword:
+      return false;
+  }
 }
 
 bool PasswordCheckDelegate::RemoveInsecureCredential(
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
index cf921c25..9ff08a8 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
@@ -71,16 +71,8 @@
   auto parameters =
       api::passwords_private::RemoveSavedPassword::Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(parameters);
-  GetDelegate(browser_context())->RemoveSavedPasswords({parameters->id});
-  return RespondNow(NoArguments());
-}
-
-// PasswordsPrivateRemoveSavedPasswordsFunction
-ResponseAction PasswordsPrivateRemoveSavedPasswordsFunction::Run() {
-  auto parameters =
-      api::passwords_private::RemoveSavedPasswords::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(parameters);
-  GetDelegate(browser_context())->RemoveSavedPasswords(parameters->ids);
+  GetDelegate(browser_context())
+      ->RemoveSavedPassword(parameters->id, parameters->from_stores);
   return RespondNow(NoArguments());
 }
 
@@ -89,16 +81,7 @@
   auto parameters =
       api::passwords_private::RemovePasswordException::Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(parameters);
-  GetDelegate(browser_context())->RemovePasswordExceptions({parameters->id});
-  return RespondNow(NoArguments());
-}
-
-// PasswordsPrivateRemovePasswordExceptionsFunction
-ResponseAction PasswordsPrivateRemovePasswordExceptionsFunction::Run() {
-  auto parameters =
-      api::passwords_private::RemovePasswordExceptions::Params::Create(args());
-  EXTENSION_FUNCTION_VALIDATE(parameters);
-  GetDelegate(browser_context())->RemovePasswordExceptions(parameters->ids);
+  GetDelegate(browser_context())->RemovePasswordException(parameters->id);
   return RespondNow(NoArguments());
 }
 
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_api.h b/chrome/browser/extensions/api/passwords_private/passwords_private_api.h
index 319ca3e..c0243cb 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_api.h
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_api.h
@@ -53,18 +53,6 @@
   ResponseAction Run() override;
 };
 
-class PasswordsPrivateRemoveSavedPasswordsFunction : public ExtensionFunction {
- public:
-  DECLARE_EXTENSION_FUNCTION("passwordsPrivate.removeSavedPasswords",
-                             PASSWORDSPRIVATE_REMOVESAVEDPASSWORDS)
-
- protected:
-  ~PasswordsPrivateRemoveSavedPasswordsFunction() override = default;
-
-  // ExtensionFunction overrides.
-  ResponseAction Run() override;
-};
-
 class PasswordsPrivateRemovePasswordExceptionFunction
     : public ExtensionFunction {
  public:
@@ -78,19 +66,6 @@
   ResponseAction Run() override;
 };
 
-class PasswordsPrivateRemovePasswordExceptionsFunction
-    : public ExtensionFunction {
- public:
-  DECLARE_EXTENSION_FUNCTION("passwordsPrivate.removePasswordExceptions",
-                             PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTIONS)
-
- protected:
-  ~PasswordsPrivateRemovePasswordExceptionsFunction() override = default;
-
-  // ExtensionFunction overrides.
-  ResponseAction Run() override;
-};
-
 class PasswordsPrivateUndoRemoveSavedPasswordOrExceptionFunction
     : public ExtensionFunction {
  public:
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc
index a87932a..0b26b47b 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc
@@ -199,23 +199,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest,
-                       RemoveAndUndoRemoveSavedPasswordsBatch) {
-  EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemoveSavedPasswordsBatch"))
-      << message_;
-}
-
-IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest,
                        RemoveAndUndoRemovePasswordException) {
   EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemovePasswordException"))
       << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest,
-                       RemoveAndUndoRemovePasswordExceptionsBatch) {
-  EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemovePasswordExceptionsBatch"))
-      << message_;
-}
-
 IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RequestPlaintextPassword) {
   EXPECT_TRUE(RunPasswordsSubtest("requestPlaintextPassword")) << message_;
 }
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h
index be8983a..a392a0d 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h
@@ -95,13 +95,15 @@
       const std::vector<int>& ids,
       const api::passwords_private::ChangeSavedPasswordParams& params) = 0;
 
-  // Removes the saved password entries corresponding to the |ids| generated for
-  // each entry of the password list. Any invalid id will be ignored.
-  virtual void RemoveSavedPasswords(const std::vector<int>& ids) = 0;
+  // Removes the saved password entry corresponding to the |id| in the
+  // specified |from_stores|. Any invalid id will be ignored.
+  virtual void RemoveSavedPassword(
+      int id,
+      api::passwords_private::PasswordStoreSet from_stores) = 0;
 
-  // Removes the password exceptions entries corresponding corresponding to
-  // |ids|. Any invalid id will be ignored.
-  virtual void RemovePasswordExceptions(const std::vector<int>& ids) = 0;
+  // Removes the password exception entry corresponding to |id|. Any invalid id
+  // will be ignored.
+  virtual void RemovePasswordException(int id) = 0;
 
   // Undoes the last removal of a saved password or exception.
   virtual void UndoRemoveSavedPasswordOrException() = 0;
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
index 27ebe3d..3b799fa 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
@@ -129,6 +129,25 @@
   }
 }
 
+base::flat_set<password_manager::PasswordForm::Store>
+ConvertToPasswordFormStores(
+    extensions::api::passwords_private::PasswordStoreSet store) {
+  switch (store) {
+    case extensions::api::passwords_private::
+        PASSWORD_STORE_SET_DEVICE_AND_ACCOUNT:
+      return {password_manager::PasswordForm::Store::kProfileStore,
+              password_manager::PasswordForm::Store::kAccountStore};
+    case extensions::api::passwords_private::PASSWORD_STORE_SET_DEVICE:
+      return {password_manager::PasswordForm::Store::kProfileStore};
+    case extensions::api::passwords_private::PASSWORD_STORE_SET_ACCOUNT:
+      return {password_manager::PasswordForm::Store::kAccountStore};
+    default:
+      break;
+  }
+  NOTREACHED();
+  return {};
+}
+
 }  // namespace
 
 namespace extensions {
@@ -260,11 +279,15 @@
   // process.
   auto forms_to_edit =
       saved_passwords_presenter_.GetCorrespondingPasswordForms(entry->key());
-  bool success = saved_passwords_presenter_.EditSavedCredentials(to_edit);
-  if (!success) {
-    return absl::nullopt;
+  switch (saved_passwords_presenter_.EditSavedCredentials(to_edit)) {
+    case password_manager::SavedPasswordsPresenter::EditResult::kSuccess:
+    case password_manager::SavedPasswordsPresenter::EditResult::kNothingChanged:
+      break;
+    case password_manager::SavedPasswordsPresenter::EditResult::kNotFound:
+    case password_manager::SavedPasswordsPresenter::EditResult::kAlreadyExisits:
+    case password_manager::SavedPasswordsPresenter::EditResult::kEmptyPassword:
+      return absl::nullopt;
   }
-
   api::passwords_private::CredentialIds new_ids;
   for (auto& form : forms_to_edit) {
     // Calculate the new IDs using the new username and password.
@@ -284,38 +307,41 @@
   return new_ids;
 }
 
-void PasswordsPrivateDelegateImpl::RemoveSavedPasswords(
-    const std::vector<int>& ids) {
+void PasswordsPrivateDelegateImpl::RemoveSavedPassword(
+    int id,
+    api::passwords_private::PasswordStoreSet from_stores) {
   ExecuteFunction(
       base::BindOnce(&PasswordsPrivateDelegateImpl::RemoveEntryInternal,
-                     base::Unretained(this), ids));
+                     base::Unretained(this), id, from_stores));
 }
 
 void PasswordsPrivateDelegateImpl::RemoveEntryInternal(
-    const std::vector<int>& ids) {
-  DCHECK(!ids.empty());
-  for (int id : ids) {
-    const CredentialUIEntry* entry = credential_id_generator_.TryGetKey(id);
-    if (!entry) {
-      continue;
-    }
-    saved_passwords_presenter_.RemoveCredential(*entry);
+    int id,
+    api::passwords_private::PasswordStoreSet from_stores) {
+  const CredentialUIEntry* entry = credential_id_generator_.TryGetKey(id);
+  if (!entry) {
+    return;
+  }
 
-    if (entry->blocked_by_user) {
-      base::RecordAction(
-          base::UserMetricsAction("PasswordManager_RemovePasswordException"));
-    } else {
-      base::RecordAction(
-          base::UserMetricsAction("PasswordManager_RemoveSavedPassword"));
-    }
+  CredentialUIEntry copy = *entry;
+  copy.stored_in = ConvertToPasswordFormStores(from_stores);
+
+  saved_passwords_presenter_.RemoveCredential(copy);
+
+  if (entry->blocked_by_user) {
+    base::RecordAction(
+        base::UserMetricsAction("PasswordManager_RemovePasswordException"));
+  } else {
+    base::RecordAction(
+        base::UserMetricsAction("PasswordManager_RemoveSavedPassword"));
   }
 }
 
-void PasswordsPrivateDelegateImpl::RemovePasswordExceptions(
-    const std::vector<int>& ids) {
-  ExecuteFunction(
-      base::BindOnce(&PasswordsPrivateDelegateImpl::RemoveEntryInternal,
-                     base::Unretained(this), ids));
+void PasswordsPrivateDelegateImpl::RemovePasswordException(int id) {
+  ExecuteFunction(base::BindOnce(
+      &PasswordsPrivateDelegateImpl::RemoveEntryInternal,
+      base::Unretained(this), id,
+      api::passwords_private::PASSWORD_STORE_SET_DEVICE_AND_ACCOUNT));
 }
 
 void PasswordsPrivateDelegateImpl::UndoRemoveSavedPasswordOrException() {
@@ -382,24 +408,15 @@
     // store where the credential is stored. This can be removed when the UI
     // will support the new model.
 
-    // Frontend id is used to group identical credentials no matter what
-    // storage they use. It should be generated from the original credential.
-    int frontend_id = credential_id_generator_.GenerateId(credential);
+    int id = credential_id_generator_.GenerateId(credential);
     for (const password_manager::PasswordForm::Store& store :
          credential.stored_in) {
-      // A copy of credential with a single storage should be created to obtain
-      // id. This is important because the credential can be removed only from
-      // one storage at a time. If credential is present only on one storage
-      // |frontend_id| and |id| are equal.
-      CredentialUIEntry copy_for_this_store = credential;
-      copy_for_this_store.stored_in = {store};
-      int id = credential_id_generator_.GenerateId(copy_for_this_store);
       if (credential.blocked_by_user) {
         api::passwords_private::ExceptionEntry current_exception_entry;
         current_exception_entry.urls =
             CreateUrlCollectionFromCredential(credential);
         current_exception_entry.id = id;
-        current_exception_entry.frontend_id = frontend_id;
+        current_exception_entry.frontend_id = id;
 
         current_exception_entry.from_account_store =
             store == password_manager::PasswordForm::Store::kAccountStore;
@@ -410,7 +427,7 @@
         entry.username = base::UTF16ToUTF8(credential.username);
         entry.password_note = base::UTF16ToUTF8(credential.note.value);
         entry.id = id;
-        entry.frontend_id = frontend_id;
+        entry.frontend_id = id;
 
         if (!credential.federation_origin.opaque()) {
           std::u16string formatted_origin =
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h
index 41c082cc8..91431f2 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h
@@ -66,8 +66,10 @@
   absl::optional<api::passwords_private::CredentialIds> ChangeSavedPassword(
       const std::vector<int>& ids,
       const api::passwords_private::ChangeSavedPasswordParams& params) override;
-  void RemoveSavedPasswords(const std::vector<int>& ids) override;
-  void RemovePasswordExceptions(const std::vector<int>& ids) override;
+  void RemoveSavedPassword(
+      int id,
+      api::passwords_private::PasswordStoreSet from_stores) override;
+  void RemovePasswordException(int id) override;
   void UndoRemoveSavedPasswordOrException() override;
   void RequestPlaintextPassword(int id,
                                 api::passwords_private::PlaintextReason reason,
@@ -137,14 +139,6 @@
 #endif  // defined(UNIT_TEST)
 
  private:
-  struct CredentialUIEntryLess {
-    bool operator()(const password_manager::CredentialUIEntry& lhs,
-                    const password_manager::CredentialUIEntry& rhs) const {
-      return std::tie(lhs.key(), lhs.stored_in) <
-             std::tie(rhs.key(), rhs.stored_in);
-    }
-  };
-
   // password_manager::SavedPasswordsPresenter::Observer implementation.
   void OnSavedPasswordsChanged(
       password_manager::SavedPasswordsPresenter::SavedPasswordsView passwords)
@@ -162,7 +156,9 @@
   void SetCredentials(
       const std::vector<password_manager::CredentialUIEntry>& credentials);
 
-  void RemoveEntryInternal(const std::vector<int>& ids);
+  void RemoveEntryInternal(
+      int id,
+      api::passwords_private::PasswordStoreSet from_stores);
   void UndoRemoveSavedPasswordOrExceptionInternal();
 
   // Callback for when the password list has been written to the destination.
@@ -223,7 +219,9 @@
   ExceptionEntries current_exceptions_;
 
   // An id generator for saved passwords and blocked websites.
-  IdGenerator<password_manager::CredentialUIEntry, int, CredentialUIEntryLess>
+  IdGenerator<password_manager::CredentialUIEntry,
+              int,
+              password_manager::CredentialUIEntry::Less>
       credential_id_generator_;
 
   // Whether SetCredentials has been called, and whether this class has been
diff --git a/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc b/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc
index 095799bf1..e0f8519 100644
--- a/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc
+++ b/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc
@@ -112,55 +112,43 @@
   return newIds;
 }
 
-void TestPasswordsPrivateDelegate::RemoveSavedPasswords(
-    const std::vector<int>& ids) {
+void TestPasswordsPrivateDelegate::RemoveSavedPassword(
+    int id,
+    api::passwords_private::PasswordStoreSet from_stores) {
   if (current_entries_.empty())
     return;
 
-  // Since this is just mock data, remove the first |ids.size()| elements
-  // regardless of the data contained.
-  auto first_remaining = (ids.size() <= current_entries_.size())
-                             ? current_entries_.begin() + ids.size()
-                             : current_entries_.end();
-  last_deleted_entries_batch_.assign(
-      std::make_move_iterator(current_entries_.begin()),
-      std::make_move_iterator(first_remaining));
-  current_entries_.erase(current_entries_.begin(), first_remaining);
+  // Since this is just mock data, remove the first element regardless of the
+  // data contained. One case where this logic is especially false is when the
+  // password is stored in both stores and |store| only specifies one of them
+  // (in that case the number of entries shouldn't change).
+  last_deleted_entry_ = std::move(current_entries_[0]);
+  current_entries_.erase(current_entries_.begin());
   SendSavedPasswordsList();
 }
 
-void TestPasswordsPrivateDelegate::RemovePasswordExceptions(
-    const std::vector<int>& ids) {
+void TestPasswordsPrivateDelegate::RemovePasswordException(int id) {
   if (current_exceptions_.empty())
     return;
 
-  // Since this is just mock data, remove the first |ids.size()| elements
-  // regardless of the data contained.
-  auto first_remaining = (ids.size() <= current_exceptions_.size())
-                             ? current_exceptions_.begin() + ids.size()
-                             : current_exceptions_.end();
-  last_deleted_exceptions_batch_.assign(
-      std::make_move_iterator(current_exceptions_.begin()),
-      std::make_move_iterator(first_remaining));
-  current_exceptions_.erase(current_exceptions_.begin(), first_remaining);
+  // Since this is just mock data, remove the first element regardless of the
+  // data contained.
+  last_deleted_exception_ = std::move(current_exceptions_[0]);
+  current_exceptions_.erase(current_exceptions_.begin());
   SendPasswordExceptionsList();
 }
 
 // Simplified version of undo logic, only use for testing.
 void TestPasswordsPrivateDelegate::UndoRemoveSavedPasswordOrException() {
-  if (!last_deleted_entries_batch_.empty()) {
-    current_entries_.insert(
-        current_entries_.begin(),
-        std::make_move_iterator(last_deleted_entries_batch_.begin()),
-        std::make_move_iterator(last_deleted_entries_batch_.end()));
-    last_deleted_entries_batch_.clear();
+  if (last_deleted_entry_.has_value()) {
+    current_entries_.insert(current_entries_.begin(),
+                            std::move(last_deleted_entry_.value()));
+    last_deleted_entry_ = absl::nullopt;
     SendSavedPasswordsList();
-  } else if (!last_deleted_exceptions_batch_.empty()) {
-    current_exceptions_.insert(
-        current_exceptions_.begin(),
-        std::make_move_iterator(last_deleted_exceptions_batch_.begin()),
-        std::make_move_iterator(last_deleted_exceptions_batch_.end()));
-    last_deleted_exceptions_batch_.clear();
+  } else if (last_deleted_exception_.has_value()) {
+    current_exceptions_.insert(current_exceptions_.begin(),
+                               std::move(last_deleted_exception_.value()));
+    last_deleted_exception_ = absl::nullopt;
     SendPasswordExceptionsList();
   }
 }
diff --git a/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h b/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h
index 6428380d..8999d359 100644
--- a/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h
+++ b/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h
@@ -45,8 +45,10 @@
   absl::optional<api::passwords_private::CredentialIds> ChangeSavedPassword(
       const std::vector<int>& ids,
       const api::passwords_private::ChangeSavedPasswordParams& params) override;
-  void RemoveSavedPasswords(const std::vector<int>& id) override;
-  void RemovePasswordExceptions(const std::vector<int>& ids) override;
+  void RemoveSavedPassword(
+      int id,
+      api::passwords_private::PasswordStoreSet from_store) override;
+  void RemovePasswordException(int id) override;
   // Simplified version of undo logic, only use for testing.
   void UndoRemoveSavedPasswordOrException() override;
   void RequestPlaintextPassword(int id,
@@ -149,12 +151,11 @@
   std::vector<api::passwords_private::ExceptionEntry> current_exceptions_;
 
   // Simplified version of an undo manager that only allows undoing and redoing
-  // the very last deletion. When the batches are *empty*, this means there is
+  // the very last deletion. When the entries are nullopt, this means there is
   // no previous deletion to undo.
-  std::vector<api::passwords_private::PasswordUiEntry>
-      last_deleted_entries_batch_;
-  std::vector<api::passwords_private::ExceptionEntry>
-      last_deleted_exceptions_batch_;
+  absl::optional<api::passwords_private::PasswordUiEntry> last_deleted_entry_;
+  absl::optional<api::passwords_private::ExceptionEntry>
+      last_deleted_exception_;
 
   absl::optional<std::u16string> plaintext_password_ = u"plaintext";
 
diff --git a/chrome/browser/extensions/offscreen_document_browsertest.cc b/chrome/browser/extensions/offscreen_document_browsertest.cc
index 15dc90c..0aebedb 100644
--- a/chrome/browser/extensions/offscreen_document_browsertest.cc
+++ b/chrome/browser/extensions/offscreen_document_browsertest.cc
@@ -4,7 +4,6 @@
 
 #include "extensions/browser/offscreen_document_host.h"
 
-#include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/values_test_util.h"
 #include "chrome/browser/devtools/devtools_window.h"
@@ -580,73 +579,4 @@
   expect_navigation_failure(extension->GetResourceURL("other.html"));
 }
 
-// Tests calling window.close() in an offscreen document.
-IN_PROC_BROWSER_TEST_F(OffscreenDocumentBrowserTest, CallWindowClose) {
-  static constexpr char kManifest[] =
-      R"({
-           "name": "Offscreen Document Test",
-           "manifest_version": 3,
-           "version": "0.1"
-         })";
-  TestExtensionDir test_dir;
-  test_dir.WriteManifest(kManifest);
-  test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
-                     "<html>offscreen</html>");
-
-  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
-  ASSERT_TRUE(extension);
-  const GURL offscreen_url = extension->GetResourceURL("offscreen.html");
-
-  {
-    std::unique_ptr<OffscreenDocumentHost> offscreen_document =
-        CreateOffscreenDocument(*extension, offscreen_url);
-    // Create a simple handler for the window.close() call that deletes the
-    // document.
-    base::RunLoop run_loop;
-    auto close_handler = [&run_loop, &offscreen_document](ExtensionHost* host) {
-      ASSERT_EQ(offscreen_document.get(), host);
-      offscreen_document.reset();
-      run_loop.Quit();
-    };
-    offscreen_document->SetCloseHandler(
-        base::BindOnce(base::BindLambdaForTesting(close_handler)));
-    content::ExecuteScriptAsync(offscreen_document->host_contents(),
-                                "window.close();");
-    run_loop.Run();
-    // The close handler should have been invoked.
-    EXPECT_EQ(nullptr, offscreen_document);
-  }
-
-  {
-    std::unique_ptr<OffscreenDocumentHost> offscreen_document =
-        CreateOffscreenDocument(*extension, offscreen_url);
-
-    // Repeat the test, but don't actually close the document in response to
-    // the call (which simulates an asynchronous close). This allows the
-    // window to call close() multiple times. Even though it does so, we should
-    // only receive the signal from the OffscreenDocumentHost once.
-    size_t close_count = 0;
-    auto close_handler = [&close_count,
-                          &offscreen_document](ExtensionHost* host) {
-      ASSERT_EQ(offscreen_document.get(), host);
-      ++close_count;
-    };
-    offscreen_document->SetCloseHandler(
-        base::BindOnce(base::BindLambdaForTesting(close_handler)));
-
-    content::WebContents* contents = offscreen_document->host_contents();
-    ASSERT_TRUE(content::ExecuteScript(contents, "window.close();"));
-    // Unfortunately, ExecuteScript() and the call to
-    // WebContentsDelegate::CloseContents() race with each other. Add an ugly
-    // RunUntilIdle() to "solve" it.
-    base::RunLoop().RunUntilIdle();
-    EXPECT_EQ(1u, close_count);
-    // Repeat the close() call. It shouldn't fire the close handler a second
-    // time.
-    ASSERT_TRUE(content::ExecuteScript(contents, "window.close();"));
-    base::RunLoop().RunUntilIdle();
-    EXPECT_EQ(1u, close_count);
-  }
-}
-
 }  // namespace extensions
diff --git a/chrome/browser/first_party_sets/first_party_sets_policy_browsertest.cc b/chrome/browser/first_party_sets/first_party_sets_policy_browsertest.cc
index 132b8db..87ed3af 100644
--- a/chrome/browser/first_party_sets/first_party_sets_policy_browsertest.cc
+++ b/chrome/browser/first_party_sets/first_party_sets_policy_browsertest.cc
@@ -429,7 +429,8 @@
                                          PrefState::kDisabled,
                                          PrefState::kEnabled)));
 
-class OverridesPolicyEmptyBrowsertest : public EnabledPolicyBrowsertest {
+class DISABLED_OverridesPolicyEmptyBrowsertest
+    : public EnabledPolicyBrowsertest {
  public:
   void SetUpPolicyMapWithOverridesPolicy() override {
     // POLICY_LEVEL_MANDATORY - since administrators will control FPS policy
@@ -444,7 +445,7 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_P(OverridesPolicyEmptyBrowsertest,
+IN_PROC_BROWSER_TEST_P(DISABLED_OverridesPolicyEmptyBrowsertest,
                        SetCookiesFromSamePartyContext) {
   // The initial First-Party Sets were:
   // {owner: A, members: [B, C]}
@@ -463,13 +464,14 @@
 
 INSTANTIATE_TEST_SUITE_P(
     FirstPartySets,
-    OverridesPolicyEmptyBrowsertest,
+    DISABLED_OverridesPolicyEmptyBrowsertest,
     ::testing::Combine(::testing::Bool(),
                        ::testing::Values(PrefState::kDefault,
                                          PrefState::kDisabled,
                                          PrefState::kEnabled)));
 
-class OverridesPolicyReplacementBrowsertest : public EnabledPolicyBrowsertest {
+class DISABLED_OverridesPolicyReplacementBrowsertest
+    : public EnabledPolicyBrowsertest {
  public:
   void SetUpPolicyMapWithOverridesPolicy() override {
     // POLICY_LEVEL_MANDATORY - since administrators will control FPS policy
@@ -496,7 +498,7 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_P(OverridesPolicyReplacementBrowsertest,
+IN_PROC_BROWSER_TEST_P(DISABLED_OverridesPolicyReplacementBrowsertest,
                        SetCookiesFromSamePartyContext) {
   // The initial First-Party Sets were:
   // {owner: A, members: [B, C]}
@@ -527,13 +529,14 @@
 
 INSTANTIATE_TEST_SUITE_P(
     FirstPartySets,
-    OverridesPolicyReplacementBrowsertest,
+    DISABLED_OverridesPolicyReplacementBrowsertest,
     ::testing::Combine(::testing::Bool(),
                        ::testing::Values(PrefState::kDefault,
                                          PrefState::kDisabled,
                                          PrefState::kEnabled)));
 
-class OverridesPolicyAdditionBrowsertest : public EnabledPolicyBrowsertest {
+class DISABLED_OverridesPolicyAdditionBrowsertest
+    : public EnabledPolicyBrowsertest {
  public:
   void SetUpPolicyMapWithOverridesPolicy() override {
     // POLICY_LEVEL_MANDATORY - since administrators will control FPS policy
@@ -559,7 +562,7 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_P(OverridesPolicyAdditionBrowsertest,
+IN_PROC_BROWSER_TEST_P(DISABLED_OverridesPolicyAdditionBrowsertest,
                        SetCookiesFromSamePartyContext) {
   // The initial First-Party Sets were:
   // {owner: A, members: [B, C]}
@@ -578,13 +581,13 @@
 
 INSTANTIATE_TEST_SUITE_P(
     FirstPartySets,
-    OverridesPolicyAdditionBrowsertest,
+    DISABLED_OverridesPolicyAdditionBrowsertest,
     ::testing::Combine(::testing::Bool(),
                        ::testing::Values(PrefState::kDefault,
                                          PrefState::kDisabled,
                                          PrefState::kEnabled)));
 
-class OverridesPolicyReplacementAndAdditionBrowsertest
+class DISABLED_OverridesPolicyReplacementAndAdditionBrowsertest
     : public EnabledPolicyBrowsertest {
  public:
   void SetUpPolicyMapWithOverridesPolicy() override {
@@ -616,8 +619,9 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_P(OverridesPolicyReplacementAndAdditionBrowsertest,
-                       SetCookiesFromSamePartyContext) {
+IN_PROC_BROWSER_TEST_P(
+    DISABLED_OverridesPolicyReplacementAndAdditionBrowsertest,
+    SetCookiesFromSamePartyContext) {
   // The initial First-Party Sets were:
   // {owner: A, members: [B, C]}
   //
@@ -654,7 +658,7 @@
 
 INSTANTIATE_TEST_SUITE_P(
     FirstPartySets,
-    OverridesPolicyReplacementAndAdditionBrowsertest,
+    DISABLED_OverridesPolicyReplacementAndAdditionBrowsertest,
     ::testing::Combine(::testing::Bool(),
                        ::testing::Values(PrefState::kDefault,
                                          PrefState::kDisabled,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7b8c208..7a3cf85 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1450,6 +1450,11 @@
     "expiry_milestone": 108
   },
   {
+    "name": "drop-input-events-before-first-paint",
+    "owners": [ "mehdika", "blink-interactions-team@google.com" ],
+    "expiry_milestone": 109
+  },
+  {
     "name": "durable-client-hints-cache",
     "owners": [ "abeyad", "aarontag"],
     "expiry_milestone": 110
@@ -3111,7 +3116,7 @@
   {
     "name": "enable-webrtc-hide-local-ips-with-mdns",
     "owners": [ "hta" ],
-    "expiry_milestone": 100
+    "expiry_milestone": 120
   },
   {
     "name": "enable-webrtc-hybrid-agc",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 625b61b..5fe06f57 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1298,6 +1298,13 @@
     "Reduces the velocity of horizontal flings to 20% of their original"
     "velocity.";
 
+extern const char kDropInputEventsBeforeFirstPaintName[] =
+    "Drop Input Events Before First Paint";
+extern const char kDropInputEventsBeforeFirstPaintDescription[] =
+    "Before the user can see the first paint of a new page they cannot "
+    "intentionally interact with elements on that page. By dropping the events "
+    "we prevent accidental interaction with a page the user has not seen yet.";
+
 const char kEnableCssSelectorFragmentAnchorName[] =
     "Enables CSS selector fragment anchors";
 const char kEnableCssSelectorFragmentAnchorDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f5d6dee..db21359 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -724,6 +724,9 @@
 extern const char kRetailCouponsName[];
 extern const char kRetailCouponsDescription[];
 
+extern const char kDropInputEventsBeforeFirstPaintName[];
+extern const char kDropInputEventsBeforeFirstPaintDescription[];
+
 extern const char kEnableCssSelectorFragmentAnchorName[];
 extern const char kEnableCssSelectorFragmentAnchorDescription[];
 
diff --git a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManager.java b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManager.java
index bd8cbb29..301eb70 100644
--- a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManager.java
+++ b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManager.java
@@ -67,7 +67,7 @@
             } else {
                 incognitoReauthCallback.onIncognitoReauthFailure();
             }
-        });
+        }, /*useLastValidAuth=*/false);
     }
     /**
      * @return A boolean indicating whether the platform version supports reauth and the
diff --git a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManagerTest.java b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManagerTest.java
index d8e1bb78..55aa2a7d 100644
--- a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManagerTest.java
+++ b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthManagerTest.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.incognito.reauth;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
@@ -79,7 +80,7 @@
             return true;
         })
                 .when(mReauthenticatorBridgeMock)
-                .reauthenticate(notNull());
+                .reauthenticate(notNull(), /*useLastValidAuth=*/eq(false));
 
         mIncognitoReauthManager.startReauthenticationFlow(mIncognitoReauthCallbackMock);
         verify(mIncognitoReauthCallbackMock).onIncognitoReauthSuccess();
@@ -97,7 +98,7 @@
             return false;
         })
                 .when(mReauthenticatorBridgeMock)
-                .reauthenticate(notNull());
+                .reauthenticate(notNull(), /*useLastValidAuth=*/eq(false));
 
         mIncognitoReauthManager.startReauthenticationFlow(mIncognitoReauthCallbackMock);
         verify(mIncognitoReauthCallbackMock).onIncognitoReauthFailure();
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
index 1b2fe7d..7193458 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
@@ -337,7 +337,7 @@
             if (reauthSucceeded) {
                 startAutomatedPasswordChange(credential);
             }
-        });
+        }, true);
     }
 
     private void startAutomatedPasswordChange(CompromisedCredential credential) {
diff --git a/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java b/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
index 547b467..959af516 100644
--- a/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
+++ b/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
@@ -105,6 +105,7 @@
             "PasswordManager.BulkCheck.PasswordCheckReferrerAndroid2";
     private static final String PASSWORD_CHECK_USER_ACTION_HISTOGRAM =
             "PasswordManager.BulkCheck.UserActionAndroid";
+    private static final boolean USE_LAST_VALID_AUTH = true;
 
     @Rule
     public final JniMocker mJniMocker = new JniMocker();
@@ -646,7 +647,7 @@
             return true;
         })
                 .when(mReauthenticatorBridge)
-                .reauthenticate(notNull());
+                .reauthenticate(notNull(), eq(USE_LAST_VALID_AUTH));
         // There is a auto change button, a user clicks it.
         mMediator.onChangePasswordWithScriptButtonClick(BOB);
         verify(mDelegate, never()).onAutomatedPasswordChangeStarted(eq(BOB));
@@ -680,7 +681,7 @@
             return true;
         })
                 .when(mReauthenticatorBridge)
-                .reauthenticate(notNull());
+                .reauthenticate(notNull(), eq(USE_LAST_VALID_AUTH));
         // There is a auto change button, a user clicks it.
         mMediator.onChangePasswordWithScriptButtonClick(BOB);
         verify(mDelegate).onAutomatedPasswordChangeStarted(eq(BOB));
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn
index e491b80..6b35c58 100644
--- a/chrome/browser/password_manager/android/BUILD.gn
+++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -169,6 +169,7 @@
 
   sources = [
     "java/src/org/chromium/chrome/browser/password_manager/tests/FakePasswordStoreAndroidBackendTest.java",
+    "junit/src/org/chromium/chrome/browser/password_manager/FakePasswordSettingsAccessorFactoryImplTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupClientMetricsRecorderTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerAndroidBackendUtilTest.java",
     "junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java",
@@ -289,6 +290,8 @@
 android_library("test_support_java") {
   testonly = true
   sources = [
+    "java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSettingsAccessor.java",
+    "java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSettingsAccessorFactoryImpl.java",
     "java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordStoreAndroidBackend.java",
     "java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordStoreAndroidBackendFactoryImpl.java",
     "java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSyncControllerDelegate.java",
@@ -297,6 +300,7 @@
 
   deps = [
     ":backend_interface_java",
+    ":settings_interface_java",
     "//base:base_java",
     "//components/password_manager/core/browser:password_manager_java_enums",
     "//components/password_manager/core/browser:unified_password_manager_proto_java",
diff --git a/chrome/browser/password_manager/android/account_chooser_dialog_android.cc b/chrome/browser/password_manager/android/account_chooser_dialog_android.cc
index 3b3038f..26ac3d0d 100644
--- a/chrome/browser/password_manager/android/account_chooser_dialog_android.cc
+++ b/chrome/browser/password_manager/android/account_chooser_dialog_android.cc
@@ -251,7 +251,8 @@
     authenticator_->Authenticate(
         device_reauth::BiometricAuthRequester::kAccountChooserDialog,
         base::BindOnce(&AccountChooserDialogAndroid::OnReauthCompleted,
-                       base::Unretained(this), index));
+                       base::Unretained(this), index),
+        /*use_last_valid_auth=*/true);
     // The credential handling will only happen after the authentication
     // finishes.
     return false;
diff --git a/chrome/browser/password_manager/android/account_chooser_dialog_android_unittest.cc b/chrome/browser/password_manager/android/account_chooser_dialog_android_unittest.cc
index 8cb7ed6..2f6e923 100644
--- a/chrome/browser/password_manager/android/account_chooser_dialog_android_unittest.cc
+++ b/chrome/browser/password_manager/android/account_chooser_dialog_android_unittest.cc
@@ -209,7 +209,8 @@
               CanAuthenticate(BiometricAuthRequester::kAccountChooserDialog))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kAccountChooserDialog, _))
+              Authenticate(BiometricAuthRequester::kAccountChooserDialog, _,
+                           /*use_last_valid_auth=*/true))
       .WillOnce(RunOnceCallback<1>(true));
 
   std::unique_ptr<password_manager::PasswordForm> form =
@@ -230,7 +231,8 @@
               CanAuthenticate(BiometricAuthRequester::kAccountChooserDialog))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kAccountChooserDialog, _))
+              Authenticate(BiometricAuthRequester::kAccountChooserDialog, _,
+                           /*use_last_valid_auth=*/true))
       .WillOnce(RunOnceCallback<1>(false));
 
   std::unique_ptr<password_manager::PasswordForm> form =
@@ -251,7 +253,8 @@
               CanAuthenticate(BiometricAuthRequester::kAccountChooserDialog))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kAccountChooserDialog, _));
+              Authenticate(BiometricAuthRequester::kAccountChooserDialog, _,
+                           /*use_last_valid_auth=*/true));
 
   dialog->OnCredentialClicked(base::android::AttachCurrentThread(),
                               nullptr /* obj */, 1 /* credential_item */,
diff --git a/chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller.cc b/chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller.cc
index 5db5718..c5bcb83 100644
--- a/chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller.cc
+++ b/chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller.cc
@@ -110,7 +110,8 @@
       authenticator_->Authenticate(
           device_reauth::BiometricAuthRequester::kAllPasswordsList,
           base::BindOnce(&AllPasswordsBottomSheetController::OnReauthCompleted,
-                         base::Unretained(this), password));
+                         base::Unretained(this), password),
+          /*use_last_valid_auth=*/true);
       return;
     }
 
diff --git a/chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller_unittest.cc b/chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller_unittest.cc
index 129a6e4..5114f77 100644
--- a/chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller_unittest.cc
+++ b/chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller_unittest.cc
@@ -242,7 +242,8 @@
       .WillOnce(Return(authenticator()));
   EXPECT_CALL(*authenticator().get(), CanAuthenticate).WillOnce(Return(true));
   EXPECT_CALL(*authenticator().get(),
-              Authenticate(BiometricAuthRequester::kAllPasswordsList, _))
+              Authenticate(BiometricAuthRequester::kAllPasswordsList, _,
+                           /*use_last_valid_auth=*/true))
       .WillOnce(RunOnceCallback<1>(true));
 
   EXPECT_CALL(driver(), FillIntoFocusedField(true, std::u16string(kPassword)));
@@ -258,7 +259,8 @@
       .WillOnce(Return(authenticator()));
   EXPECT_CALL(*authenticator().get(), CanAuthenticate).WillOnce(Return(true));
   EXPECT_CALL(*authenticator().get(),
-              Authenticate(BiometricAuthRequester::kAllPasswordsList, _))
+              Authenticate(BiometricAuthRequester::kAllPasswordsList, _,
+                           /*use_last_valid_auth=*/true))
       .WillOnce(RunOnceCallback<1>(false));
 
   EXPECT_CALL(driver(), FillIntoFocusedField(true, std::u16string(kPassword)))
@@ -275,7 +277,8 @@
       .WillOnce(Return(authenticator()));
   EXPECT_CALL(*authenticator().get(), CanAuthenticate).WillOnce(Return(true));
   EXPECT_CALL(*authenticator().get(),
-              Authenticate(BiometricAuthRequester::kAllPasswordsList, _));
+              Authenticate(BiometricAuthRequester::kAllPasswordsList, _,
+                           /*use_last_valid_auth=*/true));
 
   EXPECT_CALL(driver(), FillIntoFocusedField(true, std::u16string(kPassword)))
       .Times(0);
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java
index 9803a8c..5b64d62 100644
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java
@@ -295,6 +295,19 @@
         return false;
     }
 
+    public static void resetUpmUnenrollment() {
+        // Exit early if Chrome doesn't need UPM UI. Assumes the unenroll pref isn't included in the
+        // usesUnifiedPasswordManagementUI check.
+        if (!PasswordManagerHelper.usesUnifiedPasswordManagerUI()) return;
+        PrefService prefs = UserPrefs.get(Profile.getLastUsedRegularProfile());
+
+        // Exit early if the user is not unenrolled.
+        if (!prefs.getBoolean(Pref.UNENROLLED_FROM_GOOGLE_MOBILE_SERVICES_DUE_TO_ERRORS)) return;
+
+        // Re-enroll the user by resetting the enroll pref. Other state reset happens on unenroll.
+        prefs.setBoolean(Pref.UNENROLLED_FROM_GOOGLE_MOBILE_SERVICES_DUE_TO_ERRORS, false);
+    }
+
     @VisibleForTesting
     static void launchTheCredentialManager(@ManagePasswordsReferrer int referrer,
             CredentialManagerLauncher credentialManagerLauncher, SyncService syncService,
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java
index 5a50156..27a72d00 100644
--- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java
@@ -16,6 +16,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.Date;
 
 /**
  * Java-counterpart of the native PasswordStoreAndroidBackendBridgeImpl. It's part of the password
@@ -52,6 +53,27 @@
     }
 
     @CalledByNative
+    void subscribe(@JobId int jobId, String syncingAccount) {
+        // Fire a call to get all logins between epoch+1ms and epoch+2ms. This is a valid range but
+        // will almost certainly return empty which minimizes the overhead of transmitting unused
+        // password data.
+        mBackend.getAllLoginsBetween(new Date(1), new Date(2), getAccount(syncingAccount),
+                unused_pwds
+                -> {
+                    if (mNativeBackendBridge == 0) return;
+                    PasswordStoreAndroidBackendBridgeImplJni.get().onSubscribed(
+                            mNativeBackendBridge, jobId);
+                },
+                exception -> {
+                    if (mNativeBackendBridge == 0) return;
+                    PasswordStoreAndroidBackendBridgeImplJni.get().onSubscribeFailed(
+                            mNativeBackendBridge, jobId,
+                            PasswordManagerAndroidBackendUtil.getBackendError(exception),
+                            PasswordManagerAndroidBackendUtil.getApiErrorCode(exception));
+                });
+    }
+
+    @CalledByNative
     void getAllLogins(@JobId int jobId, String syncingAccount) {
         mBackend.getAllLogins(getAccount(syncingAccount), passwords -> {
             if (mNativeBackendBridge == 0) return;
@@ -133,5 +155,8 @@
         void onLoginChanged(long nativePasswordStoreAndroidBackendBridgeImpl, @JobId int jobId);
         void onError(long nativePasswordStoreAndroidBackendBridgeImpl, @JobId int jobId,
                 int errorType, int apiErrorCode);
+        void onSubscribed(long nativePasswordStoreAndroidBackendBridgeImpl, @JobId int jobId);
+        void onSubscribeFailed(long nativePasswordStoreAndroidBackendBridgeImpl, @JobId int jobId,
+                int errorType, int apiErrorCode);
     }
 }
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSettingsAccessor.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSettingsAccessor.java
new file mode 100644
index 0000000..e8022dc1
--- /dev/null
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSettingsAccessor.java
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.password_manager.tests.utils;
+
+import android.accounts.Account;
+
+import com.google.common.base.Optional;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.password_manager.PasswordSettingsAccessor;
+
+/**
+ * Fake {@link PasswordSettingsAccessor} to be used in integration tests.
+ */
+public class FakePasswordSettingsAccessor implements PasswordSettingsAccessor {
+    @Override
+    public void getOfferToSavePasswords(Optional<Account> account,
+            Callback<Optional<Boolean>> successCallback, Callback<Exception> failureCallback) {
+        // TODO(crbug/1336641): Implement the method of the fake accessor.
+    }
+
+    @Override
+    public void setOfferToSavePasswords(boolean offerToSavePasswordsEnabled,
+            Optional<Account> account, Callback<Void> successCallback,
+            Callback<Exception> failureCallback) {
+        // TODO(crbug/1336641): Implement the method of the fake accessor.
+    }
+
+    @Override
+    public void getAutoSignIn(Optional<Account> account,
+            Callback<Optional<Boolean>> successCallback, Callback<Exception> failureCallback) {
+        // TODO(crbug/1336641): Implement the method of the fake accessor.
+    }
+
+    @Override
+    public void setAutoSignIn(boolean autoSignInEnabled, Optional<Account> account,
+            Callback<Void> successCallback, Callback<Exception> failureCallback) {
+        // TODO(crbug/1336641): Implement the method of the fake accessor.
+    }
+}
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSettingsAccessorFactoryImpl.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSettingsAccessorFactoryImpl.java
new file mode 100644
index 0000000..2f06ba2
--- /dev/null
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/tests/utils/FakePasswordSettingsAccessorFactoryImpl.java
@@ -0,0 +1,27 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.password_manager.tests.utils;
+
+import org.chromium.chrome.browser.password_manager.PasswordSettingsAccessor;
+import org.chromium.chrome.browser.password_manager.PasswordSettingsAccessorFactory;
+
+/**
+ * The factory for creating fake {@link PasswordSettingsAccessor} to be used in integration
+ * tests.
+ */
+public class FakePasswordSettingsAccessorFactoryImpl extends PasswordSettingsAccessorFactory {
+    /**
+     * Returns the fake implementation of {@link PasswordSettingsAccessor} used for tests.
+     */
+    @Override
+    public PasswordSettingsAccessor createAccessor() {
+        return new FakePasswordSettingsAccessor();
+    }
+
+    @Override
+    public boolean canCreateAccessor() {
+        return true;
+    }
+}
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/FakePasswordSettingsAccessorFactoryImplTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/FakePasswordSettingsAccessorFactoryImplTest.java
new file mode 100644
index 0000000..bf1e909
--- /dev/null
+++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/FakePasswordSettingsAccessorFactoryImplTest.java
@@ -0,0 +1,40 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.password_manager;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Batch;
+import org.chromium.chrome.browser.password_manager.tests.utils.FakePasswordSettingsAccessor;
+import org.chromium.chrome.browser.password_manager.tests.utils.FakePasswordSettingsAccessorFactoryImpl;
+
+/** Tests for the methods of {@link FakePasswordSettingsAccessorFactoryImpl}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+@Batch(Batch.PER_CLASS)
+public class FakePasswordSettingsAccessorFactoryImplTest {
+    FakePasswordSettingsAccessorFactoryImpl mFakePasswordSettingsAccessorFactoryImpl;
+    @Before
+    public void setUp() {
+        mFakePasswordSettingsAccessorFactoryImpl = new FakePasswordSettingsAccessorFactoryImpl();
+    }
+
+    @Test
+    public void testCreateAccessor() {
+        assertTrue(mFakePasswordSettingsAccessorFactoryImpl.createAccessor()
+                           instanceof FakePasswordSettingsAccessor);
+    }
+
+    @Test
+    public void testCanCreateAccessor() {
+        assertTrue(mFakePasswordSettingsAccessorFactoryImpl.canCreateAccessor());
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java
index 1a5576f9..ec03908 100644
--- a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java
+++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
@@ -299,6 +300,49 @@
 
     @Test
     @EnableFeatures(ChromeFeatureList.UNIFIED_PASSWORD_MANAGER_ANDROID)
+    public void testResetsUnenrollment() {
+        SyncService.overrideForTests(mSyncServiceMock);
+        when(mSyncServiceMock.getChosenDataTypes())
+                .thenReturn(CollectionUtil.newHashSet(ModelType.PASSWORDS));
+        when(mSyncServiceMock.isSyncFeatureEnabled()).thenReturn(true);
+        when(mSyncServiceMock.isEngineInitialized()).thenReturn(true);
+        when(mSyncServiceMock.hasSyncConsent()).thenReturn(true);
+
+        when(mPrefService.getBoolean(Pref.UNENROLLED_FROM_GOOGLE_MOBILE_SERVICES_DUE_TO_ERRORS))
+                .thenReturn(true);
+        PasswordManagerHelper.resetUpmUnenrollment();
+
+        verify(mPrefService)
+                .setBoolean(
+                        eq(Pref.UNENROLLED_FROM_GOOGLE_MOBILE_SERVICES_DUE_TO_ERRORS), eq(false));
+
+        SyncService.resetForTests();
+    }
+
+    @Test
+    @EnableFeatures(ChromeFeatureList.UNIFIED_PASSWORD_MANAGER_ANDROID)
+    public void testDoesntResetUnenrollmentIfUnnecessary() {
+        SyncService.overrideForTests(mSyncServiceMock);
+        when(mSyncServiceMock.getChosenDataTypes())
+                .thenReturn(CollectionUtil.newHashSet(ModelType.PASSWORDS));
+        when(mSyncServiceMock.isSyncFeatureEnabled()).thenReturn(true);
+        when(mSyncServiceMock.isEngineInitialized()).thenReturn(true);
+        when(mSyncServiceMock.hasSyncConsent()).thenReturn(true);
+
+        when(mPrefService.getBoolean(Pref.UNENROLLED_FROM_GOOGLE_MOBILE_SERVICES_DUE_TO_ERRORS))
+                .thenReturn(false);
+        PasswordManagerHelper.resetUpmUnenrollment();
+
+        // If the pref isn't set, don't touch the pref!
+        verify(mPrefService, never())
+                .setBoolean(eq(Pref.UNENROLLED_FROM_GOOGLE_MOBILE_SERVICES_DUE_TO_ERRORS),
+                        anyBoolean());
+
+        SyncService.resetForTests();
+    }
+
+    @Test
+    @EnableFeatures(ChromeFeatureList.UNIFIED_PASSWORD_MANAGER_ANDROID)
     public void testLaunchesCredentialManagerSync() {
         chooseToSyncPasswordsWithoutCustomPassphrase();
 
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
index dc1a55ae..73fbede 100644
--- a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
+++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
@@ -42,6 +42,8 @@
 import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.sync.protocol.PasswordSpecificsData;
 
+import java.util.Date;
+
 /**
  * Tests that bridge calls as invoked by the password store reach the backend and return correctly.
  */
@@ -159,6 +161,42 @@
     }
 
     @Test
+    public void testSubscribeCallsApiWithMinimalListCallAndForwardsErrorStateToBridge() {
+        final int kTestTaskId = 42069;
+
+        // Ensure the backend is called with a valid failure callback.
+        mBackendBridge.subscribe(kTestTaskId, null);
+        ArgumentCaptor<Callback<Exception>> failureCallback =
+                ArgumentCaptor.forClass(Callback.class);
+        verify(mBackendMock)
+                .getAllLoginsBetween(eq(new Date(1)), eq(new Date(2)), eq(Optional.absent()), any(),
+                        failureCallback.capture());
+        assertNotNull(failureCallback.getValue());
+
+        Exception kExpectedException = new ApiException(new Status(CommonStatusCodes.ERROR, ""));
+        failureCallback.getValue().onResult(kExpectedException);
+        verify(mBridgeJniMock)
+                .onSubscribeFailed(sDummyNativePointer, kTestTaskId,
+                        AndroidBackendErrorType.EXTERNAL_ERROR, CommonStatusCodes.ERROR);
+    }
+
+    @Test
+    public void testSubscribeCallsApiWithMinimalListCallAndForwardsSuccessToBridge() {
+        final int kTestTaskId = 42069;
+
+        // Ensure the backend is called with a valid success callback.
+        mBackendBridge.subscribe(kTestTaskId, null);
+        ArgumentCaptor<Callback<byte[]>> successCallback = ArgumentCaptor.forClass(Callback.class);
+        verify(mBackendMock)
+                .getAllLoginsBetween(eq(new Date(1)), eq(new Date(2)), eq(Optional.absent()),
+                        successCallback.capture(), any());
+        assertNotNull(successCallback.getValue());
+
+        successCallback.getValue().onResult(sTestLogins.build().toByteArray());
+        verify(mBridgeJniMock).onSubscribed(sDummyNativePointer, kTestTaskId);
+    }
+
+    @Test
     public void testDoesNotStartResolutionOnAPIFailure() throws PendingIntent.CanceledException {
         final int kTestTaskId = 42069;
 
diff --git a/chrome/browser/password_manager/android/password_accessory_controller_impl.cc b/chrome/browser/password_manager/android/password_accessory_controller_impl.cc
index b09897b..2ecda192 100644
--- a/chrome/browser/password_manager/android/password_accessory_controller_impl.cc
+++ b/chrome/browser/password_manager/android/password_accessory_controller_impl.cc
@@ -219,7 +219,8 @@
   authenticator_->Authenticate(
       device_reauth::BiometricAuthRequester::kFallbackSheet,
       base::BindOnce(&PasswordAccessoryControllerImpl::OnReauthCompleted,
-                     base::Unretained(this), selection));
+                     base::Unretained(this), selection),
+      /*use_last_valid_auth=*/true);
 }
 
 // static
diff --git a/chrome/browser/password_manager/android/password_accessory_controller_impl_unittest.cc b/chrome/browser/password_manager/android/password_accessory_controller_impl_unittest.cc
index 2e9a16b4..47b42e0 100644
--- a/chrome/browser/password_manager/android/password_accessory_controller_impl_unittest.cc
+++ b/chrome/browser/password_manager/android/password_accessory_controller_impl_unittest.cc
@@ -949,7 +949,8 @@
   EXPECT_CALL(*mock_authenticator_.get(), CanAuthenticate)
       .WillOnce(Return(true));
   EXPECT_CALL(*mock_authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kFallbackSheet, _))
+              Authenticate(BiometricAuthRequester::kFallbackSheet, _,
+                           /*use_last_valid_auth=*/true))
       .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/true));
   EXPECT_CALL(*driver(),
               FillIntoFocusedField(selected_field.is_obfuscated(),
@@ -981,7 +982,8 @@
   EXPECT_CALL(*mock_authenticator_.get(), CanAuthenticate)
       .WillOnce(Return(true));
   EXPECT_CALL(*mock_authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kFallbackSheet, _))
+              Authenticate(BiometricAuthRequester::kFallbackSheet, _,
+                           /*use_last_valid_auth=*/true))
       .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/false));
   EXPECT_CALL(*driver(),
               FillIntoFocusedField(selected_field.is_obfuscated(),
@@ -1014,7 +1016,8 @@
   EXPECT_CALL(*mock_authenticator_.get(), CanAuthenticate)
       .WillOnce(Return(true));
   EXPECT_CALL(*mock_authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kFallbackSheet, _));
+              Authenticate(BiometricAuthRequester::kFallbackSheet, _,
+                           /*use_last_valid_auth=*/true));
 
   EXPECT_CALL(*driver(),
               FillIntoFocusedField(selected_field.is_obfuscated(),
diff --git a/chrome/browser/password_manager/android/password_store_android_backend.cc b/chrome/browser/password_manager/android/password_store_android_backend.cc
index 9a0712e..1cbb5164f 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend.cc
+++ b/chrome/browser/password_manager/android/password_store_android_backend.cc
@@ -369,8 +369,11 @@
   lifecycle_helper_->RegisterObserver(base::BindRepeating(
       &PasswordStoreAndroidBackend::OnForegroundSessionStart,
       base::Unretained(this)));
-  // TODO(https://crbug.com/1229650): Create subscription before completion.
-  std::move(completion).Run(/*success=*/true);
+
+  main_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&PasswordStoreAndroidBackend::Subscribe,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(completion)));
 }
 
 void PasswordStoreAndroidBackend::Shutdown(
@@ -679,7 +682,6 @@
     // If the user is experiencing an error unresolvable by Chrome or by the
     // user, unenroll the user from the UPM experience.
     int api_error = error.api_error_code.value();
-
     if (IsUnrecoverableError(
             static_cast<AndroidBackendAPIErrorCode>(api_error))) {
       if (!prefs_->GetBoolean(
@@ -688,6 +690,8 @@
             "PasswordManager.UnenrolledFromUPMDueToErrors", true);
         prefs_->SetBoolean(
             prefs::kUnenrolledFromGoogleMobileServicesDueToErrors, true);
+        LOG(ERROR) << "Unenrolled from UPM due to error with code: "
+                   << api_error;
       }
 
       // Reset migration prefs so when the user can join the experiment again,
@@ -716,6 +720,41 @@
   }
 }
 
+void PasswordStoreAndroidBackend::OnSubscribed(
+    PasswordStoreAndroidBackendBridge::JobId job_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  absl::optional<JobReturnHandler> reply = GetAndEraseJob(job_id);
+  if (!reply.has_value())
+    return;  // Task was cleaned up after returning from backgrounding.
+
+  reply->RecordMetrics(absl::nullopt);
+  // Without error, report a success back. The result can be omitted.
+  main_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(*reply).Get<LoginsOrErrorReply>(),
+                                LoginsResult()));
+}
+
+void PasswordStoreAndroidBackend::OnSubscribeFailed(
+    PasswordStoreAndroidBackendBridge::JobId job_id,
+    AndroidBackendError error) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_);
+  absl::optional<JobReturnHandler> reply = GetAndEraseJob(job_id);
+  if (!reply.has_value())
+    return;  // Task was cleaned up after returning from backgrounding.
+
+  if (error.api_error_code.has_value() && sync_service_) {
+    DCHECK_EQ(AndroidBackendErrorType::kExternalError, error.type);
+    RecordApiErrorInCombinationWithSyncStatus(error.api_error_code.value(),
+                                              sync_service_->GetAuthError());
+  }
+  PasswordStoreBackendError reported_error =
+      BackendErrorFromAndroidBackendError(error);
+  reply->RecordMetrics(std::move(error));
+  main_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(*reply).Get<LoginsOrErrorReply>(),
+                                reported_error));
+}
+
 template <typename Callback>
 void PasswordStoreAndroidBackend::QueueNewJob(JobId job_id,
                                               Callback callback,
@@ -739,6 +778,20 @@
   return reply;
 }
 
+void PasswordStoreAndroidBackend::Subscribe(
+    base::OnceCallback<void(bool)> completion) {
+  // TODO(https://crbug.com/1229650): Once subscribe API exists, ensure this
+  // call repeats for sync changes.
+  JobId job_id =
+      bridge_->Subscribe(GetAccount(GetSyncingAccount(sync_service_)));
+  auto is_success = [](LoginsResultOrError logins_or_error) -> bool {
+    // Fake subscribe are successful if they have any result and no error.
+    return absl::holds_alternative<LoginsResult>(logins_or_error);
+  };
+  QueueNewJob(job_id, base::BindOnce(is_success).Then(std::move(completion)),
+              MetricInfix("InitialListAsync"));
+}
+
 void PasswordStoreAndroidBackend::GetLoginsAsync(const PasswordFormDigest& form,
                                                  bool include_psl,
                                                  LoginsOrErrorReply callback) {
diff --git a/chrome/browser/password_manager/android/password_store_android_backend.h b/chrome/browser/password_manager/android/password_store_android_backend.h
index 7fe1020..11ff5b9 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend.h
+++ b/chrome/browser/password_manager/android/password_store_android_backend.h
@@ -156,11 +156,17 @@
                        PasswordChanges changes) override;
   void OnError(PasswordStoreAndroidBackendBridge::JobId job_id,
                AndroidBackendError error) override;
+  void OnSubscribed(PasswordStoreAndroidBackendBridge::JobId job_id) override;
+  void OnSubscribeFailed(PasswordStoreAndroidBackendBridge::JobId job_id,
+                         AndroidBackendError error) override;
 
   template <typename Callback>
   void QueueNewJob(JobId job_id, Callback callback, MetricInfix metric_infix);
   absl::optional<JobReturnHandler> GetAndEraseJob(JobId job_id);
 
+  // Initial, early ping to GMS. Calls completion with true iff successful.
+  void Subscribe(base::OnceCallback<void(bool)> completion);
+
   // Gets logins matching |form|.
   void GetLoginsAsync(const PasswordFormDigest& form,
                       bool include_psl,
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge.h b/chrome/browser/password_manager/android/password_store_android_backend_bridge.h
index 97e8ef4a1..98aa943f 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend_bridge.h
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge.h
@@ -53,6 +53,13 @@
     // Asynchronous response called with the `job_id` which was passed to the
     // corresponding call to `PasswordStoreAndroidBackendBridge`.
     virtual void OnError(JobId job_id, AndroidBackendError error) = 0;
+
+    // Asynchronous success called with the `job_id` which was passed to a
+    // subscribe call (or fake thereof) to `PasswordStoreAndroidBackendBridge`.
+    virtual void OnSubscribed(JobId job_id) = 0;
+    // Asynchronous failure called with the `job_id` which was passed to a
+    // subscribe call (or fake thereof) to `PasswordStoreAndroidBackendBridge`.
+    virtual void OnSubscribeFailed(JobId job_id, AndroidBackendError error) = 0;
   };
 
   virtual ~PasswordStoreAndroidBackendBridge() = default;
@@ -60,6 +67,13 @@
   // Sets the `consumer` that is notified on job completion.
   virtual void SetConsumer(base::WeakPtr<Consumer> consumer) = 0;
 
+  // Triggers an asynchronous request signaling interest in passwords. The
+  // registered `Consumer` is notified with `OnSubscribed` when the job with
+  // the returnde JobId has been completed. `syncing_account` is used to decide
+  // which storage to use. If `syncing_account` is absl::nullopt local storage
+  // will be used.
+  [[nodiscard]] virtual JobId Subscribe(Account account) = 0;
+
   // Triggers an asynchronous request to retrieve all stored passwords. The
   // registered `Consumer` is notified with `OnCompleteWithLogins` when the
   // job with the returned JobId succeeds. `syncing_account` is used to decide
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc
index dcf0eff..df63c96 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc
@@ -54,6 +54,16 @@
           .value());
 }
 
+password_manager::AndroidBackendError wrapInBackendError(jint error_type,
+                                                         jint api_error_code) {
+  password_manager::AndroidBackendError error{
+      static_cast<password_manager::AndroidBackendErrorType>(error_type)};
+  if (error.type == password_manager::AndroidBackendErrorType::kExternalError) {
+    error.api_error_code = static_cast<int>(api_error_code);
+  }
+  return error;
+}
+
 }  // namespace
 
 namespace password_manager {
@@ -100,17 +110,46 @@
   DCHECK(consumer_);
   // Posting the tasks to the same sequence prevents that synchronous responses
   // try to finish tasks before their registration was completed.
-  password_manager::AndroidBackendError error{
-      static_cast<password_manager::AndroidBackendErrorType>(error_type)};
-
-  if (error.type == password_manager::AndroidBackendErrorType::kExternalError) {
-    error.api_error_code = static_cast<int>(api_error_code);
-  }
-
   base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(&PasswordStoreAndroidBackendBridge::Consumer::OnError,
-                     consumer_, JobId(job_id), std::move(error)));
+                     consumer_, JobId(job_id),
+                     wrapInBackendError(error_type, api_error_code)));
+}
+
+void PasswordStoreAndroidBackendBridgeImpl::OnSubscribed(JNIEnv* env,
+                                                         jint job_id) {
+  DCHECK(consumer_);
+  // Posting the tasks to the same sequence prevents that synchronous responses
+  // try to finish tasks before their registration was completed.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&PasswordStoreAndroidBackendBridge::Consumer::OnSubscribed,
+                     consumer_, JobId(job_id)));
+}
+
+void PasswordStoreAndroidBackendBridgeImpl::OnSubscribeFailed(
+    JNIEnv* env,
+    jint job_id,
+    jint error_type,
+    jint api_error_code) {
+  DCHECK(consumer_);
+  // Posting the tasks to the same sequence prevents that synchronous responses
+  // try to finish tasks before their registration was completed.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &PasswordStoreAndroidBackendBridge::Consumer::OnSubscribeFailed,
+          consumer_, JobId(job_id),
+          wrapInBackendError(error_type, api_error_code)));
+}
+
+JobId PasswordStoreAndroidBackendBridgeImpl::Subscribe(Account account) {
+  JobId job_id = GetNextJobId();
+  Java_PasswordStoreAndroidBackendBridgeImpl_subscribe(
+      base::android::AttachCurrentThread(), java_object_, job_id.value(),
+      GetJavaStringFromAccount(std::move(account)));
+  return job_id;
 }
 
 JobId PasswordStoreAndroidBackendBridgeImpl::GetAllLogins(Account account) {
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h
index fc507b8..2036b76 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h
+++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h
@@ -42,9 +42,19 @@
   // an exception.
   void OnError(JNIEnv* env, jint job_id, jint error_type, jint api_error_code);
 
+  // Called via JNI. Called when the api subscribe call with `job_id` succeeded.
+  void OnSubscribed(JNIEnv* env, jint job_id);
+
+  // Called via JNI. Called when the api subscribe call with `job_id` failed.
+  void OnSubscribeFailed(JNIEnv* env,
+                         jint job_id,
+                         jint error_type,
+                         jint api_error_code);
+
  private:
   // Implements PasswordStoreAndroidBackendBridge interface.
   void SetConsumer(base::WeakPtr<Consumer> consumer) override;
+  [[nodiscard]] JobId Subscribe(Account account) override;
   [[nodiscard]] JobId GetAllLogins(Account account) override;
   [[nodiscard]] JobId GetAutofillableLogins(Account account) override;
   [[nodiscard]] JobId GetLoginsForSignonRealm(const std::string& signon_realm,
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc b/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc
index fc62687..f1519bd 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc
+++ b/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc
@@ -34,6 +34,7 @@
 namespace password_manager {
 namespace {
 
+using base::Bucket;
 using testing::_;
 using testing::ElementsAre;
 using testing::Eq;
@@ -54,6 +55,7 @@
     "PasswordManager.UnenrolledFromUPMDueToErrors";
 constexpr char kUPMActiveHistogram[] =
     "PasswordManager.UnifiedPasswordManager.ActiveStatus";
+constexpr JobId kJobId{1337};
 
 MATCHER_P(ExpectError, expectation, "") {
   return absl::holds_alternative<PasswordStoreBackendError>(arg) &&
@@ -119,6 +121,7 @@
     : public PasswordStoreAndroidBackendBridge {
  public:
   MOCK_METHOD(void, SetConsumer, (base::WeakPtr<Consumer>), (override));
+  MOCK_METHOD(JobId, Subscribe, (Account), (override));
   MOCK_METHOD(JobId, GetAllLogins, (Account), (override));
   MOCK_METHOD(JobId, GetAutofillableLogins, (Account), (override));
   MOCK_METHOD(JobId,
@@ -147,6 +150,7 @@
         base::PassKey<class PasswordStoreAndroidBackendTest>(),
         CreateMockBridge(), CreateFakeLifecycleHelper(),
         CreatePasswordSyncControllerDelegate(), &prefs_);
+    ON_CALL(*bridge(), Subscribe(_)).WillByDefault(Return(kJobId));
   }
 
   ~PasswordStoreAndroidBackendTest() override {
@@ -192,7 +196,7 @@
  private:
   std::unique_ptr<PasswordStoreAndroidBackendBridge> CreateMockBridge() {
     auto unique_bridge =
-        std::make_unique<StrictMock<MockPasswordStoreAndroidBackendBridge>>();
+        std::make_unique<NiceMock<MockPasswordStoreAndroidBackendBridge>>();
     bridge_ = unique_bridge.get();
     EXPECT_CALL(*bridge_, SetConsumer);
     return unique_bridge;
@@ -214,7 +218,7 @@
   }
 
   std::unique_ptr<PasswordStoreAndroidBackend> backend_;
-  raw_ptr<StrictMock<MockPasswordStoreAndroidBackendBridge>> bridge_;
+  raw_ptr<NiceMock<MockPasswordStoreAndroidBackendBridge>> bridge_;
   raw_ptr<FakePasswordManagerLifecycleHelper> lifecycle_helper_;
   raw_ptr<PasswordSyncControllerDelegateAndroid> sync_controller_delegate_;
   syncer::TestSyncService sync_service_;
@@ -223,11 +227,61 @@
       password_manager::features::kUnifiedPasswordManagerAndroid};
 };
 
-TEST_F(PasswordStoreAndroidBackendTest, CallsCompletionCallbackAfterInit) {
-  base::MockCallback<base::OnceCallback<void(bool)>> completion_callback;
-  EXPECT_CALL(completion_callback, Run(true));
+TEST_F(PasswordStoreAndroidBackendTest, CallsBridgeToSubscribe_Success) {
+  base::HistogramTester histogram_tester;
+  EnableSyncForTestAccount();
+  backend().OnSyncServiceInitialized(sync_service());
+
+  base::MockCallback<base::OnceCallback<void(bool)>> mock_reply;
+  EXPECT_CALL(*bridge(), Subscribe(ExpectSyncingAccount(kTestAccount)))
+      .WillOnce(Return(kJobId));
   backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(),
-                        base::RepeatingClosure(), completion_callback.Get());
+                        base::RepeatingClosure(), mock_reply.Get());
+  RunUntilIdle();  // Wait for the call to be queued.
+
+  EXPECT_CALL(mock_reply, Run(true));
+  consumer().OnSubscribed(kJobId);
+  RunUntilIdle();
+
+  histogram_tester.ExpectBucketCount(
+      "PasswordManager.PasswordStoreAndroidBackend.InitialListAsync.Success",
+      true, 1);
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "PasswordManager.PasswordStoreAndroidBackend.InitialListAsync"),
+      ElementsAre(Bucket(/* Requested */ 0, 1), Bucket(/* Completed */ 2, 1)));
+}
+
+TEST_F(PasswordStoreAndroidBackendTest, CallsBridgeToSubscribe_Error) {
+  base::HistogramTester histogram_tester;
+  EnableSyncForTestAccount();
+  backend().OnSyncServiceInitialized(sync_service());
+
+  base::MockCallback<base::OnceCallback<void(bool)>> mock_reply;
+  EXPECT_CALL(*bridge(), Subscribe(ExpectSyncingAccount(kTestAccount)))
+      .WillOnce(Return(kJobId));
+  backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(),
+                        base::RepeatingClosure(), mock_reply.Get());
+  RunUntilIdle();  // Wait for the call to be queued.
+
+  EXPECT_CALL(mock_reply, Run(false));
+  consumer().OnSubscribeFailed(
+      kJobId,
+      AndroidBackendError(AndroidBackendErrorType::kSyncServiceUnavailable));
+  RunUntilIdle();
+
+  histogram_tester.ExpectBucketCount(
+      "PasswordManager.PasswordStoreAndroidBackend.InitialListAsync.Success",
+      false, 1);
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "PasswordManager.PasswordStoreAndroidBackend.InitialListAsync."
+          "ErrorCode"),
+      ElementsAre(Bucket(AndroidBackendErrorType::kSyncServiceUnavailable, 1)));
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "PasswordManager.PasswordStoreAndroidBackend.InitialListAsync"),
+      ElementsAre(Bucket(/* Requested */ 0, 1), Bucket(/* Completed */ 2, 1)));
 }
 
 TEST_F(PasswordStoreAndroidBackendTest, CallsBridgeForLogins) {
@@ -236,7 +290,6 @@
   EnableSyncForTestAccount();
   backend().OnSyncServiceInitialized(sync_service());
 
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAllLogins(ExpectSyncingAccount(kTestAccount)))
       .WillOnce(Return(kJobId));
@@ -363,7 +416,6 @@
 TEST_F(PasswordStoreAndroidBackendTest, CallsBridgeForAutofillableLogins) {
   backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(),
                         base::RepeatingClosure(), base::DoNothing());
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAutofillableLogins).WillOnce(Return(kJobId));
   backend().GetAutofillableLoginsAsync(mock_reply.Get());
@@ -378,7 +430,6 @@
 TEST_F(PasswordStoreAndroidBackendTest, CallsBridgeForLoginsForAccount) {
   backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(),
                         base::RepeatingClosure(), base::DoNothing());
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
   absl::optional<std::string> account = "mytestemail@gmail.com";
@@ -395,7 +446,6 @@
   DisableSyncFeature();
   backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(),
                         base::RepeatingClosure(), base::DoNothing());
-  const JobId kJobId{13388};
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
 
   PasswordForm form =
@@ -529,7 +579,6 @@
   EnableSyncForTestAccount();
   backend().OnSyncServiceInitialized(sync_service());
 
-  const JobId kJobId{13388};
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
 
   PasswordForm form =
@@ -551,7 +600,6 @@
   DisableSyncFeature();
   backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(),
                         base::RepeatingClosure(), base::DoNothing());
-  const JobId kJobId{13388};
   base::MockCallback<PasswordChangesOrErrorReply> mock_reply;
 
   PasswordForm form =
@@ -577,7 +625,6 @@
                         base::RepeatingClosure(), base::DoNothing());
   backend().OnSyncServiceInitialized(sync_service());
 
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
@@ -617,7 +664,6 @@
                         base::RepeatingClosure(), base::DoNothing());
   backend().OnSyncServiceInitialized(sync_service());
 
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
@@ -656,7 +702,6 @@
                         base::RepeatingClosure(), base::DoNothing());
   backend().OnSyncServiceInitialized(sync_service());
 
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
@@ -700,7 +745,6 @@
   ASSERT_FALSE(sync_service()->GetAuthError().IsTransientError());
   ASSERT_FALSE(sync_service()->GetAuthError().IsPersistentError());
 
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
@@ -733,7 +777,6 @@
   ASSERT_TRUE(transient_error.IsTransientError());
   SetSyncAuthError(transient_error);
 
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
@@ -766,7 +809,6 @@
   ASSERT_TRUE(persistent_error.IsPersistentError());
   SetSyncAuthError(persistent_error);
 
-  const JobId kJobId{1337};
   base::MockCallback<LoginsOrErrorReply> mock_reply;
   EXPECT_CALL(*bridge(), GetAllLogins).WillOnce(Return(kJobId));
   backend().GetAllLoginsAsync(mock_reply.Get());
diff --git a/chrome/browser/policy/status_provider/device_active_directory_policy_status_provider.cc b/chrome/browser/policy/status_provider/device_active_directory_policy_status_provider.cc
index 3d5aac87..7b67901 100644
--- a/chrome/browser/policy/status_provider/device_active_directory_policy_status_provider.cc
+++ b/chrome/browser/policy/status_provider/device_active_directory_policy_status_provider.cc
@@ -14,8 +14,8 @@
     : UserActiveDirectoryPolicyStatusProvider(policy_manager, nullptr),
       enterprise_domain_manager_(enterprise_domain_manager) {}
 
-void DeviceActiveDirectoryPolicyStatusProvider::GetStatus(
-    base::DictionaryValue* dict) {
-  UserActiveDirectoryPolicyStatusProvider::GetStatus(dict);
-  dict->SetStringKey("enterpriseDomainManager", enterprise_domain_manager_);
+base::Value::Dict DeviceActiveDirectoryPolicyStatusProvider::GetStatus() {
+  base::Value::Dict dict = UserActiveDirectoryPolicyStatusProvider::GetStatus();
+  dict.Set("enterpriseDomainManager", enterprise_domain_manager_);
+  return dict;
 }
diff --git a/chrome/browser/policy/status_provider/device_active_directory_policy_status_provider.h b/chrome/browser/policy/status_provider/device_active_directory_policy_status_provider.h
index ce59bca1..ba1b712 100644
--- a/chrome/browser/policy/status_provider/device_active_directory_policy_status_provider.h
+++ b/chrome/browser/policy/status_provider/device_active_directory_policy_status_provider.h
@@ -9,10 +9,6 @@
 
 #include "chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.h"
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 namespace policy {
 class ActiveDirectoryPolicyManager;
 }  // namespace policy
@@ -33,7 +29,7 @@
   ~DeviceActiveDirectoryPolicyStatusProvider() override = default;
 
   // PolicyStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
  private:
   std::string enterprise_domain_manager_;
diff --git a/chrome/browser/policy/status_provider/device_cloud_policy_status_provider_chromeos.cc b/chrome/browser/policy/status_provider/device_cloud_policy_status_provider_chromeos.cc
index 5a347e1b..5d22794 100644
--- a/chrome/browser/policy/status_provider/device_cloud_policy_status_provider_chromeos.cc
+++ b/chrome/browser/policy/status_provider/device_cloud_policy_status_provider_chromeos.cc
@@ -21,9 +21,10 @@
 DeviceCloudPolicyStatusProviderChromeOS::
     ~DeviceCloudPolicyStatusProviderChromeOS() = default;
 
-void DeviceCloudPolicyStatusProviderChromeOS::GetStatus(
-    base::DictionaryValue* dict) {
-  policy::PolicyStatusProvider::GetStatusFromCore(core_, dict);
-  dict->SetStringKey("enterpriseDomainManager", enterprise_domain_manager_);
-  GetOffHoursStatus(dict);
+base::Value::Dict DeviceCloudPolicyStatusProviderChromeOS::GetStatus() {
+  base::Value::Dict dict =
+      policy::PolicyStatusProvider::GetStatusFromCore(core_);
+  dict.Set("enterpriseDomainManager", enterprise_domain_manager_);
+  GetOffHoursStatus(&dict);
+  return dict;
 }
diff --git a/chrome/browser/policy/status_provider/device_cloud_policy_status_provider_chromeos.h b/chrome/browser/policy/status_provider/device_cloud_policy_status_provider_chromeos.h
index 339dd09..87204b9d 100644
--- a/chrome/browser/policy/status_provider/device_cloud_policy_status_provider_chromeos.h
+++ b/chrome/browser/policy/status_provider/device_cloud_policy_status_provider_chromeos.h
@@ -7,10 +7,6 @@
 
 #include "chrome/browser/policy/status_provider/cloud_policy_core_status_provider.h"
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 namespace policy {
 class BrowserPolicyConnectorAsh;
 }  // namespace policy
@@ -30,7 +26,7 @@
   ~DeviceCloudPolicyStatusProviderChromeOS() override;
 
   // CloudPolicyCoreStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
  private:
   std::string enterprise_domain_manager_;
diff --git a/chrome/browser/policy/status_provider/device_local_account_policy_status_provider.cc b/chrome/browser/policy/status_provider/device_local_account_policy_status_provider.cc
index 88ced68..5f032073 100644
--- a/chrome/browser/policy/status_provider/device_local_account_policy_status_provider.cc
+++ b/chrome/browser/policy/status_provider/device_local_account_policy_status_provider.cc
@@ -21,22 +21,22 @@
   service_->RemoveObserver(this);
 }
 
-void DeviceLocalAccountPolicyStatusProvider::GetStatus(
-    base::DictionaryValue* dict) {
+base::Value::Dict DeviceLocalAccountPolicyStatusProvider::GetStatus() {
   const policy::DeviceLocalAccountPolicyBroker* broker =
       service_->GetBrokerForUser(user_id_);
+  base::Value::Dict dict;
   if (broker) {
-    policy::PolicyStatusProvider::GetStatusFromCore(broker->core(), dict);
+    dict = policy::PolicyStatusProvider::GetStatusFromCore(broker->core());
   } else {
-    dict->SetBoolKey("error", true);
-    dict->SetStringKey("status",
-                       policy::FormatStoreStatus(
+    dict.Set("error", true);
+    dict.Set("status", policy::FormatStoreStatus(
                            policy::CloudPolicyStore::STATUS_BAD_STATE,
                            policy::CloudPolicyValidatorBase::VALIDATION_OK));
-    dict->SetStringKey("username", std::string());
+    dict.Set("username", std::string());
   }
-  ExtractDomainFromUsername(dict);
-  dict->SetBoolKey("publicAccount", true);
+  ExtractDomainFromUsername(&dict);
+  dict.Set("publicAccount", true);
+  return dict;
 }
 
 void DeviceLocalAccountPolicyStatusProvider::OnPolicyUpdated(
diff --git a/chrome/browser/policy/status_provider/device_local_account_policy_status_provider.h b/chrome/browser/policy/status_provider/device_local_account_policy_status_provider.h
index ec4eb3c..3ad5cf2 100644
--- a/chrome/browser/policy/status_provider/device_local_account_policy_status_provider.h
+++ b/chrome/browser/policy/status_provider/device_local_account_policy_status_provider.h
@@ -10,10 +10,6 @@
 #include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
 #include "components/policy/core/browser/webui/policy_status_provider.h"
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 // A cloud policy status provider that reads policy status from the policy core
 // associated with the device-local account specified by |user_id| at
 // construction time. The indirection via user ID and
@@ -36,7 +32,7 @@
   ~DeviceLocalAccountPolicyStatusProvider() override;
 
   // PolicyStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
   // policy::DeviceLocalAccountPolicyService::Observer implementation.
   void OnPolicyUpdated(const std::string& user_id) override;
diff --git a/chrome/browser/policy/status_provider/device_policy_status_provider_lacros.cc b/chrome/browser/policy/status_provider/device_policy_status_provider_lacros.cc
index e77eeb0..01faa7e 100644
--- a/chrome/browser/policy/status_provider/device_policy_status_provider_lacros.cc
+++ b/chrome/browser/policy/status_provider/device_policy_status_provider_lacros.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/policy/status_provider/device_policy_status_provider_lacros.h"
 
 #include <utility>
+#include "base/values.h"
 
 DevicePolicyStatusProviderLacros::DevicePolicyStatusProviderLacros()
     : PolicyStatusProvider() {}
@@ -16,7 +17,6 @@
   device_policy_status_ = std::move(status);
 }
 
-void DevicePolicyStatusProviderLacros::GetStatus(base::DictionaryValue* dict) {
-  static_cast<base::Value&>(*dict) =
-      base::Value(std::move(device_policy_status_));
+base::Value::Dict DevicePolicyStatusProviderLacros::GetStatus() {
+  return device_policy_status_.Clone();
 }
diff --git a/chrome/browser/policy/status_provider/device_policy_status_provider_lacros.h b/chrome/browser/policy/status_provider/device_policy_status_provider_lacros.h
index adf9054..4f8b0c4 100644
--- a/chrome/browser/policy/status_provider/device_policy_status_provider_lacros.h
+++ b/chrome/browser/policy/status_provider/device_policy_status_provider_lacros.h
@@ -17,7 +17,7 @@
   void SetDevicePolicyStatus(base::Value::Dict status);
 
   // PolicyStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
  private:
   base::Value::Dict device_policy_status_;
diff --git a/chrome/browser/policy/status_provider/status_provider_util.cc b/chrome/browser/policy/status_provider/status_provider_util.cc
index 4622a4d..317d8833 100644
--- a/chrome/browser/policy/status_provider/status_provider_util.cc
+++ b/chrome/browser/policy/status_provider/status_provider_util.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/policy/status_provider/status_provider_util.h"
 
+#include "base/values.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/policy/off_hours/device_off_hours_controller.h"
@@ -16,13 +17,13 @@
 #include "components/enterprise/browser/controller/browser_dm_token_storage.h"
 #endif
 
-void ExtractDomainFromUsername(base::DictionaryValue* dict) {
-  const std::string* username = dict->FindStringKey("username");
+void ExtractDomainFromUsername(base::Value::Dict* dict) {
+  const std::string* username = dict->FindString("username");
   if (username && !username->empty())
-    dict->SetStringKey("domain", gaia::ExtractDomainName(*username));
+    dict->Set("domain", gaia::ExtractDomainName(*username));
 }
 
-void GetUserAffiliationStatus(base::DictionaryValue* dict, Profile* profile) {
+void GetUserAffiliationStatus(base::Value::Dict* dict, Profile* profile) {
   CHECK(profile);
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -30,7 +31,7 @@
       ash::ProfileHelper::Get()->GetUserByProfile(profile);
   if (!user)
     return;
-  dict->SetBoolKey("isAffiliated", user->IsAffiliated());
+  dict->Set("isAffiliated", user->IsAffiliated());
 #else
   // Don't show affiliation status if the browser isn't enrolled in CBCM.
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -40,28 +41,27 @@
     if (!policy::BrowserDMTokenStorage::Get()->RetrieveDMToken().is_valid())
       return;
   }
-  dict->SetBoolKey("isAffiliated",
-                   chrome::enterprise_util::IsProfileAffiliated(profile));
+  dict->Set("isAffiliated",
+            chrome::enterprise_util::IsProfileAffiliated(profile));
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-void GetOffHoursStatus(base::DictionaryValue* dict) {
+void GetOffHoursStatus(base::Value::Dict* dict) {
   policy::off_hours::DeviceOffHoursController* off_hours_controller =
       ash::DeviceSettingsService::Get()->device_off_hours_controller();
   if (off_hours_controller) {
-    dict->SetBoolKey("isOffHoursActive",
-                     off_hours_controller->is_off_hours_mode());
+    dict->Set("isOffHoursActive", off_hours_controller->is_off_hours_mode());
   }
 }
 
-void GetUserManager(base::DictionaryValue* dict, Profile* profile) {
+void GetUserManager(base::Value::Dict* dict, Profile* profile) {
   CHECK(profile);
 
   absl::optional<std::string> account_manager =
       chrome::GetAccountManagerIdentity(profile);
   if (account_manager) {
-    dict->SetStringKey("enterpriseDomainManager", *account_manager);
+    dict->Set("enterpriseDomainManager", *account_manager);
   }
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/policy/status_provider/status_provider_util.h b/chrome/browser/policy/status_provider/status_provider_util.h
index 472741d..0962e18 100644
--- a/chrome/browser/policy/status_provider/status_provider_util.h
+++ b/chrome/browser/policy/status_provider/status_provider_util.h
@@ -8,11 +8,11 @@
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
 
-void ExtractDomainFromUsername(base::DictionaryValue* dict);
+void ExtractDomainFromUsername(base::Value::Dict* dict);
 
 // Adds a new entry to |dict| with the affiliation status of the user associated
 // with |profile|. This method shouldn't be called for device scope status.
-void GetUserAffiliationStatus(base::DictionaryValue* dict, Profile* profile);
+void GetUserAffiliationStatus(base::Value::Dict* dict, Profile* profile);
 
 // MachineStatus box labels itself as `machine policies` on desktop. In the
 // domain of mobile devices such as iOS or Android we want to label this box as
@@ -21,12 +21,12 @@
 std::string GetMachineStatusLegendKey();
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-void GetOffHoursStatus(base::DictionaryValue* dict);
+void GetOffHoursStatus(base::Value::Dict* dict);
 
 // Adds a new entry to |dict| with the enterprise domain manager of the user
 // associated with |profile|. This method shouldn't be called for device scope
 // status.
-void GetUserManager(base::DictionaryValue* dict, Profile* profile);
+void GetUserManager(base::Value::Dict* dict, Profile* profile);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #endif  // CHROME_BROWSER_POLICY_STATUS_PROVIDER_STATUS_PROVIDER_UTIL_H_
diff --git a/chrome/browser/policy/status_provider/updater_status_provider.cc b/chrome/browser/policy/status_provider/updater_status_provider.cc
index f790d56..9d1f3fa 100644
--- a/chrome/browser/policy/status_provider/updater_status_provider.cc
+++ b/chrome/browser/policy/status_provider/updater_status_provider.cc
@@ -32,18 +32,19 @@
   NotifyStatusChange();
 }
 
-void UpdaterStatusProvider::GetStatus(base::DictionaryValue* dict) {
+base::Value::Dict UpdaterStatusProvider::GetStatus() {
+  base::Value::Dict dict;
   if (!domain_.empty())
-    dict->SetStringKey("domain", domain_);
+    dict.Set("domain", domain_);
   if (!updater_status_)
-    return;
+    return dict;
   if (!updater_status_->version.empty())
-    dict->SetStringKey("version", base::WideToUTF8(updater_status_->version));
+    dict.Set("version", base::WideToUTF8(updater_status_->version));
   if (!updater_status_->last_checked_time.is_null()) {
-    dict->SetStringKey(
-        "timeSinceLastRefresh",
-        GetTimeSinceLastActionString(updater_status_->last_checked_time));
+    dict.Set("timeSinceLastRefresh",
+             GetTimeSinceLastActionString(updater_status_->last_checked_time));
   }
+  return dict;
 }
 
 // static
diff --git a/chrome/browser/policy/status_provider/updater_status_provider.h b/chrome/browser/policy/status_provider/updater_status_provider.h
index 10dd94b4..ebab196 100644
--- a/chrome/browser/policy/status_provider/updater_status_provider.h
+++ b/chrome/browser/policy/status_provider/updater_status_provider.h
@@ -11,16 +11,12 @@
 
 struct GoogleUpdateState;
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 class UpdaterStatusProvider : public policy::PolicyStatusProvider {
  public:
   UpdaterStatusProvider();
   ~UpdaterStatusProvider() override;
   void SetUpdaterStatus(std::unique_ptr<GoogleUpdateState> status);
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
  private:
   static std::string FetchActiveDirectoryDomain();
diff --git a/chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.cc b/chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.cc
index 1fe2a6b..8ee2a3f9 100644
--- a/chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.cc
+++ b/chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.cc
@@ -29,8 +29,7 @@
   policy_manager_->store()->RemoveObserver(this);
 }
 
-void UserActiveDirectoryPolicyStatusProvider::GetStatus(
-    base::DictionaryValue* dict) {
+base::Value::Dict UserActiveDirectoryPolicyStatusProvider::GetStatus() {
   const enterprise_management::PolicyData* policy =
       policy_manager_->store()->policy();
   const std::string client_id = policy ? policy->device_id() : std::string();
@@ -38,13 +37,14 @@
   const std::u16string status =
       policy::FormatStoreStatus(policy_manager_->store()->status(),
                                 policy_manager_->store()->validation_status());
-  dict->SetStringKey("status", status);
-  dict->SetStringKey("username", username);
-  dict->SetStringKey("clientId", client_id);
+  base::Value::Dict dict;
+  dict.Set("status", status);
+  dict.Set("username", username);
+  dict.Set("clientId", client_id);
 
   const base::TimeDelta refresh_interval =
       policy_manager_->scheduler()->interval();
-  dict->SetStringKey(
+  dict.Set(
       "refreshInterval",
       ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION,
                              ui::TimeFormat::LENGTH_SHORT, refresh_interval));
@@ -53,22 +53,23 @@
       (policy && policy->has_timestamp())
           ? base::Time::FromJavaTime(policy->timestamp())
           : base::Time();
-  dict->SetStringKey("timeSinceLastRefresh",
-                     GetTimeSinceLastActionString(last_refresh_time));
+  dict.Set("timeSinceLastRefresh",
+           GetTimeSinceLastActionString(last_refresh_time));
 
   const base::Time last_refresh_attempt_time =
       policy_manager_->scheduler()->last_refresh_attempt();
-  dict->SetStringKey("timeSinceLastFetchAttempt",
-                     GetTimeSinceLastActionString(last_refresh_attempt_time));
+  dict.Set("timeSinceLastFetchAttempt",
+           GetTimeSinceLastActionString(last_refresh_attempt_time));
 
   // Check if profile is present. Note that profile is not present if object is
   // an instance of DeviceActiveDirectoryPolicyStatusProvider that inherits from
   // UserActiveDirectoryPolicyStatusProvider.
   // TODO(b/182585903): Extend browser test to cover Active Directory case.
   if (profile_) {
-    GetUserAffiliationStatus(dict, profile_);
-    GetUserManager(dict, profile_);
+    GetUserAffiliationStatus(&dict, profile_);
+    GetUserManager(&dict, profile_);
   }
+  return dict;
 }
 
 void UserActiveDirectoryPolicyStatusProvider::OnStoreLoaded(
diff --git a/chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.h b/chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.h
index b5cad3c..e5ea3d8 100644
--- a/chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.h
+++ b/chrome/browser/policy/status_provider/user_active_directory_policy_status_provider.h
@@ -10,10 +10,6 @@
 
 class Profile;
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 namespace policy {
 class ActiveDirectoryPolicyManager;
 }  // namespace policy
@@ -35,7 +31,7 @@
   ~UserActiveDirectoryPolicyStatusProvider() override;
 
   // PolicyStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
   // policy::CloudPolicyStore::Observer implementation.
   void OnStoreLoaded(policy::CloudPolicyStore* store) override;
diff --git a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.cc b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.cc
index ec0f91c9..f55c106 100644
--- a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.cc
+++ b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.cc
@@ -17,10 +17,12 @@
 
 UserCloudPolicyStatusProvider::~UserCloudPolicyStatusProvider() = default;
 
-void UserCloudPolicyStatusProvider::GetStatus(base::DictionaryValue* dict) {
+base::Value::Dict UserCloudPolicyStatusProvider::GetStatus() {
   if (!core_->store()->is_managed())
-    return;
-  policy::PolicyStatusProvider::GetStatusFromCore(core_, dict);
-  ExtractDomainFromUsername(dict);
-  GetUserAffiliationStatus(dict, profile_);
+    return {};
+  base::Value::Dict dict =
+      policy::PolicyStatusProvider::GetStatusFromCore(core_);
+  ExtractDomainFromUsername(&dict);
+  GetUserAffiliationStatus(&dict, profile_);
+  return dict;
 }
diff --git a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.h b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.h
index 33d9c02..f4f88dd 100644
--- a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.h
+++ b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.h
@@ -10,10 +10,6 @@
 
 class Profile;
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 namespace policy {
 class CloudPolicyCore;
 }  // namespace policy
@@ -31,7 +27,7 @@
   ~UserCloudPolicyStatusProvider() override;
 
   // CloudPolicyCoreStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
  private:
   raw_ptr<Profile> profile_;
diff --git a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.cc b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.cc
index 009ab78..f7c1f67 100644
--- a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.cc
+++ b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.cc
@@ -20,11 +20,11 @@
 UserCloudPolicyStatusProviderChromeOS::
     ~UserCloudPolicyStatusProviderChromeOS() = default;
 
-void UserCloudPolicyStatusProviderChromeOS::GetStatus(
-    base::DictionaryValue* dict) {
+base::Value::Dict UserCloudPolicyStatusProviderChromeOS::GetStatus() {
   if (!core_->store()->is_managed())
-    return;
-  UserCloudPolicyStatusProvider::GetStatus(dict);
-  GetUserAffiliationStatus(dict, profile_);
-  GetUserManager(dict, profile_);
+    return {};
+  base::Value::Dict dict = UserCloudPolicyStatusProvider::GetStatus();
+  GetUserAffiliationStatus(&dict, profile_);
+  GetUserManager(&dict, profile_);
+  return dict;
 }
diff --git a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.h b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.h
index b1fc352..b23dbc04 100644
--- a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.h
+++ b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.h
@@ -9,10 +9,6 @@
 
 class Profile;
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 namespace policy {
 class CloudPolicyCore;
 }  // namespace policy
@@ -32,7 +28,7 @@
   ~UserCloudPolicyStatusProviderChromeOS() override;
 
   // CloudPolicyCoreStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
  private:
   Profile* profile_;
diff --git a/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.cc b/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.cc
index 4593feca..0609054d 100644
--- a/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.cc
+++ b/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.cc
@@ -20,31 +20,32 @@
 
 UserPolicyStatusProviderLacros::~UserPolicyStatusProviderLacros() = default;
 
-void UserPolicyStatusProviderLacros::GetStatus(base::DictionaryValue* dict) {
+base::Value::Dict UserPolicyStatusProviderLacros::GetStatus() {
   enterprise_management::PolicyData* policy = loader_->GetPolicyData();
   if (!policy)
-    return;
-  GetStatusFromPolicyData(policy, dict);
-  ExtractDomainFromUsername(dict);
-  GetUserAffiliationStatus(dict, profile_);
+    return {};
+  base::Value::Dict dict = GetStatusFromPolicyData(policy);
+  ExtractDomainFromUsername(&dict);
+  GetUserAffiliationStatus(&dict, profile_);
 
   // Get last fetched time from policy, since we have no refresh scheduler here.
   base::Time last_refresh_time =
       policy && policy->has_timestamp()
           ? base::Time::FromJavaTime(policy->timestamp())
           : base::Time();
-  dict->SetStringKey("timeSinceLastRefresh",
-                     GetTimeSinceLastActionString(last_refresh_time));
+  dict.Set("timeSinceLastRefresh",
+           GetTimeSinceLastActionString(last_refresh_time));
 
   const base::Time last_refresh_attempt_time = loader_->last_fetch_timestamp();
-  dict->SetStringKey("timeSinceLastFetchAttempt",
-                     GetTimeSinceLastActionString(last_refresh_attempt_time));
+  dict.Set("timeSinceLastFetchAttempt",
+           GetTimeSinceLastActionString(last_refresh_attempt_time));
 
   // TODO(https://crbug.com/1243869): Pass this information from Ash through
   // Mojo. Assume no error for now.
-  dict->SetBoolKey("error", false);
-  dict->SetStringKey(
-      "status", FormatStoreStatus(
-                    policy::CloudPolicyStore::STATUS_OK,
-                    policy::CloudPolicyValidatorBase::Status::VALIDATION_OK));
+  dict.Set("error", false);
+  dict.Set("status",
+           FormatStoreStatus(
+               policy::CloudPolicyStore::STATUS_OK,
+               policy::CloudPolicyValidatorBase::Status::VALIDATION_OK));
+  return dict;
 }
diff --git a/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.h b/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.h
index d891aea..97d5e55 100644
--- a/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.h
+++ b/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.h
@@ -9,10 +9,6 @@
 
 class Profile;
 
-namespace base {
-class DictionaryValue;
-}  // namespace base
-
 namespace policy {
 class PolicyLoaderLacros;
 }  // namespace policy
@@ -31,7 +27,7 @@
   ~UserPolicyStatusProviderLacros() override;
 
   // CloudPolicyCoreStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
  private:
   raw_ptr<Profile> profile_;
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
index 4e15f41..ad8b1f5 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
@@ -338,7 +338,7 @@
       // Case 4: Prerender has started and taken the response away. No action is
       // needed.
       return;
-    case SearchPrefetchStatus::kServed:
+    case SearchPrefetchStatus::kPrefetchServedForRealNavigation:
     case SearchPrefetchStatus::kPrerenderActivated:
       NOTREACHED();
   }
@@ -424,7 +424,7 @@
 void BaseSearchPrefetchRequest::MarkPrefetchAsServed() {
   DCHECK(current_status_ == SearchPrefetchStatus::kCanBeServedAndUserClicked ||
          current_status_ == SearchPrefetchStatus::kComplete);
-  current_status_ = SearchPrefetchStatus::kServed;
+  current_status_ = SearchPrefetchStatus::kPrefetchServedForRealNavigation;
   UMA_HISTOGRAM_TIMES("Omnibox.SearchPrefetch.ClickToNavigationIntercepted",
                       base::TimeTicks::Now() - time_clicked_);
 }
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
index e290c91..4389be8 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h
@@ -44,7 +44,7 @@
   // The request was cancelled before completion. This is a terminal state.
   kRequestCancelled = 6,
   // The request was served to the navigation stack. This is a terminal state.
-  kServed = 7,
+  kPrefetchServedForRealNavigation = 7,
   // The request was served to the prerender navigation stack. It may move to
   // |kPrerenderedAndClicked| when the user navigates to the result in omnibox
   // or |kRequestCancelled| if the user closes omnibox.
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index bb976ff..4016226 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -789,7 +789,7 @@
   EXPECT_TRUE(base::Contains(inner_html, "prefetch"));
   histogram_tester.ExpectUniqueSample(
       "Omnibox.SearchPrefetch.PrefetchFinalStatus.SuggestionPrefetch",
-      SearchPrefetchStatus::kServed, 1);
+      SearchPrefetchStatus::kPrefetchServedForRealNavigation, 1);
   histogram_tester.ExpectTotalCount(
       "Omnibox.SearchPrefetch.ClickToNavigationIntercepted", 1);
   histogram_tester.ExpectTotalCount(
diff --git a/chrome/browser/resources/chromeos/assistant_optin/BUILD.gn b/chrome/browser/resources/chromeos/assistant_optin/BUILD.gn
index 8331fc3..0eedea57 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/BUILD.gn
+++ b/chrome/browser/resources/chromeos/assistant_optin/BUILD.gn
@@ -77,6 +77,8 @@
     "assistant_common_styles.html",
     "assistant_loading.html",
     "assistant_loading.js",
+    "assistant_optin.html",
+    "assistant_optin.js",
     "assistant_optin_flow.html",
     "assistant_optin_flow.js",
     "assistant_related_info.html",
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.html b/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.html
index 5671e488..45187e67 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.html
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.html
@@ -7,42 +7,15 @@
 <head>
 <meta charset="utf-8">
 
-<script src="chrome://resources/polymer/v1_0/html-imports/html-imports.min.js"></script>
-<link rel="import" href="chrome://resources/html/polymer.html">
+<link rel="stylesheet" href="chrome://resources/chromeos/colors/cros_styles.css">
+<link rel="stylesheet" href="./oobe_screen.css">
+<link rel="stylesheet" href="./oobe.css">
 
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
-
-<link rel="import" href="/components/common_styles/common_styles.html">
-<link rel="import" href="/components/behaviors/oobe_dialog_host_behavior.html">
-<link rel="import" href="/components/behaviors/oobe_i18n_behavior.html">
-
-<link rel="import" href="/assistant_optin/assistant_optin_flow.html">
-
-<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
-<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
-<link rel="stylesheet" href="../login/oobe_screen.css">
-<link rel="stylesheet" href="../login/oobe.css">
-<style include="cr-icons cr-shared-style oobe-common-styles"></style>
-
-<script src="chrome://resources/js/cr.js"></script>
-<script src="chrome://resources/js/load_time_data.js"></script>
-<script src="chrome://resources/js/assert.js"></script>
-<script src="chrome://resources/js/util.js"></script>
-<script src="strings.js"></script>
-<script src="./assistant_optin.js"></script>
+<script type="module" src="./assistant_optin/assistant_optin.js"></script>
 </head>
+
 <body>
-
-<assistant-optin-flow-element id="assistant-optin-flow-card">
-</assistant-optin-flow-element>
-
+  <assistant-optin-flow-element id="assistant-optin-flow-card">
+  </assistant-optin-flow-element>
 </body>
 </html>
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.js
index 633db31..00bcbd99 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.js
@@ -2,83 +2,87 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-HTMLImports.whenReady(() => {
-  // <include src="../login/components/behaviors/multi_step_behavior.js">
-  // Need the display manager to imitate the oobe flow.
-  // <include src="../login/components/display_manager_types.js">
-  // <include src="../login/display_manager.js">
-  // <include src="../login/components/oobe_types.js">
-  // <include src="browser_proxy.js">
+import {$} from 'chrome://resources/js/util.m.js';
 
-  cr.define('login.AssistantOptInFlowScreen', function() {
-    return {
+import './assistant_optin_flow.m.js';
+import {BrowserProxyImpl} from './browser_proxy.m.js';
 
-      /**
-       * Starts the assistant opt-in flow.
-       */
-      show() {
-        var url = new URL(document.URL);
-        $('assistant-optin-flow-card').onBeforeShow();
-        $('assistant-optin-flow-card')
-            .onShow(
-                url.searchParams.get('flow-type'),
-                url.searchParams.get('caption-bar-height'));
-      },
+class InSessionAssistantScreen {
+    /**
+     * Starts the assistant opt-in flow.
+     */
+    static show() {
+      var url = new URL(document.URL);
+      $('assistant-optin-flow-card').onBeforeShow();
+      $('assistant-optin-flow-card')
+          .onShow(
+              url.searchParams.get('flow-type'),
+              url.searchParams.get('caption-bar-height'));
+    }
 
-      /**
-       * Reloads localized strings.
-       * @param {!Object} data New dictionary with i18n values.
-       */
-      reloadContent(data) {
-        $('assistant-optin-flow-card').reloadContent(data);
-      },
+    /**
+     * Reloads localized strings.
+     * @param {!Object} data New dictionary with i18n values.
+     */
+    static reloadContent(data) {
+      $('assistant-optin-flow-card').reloadContent(data);
+    }
 
-      /**
-       * Add a setting zippy object in the corresponding screen.
-       * @param {string} type type of the setting zippy.
-       * @param {!Object} data String and url for the setting zippy.
-       */
-      addSettingZippy(type, data) {
-        $('assistant-optin-flow-card').addSettingZippy(type, data);
-      },
+    /**
+     * Add a setting zippy object in the corresponding screen.
+     * @param {string} type type of the setting zippy.
+     * @param {!Object} data String and url for the setting zippy.
+     */
+    static addSettingZippy(type, data) {
+      $('assistant-optin-flow-card').addSettingZippy(type, data);
+    }
 
-      /**
-       * Show the next screen in the flow.
-       */
-      showNextScreen() {
-        $('assistant-optin-flow-card').showNextScreen();
-      },
+    /**
+     * Show the next screen in the flow.
+     */
+    static showNextScreen() {
+      $('assistant-optin-flow-card').showNextScreen();
+    }
 
-      /**
-       * Called when the Voice match state is updated.
-       * @param {string} state the voice match state.
-       */
-      onVoiceMatchUpdate(state) {
-        $('assistant-optin-flow-card').onVoiceMatchUpdate(state);
-      },
+    /**
+     * Called when the Voice match state is updated.
+     * @param {string} state the voice match state.
+     */
+    static onVoiceMatchUpdate(state) {
+      $('assistant-optin-flow-card').onVoiceMatchUpdate(state);
+    }
 
-      /**
-       * Called to show the next settings when there are multiple unbundled
-       * activity control settings in the Value prop screen.
-       */
-      onValuePropUpdate() {
-        $('assistant-optin-flow-card').onValuePropUpdate();
-      },
+    /**
+     * Called to show the next settings when there are multiple unbundled
+     * activity control settings in the Value prop screen.
+     */
+    static onValuePropUpdate() {
+      $('assistant-optin-flow-card').onValuePropUpdate();
+    }
 
-      /**
-       * Called when the flow finished and close the dialog.
-       */
-      closeDialog() {
-        assistant.BrowserProxyImpl.getInstance().dialogClose();
-      },
-    };
-  });
+    /**
+     * Called when the flow finished and close the dialog.
+     */
+    static closeDialog() {
+      BrowserProxyImpl.getInstance().dialogClose();
+    }
+}
 
+function initializeInSessionAssistant() {
   if (document.readyState === 'loading') {
-    document.addEventListener('DOMContentLoaded', function() {
-      login.AssistantOptInFlowScreen.show();
-    });
-  } else {
-    login.AssistantOptInFlowScreen.show();
+    return;
   }
-});
+  document.removeEventListener('DOMContentLoaded', initializeInSessionAssistant);
+
+  login.AssistantOptInFlowScreen.show();
+}
+
+
+window.login = {};
+window.login.AssistantOptInFlowScreen = InSessionAssistantScreen;
+
+if (document.readyState === 'loading') {
+  document.addEventListener('DOMContentLoaded', initializeInSessionAssistant);
+} else {
+  initializeInSessionAssistant();
+}
diff --git a/chrome/browser/resources/chromeos/login/components/web_view_loader.js b/chrome/browser/resources/chromeos/login/components/web_view_loader.js
index c0c295df..d002758 100644
--- a/chrome/browser/resources/chromeos/login/components/web_view_loader.js
+++ b/chrome/browser/resources/chromeos/login/components/web_view_loader.js
@@ -152,6 +152,12 @@
       return;
     }
 
+    if (details && details.error == 'net::ERR_ABORTED') {
+      // Retry triggers net::ERR_ABORTED, so ignore it.
+      // TODO(crbug.com/1327977): Load an embedded offline copy as a fallback.
+      return;
+    }
+
     if (this.reloadRequested_) {
       this.loadWithFallbackTimer();
     } else {
diff --git a/chrome/browser/resources/chromeos/login/oobe_conditional_resources.grd b/chrome/browser/resources/chromeos/login/oobe_conditional_resources.grd
index d1f2652..c810b8e 100644
--- a/chrome/browser/resources/chromeos/login/oobe_conditional_resources.grd
+++ b/chrome/browser/resources/chromeos/login/oobe_conditional_resources.grd
@@ -25,8 +25,6 @@
       <structure name="IDR_KEYBOARD_UTILS_FOR_INJECTION_JS" file="components/keyboard_utils_for_injection.js" flattenhtml="true" type="chrome_html" />
       <structure name="IDR_KEYBOARD_UTILS_FOR_INJECTION_M_JS" file="components/keyboard_utils_for_injection.m.js" flattenhtml="true" type="chrome_html" />
 
-      <structure name="IDR_ASSISTANT_OPTIN_HTML" file="..\assistant_optin\assistant_optin.html" flattenhtml="true" allowexternalscript="true" type="chrome_html" />
-      <structure name="IDR_ASSISTANT_OPTIN_JS" file="..\assistant_optin\assistant_optin.js" flattenhtml="true" allowexternalscript="true" type="chrome_html" />
     </structures>
     <includes>
       <!-- Resources that are served under fixed paths -->
diff --git a/chrome/browser/resources/settings/autofill_page/password_manager_proxy.ts b/chrome/browser/resources/settings/autofill_page/password_manager_proxy.ts
index f5c20fd4..8de20e6 100644
--- a/chrome/browser/resources/settings/autofill_page/password_manager_proxy.ts
+++ b/chrome/browser/resources/settings/autofill_page/password_manager_proxy.ts
@@ -81,23 +81,17 @@
    *     updated for all ids.
    */
   changeSavedPassword(
-      ids: Array<number>,
-      params: chrome.passwordsPrivate.ChangeSavedPasswordParams):
+      ids: number[], params: chrome.passwordsPrivate.ChangeSavedPasswordParams):
       Promise<chrome.passwordsPrivate.CredentialIds>;
 
   /**
    * Should remove the saved password and notify that the list has changed.
    * @param id The id for the password entry being removed. No-op if |id| is not
    *     in the list.
+   * @param fromStores The store from which credential should be removed.
    */
-  removeSavedPassword(id: number): void;
-
-  /**
-   * Should remove the saved passwords and notify that the list has changed.
-   * @param ids The ids for the password entries being removed. Any id not in
-   *    the list is ignored.
-   */
-  removeSavedPasswords(ids: number[]): void;
+  removeSavedPassword(
+      id: number, fromStores: chrome.passwordsPrivate.PasswordStoreSet): void;
 
   /**
    * Moves a list of passwords from the device to the account
@@ -132,13 +126,6 @@
   removeException(id: number): void;
 
   /**
-   * Should remove the password exceptions and notify that the list has changed.
-   * @param ids The ids for the exception url entries being removed. Any |id|
-   *     not in the list is ignored.
-   */
-  removeExceptions(ids: number[]): void;
-
-  /**
    * Should undo the last saved password or exception removal and notify that
    * the list has changed.
    */
@@ -434,12 +421,9 @@
     });
   }
 
-  removeSavedPassword(id: number) {
-    chrome.passwordsPrivate.removeSavedPassword(id);
-  }
-
-  removeSavedPasswords(ids: number[]) {
-    chrome.passwordsPrivate.removeSavedPasswords(ids);
+  removeSavedPassword(
+      id: number, fromStores: chrome.passwordsPrivate.PasswordStoreSet) {
+    chrome.passwordsPrivate.removeSavedPassword(id, fromStores);
   }
 
   movePasswordsToAccount(ids: number[]) {
@@ -466,10 +450,6 @@
     chrome.passwordsPrivate.removePasswordException(id);
   }
 
-  removeExceptions(ids: number[]) {
-    chrome.passwordsPrivate.removePasswordExceptions(ids);
-  }
-
   undoRemoveSavedPasswordOrException() {
     chrome.passwordsPrivate.undoRemoveSavedPasswordOrException();
   }
diff --git a/chrome/browser/resources/settings/autofill_page/password_removal_mixin.ts b/chrome/browser/resources/settings/autofill_page/password_removal_mixin.ts
index 31bab2a..59c1c68 100644
--- a/chrome/browser/resources/settings/autofill_page/password_removal_mixin.ts
+++ b/chrome/browser/resources/settings/autofill_page/password_removal_mixin.ts
@@ -36,10 +36,11 @@
             return false;
           }
 
-          const idToRemove = password.isPresentInAccount() ?
-              password.accountId :
-              password.deviceId;
-          PasswordManagerImpl.getInstance().removeSavedPassword(idToRemove!);
+          PasswordManagerImpl.getInstance().removeSavedPassword(
+              password.getAnyId(),
+              password.isPresentInAccount() ?
+                  chrome.passwordsPrivate.PasswordStoreSet.ACCOUNT :
+                  chrome.passwordsPrivate.PasswordStoreSet.DEVICE);
           return true;
         }
 
diff --git a/chrome/browser/resources/settings/autofill_page/password_remove_dialog.ts b/chrome/browser/resources/settings/autofill_page/password_remove_dialog.ts
index 889b6863..2c13a7c 100644
--- a/chrome/browser/resources/settings/autofill_page/password_remove_dialog.ts
+++ b/chrome/browser/resources/settings/autofill_page/password_remove_dialog.ts
@@ -111,14 +111,17 @@
   }
 
   private onRemoveButtonClick_() {
-    const idsToRemove: number[] = [];
-    if (this.removeFromAccountChecked_) {
-      idsToRemove.push(this.duplicatedPassword.accountId!);
+    let fromStores: chrome.passwordsPrivate.PasswordStoreSet =
+        chrome.passwordsPrivate.PasswordStoreSet.DEVICE;
+    if (this.removeFromAccountChecked_ && this.removeFromDeviceChecked_) {
+      fromStores = chrome.passwordsPrivate.PasswordStoreSet.DEVICE_AND_ACCOUNT;
+    } else if (this.removeFromAccountChecked_) {
+      fromStores = chrome.passwordsPrivate.PasswordStoreSet.ACCOUNT;
+    } else {
+      assert(this.removeFromDeviceChecked_);
     }
-    if (this.removeFromDeviceChecked_) {
-      idsToRemove.push(this.duplicatedPassword.deviceId!);
-    }
-    PasswordManagerImpl.getInstance().removeSavedPasswords(idsToRemove);
+    PasswordManagerImpl.getInstance().removeSavedPassword(
+        this.duplicatedPassword.getAnyId(), fromStores);
 
     this.$.dialog.close();
     this.dispatchEvent(
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_section.ts b/chrome/browser/resources/settings/autofill_page/passwords_section.ts
index cb513f6..79ca7c1 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_section.ts
+++ b/chrome/browser/resources/settings/autofill_page/passwords_section.ts
@@ -656,14 +656,7 @@
   private onRemoveExceptionButtonTap_(
       e: DomRepeatEvent<MultiStoreExceptionEntry>) {
     const exception = e.model.item;
-    const allExceptionIds: number[] = [];
-    if (exception.isPresentInAccount()) {
-      allExceptionIds.push(exception.accountId!);
-    }
-    if (exception.isPresentOnDevice()) {
-      allExceptionIds.push(exception.deviceId!);
-    }
-    this.passwordManager_.removeExceptions(allExceptionIds);
+    this.passwordManager_.removeException(exception.getAnyId());
   }
 
   /**
diff --git a/chrome/browser/resources/signin/dice_web_signin_intercept/dice_web_signin_intercept_app.html b/chrome/browser/resources/signin/dice_web_signin_intercept/dice_web_signin_intercept_app.html
index 469519d..e318de0 100644
--- a/chrome/browser/resources/signin/dice_web_signin_intercept/dice_web_signin_intercept_app.html
+++ b/chrome/browser/resources/signin/dice_web_signin_intercept/dice_web_signin_intercept_app.html
@@ -97,8 +97,8 @@
     border-radius: 50%;
     bottom: var(--badge-offset);
     height: var(--badge-width);
+    inset-inline-end: var(--badge-offset);
     position: absolute;
-    right: var(--badge-offset);
     width: var(--badge-width);
   }
 
@@ -119,7 +119,7 @@
     --badge-width: 18px;
     border: 1px solid var(--google-grey-300);
     bottom: -3px;
-    right: -9px;
+    inset-inline-end: -9px;
   }
 
   #headerV2 .work-badge > iron-icon {
diff --git a/chrome/browser/resources/signin/profile_customization/profile_customization_app.html b/chrome/browser/resources/signin/profile_customization/profile_customization_app.html
index 46931aa45..6a58f80 100644
--- a/chrome/browser/resources/signin/profile_customization/profile_customization_app.html
+++ b/chrome/browser/resources/signin/profile_customization/profile_customization_app.html
@@ -51,8 +51,8 @@
     border-radius: 50%;
     bottom: var(--badge-offset);
     height: var(--badge-width);
+    inset-inline-end: var(--badge-offset);
     position: absolute;
-    right: var(--badge-offset);
     width: var(--badge-width);
   }
 
diff --git a/chrome/browser/supervised_user/BUILD.gn b/chrome/browser/supervised_user/BUILD.gn
index f749815..b0eaf6a 100644
--- a/chrome/browser/supervised_user/BUILD.gn
+++ b/chrome/browser/supervised_user/BUILD.gn
@@ -28,11 +28,25 @@
 }
 
 if (is_android) {
+  android_resources("java_resources") {
+    sources = [
+      "android/java/res/drawable/ic_family_link.xml",
+      "android/java/res/layout/website_approval_bottom_sheet.xml",
+      "android/java/res/values/dimens.xml",
+    ]
+  }
+
   android_library("website_parent_approval_java") {
     deps = [
+      ":java_resources",
       "//base:base_java",
       "//base:jni_java",
       "//build/android:build_java",
+      "//chrome/browser/profiles/android:java",
+      "//chrome/browser/signin/services/android:java",
+      "//chrome/browser/ui/android/strings:ui_strings_grd",
+      "//components/browser_ui/bottomsheet/android:java",
+      "//components/signin/public/android:java",
       "//third_party/androidx:androidx_annotation_annotation_java",
       "//ui/android:ui_no_recycler_view_java",
       "//url:gurl_java",
@@ -40,9 +54,15 @@
     annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
     sources = [
       "android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegate.java",
+      "android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegateImpl.java",
       "android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java",
-      "android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalDelegate.java",
+      "android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalCoordinator.java",
+      "android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalMediator.java",
+      "android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalProperties.java",
+      "android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalSheetContent.java",
+      "android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalViewBinder.java",
     ]
+    resources_package = "org.chromium.chrome.browser.supervised_user"
 
     # Add the actual implementation where necessary so that downstream targets
     # can provide their own implementations.
diff --git a/chrome/browser/supervised_user/android/java/res/drawable/ic_family_link.xml b/chrome/browser/supervised_user/android/java/res/drawable/ic_family_link.xml
new file mode 100644
index 0000000..8f496a4
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/res/drawable/ic_family_link.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<!-- The official Family Link 'kite' icon, from go/icons: https://fonts.gstatic.com/s/i/productlogos/family_link/v5/192px.svg -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="192"
+    android:viewportHeight="192">
+  <path
+      android:pathData="M95.5,8c-2.9,0 -5.8,1.1 -8.1,3.2L22.9,70.6c-2.4,2.2 -3.7,5.1 -3.8,8.1l40.7,6.4L95.5,72l10,-28.6L95.5,8z"
+      android:fillColor="#4285F4"/>
+  <path
+      android:pathData="M109.6,128.7L67.4,184h22c3.7,0 7.2,-1.7 9.5,-4.7l38.6,-50.6L109.6,128.7L109.6,128.7z"
+      android:fillColor="#BDC1C6"/>
+  <path
+      android:pathData="M19.2,144.1c-3.7,0 -7.2,1.7 -9.5,4.7L8,151.1l11.9,2.7l7,9.1l14.4,-18.8H19.2z"
+      android:fillColor="#BDC1C6"/>
+  <path
+      android:pathData="M95.5,72l-76.4,6.7c-0.2,2.8 0.6,5.6 2.4,8L43.9,116l6.1,8l25.7,7.7L95.5,120l10,-24L95.5,72z"
+      android:fillColor="#34A853"/>
+  <path
+      android:pathData="M50,124l36.1,47.3c2.4,3.1 5.9,4.7 9.5,4.7l10,-28.8l-10,-27.2L50,124z"
+      android:fillColor="#FBBC04"/>
+  <path
+      android:pathData="M89.4,184c-3.7,0 -7.2,-1.7 -9.5,-4.7l-23.4,-30.6c-2.3,-3 -5.8,-4.7 -9.5,-4.7H19.2c3.7,0 7.2,1.7 9.5,4.7l23.4,30.6c2.3,3 5.8,4.7 9.5,4.7H89.4z"
+      android:fillColor="#DADCE0"/>
+  <path
+      android:pathData="M172,78.7c-0.1,-1.4 -0.4,-2.7 -0.9,-4c-0.6,-1.5 -1.6,-2.9 -2.9,-4.1l-64.5,-59.5C101.3,9 98.4,8 95.5,8l0,64l38.2,13.4L172,78.7z"
+      android:fillColor="#669DF6"/>
+  <path
+      android:pathData="M172,78.7L172,78.7L95.5,72v48l22.1,11.7l23.5,-7.7l6.1,-8l22.3,-29.2C171.4,84.4 172.1,81.5 172,78.7z"
+      android:fillColor="#5BB974"/>
+  <path
+      android:pathData="M95.5,176c3.5,0 7.1,-1.6 9.5,-4.7l36.1,-47.3l-45.6,-4V176z"
+      android:fillColor="#FCC934"/>
+</vector>
diff --git a/chrome/browser/supervised_user/android/java/res/layout/website_approval_bottom_sheet.xml b/chrome/browser/supervised_user/android/java/res/layout/website_approval_bottom_sheet.xml
new file mode 100644
index 0000000..b1690615
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/res/layout/website_approval_bottom_sheet.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:orientation="vertical">
+
+    <!-- TODO(crbug.com/1330900): check if it's really right to duplicate this from other
+    sheets? -->
+    <ImageView
+        android:id="@+id/drag_handlebar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginStart="@dimen/website_approval_horizontal_margin"
+        android:layout_marginEnd="@dimen/website_approval_horizontal_margin"
+        android:layout_marginTop="8dp"
+        android:layout_marginBottom="24dp"
+        android:importantForAccessibility="no"
+        app:srcCompat="@drawable/drag_handlebar" />
+
+    <ImageView
+        android:id="@+id/family_link_icon"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_gravity="center_horizontal"
+        android:importantForAccessibility="no"
+        app:srcCompat="@drawable/ic_family_link" />
+
+    <!-- The main text including website -->
+    <androidx.appcompat.widget.DialogTitle
+        android:id="@+id/website_approval_sheet_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginStart="@dimen/website_approval_horizontal_margin"
+        android:layout_marginEnd="@dimen/website_approval_horizontal_margin"
+        android:paddingTop="16dp"
+        android:paddingBottom="24dp"
+        android:text="@string/parent_website_approval_title"
+        android:textAlignment="center"
+        android:textAppearance="@style/TextAppearance.AlertDialogTitleStyle" />
+
+    <!-- The info box showing the website details -->
+    <org.chromium.components.browser_ui.widget.MaterialCardViewNoShadow
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/website_approval_horizontal_margin"
+        android:layout_marginEnd="@dimen/website_approval_horizontal_margin"
+        style="@style/MaterialCardStyle">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:minHeight="72dp"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:descendantFocusability="blocksDescendants">
+
+            <!-- TODO(crbug.com/1330900): replace with the actual website favicon -->
+            <ImageView
+                android:id="@+id/favicon"
+                android:layout_width="28dp"
+                android:layout_height="28dp"
+                android:layout_marginStart="15dp"
+                android:layout_marginEnd="15dp"
+                android:layout_marginTop="22dp"
+                android:layout_marginBottom="22dp"
+                android:importantForAccessibility="no"
+                android:layout_gravity="center"
+                app:srcCompat="@drawable/ic_family_link" />
+
+            <!-- Details of the domain that will be allowed, and full URL being displayed-->
+            <LinearLayout
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="24dp"
+                android:layout_marginTop="14dp"
+                android:layout_marginBottom="14dp"
+                android:layout_weight="1"
+                android:orientation="vertical">
+                <TextView
+                    android:id="@+id/all_pages_of"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:minHeight="20dp"
+                    android:ellipsize="end"
+                    android:singleLine="true"
+                    android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
+                <TextView
+                    android:id="@+id/full_url"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:minHeight="20dp"
+                    android:ellipsize="end"
+                    android:singleLine="true"
+                    android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+            </LinearLayout>
+        </LinearLayout>
+    </org.chromium.components.browser_ui.widget.MaterialCardViewNoShadow>
+
+    <!-- The approve button -->
+    <org.chromium.ui.widget.ButtonCompat
+        android:id="@+id/approve_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/website_approval_horizontal_margin"
+        android:layout_marginEnd="@dimen/website_approval_horizontal_margin"
+        android:layout_marginTop="15dp"
+        android:layout_marginBottom="18dp"
+        android:minHeight="48dp"
+        android:gravity="center"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:text="@string/parent_website_approval_approve_button"
+        style="@style/FilledButton.Flat"/>
+
+    <!-- The deny button button -->
+    <org.chromium.ui.widget.ButtonCompat
+        android:id="@+id/deny_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/website_approval_horizontal_margin"
+        android:layout_marginEnd="@dimen/website_approval_horizontal_margin"
+        android:layout_marginTop="0dp"
+        android:layout_marginBottom="15dp"
+        android:minHeight="48dp"
+        android:gravity="center"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:text="@string/parent_website_approval_deny_button"
+        style="@style/TextButton"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/chrome/browser/supervised_user/android/java/res/values/dimens.xml b/chrome/browser/supervised_user/android/java/res/values/dimens.xml
new file mode 100644
index 0000000..1a34417
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/res/values/dimens.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Website approval bottom sheet -->
+    <dimen name="website_approval_horizontal_margin">24dp</dimen>
+</resources>
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegateImpl.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegateImpl.java
new file mode 100644
index 0000000..b244bfc
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/ParentAuthDelegateImpl.java
@@ -0,0 +1,26 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.supervised_user;
+
+import org.chromium.base.Callback;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.url.GURL;
+
+/**
+ * Upstream implementation of {@link ParentAuthDelegate}.
+ * Downstream targets may provide a different implementation.
+ */
+public class ParentAuthDelegateImpl implements ParentAuthDelegate {
+    @Override
+    public boolean isLocalAuthSupported() {
+        return false;
+    }
+
+    @Override
+    public void requestLocalAuth(
+            WindowAndroid windowAndroid, GURL url, Callback<Boolean> onCompletionCallback) {
+        throw new UnsupportedOperationException("Local parent auth not supported");
+    }
+}
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
index 176b7c6..a3b8f89 100644
--- a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApproval.java
@@ -6,6 +6,7 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.browser.supervised_user.website_approval.WebsiteApprovalCoordinator;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.url.GURL;
 
@@ -20,8 +21,8 @@
      */
     @CalledByNative
     private static boolean isLocalApprovalSupported() {
-        // TODO(crbug.com/1340913): wire in ParentAuthDelegateImpl once landed in internal.
-        return false;
+        ParentAuthDelegate delegate = new ParentAuthDelegateImpl();
+        return delegate.isLocalAuthSupported();
     }
 
     /**
@@ -39,8 +40,36 @@
      * */
     @CalledByNative
     private static void requestLocalApproval(WindowAndroid windowAndroid, GURL url) {
-        // TODO(crbug.com/1340913): finish moving the approve/deny screen to the Chromium repo.
-        throw new UnsupportedOperationException("Approve/deny screen not implemented");
+        // First ask the parent to authenticate.
+        ParentAuthDelegate delegate = new ParentAuthDelegateImpl();
+        delegate.requestLocalAuth(windowAndroid, url,
+                (success) -> { onParentAuthComplete(success, windowAndroid, url); });
+    }
+
+    /** Displays the screen giving the parent the option to approve or deny the website.*/
+    private static void onParentAuthComplete(
+            boolean success, WindowAndroid windowAndroid, GURL url) {
+        if (!success) {
+            WebsiteParentApprovalJni.get().onCompletion(false);
+            return;
+        }
+
+        // Launch the bottom sheet.
+        WebsiteApprovalCoordinator websiteApprovalUi = new WebsiteApprovalCoordinator(
+                windowAndroid, url, new WebsiteApprovalCoordinator.CompletionCallback() {
+                    @Override
+                    public void onWebsiteApproved() {
+                        // TODO(crbug.com/1330897): add metrics.
+                        WebsiteParentApprovalJni.get().onCompletion(true);
+                    }
+
+                    @Override
+                    public void onWebsiteDenied() {
+                        // TODO(crbug.com/1330897): add metrics.
+                        WebsiteParentApprovalJni.get().onCompletion(false);
+                    }
+                });
+        websiteApprovalUi.show();
     }
 
     @NativeMethods
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalDelegate.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalDelegate.java
deleted file mode 100644
index da549d0..0000000
--- a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalDelegate.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.supervised_user;
-
-import org.chromium.base.Callback;
-import org.chromium.ui.base.WindowAndroid;
-import org.chromium.url.GURL;
-
-/**
- * The correct version of {@link WebsiteParentApprovalDelegateImpl} will be determined at compile
- * time via build rules.
- */
-public interface WebsiteParentApprovalDelegate {
-    /** @see {@link WebsiteParentApproval#isLocalApprovalSupported()} */
-    boolean isLocalApprovalSupported();
-
-    /** @see {@link WebsiteParentApproval#requestLocalApproval()} */
-    void requestLocalApproval(
-            WindowAndroid windowAndroid, GURL url, Callback<Boolean> onCompletionCallback);
-}
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalCoordinator.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalCoordinator.java
new file mode 100644
index 0000000..e8aa2191
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalCoordinator.java
@@ -0,0 +1,64 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.supervised_user.website_approval;
+
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.url.GURL;
+
+/**
+ * Coordinator for the bottom sheet content in the screen which allows a parent to approve or deny
+ * a website.
+ */
+public class WebsiteApprovalCoordinator {
+    private final WebsiteApprovalMediator mMediator;
+    private final BottomSheetController mBottomSheetController;
+    private final WebsiteApprovalSheetContent mSheetContent;
+
+    /**
+     * Callback to notify completion of the flow.
+     */
+    public interface CompletionCallback {
+        /**
+         * Called when the parent clicks to approve the website.
+         */
+        void onWebsiteApproved();
+
+        /**
+         * Called when the parent explicitly clicks to not approve the website.
+         */
+        void onWebsiteDenied();
+    }
+
+    /**
+     * Constructor for the co-ordinator.  Callers should then call {@link show()} to display the
+     * UI.
+     *
+     * @param url the full URL for which the request is being made (code in this module is
+     * responsible for displaying the appropriate part of the URL to the user)
+     */
+    public WebsiteApprovalCoordinator(
+            WindowAndroid windowAndroid, GURL url, CompletionCallback completionCallback) {
+        PropertyModel model = new PropertyModel.Builder(WebsiteApprovalProperties.ALL_KEYS)
+                                      .with(WebsiteApprovalProperties.URL, url)
+                                      .build();
+
+        mBottomSheetController = BottomSheetControllerProvider.from(windowAndroid);
+        mSheetContent = new WebsiteApprovalSheetContent(windowAndroid.getContext().get());
+
+        PropertyModelChangeProcessor.create(model, mSheetContent, WebsiteApprovalViewBinder::bind);
+
+        mMediator = new WebsiteApprovalMediator(completionCallback, model);
+    }
+
+    /** Displays the UI to request parent approval in a new bottom sheet. */
+    public void show() {
+        mMediator.show();
+        mBottomSheetController.requestShowContent(mSheetContent, true);
+    }
+}
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalMediator.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalMediator.java
new file mode 100644
index 0000000..fd64d57
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalMediator.java
@@ -0,0 +1,55 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.supervised_user.website_approval;
+
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
+import org.chromium.components.signin.base.AccountInfo;
+import org.chromium.components.signin.base.CoreAccountInfo;
+import org.chromium.components.signin.identitymanager.ConsentLevel;
+import org.chromium.components.signin.identitymanager.IdentityManager;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Contains the logic for the WebsiteApproval component. It sets the state of the model and reacts
+ * to events like clicks.
+ */
+class WebsiteApprovalMediator {
+    private final WebsiteApprovalCoordinator.CompletionCallback mCompletionCallback;
+    private final PropertyModel mModel;
+
+    WebsiteApprovalMediator(
+            WebsiteApprovalCoordinator.CompletionCallback completionCallback, PropertyModel model) {
+        mCompletionCallback = completionCallback;
+        mModel = model;
+    }
+
+    void show() {
+        mModel.set(WebsiteApprovalProperties.ON_CLICK_APPROVE,
+                v -> mCompletionCallback.onWebsiteApproved());
+        mModel.set(WebsiteApprovalProperties.ON_CLICK_DENY,
+                v -> mCompletionCallback.onWebsiteDenied());
+
+        // Set the child name.  We use the given name if there is one for this account, otherwise we
+        // use the full account email address.
+        IdentityManager identityManager = IdentityServicesProvider.get().getIdentityManager(
+                Profile.getLastUsedRegularProfile());
+        String childEmail = CoreAccountInfo.getEmailFrom(
+                identityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN));
+        if (childEmail == null) {
+            // This is an unexpected window condition: there is no signed in account.
+            // TODO(crbug.com/1330900): dismiss the bottom sheet.
+            return;
+        }
+        AccountInfo childAccountInfo =
+                identityManager.findExtendedAccountInfoByEmailAddress(childEmail);
+
+        String childNameProperty = childEmail;
+        if (childAccountInfo != null && !childAccountInfo.getGivenName().isEmpty()) {
+            childNameProperty = childAccountInfo.getGivenName();
+        }
+        mModel.set(WebsiteApprovalProperties.CHILD_NAME, childNameProperty);
+    }
+}
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalProperties.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalProperties.java
new file mode 100644
index 0000000..69a5e5ec
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalProperties.java
@@ -0,0 +1,27 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.supervised_user.website_approval;
+
+import android.view.View.OnClickListener;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.url.GURL;
+
+/**
+ * Properties defined here reflect the visible state of the WebsiteApproval components.
+ */
+class WebsiteApprovalProperties {
+    static final PropertyModel.WritableObjectPropertyKey<String> CHILD_NAME =
+            new PropertyModel.WritableObjectPropertyKey<>("child_name");
+    static final PropertyModel.ReadableObjectPropertyKey<GURL> URL =
+            new PropertyModel.ReadableObjectPropertyKey<>("url");
+    static final PropertyModel.WritableObjectPropertyKey<OnClickListener> ON_CLICK_APPROVE =
+            new PropertyModel.WritableObjectPropertyKey<>("on_click_approve");
+    static final PropertyModel.WritableObjectPropertyKey<OnClickListener> ON_CLICK_DENY =
+            new PropertyModel.WritableObjectPropertyKey<>("on_click_deny");
+
+    public static final PropertyKey[] ALL_KEYS = {CHILD_NAME, URL, ON_CLICK_APPROVE, ON_CLICK_DENY};
+}
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalSheetContent.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalSheetContent.java
new file mode 100644
index 0000000..24c6b27
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalSheetContent.java
@@ -0,0 +1,125 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.supervised_user.website_approval;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.DialogTitle;
+
+import org.chromium.chrome.browser.supervised_user.R;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+import org.chromium.ui.widget.ButtonCompat;
+
+/**
+ * Bottom sheet content for the screen which allows a parent to approve or deny a website.
+ */
+class WebsiteApprovalSheetContent implements BottomSheetContent {
+    private static final String TAG = "WebsiteApprovalSheetContent";
+    private final Context mContext;
+    private final View mContentView;
+
+    public WebsiteApprovalSheetContent(Context context) {
+        mContext = context;
+        mContentView = (LinearLayout) LayoutInflater.from(mContext).inflate(
+                R.layout.website_approval_bottom_sheet, null);
+    }
+
+    @Override
+    public View getContentView() {
+        return mContentView;
+    }
+
+    @Override
+    @Nullable
+    public View getToolbarView() {
+        return null;
+    }
+
+    @Override
+    public int getVerticalScrollOffset() {
+        return 0;
+    }
+
+    @Override
+    public int getPeekHeight() {
+        return HeightMode.DISABLED;
+    }
+
+    @Override
+    public float getHalfHeightRatio() {
+        return HeightMode.DISABLED;
+    }
+
+    @Override
+    public float getFullHeightRatio() {
+        return HeightMode.WRAP_CONTENT;
+    }
+
+    @Override
+    public void destroy() {}
+
+    @Override
+    @ContentPriority
+    public int getPriority() {
+        return ContentPriority.HIGH;
+    }
+
+    @Override
+    public boolean swipeToDismissEnabled() {
+        // We don't want the sheet to be too easily dismissed, as getting here requires a full
+        // manual password entry.
+        return false;
+    }
+
+    @Override
+    public int getSheetContentDescriptionStringId() {
+        return R.string.parent_website_approval_content_description;
+    }
+
+    @Override
+    public int getSheetHalfHeightAccessibilityStringId() {
+        // Half-height is disabled so no need for an accessibility string.
+        return 0;
+    }
+
+    @Override
+    public int getSheetFullHeightAccessibilityStringId() {
+        return R.string.parent_website_approval_full_height;
+    }
+
+    @Override
+    public int getSheetClosedAccessibilityStringId() {
+        return R.string.parent_website_approval_closed;
+    }
+
+    public ButtonCompat getApproveButton() {
+        return mContentView.findViewById(R.id.approve_button);
+    }
+
+    public ButtonCompat getDenyButton() {
+        return mContentView.findViewById(R.id.deny_button);
+    }
+
+    public void setTitle(String childName) {
+        DialogTitle titleView = mContentView.findViewById(R.id.website_approval_sheet_title);
+        titleView.setText(mContext.getString(R.string.parent_website_approval_title, childName));
+    }
+
+    public void setDomainText(String domain) {
+        TextView domainTextView = mContentView.findViewById(R.id.all_pages_of);
+        domainTextView.setText(
+                mContext.getString(R.string.parent_website_approval_all_of_domain, domain));
+    }
+
+    public void setFullUrlText(String url) {
+        TextView urlTextView = mContentView.findViewById(R.id.full_url);
+        urlTextView.setText(url);
+    }
+}
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalViewBinder.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalViewBinder.java
new file mode 100644
index 0000000..aafbd77
--- /dev/null
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/website_approval/WebsiteApprovalViewBinder.java
@@ -0,0 +1,38 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.supervised_user.website_approval;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Provides functions that map {@link WebsiteApprovalProperties} changes in a {@link PropertyModel}
+ * to the suitable method in {@link WebsiteApprovalSheetContent}.
+ */
+class WebsiteApprovalViewBinder {
+    /**
+     * Called whenever a property in the given model changes. It updates the given view accordingly.
+     * @param model The observed {@link PropertyModel}. Its data need to be reflected in the view.
+     * @param view The {@link WebsiteApprovalSheetContent} to update.
+     * @param propertyKey The {@link PropertyKey} which changed.
+     */
+    public static void bind(
+            PropertyModel model, WebsiteApprovalSheetContent view, PropertyKey propertyKey) {
+        if (propertyKey == WebsiteApprovalProperties.CHILD_NAME) {
+            view.setTitle(model.get(WebsiteApprovalProperties.CHILD_NAME));
+        } else if (propertyKey == WebsiteApprovalProperties.URL) {
+            view.setDomainText(model.get(WebsiteApprovalProperties.URL).getHost());
+            view.setFullUrlText(model.get(WebsiteApprovalProperties.URL).getSpec());
+        } else if (propertyKey == WebsiteApprovalProperties.ON_CLICK_APPROVE) {
+            view.getApproveButton().setOnClickListener(
+                    model.get(WebsiteApprovalProperties.ON_CLICK_APPROVE));
+        } else if (propertyKey == WebsiteApprovalProperties.ON_CLICK_DENY) {
+            view.getDenyButton().setOnClickListener(
+                    model.get(WebsiteApprovalProperties.ON_CLICK_DENY));
+        } else {
+            assert false : "Unhandled update to property: " + propertyKey;
+        }
+    }
+}
diff --git a/chrome/browser/touch_to_fill/touch_to_fill_controller.cc b/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
index a3b3f726..775d164 100644
--- a/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
+++ b/chrome/browser/touch_to_fill/touch_to_fill_controller.cc
@@ -185,7 +185,8 @@
   authenticator_->Authenticate(
       device_reauth::BiometricAuthRequester::kTouchToFill,
       base::BindOnce(&TouchToFillController::OnReauthCompleted,
-                     base::Unretained(this), credential));
+                     base::Unretained(this), credential),
+      /*use_last_valid_auth=*/true);
 }
 
 void TouchToFillController::OnWebAuthnCredentialSelected(
diff --git a/chrome/browser/touch_to_fill/touch_to_fill_controller_unittest.cc b/chrome/browser/touch_to_fill/touch_to_fill_controller_unittest.cc
index f4811e6c..b8e3bf7d 100644
--- a/chrome/browser/touch_to_fill/touch_to_fill_controller_unittest.cc
+++ b/chrome/browser/touch_to_fill/touch_to_fill_controller_unittest.cc
@@ -420,7 +420,8 @@
               CanAuthenticate(BiometricAuthRequester::kTouchToFill))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator(),
-              Authenticate(BiometricAuthRequester::kTouchToFill, _))
+              Authenticate(BiometricAuthRequester::kTouchToFill, _,
+                           /*use_last_valid_auth=*/true))
       .WillOnce(RunOnceCallback<1>(true));
   // Without |kTouchToFillPasswordSubmission|, don't trigger a submission, but
   // inform the client that a form can be submitted.
@@ -451,7 +452,8 @@
               CanAuthenticate(BiometricAuthRequester::kTouchToFill))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator(),
-              Authenticate(BiometricAuthRequester::kTouchToFill, _))
+              Authenticate(BiometricAuthRequester::kTouchToFill, _,
+                           /*use_last_valid_auth=*/true))
       .WillOnce(RunOnceCallback<1>(false));
   touch_to_fill_controller().OnCredentialSelected(credentials[0]);
 
@@ -656,7 +658,8 @@
               CanAuthenticate(BiometricAuthRequester::kTouchToFill))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator(),
-              Authenticate(BiometricAuthRequester::kTouchToFill, _));
+              Authenticate(BiometricAuthRequester::kTouchToFill, _,
+                           /*use_last_valid_auth=*/true));
   touch_to_fill_controller().OnCredentialSelected(credentials[0]);
 
   EXPECT_CALL(*authenticator(), Cancel(BiometricAuthRequester::kTouchToFill));
diff --git a/chrome/browser/ui/android/overlay/overlay_window_android.cc b/chrome/browser/ui/android/overlay/overlay_window_android.cc
index 6104018..ae7412af 100644
--- a/chrome/browser/ui/android/overlay/overlay_window_android.cc
+++ b/chrome/browser/ui/android/overlay/overlay_window_android.cc
@@ -220,18 +220,18 @@
   const viz::SurfaceId& old_surface_id = surface_layer_->surface_id().is_valid()
                                              ? surface_layer_->surface_id()
                                              : surface_id;
+  if (window_android_ && window_android_->GetCompositor() &&
+      old_surface_id != surface_id) {
+    // On Android, the new frame sink needs to be added before
+    // removing the previous surface sink.
+    window_android_->GetCompositor()->AddChildFrameSink(
+        surface_id.frame_sink_id());
+    window_android_->GetCompositor()->RemoveChildFrameSink(
+        old_surface_id.frame_sink_id());
+  }
+  // Set the surface after frame sink hierarchy update.
   surface_layer_->SetSurfaceId(surface_id,
                                cc::DeadlinePolicy::UseDefaultDeadline());
-
-  if (!window_android_ || !window_android_->GetCompositor() ||
-      old_surface_id == surface_id) {
-    return;
-  }
-
-  window_android_->GetCompositor()->RemoveChildFrameSink(
-      old_surface_id.frame_sink_id());
-  window_android_->GetCompositor()->AddChildFrameSink(
-      surface_id.frame_sink_id());
 }
 
 cc::Layer* OverlayWindowAndroid::GetLayerForTesting() {
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 465b61c..f596008 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -1864,6 +1864,29 @@
         Allow all sites
       </message>
 
+      <!-- Parent approval widget. TODO(crbug.com/1330900): enable translations once strings are finalized. -->
+      <message name="IDS_PARENT_WEBSITE_APPROVAL_TITLE" desc="The title text displayed in the bottom sheet asking a parent to allow or not allow a website."  translateable="false">
+        <ph name="CHILD_NAME">%1$s<ex>Alice</ex></ph> wants you to approve this website:
+      </message>
+      <message name="IDS_PARENT_WEBSITE_APPROVAL_ALL_OF_DOMAIN" desc="The text which tells the parent that all of a particular domain will be allowed if they approve the request."  translateable="false">
+        All pages of <ph name="DOMAIN">%1$s<ex>netflix.com</ex></ph>
+      </message>
+      <message name="IDS_PARENT_WEBSITE_APPROVAL_APPROVE_BUTTON" desc="The text displayed on the button to approve a website."  translateable="false">
+        Approve
+      </message>
+      <message name="IDS_PARENT_WEBSITE_APPROVAL_DENY_BUTTON" desc="The text displayed on the button to not approve a website."  translateable="false">
+        Don’t approve
+      </message>
+      <message name="IDS_PARENT_WEBSITE_APPROVAL_CONTENT_DESCRIPTION" desc="The content description for the bottom sheet allowing a parent to approve or deny a request to view a website."  translateable="false">
+        Option to approve or not approve a website
+      </message>
+      <message name="IDS_PARENT_WEBSITE_APPROVAL_FULL_HEIGHT" desc="Accessibility string read when the parent website approval bottom sheet is opened at full height."  translateable="false">
+        Option to approve or not approve a website opened at full height
+      </message>
+      <message name="IDS_PARENT_WEBSITE_APPROVAL_CLOSED" desc="Accessibility string read when the parent website approval bottom sheet is closed."  translateable="false">
+        Option to approve or not approve a website closed
+      </message>
+
       <!-- Sync strings in account management dialog -->
       <message name="IDS_SYNC_ON" desc="The text is displayed as a gray subtitle of a settings item titled 'Sync' and indicates that sync is on.">
         On
diff --git a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
index a999eb7e..8fd176c7 100644
--- a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
+++ b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
@@ -33,7 +33,7 @@
 #include "skia/ext/skia_utils_mac.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #import "testing/gtest_mac.h"
-#import "ui/base/test/nswindow_fullscreen_notification_waiter.h"
+#include "ui/base/cocoa/nswindow_test_util.h"
 #import "ui/base/test/scoped_fake_nswindow_focus.h"
 #include "ui/base/test/scoped_fake_nswindow_fullscreen.h"
 #import "ui/base/test/windowed_nsnotification_observer.h"
@@ -189,8 +189,8 @@
       CreateTestAppWindow("{\"alwaysOnTop\": true }");
   extensions::NativeAppWindow* window = app_window->GetBaseWindow();
   NSWindow* ns_window = app_window->GetNativeWindow().GetNativeNSWindow();
-  base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
-      [[NSWindowFullscreenNotificationWaiter alloc] initWithWindow:ns_window]);
+  ui::NSWindowFullscreenNotificationWaiter waiter(
+      app_window->GetNativeWindow());
 
   EXPECT_EQ(AppWindow::FULLSCREEN_TYPE_NONE,
             app_window->fullscreen_types_for_test());
@@ -199,7 +199,7 @@
   EXPECT_TRUE(IsNSWindowFloating(ns_window));
 
   [ns_window toggleFullScreen:nil];
-  [waiter waitForEnterCount:1 exitCount:0];
+  waiter.WaitForEnterAndExitCount(1, 0);
   EXPECT_TRUE(app_window->fullscreen_types_for_test() &
               AppWindow::FULLSCREEN_TYPE_OS);
   EXPECT_TRUE(window->IsFullscreen());
@@ -208,7 +208,7 @@
 
   app_window->Restore();
   EXPECT_FALSE(window->IsFullscreenOrPending());
-  [waiter waitForEnterCount:1 exitCount:1];
+  waiter.WaitForEnterAndExitCount(1, 1);
   EXPECT_EQ(AppWindow::FULLSCREEN_TYPE_NONE,
             app_window->fullscreen_types_for_test());
   EXPECT_FALSE(window->IsFullscreen());
@@ -217,7 +217,7 @@
 
   app_window->Fullscreen();
   EXPECT_TRUE(window->IsFullscreenOrPending());
-  [waiter waitForEnterCount:2 exitCount:1];
+  waiter.WaitForEnterAndExitCount(2, 1);
   EXPECT_TRUE(app_window->fullscreen_types_for_test() &
               AppWindow::FULLSCREEN_TYPE_WINDOW_API);
   EXPECT_TRUE(window->IsFullscreen());
@@ -225,7 +225,7 @@
   EXPECT_FALSE(IsNSWindowFloating(ns_window));
 
   [ns_window toggleFullScreen:nil];
-  [waiter waitForEnterCount:2 exitCount:2];
+  waiter.WaitForEnterAndExitCount(2, 2);
   EXPECT_EQ(AppWindow::FULLSCREEN_TYPE_NONE,
             app_window->fullscreen_types_for_test());
   EXPECT_FALSE(window->IsFullscreen());
@@ -409,11 +409,7 @@
 }
 
 // Test Maximize, Fullscreen, Restore combinations.
-// Disabled because ScopedFakeNSWindowFullscreen is incompatible with
-// NSWindowFullscreenNotificationWaiter.
-// https://crbug.com/1307803
-IN_PROC_BROWSER_TEST_F(NativeAppWindowCocoaBrowserTest,
-                       DISABLED_MaximizeFullscreen) {
+IN_PROC_BROWSER_TEST_F(NativeAppWindowCocoaBrowserTest, MaximizeFullscreen) {
   ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen;
 
   SetUpAppWithWindows(1);
@@ -421,8 +417,8 @@
   extensions::NativeAppWindow* window = app_window->GetBaseWindow();
   NSWindow* ns_window = app_window->GetNativeWindow().GetNativeNSWindow();
   base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
-  base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
-      [[NSWindowFullscreenNotificationWaiter alloc] initWithWindow:ns_window]);
+  ui::NSWindowFullscreenNotificationWaiter waiter(
+      app_window->GetNativeWindow());
 
   NSRect initial_frame = [ns_window frame];
   NSRect maximized_frame = [[ns_window screen] visibleFrame];
@@ -439,14 +435,14 @@
   EXPECT_NSEQ(maximized_frame, [ns_window frame]);
   EXPECT_TRUE(window->IsMaximized());
 
-  EXPECT_EQ(0, [waiter enterCount]);
+  EXPECT_EQ(0, waiter.enter_count());
   app_window->Fullscreen();
-  [waiter waitForEnterCount:1 exitCount:0];
+  waiter.WaitForEnterAndExitCount(1, 0);
   EXPECT_FALSE(window->IsMaximized());
   EXPECT_TRUE(window->IsFullscreen());
 
   app_window->Restore();
-  [waiter waitForEnterCount:1 exitCount:1];
+  waiter.WaitForEnterAndExitCount(1, 1);
   EXPECT_NSEQ(maximized_frame, [ns_window frame]);
   EXPECT_TRUE(window->IsMaximized());
   EXPECT_FALSE(window->IsFullscreen());
@@ -457,7 +453,7 @@
 
   // Fullscreen, Maximize, Restore.
   app_window->Fullscreen();
-  [waiter waitForEnterCount:2 exitCount:1];
+  waiter.WaitForEnterAndExitCount(2, 1);
   EXPECT_FALSE(window->IsMaximized());
   EXPECT_TRUE(window->IsFullscreen());
 
@@ -466,7 +462,7 @@
   EXPECT_TRUE(window->IsFullscreen());
 
   app_window->Restore();
-  [waiter waitForEnterCount:2 exitCount:2];
+  waiter.WaitForEnterAndExitCount(2, 2);
   EXPECT_NSEQ(initial_frame, [ns_window frame]);
   EXPECT_FALSE(window->IsMaximized());
   EXPECT_FALSE(window->IsFullscreen());
@@ -546,17 +542,17 @@
 
   // If a window is made fullscreen by the API, fullscreen should be enabled so
   // the user can exit fullscreen.
-  base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
-      [[NSWindowFullscreenNotificationWaiter alloc] initWithWindow:ns_window]);
+  ui::NSWindowFullscreenNotificationWaiter waiter(
+      app_window->GetNativeWindow());
   app_window->SetFullscreen(AppWindow::FULLSCREEN_TYPE_WINDOW_API, true);
-  [waiter waitForEnterCount:1 exitCount:0];
+  waiter.WaitForEnterAndExitCount(1, 0);
   EXPECT_TRUE([ns_window collectionBehavior] &
               NSWindowCollectionBehaviorFullScreenPrimary);
   EXPECT_EQ(NSWidth([[ns_window contentView] frame]),
             NSWidth([ns_window frame]));
   // Once it leaves fullscreen, it is disabled again.
   app_window->SetFullscreen(AppWindow::FULLSCREEN_TYPE_WINDOW_API, false);
-  [waiter waitForEnterCount:1 exitCount:1];
+  waiter.WaitForEnterAndExitCount(1, 1);
   EXPECT_FALSE([ns_window collectionBehavior] &
                NSWindowCollectionBehaviorFullScreenPrimary);
 }
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
index 581a046..288be5a 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
@@ -114,6 +114,17 @@
     }
   }
 
+  void ResetLoadingEntryIfNecessary() {
+    if (loading_entry_ && loading_entry_->CachedView()) {
+      // The available callback here is used for showing the entry once it has
+      // loaded. We need to reset this to make sure it is not triggered to be
+      // shown once available.
+      SidePanelUtil::GetSidePanelContentProxy(loading_entry_->CachedView())
+          ->ResetAvailableCallback();
+    }
+    loading_entry_ = nullptr;
+  }
+
   SidePanelEntry* loading_entry() const { return loading_entry_; }
 
  private:
@@ -124,14 +135,6 @@
     std::move(loaded_callback_).Run(entry, absl::nullopt);
   }
 
-  void ResetLoadingEntryIfNecessary() {
-    if (loading_entry_ && loading_entry_->CachedView()) {
-      SidePanelUtil::GetSidePanelContentProxy(loading_entry_->CachedView())
-          ->ResetAvailableCallback();
-    }
-    loading_entry_ = nullptr;
-  }
-
   // When true, don't delay switching panels.
   bool show_immediately_for_testing_;
   // If the SidePanelEntry is ever discarded by the SidePanelCoordinator then we
@@ -194,11 +197,19 @@
           GetContentView()->GetViewByID(kSidePanelContentWrapperViewId));
   DCHECK(content_wrapper);
 
-  auto* current_entry = content_wrapper->loading_entry()
-                            ? content_wrapper->loading_entry()
-                            : current_entry_.get();
-  // Do not load the same entry if it's already loading or loaded.
-  if (current_entry == entry) {
+  // If we are already loading this entry, do nothing.
+  if (content_wrapper->loading_entry() == entry)
+    return;
+
+  // If we are already showing this entry, make sure we prevent any loading
+  // entry from showing once the load has finished. Say if we are showing A then
+  // trigger B to show but switch back to A while B is still loading (and not
+  // yet shown) we want to make sure B will not then be shown when it has
+  // finished loading. Note, this does not cancel the triggered load of B, B
+  // remains cached.
+  if (current_entry_.get() == entry) {
+    if (content_wrapper->loading_entry())
+      content_wrapper->ResetLoadingEntryIfNecessary();
     return;
   }
 
@@ -289,6 +300,14 @@
   return combobox_model_->GetIdAt(header_combobox_->GetSelectedIndex());
 }
 
+SidePanelEntry* SidePanelCoordinator::GetLoadingEntryForTesting() const {
+  SidePanelContentSwappingContainer* content_wrapper =
+      static_cast<SidePanelContentSwappingContainer*>(
+          GetContentView()->GetViewByID(kSidePanelContentWrapperViewId));
+  DCHECK(content_wrapper);
+  return content_wrapper->loading_entry();
+}
+
 bool SidePanelCoordinator::IsSidePanelShowing() {
   return GetContentView() != nullptr;
 }
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
index 2b0667f..00537940 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
@@ -62,6 +62,8 @@
 
   SidePanelEntry::Id GetComboboxDisplayedEntryIdForTesting() const;
 
+  SidePanelEntry* GetLoadingEntryForTesting() const;
+
   bool IsSidePanelShowing();
 
   void AddSidePanelViewStateObserver(SidePanelViewStateObserver* observer);
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
index 0190b98..5e8ada0 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator_unittest.cc
@@ -809,10 +809,24 @@
         }));
     loading_content_entry2_ = entry2.get();
     global_registry_->Register(std::move(entry2));
+
+    // Add a kAssistant entry to the global registry with content available.
+    std::unique_ptr<SidePanelEntry> entry3 = std::make_unique<SidePanelEntry>(
+        SidePanelEntry::Id::kAssistant, u"testing3",
+        ui::ImageModel::FromVectorIcon(kReadLaterIcon, ui::kColorIcon),
+        base::BindRepeating([]() {
+          auto view = std::make_unique<views::View>();
+          SidePanelUtil::GetSidePanelContentProxy(view.get())
+              ->SetAvailable(true);
+          return view;
+        }));
+    loaded_content_entry1_ = entry3.get();
+    global_registry_->Register(std::move(entry3));
   }
 
   raw_ptr<SidePanelEntry> loading_content_entry1_;
   raw_ptr<SidePanelEntry> loading_content_entry2_;
+  raw_ptr<SidePanelEntry> loaded_content_entry1_;
 };
 
 TEST_F(SidePanelCoordinatorLoadingContentTest,
@@ -846,3 +860,103 @@
   EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
             loading_content_entry2_->id());
 }
+
+TEST_F(SidePanelCoordinatorLoadingContentTest,
+       TriggerSwitchToNewEntryDuringContentLoad) {
+  coordinator_->Show(loaded_content_entry1_->id());
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loaded_content_entry1_->id());
+
+  // Switch to loading_content_entry1_ that has loading content.
+  coordinator_->Show(loading_content_entry1_->id());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), loaded_content_entry1_->id());
+  views::View* loading_content1 = loading_content_entry1_->CachedView();
+  EXPECT_NE(loading_content1, nullptr);
+  SidePanelContentProxy* loading_content_proxy1 =
+      SidePanelUtil::GetSidePanelContentProxy(loading_content1);
+  EXPECT_FALSE(loading_content_proxy1->IsAvailable());
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loaded_content_entry1_->id());
+  // Verify the loading_content_entry1_ is the loading entry.
+  EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry1_);
+
+  // While that entry is loading, switch to a different entry with content that
+  // needs to load.
+  coordinator_->Show(loading_content_entry2_->id());
+  views::View* loading_content2 = loading_content_entry2_->CachedView();
+  EXPECT_NE(loading_content2, nullptr);
+  SidePanelContentProxy* loading_content_proxy2 =
+      SidePanelUtil::GetSidePanelContentProxy(loading_content2);
+  EXPECT_FALSE(loading_content_proxy2->IsAvailable());
+  // Verify the loading_content_entry2_ is no longer the loading entry.
+  EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry2_);
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loaded_content_entry1_->id());
+
+  // Set loading_content_entry1_ as available and verify it is not made the
+  // active entry.
+  loading_content_proxy1->SetAvailable(true);
+  EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry2_);
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loaded_content_entry1_->id());
+
+  // Set loading_content_entry2_ as available and verify it is made the active
+  // entry.
+  loading_content_proxy2->SetAvailable(true);
+  EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), nullptr);
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loading_content_entry2_->id());
+}
+
+TEST_F(SidePanelCoordinatorLoadingContentTest,
+       TriggerSwitchToCurrentVisibleEntryDuringContentLoad) {
+  coordinator_->Show(loading_content_entry1_->id());
+  EXPECT_FALSE(browser_view()->right_aligned_side_panel()->GetVisible());
+  // A loading entry's view should be stored as the cached view and be
+  // unavailable.
+  views::View* loading_content = loading_content_entry1_->CachedView();
+  EXPECT_NE(loading_content, nullptr);
+  SidePanelContentProxy* loading_content_proxy1 =
+      SidePanelUtil::GetSidePanelContentProxy(loading_content);
+  EXPECT_FALSE(loading_content_proxy1->IsAvailable());
+  EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry1_);
+  // Set the content proxy to available.
+  loading_content_proxy1->SetAvailable(true);
+  EXPECT_TRUE(browser_view()->right_aligned_side_panel()->GetVisible());
+
+  // Switch to loading_content_entry2_ that has loading content.
+  coordinator_->Show(loading_content_entry2_->id());
+  EXPECT_TRUE(GetLastActiveEntryId().has_value());
+  EXPECT_EQ(GetLastActiveEntryId().value(), loading_content_entry1_->id());
+  loading_content = loading_content_entry2_->CachedView();
+  EXPECT_NE(loading_content, nullptr);
+  SidePanelContentProxy* loading_content_proxy2 =
+      SidePanelUtil::GetSidePanelContentProxy(loading_content);
+  EXPECT_FALSE(loading_content_proxy2->IsAvailable());
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loading_content_entry1_->id());
+  // Verify the loading_content_entry2_ is the loading entry.
+  EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), loading_content_entry2_);
+
+  // While that entry is loading, switch back to the currently showing entry.
+  coordinator_->Show(loading_content_entry1_->id());
+  // Verify the loading_content_entry2_ is no longer the loading entry.
+  EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), nullptr);
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loading_content_entry1_->id());
+
+  // Set loading_content_entry2_ as available and verify it is not made the
+  // active entry.
+  loading_content_proxy2->SetAvailable(true);
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loading_content_entry1_->id());
+
+  // Show loading_content_entry2_ and verify it shows without availability
+  // needing to be set again.
+  coordinator_->Show(loading_content_entry2_->id());
+  EXPECT_EQ(coordinator_->GetLoadingEntryForTesting(), nullptr);
+  EXPECT_EQ(coordinator_->GetComboboxDisplayedEntryIdForTesting(),
+            loading_content_entry2_->id());
+}
diff --git a/chrome/browser/ui/webui/chromeos/assistant_optin/assistant_optin_ui.cc b/chrome/browser/ui/webui/chromeos/assistant_optin/assistant_optin_ui.cc
index a6e20b9..7c28905 100644
--- a/chrome/browser/ui/webui/chromeos/assistant_optin/assistant_optin_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/assistant_optin/assistant_optin_ui.cc
@@ -88,8 +88,7 @@
   source->UseStringsJs();
   source->AddResourcePaths(
       base::make_span(kAssistantOptinResources, kAssistantOptinResourcesSize));
-  source->AddResourcePath("assistant_optin.js", IDR_ASSISTANT_OPTIN_JS);
-  source->SetDefaultResource(IDR_ASSISTANT_OPTIN_HTML);
+  source->SetDefaultResource(IDR_ASSISTANT_OPTIN_ASSISTANT_OPTIN_HTML);
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::WorkerSrc, "worker-src blob: 'self';");
   source->DisableTrustedTypesCSP();
diff --git a/chrome/browser/ui/webui/policy/policy_ui_handler.cc b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
index e6da57a..64b057a1 100644
--- a/chrome/browser/ui/webui/policy/policy_ui_handler.cc
+++ b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
@@ -547,50 +547,42 @@
   FireWebUIListener("status-updated", GetStatusValue(/*for_webui*/ true));
 }
 
-base::DictionaryValue PolicyUIHandler::GetStatusValue(bool for_webui) const {
-  std::unique_ptr<base::DictionaryValue> device_status(
-      new base::DictionaryValue);
-  device_status_provider_->GetStatus(device_status.get());
-  std::unique_ptr<base::DictionaryValue> user_status(new base::DictionaryValue);
-  user_status_provider_->GetStatus(user_status.get());
-  const std::string* username = user_status->FindStringKey("username");
+base::Value PolicyUIHandler::GetStatusValue(bool for_webui) const {
+  base::Value::Dict device_status = device_status_provider_->GetStatus();
+  base::Value::Dict user_status = user_status_provider_->GetStatus();
+  const std::string* username = user_status.FindString("username");
   if (username && !username->empty())
-    user_status->SetStringKey("domain", gaia::ExtractDomainName(*username));
+    user_status.Set("domain", gaia::ExtractDomainName(*username));
 
-  std::unique_ptr<base::DictionaryValue> machine_status(
-      new base::DictionaryValue);
-  machine_status_provider_->GetStatus(machine_status.get());
+  base::Value::Dict machine_status = machine_status_provider_->GetStatus();
 
-  std::unique_ptr<base::DictionaryValue> updater_status(
-      new base::DictionaryValue);
-  updater_status_provider_->GetStatus(updater_status.get());
+  base::Value::Dict updater_status = updater_status_provider_->GetStatus();
 
-  base::DictionaryValue status;
-  if (!device_status->DictEmpty()) {
+  base::Value::Dict status;
+  if (!device_status.empty()) {
     if (for_webui)
-      device_status->SetStringKey("boxLegendKey", "statusDevice");
+      device_status.Set("boxLegendKey", "statusDevice");
     status.Set("device", std::move(device_status));
   }
 
-  if (!machine_status->DictEmpty()) {
+  if (!machine_status.empty()) {
     if (for_webui)
-      machine_status->SetStringKey("boxLegendKey", GetMachineStatusLegendKey());
-
+      machine_status.Set("boxLegendKey", GetMachineStatusLegendKey());
     status.Set("machine", std::move(machine_status));
   }
 
-  if (!user_status->DictEmpty()) {
+  if (!user_status.empty()) {
     if (for_webui)
-      user_status->SetStringKey("boxLegendKey", "statusUser");
+      user_status.Set("boxLegendKey", "statusUser");
     status.Set("user", std::move(user_status));
   }
 
-  if (!updater_status->DictEmpty()) {
+  if (!updater_status.empty()) {
     if (for_webui)
-      updater_status->SetStringKey("boxLegendKey", "statusUpdater");
+      updater_status.Set("boxLegendKey", "statusUpdater");
     status.Set("updater", std::move(updater_status));
   }
-  return status;
+  return base::Value(std::move(status));
 }
 
 void PolicyUIHandler::HandleExportPoliciesJson(const base::Value::List& args) {
diff --git a/chrome/browser/ui/webui/policy/policy_ui_handler.h b/chrome/browser/ui/webui/policy/policy_ui_handler.h
index 8268a96..3994550 100644
--- a/chrome/browser/ui/webui/policy/policy_ui_handler.h
+++ b/chrome/browser/ui/webui/policy/policy_ui_handler.h
@@ -119,7 +119,7 @@
   // enabled (device and/or user), a dictionary containing status information.
   // If |for_webui| is true, values needed for webui will be included
   // additionally.
-  base::DictionaryValue GetStatusValue(bool for_webui) const;
+  base::Value GetStatusValue(bool for_webui) const;
 
   // Build a JSON string of all the policies.
   std::string GetPoliciesAsJson();
diff --git a/chrome/browser/web_applications/app_service/web_apps.cc b/chrome/browser/web_applications/app_service/web_apps.cc
index 60ef747..b3227ae 100644
--- a/chrome/browser/web_applications/app_service/web_apps.cc
+++ b/chrome/browser/web_applications/app_service/web_apps.cc
@@ -149,6 +149,13 @@
                               std::move(callback));
 }
 
+void WebApps::Launch(const std::string& app_id,
+                     int32_t event_flags,
+                     apps::LaunchSource launch_source,
+                     apps::WindowInfoPtr window_info) {
+  // TODO(crbug.com/1253250): Add the implementation.
+}
+
 void WebApps::LaunchAppWithParams(apps::AppLaunchParams&& params,
                                   apps::LaunchCallback callback) {
   publisher_helper().LaunchAppWithParams(std::move(params));
diff --git a/chrome/browser/web_applications/app_service/web_apps.h b/chrome/browser/web_applications/app_service/web_apps.h
index ef58c75..f1ae852 100644
--- a/chrome/browser/web_applications/app_service/web_apps.h
+++ b/chrome/browser/web_applications/app_service/web_apps.h
@@ -18,6 +18,7 @@
 #include "chrome/browser/web_applications/app_service/web_app_publisher_helper.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
+#include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/cpp/publisher_base.h"
@@ -90,6 +91,10 @@
                 int32_t size_hint_in_dip,
                 bool allow_placeholder_icon,
                 apps::LoadIconCallback callback) override;
+  void Launch(const std::string& app_id,
+              int32_t event_flags,
+              apps::LaunchSource launch_source,
+              apps::WindowInfoPtr window_info) override;
   void LaunchAppWithParams(apps::AppLaunchParams&& params,
                            apps::LaunchCallback callback) override;
   void LaunchShortcut(const std::string& app_id,
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 5718e2c..9f732a5 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1657259924-75056b5dbf99cb9107b9e64309591676138fb661.profdata
+chrome-linux-main-1657280473-97760b0045a9dcf9604efbbd502abaae1943b462.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 63fdc01e..d36089b 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1657238380-a69973cb64c57b47020c4d16e861893d5f07a9e8.profdata
+chrome-mac-main-1657259924-b2a8bdfd6316a9ec22c0cba446b813e5a0750596.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 87bfc65..c59f6fd 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1657249050-2276cbfd2eaf7dbdd2a6b6a9d2bc0069faaf03b3.profdata
+chrome-win32-main-1657270777-ae18d351a8c7ebafd25c4e2a096055a4815b06fb.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 2dfd825..04476615 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1657249050-c50aa1d9b7e19b31fd9de89971d7075df40c84e8.profdata
+chrome-win64-main-1657270777-7750841acf3beec2b6c0fb266bc0a81eb66804d0.profdata
diff --git a/chrome/common/extensions/api/passwords_private.idl b/chrome/common/extensions/api/passwords_private.idl
index 28ce5e1..701f334 100644
--- a/chrome/common/extensions/api/passwords_private.idl
+++ b/chrome/common/extensions/api/passwords_private.idl
@@ -38,6 +38,16 @@
     PHISHED_AND_LEAKED
   };
 
+  enum PasswordStoreSet {
+    // Corresponds to profile-scoped password store.
+    DEVICE,
+    // Corresponds to Gaia-account-scoped password store (i.e. account store).
+    ACCOUNT,
+    // Corresponds to both profile-scoped and Gaia-account-scoped password
+    // stores.
+    DEVICE_AND_ACCOUNT
+  };
+
   enum PasswordCheckState {
     // idle state, e.g. after successfully running to completion.
     IDLE,
@@ -275,28 +285,19 @@
         ChangeSavedPasswordParams params,
         optional ChangeSavedPasswordCallback callback);
 
-    // Removes the saved password corresponding to |id|. If no saved password
-    // for this pair exists, this function is a no-op. |id|: The id for the
-    // password entry being removed.
-    static void removeSavedPassword(long id);
-
-    // Removes the saved password corresponding to |ids|. If no saved password
-    // exists for a certain id, that id is ignored. Undoing this operation via
-    // undoRemoveSavedPasswordOrException will restore all the removed passwords
-    // in the batch.
-    static void removeSavedPasswords(long[] ids);
+    // Removes the saved password corresponding to |id| in |fromStores|. If no
+    // saved password for this pair exists, this function is a no-op.
+    // |id|: The id for the password entry being removed.
+    // |fromStores|: The store(s) from which the password entry is being
+    // removed.
+    static void removeSavedPassword(long id, PasswordStoreSet fromStores);
 
     // Removes the saved password exception corresponding to |id|. If
-    // no exception with this id exists, this function is a no-op.
-    // |id|: The id for the exception url entry being removed.
+    // no exception with this id exists, this function is a no-op. This will
+    // remove exception from both stores.
+    // |id|: The id for the exception url entry is being removed.
     static void removePasswordException(long id);
 
-    // Removes the saved password exceptions corresponding to |ids|. If
-    // no exception exists for a certain id, that id is ignored. Undoing this
-    // operation via undoRemoveSavedPasswordOrException will restore all the
-    // removed exceptions in the batch.
-    static void removePasswordExceptions(long[] ids);
-
     // Undoes the last removal of saved password(s) or exception(s).
     static void undoRemoveSavedPasswordOrException();
 
diff --git a/chrome/common/profiler/thread_profiler_browsertest.cc b/chrome/common/profiler/thread_profiler_browsertest.cc
index 7468fd2..0105371 100644
--- a/chrome/common/profiler/thread_profiler_browsertest.cc
+++ b/chrome/common/profiler/thread_profiler_browsertest.cc
@@ -156,7 +156,6 @@
 
 #if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_ARMEL)
 // These threads are not currently profiled on Android.
-#define MAYBE_BrowserProcessMainThread DISABLED_BrowserProcessMainThread
 #define MAYBE_BrowserProcessIOThread DISABLED_BrowserProcessIOThread
 #define MAYBE_GpuProcessMainThread DISABLED_GpuProcessMainThread
 #define MAYBE_GpuProcessIOThread DISABLED_GpuProcessIOThread
@@ -165,7 +164,6 @@
 #define MAYBE_NetworkServiceProcessIOThread \
   DISABLED_NetworkServiceProcessIOThread
 #else
-#define MAYBE_BrowserProcessMainThread BrowserProcessMainThread
 #define MAYBE_BrowserProcessIOThread BrowserProcessIOThread
 #define MAYBE_GpuProcessMainThread GpuProcessMainThread
 #define MAYBE_GpuProcessIOThread GpuProcessIOThread
@@ -177,8 +175,7 @@
 // processes/threads. We've seen multiple breakages previously where profiles
 // were dropped as a result of bugs introduced by mojo refactorings.
 
-IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest,
-                       MAYBE_BrowserProcessMainThread) {
+IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest, BrowserProcessMainThread) {
   EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
                              metrics::BROWSER_PROCESS, metrics::MAIN_THREAD));
 }
diff --git a/chrome/common/profiler/thread_profiler_platform_configuration.cc b/chrome/common/profiler/thread_profiler_platform_configuration.cc
index 2bb8113..be9c3ea 100644
--- a/chrome/common/profiler/thread_profiler_platform_configuration.cc
+++ b/chrome/common/profiler/thread_profiler_platform_configuration.cc
@@ -226,6 +226,8 @@
   switch (process) {
     case metrics::CallStackProfileParams::Process::kRenderer:
       return true;
+    case metrics::CallStackProfileParams::Process::kBrowser:
+      return thread == metrics::CallStackProfileParams::Thread::kMain;
 
     default:
       return false;
diff --git a/chrome/common/profiler/thread_profiler_platform_configuration_unittest.cc b/chrome/common/profiler/thread_profiler_platform_configuration_unittest.cc
index 668c89e..eb43539 100644
--- a/chrome/common/profiler/thread_profiler_platform_configuration_unittest.cc
+++ b/chrome/common/profiler/thread_profiler_platform_configuration_unittest.cc
@@ -180,7 +180,7 @@
 MAYBE_PLATFORM_CONFIG_TEST_F(ThreadProfilerPlatformConfigurationTest,
                              IsEnabledForThread) {
 #if BUILDFLAG(IS_ANDROID)
-  EXPECT_FALSE(config()->IsEnabledForThread(
+  EXPECT_TRUE(config()->IsEnabledForThread(
       metrics::CallStackProfileParams::Process::kBrowser,
       metrics::CallStackProfileParams::Thread::kMain));
   EXPECT_FALSE(config()->IsEnabledForThread(
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 7564ba3e..94f969f 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -276,9 +276,8 @@
 const char kSyncLearnMoreURL[] =
     "https://support.google.com/chrome/?p=settings_sign_in";
 
-// TODO(https://crbug.com/1282157): convert this to a p-link.
 const char kSigninInterceptManagedDisclaimerLearnMoreURL[] =
-    "https://support.google.com/chrome/a/answer/11198768";
+    "https://support.google.com/chrome/a/?p=profile_separation";
 
 #if !BUILDFLAG(IS_ANDROID)
 const char kSyncTrustedVaultOptInURL[] =
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index c68fed81..4133572 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3623,6 +3623,7 @@
         "../browser/ash/login/password_change_browsertest.cc",
         "../browser/ash/login/proxy_auth_dialog_browsertest.cc",
         "../browser/ash/login/quick_unlock/pin_migration_browsertest.cc",
+        "../browser/ash/login/reporting/login_logout_reporter_browsertest.cc",
         "../browser/ash/login/reset_browsertest.cc",
         "../browser/ash/login/saml/fake_saml_idp_mixin.cc",
         "../browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.cc",
@@ -4158,6 +4159,7 @@
         "//chromeos/dbus/cryptohome:cryptohome_proto",
         "//chromeos/dbus/dlp",
         "//chromeos/dbus/image_burner",
+        "//chromeos/dbus/missive:test_support",
         "//chromeos/dbus/power",
         "//chromeos/login/login_state:test_support",
         "//chromeos/process_proxy",
diff --git a/chrome/test/data/extensions/api_test/debugger_file_access/background.js b/chrome/test/data/extensions/api_test/debugger_file_access/background.js
index b2d2c63..0acf70f 100644
--- a/chrome/test/data/extensions/api_test/debugger_file_access/background.js
+++ b/chrome/test/data/extensions/api_test/debugger_file_access/background.js
@@ -91,10 +91,13 @@
             responded = true;
             if (expectFileAccess) {
               chrome.test.assertNoLastError();
-              chrome.tabs.remove(tabId);
             } else {
-              chrome.test.assertLastError('Detached while handling command.');
+              chrome.test.assertLastError(JSON.stringify({
+                code: -32000,
+                message: 'Navigating to local URL is not allowed'
+              }));
             }
+            chrome.tabs.remove(tabId);
           }
 
           function onDetach(from, reason) {
diff --git a/chrome/test/data/extensions/api_test/passwords_private/test.js b/chrome/test/data/extensions/api_test/passwords_private/test.js
index 441f093..a285996 100644
--- a/chrome/test/data/extensions/api_test/passwords_private/test.js
+++ b/chrome/test/data/extensions/api_test/passwords_private/test.js
@@ -148,7 +148,8 @@
 
       if (numCalls == 1) {
         numSavedPasswords = savedPasswordsList.length;
-        chrome.passwordsPrivate.removeSavedPassword(savedPasswordsList[0].id);
+        chrome.passwordsPrivate.removeSavedPassword(savedPasswordsList[0].id,
+            chrome.passwordsPrivate.PasswordStoreSet.DEVICE);
       } else if (numCalls == 2) {
         chrome.test.assertEq(savedPasswordsList.length, numSavedPasswords - 1);
         chrome.passwordsPrivate.undoRemoveSavedPasswordOrException();
@@ -164,34 +165,6 @@
     chrome.passwordsPrivate.getSavedPasswordList(callback);
   },
 
-  function removeAndUndoRemoveSavedPasswordsBatch() {
-    var numCalls = 0;
-    var numSavedPasswords;
-
-    var callback = function(savedPasswordsList) {
-      numCalls++;
-
-      if (numCalls == 1) {
-        numSavedPasswords = savedPasswordsList.length;
-        // There should be at least two passwords for this test to make sense.
-        chrome.test.assertTrue(numSavedPasswords >= 2);
-        chrome.passwordsPrivate.removeSavedPasswords(
-            Array(savedPasswordsList[0].id, savedPasswordsList[1].id));
-      } else if (numCalls == 2) {
-        chrome.test.assertEq(savedPasswordsList.length, numSavedPasswords - 2);
-        chrome.passwordsPrivate.undoRemoveSavedPasswordOrException();
-      } else if (numCalls == 3) {
-        chrome.test.assertEq(savedPasswordsList.length, numSavedPasswords);
-        chrome.test.succeed();
-      } else {
-        chrome.test.fail();
-      }
-    };
-
-    chrome.passwordsPrivate.onSavedPasswordsListChanged.addListener(callback);
-    chrome.passwordsPrivate.getSavedPasswordList(callback);
-  },
-
   function removeAndUndoRemovePasswordException() {
     var numCalls = 0;
     var numPasswordExceptions;
@@ -220,36 +193,6 @@
     chrome.passwordsPrivate.getPasswordExceptionList(callback);
   },
 
-  function removeAndUndoRemovePasswordExceptionsBatch() {
-    var numCalls = 0;
-    var numPasswordExceptions;
-    var callback = function(passwordExceptionsList) {
-      numCalls++;
-
-      if (numCalls == 1) {
-        numPasswordExceptions = passwordExceptionsList.length;
-        // There should be at least two exceptions for this test to make sense.
-        chrome.test.assertTrue(numPasswordExceptions >= 2);
-        chrome.passwordsPrivate.removePasswordExceptions(
-            Array(passwordExceptionsList[0].id, passwordExceptionsList[1].id));
-      } else if (numCalls == 2) {
-        chrome.test.assertEq(
-            passwordExceptionsList.length, numPasswordExceptions - 2);
-        chrome.passwordsPrivate.undoRemoveSavedPasswordOrException();
-      } else if (numCalls == 3) {
-        chrome.test.assertEq(
-            passwordExceptionsList.length, numPasswordExceptions);
-        chrome.test.succeed();
-      } else {
-        chrome.test.fail();
-      }
-    };
-
-    chrome.passwordsPrivate.onPasswordExceptionsListChanged.addListener(
-        callback);
-    chrome.passwordsPrivate.getPasswordExceptionList(callback);
-  },
-
   function requestPlaintextPassword() {
     chrome.passwordsPrivate.requestPlaintextPassword(
         0, chrome.passwordsPrivate.PlaintextReason.VIEW, password => {
diff --git a/chrome/test/data/webui/settings/autofill_section_test.ts b/chrome/test/data/webui/settings/autofill_section_test.ts
index 4ee30af6..44f1554 100644
--- a/chrome/test/data/webui/settings/autofill_section_test.ts
+++ b/chrome/test/data/webui/settings/autofill_section_test.ts
@@ -517,8 +517,8 @@
     address.fullNames = ['Name'];
     address.companyName = 'Organization';
     address.addressLines = 'Street address';
-    address.addressLevel2 = 'City';
     address.addressLevel1 = 'State';
+    address.addressLevel2 = 'City';
     address.postalCode = 'ZIP code';
     address.countryCode = 'US';
     address.phoneNumbers = ['Phone'];
@@ -595,12 +595,15 @@
   test('verifyEditingGBAddress', function() {
     const address = createEmptyAddressEntry();
     const company_enabled = loadTimeData.getBoolean('EnableCompanyName');
-    const honorific_enabled = loadTimeData.getBoolean('showHonorific');
+    const honorific_enabled = loadTimeData.getBoolean('EnableHonorificPrefix');
+    const extended_address_format_enabled =
+        loadTimeData.getBoolean('EnableExtendedAddressFormat');
 
     address.honorific = 'Lord';
     address.fullNames = ['Name'];
     address.companyName = 'Organization';
     address.addressLines = 'Street address';
+    address.addressLevel1 = 'County';
     address.addressLevel2 = 'Post town';
     address.postalCode = 'Postal code';
     address.countryCode = 'GB';
@@ -610,7 +613,8 @@
     return createAddressDialog(address).then(function(dialog) {
       const rows = dialog.$.dialog.querySelectorAll('.address-row');
       assertEquals(
-          6 + (company_enabled ? 1 : 0) + (honorific_enabled ? 1 : 0),
+          6 + (company_enabled ? 1 : 0) + (honorific_enabled ? 1 : 0) +
+              (extended_address_format_enabled ? 1 : 0),
           rows.length);
 
       let index = 0;
@@ -669,6 +673,15 @@
       assertEquals(1, cols.length);
       assertEquals(address.postalCode, cols[0]!.value);
       index++;
+      // County
+      if (extended_address_format_enabled) {
+        row = rows[index]!;
+        cols = row.querySelectorAll<SettingsTextareaElement|CrInputElement>(
+            '.address-column');
+        assertEquals(1, cols.length);
+        assertEquals(address.addressLevel1, cols[0]!.value);
+      }
+      index++;
       // Phone, Email
       row = rows[index]!;
       cols = row.querySelectorAll<SettingsTextareaElement|CrInputElement>(
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index 2ebba8e..01e0075 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -236,7 +236,8 @@
   // Use 'EnableCompanyName' to inform tests that the feature is enabled.
   const loadTimeDataOverride = {};
   loadTimeDataOverride['EnableCompanyName'] = true;
-  loadTimeDataOverride['showHonorific'] = true;
+  loadTimeDataOverride['EnableHonorificPrefix'] = true;
+  loadTimeDataOverride['EnableExtendedAddressFormat'] = true;
   loadTimeData.overrideValues(loadTimeDataOverride);
   mocha.run();
 });
diff --git a/chrome/test/data/webui/settings/password_view_test.ts b/chrome/test/data/webui/settings/password_view_test.ts
index 6267e80..65cd100 100644
--- a/chrome/test/data/webui/settings/password_view_test.ts
+++ b/chrome/test/data/webui/settings/password_view_test.ts
@@ -415,8 +415,11 @@
 
         page.shadowRoot!.querySelector<HTMLButtonElement>(
                             '#deleteButton')!.click();
-        const id = await passwordManager.whenCalled('removeSavedPassword');
+        const {id, fromStores} =
+            await passwordManager.whenCalled('removeSavedPassword');
         assertEquals(ID, id);
+        assertEquals(
+            chrome.passwordsPrivate.PasswordStoreSet.DEVICE, fromStores);
         await flushTasks();
 
         assertEquals(routes.PASSWORDS, Router.getInstance().getCurrentRoute());
@@ -434,14 +437,14 @@
           createPasswordEntry({
             url: SITE,
             username: USERNAME,
-            id: 0,
+            id: ID,
             frontendId: ID,
             fromAccountStore: false
           }),
           createPasswordEntry({
             url: SITE,
             username: USERNAME,
-            id: 1,
+            id: ID,
             frontendId: ID,
             fromAccountStore: true
           })
@@ -450,7 +453,7 @@
         document.body.appendChild(page);
         const params = new URLSearchParams({
           deviceId: '0',
-          accountId: '1',
+          accountId: '0',
         });
         Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
         await flushTasks();
@@ -463,12 +466,17 @@
         assertTrue(!!dialog);
         assertDeepEquals(
             createMultiStorePasswordEntry(
-                {url: SITE, username: USERNAME, deviceId: 0, accountId: 1}),
+                {url: SITE, username: USERNAME, deviceId: ID, accountId: ID}),
             dialog.duplicatedPassword);
 
         // click delete on the dialog.
         dialog.$.removeButton.click();
-        await passwordManager.whenCalled('removeSavedPasswords');
+        const {id, fromStores} =
+            await passwordManager.whenCalled('removeSavedPassword');
+        assertEquals(ID, id);
+        assertEquals(
+            chrome.passwordsPrivate.PasswordStoreSet.DEVICE_AND_ACCOUNT,
+            fromStores);
         await flushTasks();
 
         assertEquals(
diff --git a/chrome/test/data/webui/settings/passwords_section_test.ts b/chrome/test/data/webui/settings/passwords_section_test.ts
index d2ff95b..f2bac57 100644
--- a/chrome/test/data/webui/settings/passwords_section_test.ts
+++ b/chrome/test/data/webui/settings/passwords_section_test.ts
@@ -651,7 +651,7 @@
     firstNode.$.moreActionsButton.click();
     passwordsSection.$.passwordsListHandler.$.menuRemovePassword.click();
 
-    const id = await passwordManager.whenCalled('removeSavedPassword');
+    const {id} = await passwordManager.whenCalled('removeSavedPassword');
     // Verify that the expected value was passed to the proxy.
     assertEquals(firstPassword.id, id);
     assertEquals(
@@ -975,12 +975,12 @@
     // called on |passwordManager| and continues recursively until no more items
     // exist.
     function removeNextRecursive(): Promise<void> {
-      passwordManager.resetResolver('removeExceptions');
+      passwordManager.resetResolver('removeException');
       clickRemoveButton();
-      return passwordManager.whenCalled('removeExceptions').then(ids => {
+      return passwordManager.whenCalled('removeException').then(id => {
         // Verify that the event matches the expected value.
         assertTrue(item < exceptionList.length);
-        assertDeepEquals(ids, [exceptionList[item]!.id]);
+        assertEquals(id, exceptionList[item]!.id);
 
         if (++item < exceptionList.length) {
           return removeNextRecursive();
@@ -1000,7 +1000,7 @@
     const deviceCopy =
         createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: false});
     const accountCopy =
-        createPasswordEntry({frontendId: 42, id: 1, fromAccountStore: true});
+        createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: true});
 
     const passwordsSection = elementFactory.createPasswordsSection(
         passwordManager, [], [deviceCopy, accountCopy]);
@@ -1009,10 +1009,9 @@
         getDomRepeatChildren(passwordsSection.$.passwordExceptionsList);
     mergedEntry!.querySelector<HTMLElement>('#removeExceptionButton')!.click();
 
-    // Verify both ids get passed to the proxy.
-    const ids = await passwordManager.whenCalled('removeExceptions');
-    assertTrue(ids.includes(deviceCopy.id));
-    assertTrue(ids.includes(accountCopy.id));
+    // Verify id is passed to the proxy.
+    const id = await passwordManager.whenCalled('removeException');
+    assertEquals(deviceCopy.id, id);
   });
 
   test('showSavedPasswordListItem', async function() {
@@ -1438,7 +1437,7 @@
       const accountCopy =
           createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: true});
       const deviceCopy =
-          createPasswordEntry({frontendId: 42, id: 1, fromAccountStore: false});
+          createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: false});
       const passwordsSection = elementFactory.createPasswordsSection(
           passwordManager, [accountCopy, deviceCopy], []);
 
@@ -1464,10 +1463,12 @@
           removeDialog.$.removeFromAccountCheckbox.checked &&
           removeDialog.$.removeFromDeviceCheckbox.checked);
       removeDialog.$.removeButton.click();
-      const removedIds =
-          await passwordManager.whenCalled('removeSavedPasswords');
-      assertTrue(removedIds.includes(accountCopy.id));
-      assertTrue(removedIds.includes(deviceCopy.id));
+      const {id, fromStores} =
+          await passwordManager.whenCalled('removeSavedPassword');
+      assertEquals(accountCopy.id, id);
+      assertEquals(
+          chrome.passwordsPrivate.PasswordStoreSet.DEVICE_AND_ACCOUNT,
+          fromStores);
     });
 
     // Test verifies that if the user attempts to remove a password stored
@@ -1477,7 +1478,7 @@
       const accountCopy =
           createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: true});
       const deviceCopy =
-          createPasswordEntry({frontendId: 42, id: 1, fromAccountStore: false});
+          createPasswordEntry({frontendId: 42, id: 0, fromAccountStore: false});
       const passwordsSection = elementFactory.createPasswordsSection(
           passwordManager, [accountCopy, deviceCopy], []);
 
@@ -1505,9 +1506,10 @@
           !removeDialog.$.removeFromAccountCheckbox.checked &&
           removeDialog.$.removeFromDeviceCheckbox.checked);
       removeDialog.$.removeButton.click();
-      const removedIds =
-          await passwordManager.whenCalled('removeSavedPasswords');
-      assertTrue(removedIds.includes(deviceCopy.id));
+      const {id, fromStores} =
+          await passwordManager.whenCalled('removeSavedPassword');
+      assertEquals(deviceCopy.id, id);
+      assertEquals(chrome.passwordsPrivate.PasswordStoreSet.DEVICE, fromStores);
     });
   }
 
diff --git a/chrome/test/data/webui/settings/test_password_manager_proxy.ts b/chrome/test/data/webui/settings/test_password_manager_proxy.ts
index 17c61509..7241a55a 100644
--- a/chrome/test/data/webui/settings/test_password_manager_proxy.ts
+++ b/chrome/test/data/webui/settings/test_password_manager_proxy.ts
@@ -110,10 +110,8 @@
       'recordPasswordCheckReferrer',
       'refreshScriptsIfNecessary',
       'removeException',
-      'removeExceptions',
       'removeInsecureCredential',
       'removeSavedPassword',
-      'removeSavedPasswords',
       'requestExportProgressStatus',
       'requestPlaintextPassword',
       'startAutomatedPasswordChange',
@@ -164,20 +162,16 @@
 
   recordPasswordsPageAccessInSettings() {}
 
-  removeSavedPassword(id: number) {
+  removeSavedPassword(
+      id: number, fromStores: chrome.passwordsPrivate.PasswordStoreSet) {
     this.actual_.removed.passwords++;
-    this.methodCalled('removeSavedPassword', id);
+    this.methodCalled('removeSavedPassword', {id, fromStores});
   }
 
   movePasswordsToAccount(ids: number[]) {
     this.methodCalled('movePasswordsToAccount', ids);
   }
 
-  removeSavedPasswords(ids: number[]) {
-    this.actual_.removed.passwords += ids.length;
-    this.methodCalled('removeSavedPasswords', ids);
-  }
-
   addExceptionListChangedListener(listener:
                                       PasswordExceptionListChangedListener) {
     this.actual_.listening.exceptions++;
@@ -199,11 +193,6 @@
     this.methodCalled('removeException', id);
   }
 
-  removeExceptions(ids: number[]) {
-    this.actual_.removed.exceptions += ids.length;
-    this.methodCalled('removeExceptions', ids);
-  }
-
   requestPlaintextPassword(
       id: number, reason: chrome.passwordsPrivate.PlaintextReason) {
     this.methodCalled('requestPlaintextPassword', {id, reason});
diff --git a/chrome/updater/app/server/win/com_classes_legacy.cc b/chrome/updater/app/server/win/com_classes_legacy.cc
index 1fb7a36..ec101d3 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy.cc
@@ -206,8 +206,8 @@
 
 STDMETHODIMP LegacyOnDemandImpl::get_command(BSTR command_id,
                                              IDispatch** command) {
-  return LegacyAppCommandWebImpl::CreateAppCommandWeb(
-      GetUpdaterScope(), base::UTF8ToWide(app_id()), command_id, command);
+  return Microsoft::WRL::MakeAndInitialize<LegacyAppCommandWebImpl>(
+      command, GetUpdaterScope(), base::UTF8ToWide(app_id()), command_id);
 }
 
 STDMETHODIMP LegacyOnDemandImpl::get_currentState(IDispatch** current_state) {
@@ -514,48 +514,17 @@
 LegacyAppCommandWebImpl::LegacyAppCommandWebImpl() = default;
 LegacyAppCommandWebImpl::~LegacyAppCommandWebImpl() = default;
 
-HRESULT LegacyAppCommandWebImpl::CreateAppCommandWeb(
+HRESULT LegacyAppCommandWebImpl::RuntimeClassInitialize(
     UpdaterScope scope,
     const std::wstring& app_id,
-    const std::wstring& command_id,
-    IDispatch** app_command_web) {
-  DCHECK(app_command_web);
-
-  Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl> web_impl;
-  if (HRESULT hr =
-          CreateLegacyAppCommandWebImpl(scope, app_id, command_id, web_impl);
-      FAILED(hr)) {
-    return hr;
-  }
-
-  return web_impl.CopyTo(app_command_web);
-}
-
-HRESULT LegacyAppCommandWebImpl::CreateLegacyAppCommandWebImpl(
-    UpdaterScope scope,
-    const std::wstring& app_id,
-    const std::wstring& command_id,
-    Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl>& web_impl) {
-  Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl> web;
-
-  if (HRESULT hr =
-          Microsoft::WRL::MakeAndInitialize<LegacyAppCommandWebImpl>(&web);
-      FAILED(hr)) {
-    return hr;
-  }
-
+    const std::wstring& command_id) {
   if (HRESULT hr = AppCommandRunner::LoadAppCommand(scope, app_id, command_id,
-                                                    web->app_command_runner_);
+                                                    app_command_runner_);
       FAILED(hr)) {
     return hr;
   }
 
-  if (HRESULT hr = web->InitializeTypeInfo(); FAILED(hr)) {
-    return hr;
-  }
-
-  web_impl.Swap(web);
-  return S_OK;
+  return InitializeTypeInfo();
 }
 
 STDMETHODIMP LegacyAppCommandWebImpl::get_status(UINT* status) {
diff --git a/chrome/updater/app/server/win/com_classes_legacy.h b/chrome/updater/app/server/win/com_classes_legacy.h
index b2d64bb..98dd6a65 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.h
+++ b/chrome/updater/app/server/win/com_classes_legacy.h
@@ -222,19 +222,18 @@
           IAppCommandWeb,
           IDispatch> {
  public:
-  // Creates an instance of `IAppCommandWeb` for the given `app_id` and
-  // `command_id`. Returns an error if the command format does not exist in the
-  // registry, or if the command format in the registry has an invalid
-  // formatting.
-  static HRESULT CreateAppCommandWeb(UpdaterScope scope,
-                                     const std::wstring& app_id,
-                                     const std::wstring& command_id,
-                                     IDispatch** app_command_web);
-
   LegacyAppCommandWebImpl();
   LegacyAppCommandWebImpl(const LegacyAppCommandWebImpl&) = delete;
   LegacyAppCommandWebImpl& operator=(const LegacyAppCommandWebImpl&) = delete;
 
+  // Initializes an instance of `IAppCommandWeb` for the given `scope`,
+  // `app_id`, and `command_id`. Returns an error if the command format does not
+  // exist in the registry, or if the command format in the registry has an
+  // invalid formatting, or if the type information could not be initialized.
+  HRESULT RuntimeClassInitialize(UpdaterScope scope,
+                                 const std::wstring& app_id,
+                                 const std::wstring& command_id);
+
   // Overrides for IAppCommandWeb.
   IFACEMETHODIMP get_status(UINT* status) override;
   IFACEMETHODIMP get_exitCode(DWORD* exit_code) override;
@@ -280,12 +279,6 @@
  private:
   ~LegacyAppCommandWebImpl() override;
 
-  static HRESULT CreateLegacyAppCommandWebImpl(
-      UpdaterScope scope,
-      const std::wstring& app_id,
-      const std::wstring& command_id,
-      Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl>& web_impl);
-
   HRESULT InitializeTypeInfo();
 
   base::Process process_;
diff --git a/chrome/updater/app/server/win/com_classes_legacy_unittest.cc b/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
index d047df9..e2cb896 100644
--- a/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy_unittest.cc
@@ -18,10 +18,12 @@
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/test/bind.h"
 #include "base/test/test_timeouts.h"
 #include "base/time/time.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/scoped_variant.h"
+#include "chrome/updater/test/integration_tests_impl.h"
 #include "chrome/updater/test_scope.h"
 #include "chrome/updater/unittest_util_win.h"
 #include "chrome/updater/win/test/test_executables.h"
@@ -65,39 +67,17 @@
     CreateAppCommandRegistry(GetTestScope(), app_id, command_id,
                              command_line_format);
 
-    return LegacyAppCommandWebImpl::CreateLegacyAppCommandWebImpl(
-        GetTestScope(), app_id, command_id, app_command_web);
-  }
-
-  void NoAppTest() {
-    Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl> app_command_web;
-    EXPECT_HRESULT_FAILED(
-        LegacyAppCommandWebImpl::CreateLegacyAppCommandWebImpl(
-            GetTestScope(), kAppId1, kCmdId1, app_command_web));
-  }
-
-  void NoCmdTest() {
-    Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl> app_command_web;
-    CreateAppCommandRegistry(GetTestScope(), kAppId1, kCmdId1, kCmdLineValid);
-
-    EXPECT_HRESULT_FAILED(
-        LegacyAppCommandWebImpl::CreateLegacyAppCommandWebImpl(
-            GetTestScope(), kAppId1, kCmdId2, app_command_web));
+    return Microsoft::WRL::MakeAndInitialize<LegacyAppCommandWebImpl>(
+        &app_command_web, GetTestScope(), app_id, command_id);
   }
 
   void WaitForUpdateCompletion(
-      Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl>& app_command_web,
-      const base::TimeDelta& timeout) {
-    const base::TimeTicks start_time = base::TimeTicks::Now();
-
-    while (base::TimeTicks::Now() - start_time < timeout) {
+      Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl>& app_command_web) {
+    EXPECT_TRUE(test::WaitFor(base::BindLambdaForTesting([&]() {
       UINT status = 0;
       EXPECT_HRESULT_SUCCEEDED(app_command_web->get_status(&status));
-      if (status == COMMAND_STATUS_COMPLETE)
-        return;
-
-      base::WaitableEvent().TimedWait(TestTimeouts::tiny_timeout());
-    }
+      return status == COMMAND_STATUS_COMPLETE;
+    })));
   }
 
   base::CommandLine cmd_exe_command_line_;
@@ -105,11 +85,19 @@
 };
 
 TEST_F(LegacyAppCommandWebImplTest, NoApp) {
-  NoAppTest();
+  Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl> app_command_web;
+  EXPECT_HRESULT_FAILED(
+      Microsoft::WRL::MakeAndInitialize<LegacyAppCommandWebImpl>(
+          &app_command_web, GetTestScope(), kAppId1, kCmdId1));
 }
 
 TEST_F(LegacyAppCommandWebImplTest, NoCmd) {
-  NoCmdTest();
+  Microsoft::WRL::ComPtr<LegacyAppCommandWebImpl> app_command_web;
+  CreateAppCommandRegistry(GetTestScope(), kAppId1, kCmdId1, kCmdLineValid);
+
+  EXPECT_HRESULT_FAILED(
+      Microsoft::WRL::MakeAndInitialize<LegacyAppCommandWebImpl>(
+          &app_command_web, GetTestScope(), kAppId1, kCmdId2));
 }
 
 TEST_F(LegacyAppCommandWebImplTest, Execute) {
@@ -137,7 +125,7 @@
                                base::win::ScopedVariant::kEmptyVariant,
                                base::win::ScopedVariant::kEmptyVariant));
 
-  WaitForUpdateCompletion(app_command_web, TestTimeouts::action_max_timeout());
+  WaitForUpdateCompletion(app_command_web);
 
   EXPECT_HRESULT_SUCCEEDED(app_command_web->get_status(&status));
   EXPECT_EQ(status, COMMAND_STATUS_COMPLETE);
@@ -163,7 +151,7 @@
                                base::win::ScopedVariant::kEmptyVariant,
                                base::win::ScopedVariant::kEmptyVariant,
                                base::win::ScopedVariant::kEmptyVariant));
-  WaitForUpdateCompletion(app_command_web, TestTimeouts::action_max_timeout());
+  WaitForUpdateCompletion(app_command_web);
 
   DWORD exit_code = 0;
   EXPECT_HRESULT_SUCCEEDED(app_command_web->get_exitCode(&exit_code));
@@ -231,7 +219,7 @@
 
   event.Signal();
 
-  WaitForUpdateCompletion(app_command_web, TestTimeouts::action_max_timeout());
+  WaitForUpdateCompletion(app_command_web);
 
   DWORD exit_code = 0;
   EXPECT_HRESULT_SUCCEEDED(app_command_web->get_exitCode(&exit_code));
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index 2b5498a..fb33770 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -830,8 +830,8 @@
 // If ash-chrome is newer than the browser, then some fields may not be
 // processed by the browser.
 //
-// Next version: 47
-// Next id: 47
+// Next version: 48
+// Next id: 48
 [Stable, RenamedFrom="crosapi.mojom.LacrosInitParams"]
 struct BrowserInitParams {
   // This is ash-chrome's version of the Crosapi interface. This is used by
@@ -1120,6 +1120,10 @@
   // Tells lacros whether ash uses floss for bluetooth.
   [MinVersion=46]
   bool use_floss_bluetooth@46;
+
+  // Tells lacros whether the currently logged in user is the device owner.
+  [MinVersion=47]
+  bool is_current_user_device_owner@47;
 };
 
 // Parameters to specify OpenUrl behavior.
diff --git a/chromeos/strings/chromeos_strings_nl.xtb b/chromeos/strings/chromeos_strings_nl.xtb
index e7b23b2..34d8db1 100644
--- a/chromeos/strings/chromeos_strings_nl.xtb
+++ b/chromeos/strings/chromeos_strings_nl.xtb
@@ -640,7 +640,7 @@
 <translation id="6447630859861661624">Naar Accountinstellingen gaan</translation>
 <translation id="6456394469623773452">Goed</translation>
 <translation id="6463239094587744704">{PAGE_NUMBER,plural, =0{Pagina opnieuw scannen?}=1{Pagina {PAGE_NUMBER} opnieuw scannen?}other{Pagina {PAGE_NUMBER} opnieuw scannen?}}</translation>
-<translation id="6472207088655375767">Eenmalig wachtwoord (OTP)</translation>
+<translation id="6472207088655375767">Eenmalig wachtwoord</translation>
 <translation id="6480327114083866287">Beheerd door <ph name="MANAGER" /></translation>
 <translation id="649050271426829538">Gestopt: papierstoring</translation>
 <translation id="6494974875566443634">Aanpassing</translation>
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 30f0395..fa8efed 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -417,6 +417,7 @@
       "//components/visitedlink/test:unit_tests",
       "//components/web_cache/browser:unit_tests",
       "//components/web_package:unit_tests",
+      "//components/web_package/test_support:unit_tests",
       "//components/webapps/browser:unit_tests",
       "//components/webapps/services/web_app_origin_association:unit_tests",
       "//components/webcrypto:unit_tests",
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 778ab0f..7e00dbe 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -864,6 +864,7 @@
     "strike_database_unittest.cc",
     "test_utils/test_profiles.cc",
     "test_utils/test_profiles.h",
+    "touch_to_fill_delegate_impl_unittest.cc",
     "ui/address_combobox_model_unittest.cc",
     "ui/autofill_image_fetcher_unittest.cc",
     "ui/country_combobox_model_unittest.cc",
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index af75561..7a75789 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1052,9 +1052,12 @@
   }
 
   single_field_form_fill_router_->CancelPendingQueries(this);
-  if (touch_to_fill_eligible &&
-      touch_to_fill_delegate_->TryToShowTouchToFill(query_id, form, field)) {
-    // Touch To Fill is shown.
+  if (touch_to_fill_delegate_->IsShowingTouchToFill() ||
+      (touch_to_fill_eligible &&
+       touch_to_fill_delegate_->TryToShowTouchToFill(query_id, form, field))) {
+    // Touch To Fill surface is shown, so abort showing regular Autofill UI.
+    // Now the flow is controlled by the |touch_to_fill_delegate_| instead
+    // of |external_delegate_|.
     return;
   }
   // Send Autofill suggestions (could be an empty list).
@@ -1750,6 +1753,7 @@
   credit_card_action_ = mojom::RendererFormDataAction::kPreview;
   initial_interaction_timestamp_ = TimeTicks();
   external_delegate_->Reset();
+  touch_to_fill_delegate_->Reset();
   filling_context_.clear();
   form_interactions_counter_ = std::make_unique<FormInteractionsCounter>();
 }
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
index 6380730..fffedfe 100644
--- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -177,6 +177,7 @@
               TryToShowTouchToFill,
               (int query_id, const FormData& form, const FormFieldData& field),
               (override));
+  MOCK_METHOD(bool, IsShowingTouchToFill, (), (override));
   MOCK_METHOD(void, HideTouchToFill, (), (override));
 };
 
@@ -434,6 +435,8 @@
 
     auto touch_to_fill_delegate = std::make_unique<MockTouchToFillDelegateImpl>(
         browser_autofill_manager_.get());
+    ON_CALL(*touch_to_fill_delegate, IsShowingTouchToFill())
+        .WillByDefault(Return(false));
     touch_to_fill_delegate_ = touch_to_fill_delegate.get();
     browser_autofill_manager_->SetTouchToFillDelegateImplForTest(
         std::move(touch_to_fill_delegate));
@@ -9441,6 +9444,24 @@
   EXPECT_FALSE(external_delegate_->on_suggestions_returned_seen());
 }
 
+// Tests that neither Autofill suggestions nor TTF is triggered if TTF is
+// already shown.
+TEST_F(BrowserAutofillManagerTest, ShowNothingIfTouchToFillAlreadyShown) {
+  FormData form;
+  CreateTestCreditCardFormData(&form, /*is_https=*/true,
+                               /*use_month_type=*/false);
+  FormsSeen({form});
+  const FormFieldData& field = form.fields[1];
+
+  EXPECT_CALL(*touch_to_fill_delegate_, IsShowingTouchToFill)
+      .WillOnce(Return(true));
+  EXPECT_CALL(*touch_to_fill_delegate_,
+              TryToShowTouchToFill(kDefaultPageID, _, _))
+      .Times(0);
+  TryToShowTouchToFill(kDefaultPageID, form, field, TouchToFillEligible(true));
+  EXPECT_FALSE(external_delegate_->on_suggestions_returned_seen());
+}
+
 // Desktop only tests.
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 class BrowserAutofillManagerTestForVirtualCardOption
diff --git a/components/autofill/core/browser/touch_to_fill_delegate_impl.cc b/components/autofill/core/browser/touch_to_fill_delegate_impl.cc
index 2b08c0c..4f9d4744 100644
--- a/components/autofill/core/browser/touch_to_fill_delegate_impl.cc
+++ b/components/autofill/core/browser/touch_to_fill_delegate_impl.cc
@@ -5,6 +5,8 @@
 #include "components/autofill/core/browser/touch_to_fill_delegate_impl.h"
 
 #include "components/autofill/core/browser/browser_autofill_manager.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "components/autofill/core/common/autofill_util.h"
 
 namespace autofill {
 
@@ -19,18 +21,65 @@
 bool TouchToFillDelegateImpl::TryToShowTouchToFill(int query_id,
                                                    const FormData& form,
                                                    const FormFieldData& field) {
+  // Trigger only for a credit card field/form.
+  // TODO(crbug.com/1247698): Clarify field/form requirements.
+  if (manager_->GetPopupType(form, field) != PopupType::kCreditCards)
+    return false;
+  // Trigger only on supported platforms.
   if (!manager_->client()->IsTouchToFillCreditCardSupported())
     return false;
-  // TODO(crbug.com/1247698): Add additional eligibility checks.
-  return manager_->client()->ShowTouchToFillCreditCard(GetWeakPtr());
+  // Trigger only if not shown before.
+  if (ttf_credit_card_state_ != TouchToFillState::kShouldShow)
+    return false;
+  // Trigger only on focusable empty field.
+  if (!field.is_focusable || !SanitizedFieldIsEmpty(field.value))
+    return false;
+  // Trigger only if there is at least 1 complete valid credit card on file.
+  // Complete = contains number, expiration date and name on card.
+  // Valid = unexpired with valid number format.
+  PersonalDataManager* pdm = manager_->client()->GetPersonalDataManager();
+  DCHECK(pdm);
+  std::vector<CreditCard*> cards_to_suggest = pdm->GetCreditCardsToSuggest(
+      manager_->client()->AreServerCardsSupported());
+  auto NotACompleteValidCard = [](const CreditCard* card) {
+    return card->IsExpired(AutofillClock::Now()) || !card->HasNameOnCard() ||
+           (card->record_type() != autofill::CreditCard::MASKED_SERVER_CARD &&
+            !card->HasValidCardNumber());
+  };
+  base::EraseIf(cards_to_suggest, NotACompleteValidCard);
+  if (cards_to_suggest.empty())
+    return false;
+  // Trigger only if the UI is available.
+  if (!manager_->driver()->CanShowAutofillUi())
+    return false;
+  // Finally try showing the surface.
+  if (!manager_->client()->ShowTouchToFillCreditCard(GetWeakPtr()))
+    return false;
+
+  ttf_credit_card_state_ = TouchToFillState::kIsShowing;
+  manager_->client()->HideAutofillPopup(
+      PopupHidingReason::kOverlappingWithTouchToFillSurface);
+  return true;
 }
 
+bool TouchToFillDelegateImpl::IsShowingTouchToFill() {
+  return ttf_credit_card_state_ == TouchToFillState::kIsShowing;
+}
+
+// TODO(crbug.com/1247698): Review |HideAutofillPopup| invocations and maybe
+// also trigger |HideTouchToFillCreditCard|.
 void TouchToFillDelegateImpl::HideTouchToFill() {
-  if (manager_->client()->IsTouchToFillCreditCardSupported()) {
+  if (IsShowingTouchToFill()) {
     manager_->client()->HideTouchToFillCreditCard();
+    ttf_credit_card_state_ = TouchToFillState::kWasShown;
   }
 }
 
+void TouchToFillDelegateImpl::Reset() {
+  HideTouchToFill();
+  ttf_credit_card_state_ = TouchToFillState::kShouldShow;
+}
+
 base::WeakPtr<TouchToFillDelegateImpl> TouchToFillDelegateImpl::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
diff --git a/components/autofill/core/browser/touch_to_fill_delegate_impl.h b/components/autofill/core/browser/touch_to_fill_delegate_impl.h
index 5cc785b..7e5b98a 100644
--- a/components/autofill/core/browser/touch_to_fill_delegate_impl.h
+++ b/components/autofill/core/browser/touch_to_fill_delegate_impl.h
@@ -15,10 +15,16 @@
 class BrowserAutofillManager;
 
 // Delegate for in-browser Touch To Fill (TTF) surface display and selection.
-// Currently TTF surface is eligible only for credit card forms.
+// Currently TTF surface is eligible only for credit card forms on click on
+// an empty focusable field.
+//
+// If the surface was shown once, it won't be triggered again on the same page.
+// But calling |Reset()| on navigation restores such showing eligibility.
 //
 // It is supposed to be owned by the given |BrowserAutofillManager|, and
-// interact with it and its |AutofillClient|.
+// interact with it and its |AutofillClient| and |AutofillDriver|.
+//
+// Public methods are marked virtual for testing.
 // TODO(crbug.com/1324900): Consider using more descriptive name.
 class TouchToFillDelegateImpl : public TouchToFillDelegate {
  public:
@@ -33,12 +39,26 @@
                                     const FormData& form,
                                     const FormFieldData& field);
 
+  // Returns whether the TTF surface is currently being shown.
+  virtual bool IsShowingTouchToFill();
+
   // Hides the TTF surface if one is shown.
   virtual void HideTouchToFill();
 
+  // Resets the delegate to its starting state (e.g. on navigation).
+  virtual void Reset();
+
  private:
   base::WeakPtr<TouchToFillDelegateImpl> GetWeakPtr();
 
+  enum class TouchToFillState {
+    kShouldShow,
+    kIsShowing,
+    kWasShown,
+  };
+
+  TouchToFillState ttf_credit_card_state_ = TouchToFillState::kShouldShow;
+
   const raw_ptr<BrowserAutofillManager> manager_;
 
   base::WeakPtrFactory<TouchToFillDelegateImpl> weak_ptr_factory_{this};
diff --git a/components/autofill/core/browser/touch_to_fill_delegate_impl_unittest.cc b/components/autofill/core/browser/touch_to_fill_delegate_impl_unittest.cc
new file mode 100644
index 0000000..da65d05
--- /dev/null
+++ b/components/autofill/core/browser/touch_to_fill_delegate_impl_unittest.cc
@@ -0,0 +1,297 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/touch_to_fill_delegate_impl.h"
+#include "base/test/task_environment.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/test_autofill_driver.h"
+#include "components/autofill/core/browser/test_browser_autofill_manager.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::NiceMock;
+using testing::Ref;
+using testing::Return;
+
+namespace autofill {
+
+namespace {
+
+// A constant value to use as a suggestions query ID.
+const int kQueryId = 1;
+
+class MockAutofillDriver : public TestAutofillDriver {
+ public:
+  MockAutofillDriver() = default;
+  MockAutofillDriver(const MockAutofillDriver&) = delete;
+  MockAutofillDriver& operator=(const MockAutofillDriver&) = delete;
+  ~MockAutofillDriver() override = default;
+
+  MOCK_METHOD(bool, CanShowAutofillUi, (), (const, override));
+};
+
+class MockAutofillClient : public TestAutofillClient {
+ public:
+  MockAutofillClient() = default;
+  MockAutofillClient(const MockAutofillClient&) = delete;
+  MockAutofillClient& operator=(const MockAutofillClient&) = delete;
+  ~MockAutofillClient() override = default;
+
+  MOCK_METHOD(bool, IsTouchToFillCreditCardSupported, (), (override));
+  MOCK_METHOD(bool,
+              ShowTouchToFillCreditCard,
+              (base::WeakPtr<autofill::TouchToFillDelegate>),
+              (override));
+  MOCK_METHOD(void, HideTouchToFillCreditCard, (), (override));
+  MOCK_METHOD(void, HideAutofillPopup, (PopupHidingReason reason), (override));
+};
+
+class MockBrowserAutofillManager : public TestBrowserAutofillManager {
+ public:
+  MockBrowserAutofillManager(TestAutofillDriver* driver,
+                             TestAutofillClient* client)
+      : TestBrowserAutofillManager(driver, client) {}
+  MockBrowserAutofillManager(const MockBrowserAutofillManager&) = delete;
+  MockBrowserAutofillManager& operator=(const MockBrowserAutofillManager&) =
+      delete;
+  ~MockBrowserAutofillManager() override = default;
+
+  MOCK_METHOD(PopupType,
+              GetPopupType,
+              (const FormData& form, const FormFieldData& field),
+              (override));
+};
+
+}  // namespace
+
+class TouchToFillDelegateImplUnitTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    autofill_client_.SetPrefs(test::PrefServiceForTesting());
+    autofill_client_.GetPersonalDataManager()->SetPrefService(
+        autofill_client_.GetPrefs());
+    autofill_driver_ = std::make_unique<NiceMock<MockAutofillDriver>>();
+    browser_autofill_manager_ =
+        std::make_unique<NiceMock<MockBrowserAutofillManager>>(
+            autofill_driver_.get(), &autofill_client_);
+    touch_to_fill_delegate_ = std::make_unique<TouchToFillDelegateImpl>(
+        browser_autofill_manager_.get());
+
+    // Default setup for successful |TryToShowTouchToFill|.
+    field_.is_focusable = true;
+    autofill_client_.GetPersonalDataManager()->AddCreditCard(
+        test::GetCreditCard());
+    ON_CALL(*browser_autofill_manager_, GetPopupType(_, _))
+        .WillByDefault(Return(PopupType::kCreditCards));
+    ON_CALL(autofill_client_, IsTouchToFillCreditCardSupported)
+        .WillByDefault(Return(true));
+    ON_CALL(*autofill_driver_, CanShowAutofillUi).WillByDefault(Return(true));
+    ON_CALL(autofill_client_, ShowTouchToFillCreditCard)
+        .WillByDefault(Return(true));
+  }
+
+  void TearDown() override {
+    browser_autofill_manager_.reset();
+    touch_to_fill_delegate_.reset();
+    autofill_driver_.reset();
+  }
+
+  void TryToShowTouchToFill(bool expected_success) {
+    EXPECT_CALL(autofill_client_,
+                HideAutofillPopup(
+                    PopupHidingReason::kOverlappingWithTouchToFillSurface))
+        .Times(expected_success ? 1 : 0);
+    EXPECT_EQ(expected_success, touch_to_fill_delegate_->TryToShowTouchToFill(
+                                    kQueryId, form_, field_));
+    EXPECT_EQ(expected_success,
+              touch_to_fill_delegate_->IsShowingTouchToFill());
+  }
+
+  FormData form_;
+  FormFieldData field_;
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  NiceMock<MockAutofillClient> autofill_client_;
+  std::unique_ptr<NiceMock<MockAutofillDriver>> autofill_driver_;
+  std::unique_ptr<TouchToFillDelegateImpl> touch_to_fill_delegate_;
+  std::unique_ptr<MockBrowserAutofillManager> browser_autofill_manager_;
+};
+
+TEST_F(TouchToFillDelegateImplUnitTest, TryToShowTouchToFillSucceeds) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+
+  TryToShowTouchToFill(/*expected_success=*/true);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfNotCreditCardField) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  EXPECT_CALL(*browser_autofill_manager_, GetPopupType(Ref(form_), Ref(field_)))
+      .WillOnce(Return(PopupType::kAddresses));
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfNotSupported) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  EXPECT_CALL(autofill_client_, IsTouchToFillCreditCardSupported)
+      .WillOnce(Return(false));
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfAlreadyShown) {
+  TryToShowTouchToFill(/*expected_success=*/true);
+
+  EXPECT_CALL(
+      autofill_client_,
+      HideAutofillPopup(PopupHidingReason::kOverlappingWithTouchToFillSurface))
+      .Times(0);
+  EXPECT_FALSE(
+      touch_to_fill_delegate_->TryToShowTouchToFill(kQueryId, form_, field_));
+  EXPECT_TRUE(touch_to_fill_delegate_->IsShowingTouchToFill());
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest, TryToShowTouchToFillFailsIfWasShown) {
+  TryToShowTouchToFill(/*expected_success=*/true);
+  touch_to_fill_delegate_->HideTouchToFill();
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfFieldIsNotFocusable) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  field_.is_focusable = false;
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfFieldHasValue) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  field_.value = u"Initial value";
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+
+  // But should ignore formatting characters.
+  field_.value = u"____-____-____-____";
+
+  TryToShowTouchToFill(/*expected_success=*/true);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfNoCardsOnFile) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  autofill_client_.GetPersonalDataManager()->ClearCreditCards();
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfCardIsIncomplete) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  autofill_client_.GetPersonalDataManager()->ClearCreditCards();
+  CreditCard cc_no_number = test::GetCreditCard();
+  cc_no_number.SetNumber(u"");
+  autofill_client_.GetPersonalDataManager()->AddCreditCard(cc_no_number);
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+
+  CreditCard cc_no_exp_date = test::GetCreditCard();
+  cc_no_exp_date.SetExpirationMonth(0);
+  cc_no_exp_date.SetExpirationYear(0);
+  autofill_client_.GetPersonalDataManager()->AddCreditCard(cc_no_exp_date);
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+
+  CreditCard cc_no_name = test::GetCreditCard();
+  cc_no_name.SetRawInfo(CREDIT_CARD_NAME_FULL, u"");
+  autofill_client_.GetPersonalDataManager()->AddCreditCard(cc_no_name);
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfCardIsExpired) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  autofill_client_.GetPersonalDataManager()->ClearCreditCards();
+  autofill_client_.GetPersonalDataManager()->AddCreditCard(
+      test::GetExpiredCreditCard());
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfCardNumberIsInvalid) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  autofill_client_.GetPersonalDataManager()->ClearCreditCards();
+  CreditCard cc_invalid_number = test::GetCreditCard();
+  cc_invalid_number.SetNumber(u"invalid number");
+  autofill_client_.GetPersonalDataManager()->AddCreditCard(cc_invalid_number);
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+
+  // But succeeds for existing masked server card with incomplete number.
+  autofill_client_.GetPersonalDataManager()->AddCreditCard(
+      test::GetMaskedServerCard());
+
+  TryToShowTouchToFill(/*expected_success=*/true);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest,
+       TryToShowTouchToFillFailsIfCanNotShowUi) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  EXPECT_CALL(*autofill_driver_, CanShowAutofillUi).WillOnce(Return(false));
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest, TryToShowTouchToFillFailsIfShowFails) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+  EXPECT_CALL(autofill_client_, ShowTouchToFillCreditCard)
+      .WillOnce(Return(false));
+
+  TryToShowTouchToFill(/*expected_success=*/false);
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest, HideTouchToFillDoesNothingIfNotShown) {
+  ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+
+  EXPECT_CALL(autofill_client_, HideTouchToFillCreditCard).Times(0);
+  touch_to_fill_delegate_->HideTouchToFill();
+  EXPECT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest, HideTouchToFillHidesIfShown) {
+  TryToShowTouchToFill(/*expected_success=*/true);
+
+  EXPECT_CALL(autofill_client_, HideTouchToFillCreditCard).Times(1);
+  touch_to_fill_delegate_->HideTouchToFill();
+  EXPECT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest, ResetHidesTouchToFillIfShown) {
+  TryToShowTouchToFill(/*expected_success=*/true);
+
+  EXPECT_CALL(autofill_client_, HideTouchToFillCreditCard).Times(1);
+  touch_to_fill_delegate_->Reset();
+  EXPECT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
+}
+
+TEST_F(TouchToFillDelegateImplUnitTest, ResetAllowsShowingTouchToFillAgain) {
+  TryToShowTouchToFill(/*expected_success=*/true);
+  touch_to_fill_delegate_->HideTouchToFill();
+  TryToShowTouchToFill(/*expected_success=*/false);
+
+  touch_to_fill_delegate_->Reset();
+  TryToShowTouchToFill(/*expected_success=*/true);
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/ui/popup_types.h b/components/autofill/core/browser/ui/popup_types.h
index 048ae193..dab49a3 100644
--- a/components/autofill/core/browser/ui/popup_types.h
+++ b/components/autofill/core/browser/ui/popup_types.h
@@ -62,7 +62,9 @@
   kMouseLocked = 16,
   // The password generation popup would overlap and hide autofill popup.
   kOverlappingWithPasswordGenerationPopup = 17,
-  kMaxValue = kOverlappingWithPasswordGenerationPopup
+  // The Touch To Fill surface is shown instead of autofill suggestions.
+  kOverlappingWithTouchToFillSurface = 18,
+  kMaxValue = kOverlappingWithTouchToFillSurface
 };
 
 }  // namespace autofill
diff --git a/components/autofill_assistant/browser/controller.h b/components/autofill_assistant/browser/controller.h
index cf1a6be..7c84e93 100644
--- a/components/autofill_assistant/browser/controller.h
+++ b/components/autofill_assistant/browser/controller.h
@@ -445,9 +445,6 @@
   // DOM nodes. May be nullptr.
   const raw_ptr<AnnotateDomModelService> annotate_dom_model_service_;
 
-  // Holds the client's locale.
-  std::string locale_;
-
   // The accumulated network stats of an entire flow. Used for metrics upon
   // flow completion.
   RoundtripNetworkStats accumulated_network_stats_;
diff --git a/components/browser_ui/strings/android/translations/browser_ui_strings_de.xtb b/components/browser_ui/strings/android/translations/browser_ui_strings_de.xtb
index 8083d7d..d33bb4c 100644
--- a/components/browser_ui/strings/android/translations/browser_ui_strings_de.xtb
+++ b/components/browser_ui/strings/android/translations/browser_ui_strings_de.xtb
@@ -238,7 +238,7 @@
 <translation id="6545864417968258051">Bluetooth-Suche</translation>
 <translation id="6552800053856095716">{PERMISSIONS_SUMMARY_BLOCKED,plural, =1{<ph name="PERMISSION_1" />, <ph name="PERMISSION_2" /> und <ph name="NUM_MORE" /> weitere blockiert}other{<ph name="PERMISSION_1" />, <ph name="PERMISSION_2" /> und <ph name="NUM_MORE" /> weitere blockiert}}</translation>
 <translation id="6554732001434021288">Vor <ph name="NUM_DAYS" /> Tagen zuletzt besucht</translation>
-<translation id="6561560012278703671">Ein Symbol in der Adressleiste einblenden (störende Aufforderungen zum Annehmen von Benachrichtigungen werden blockiert)</translation>
+<translation id="6561560012278703671">Unaufdringlichere Benachrichtigungen verwenden (störende Aufforderungen zum Annehmen von Benachrichtigungen werden blockiert)</translation>
 <translation id="6593061639179217415">Desktopwebsite</translation>
 <translation id="6608650720463149374"><ph name="GIGABYTES" /> GB</translation>
 <translation id="6612358246767739896">Geschützte Inhalte</translation>
diff --git a/components/browser_ui/strings/android/translations/browser_ui_strings_es.xtb b/components/browser_ui/strings/android/translations/browser_ui_strings_es.xtb
index 5b152af83..f08ee7a 100644
--- a/components/browser_ui/strings/android/translations/browser_ui_strings_es.xtb
+++ b/components/browser_ui/strings/android/translations/browser_ui_strings_es.xtb
@@ -238,7 +238,7 @@
 <translation id="6545864417968258051">Búsqueda de dispositivos Bluetooth</translation>
 <translation id="6552800053856095716">{PERMISSIONS_SUMMARY_BLOCKED,plural, =1{<ph name="PERMISSION_1" />, <ph name="PERMISSION_2" /> y <ph name="NUM_MORE" /> más bloqueados}other{<ph name="PERMISSION_1" />, <ph name="PERMISSION_2" /> y <ph name="NUM_MORE" /> más bloqueados}}</translation>
 <translation id="6554732001434021288">Última visita: hace <ph name="NUM_DAYS" /> días</translation>
-<translation id="6561560012278703671">Usar notificaciones más discretas (bloquea las notificaciones emergentes para evitar interrupciones)</translation>
+<translation id="6561560012278703671">Usar notificaciones más discretas (bloquea las notificaciones emergentes para que no te interrumpan)</translation>
 <translation id="6593061639179217415">Vista ordenador</translation>
 <translation id="6608650720463149374"><ph name="GIGABYTES" /> GB</translation>
 <translation id="6612358246767739896">Contenido protegido</translation>
diff --git a/components/cronet/android/BUILD.gn b/components/cronet/android/BUILD.gn
index 70f9accb..99a8f50 100644
--- a/components/cronet/android/BUILD.gn
+++ b/components/cronet/android/BUILD.gn
@@ -276,6 +276,7 @@
     "java/src/org/chromium/net/impl/CronetExceptionImpl.java",
     "java/src/org/chromium/net/impl/CronetLogger.java",
     "java/src/org/chromium/net/impl/CronetLoggerFactory.java",
+    "java/src/org/chromium/net/impl/CronetManifest.java",
     "java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
     "java/src/org/chromium/net/impl/NoOpLogger.java",
     "java/src/org/chromium/net/impl/Preconditions.java",
@@ -1017,6 +1018,7 @@
       "test/javatests/src/org/chromium/net/UploadDataProvidersTest.java",
       "test/javatests/src/org/chromium/net/UrlResponseInfoTest.java",
       "test/javatests/src/org/chromium/net/impl/CronetLoggerTest.java",
+      "test/javatests/src/org/chromium/net/impl/CronetManifestTest.java",
       "test/javatests/src/org/chromium/net/urlconnection/CronetBufferedOutputStreamTest.java",
       "test/javatests/src/org/chromium/net/urlconnection/CronetChunkedOutputStreamTest.java",
       "test/javatests/src/org/chromium/net/urlconnection/CronetFixedModeOutputStreamTest.java",
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java
index 4c94926..7c0573a 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java
@@ -4,22 +4,41 @@
 
 package org.chromium.net.impl;
 
+import android.content.Context;
+
 import androidx.annotation.Nullable;
 
+import org.chromium.net.impl.CronetLogger.CronetSource;
+
 /**
  * Takes care of instantiating the correct CronetLogger.
  */
 public final class CronetLoggerFactory {
     private CronetLoggerFactory() {}
+
     private static final CronetLogger sDefaultLogger = new NoOpLogger();
     private static CronetLogger sTestingLogger;
 
+    // Class that is packaged for Cronet telemetry.
+    private static final String CRONET_LOGGER_IMPL_CLASS =
+            "com.google.net.cronet.telemetry.CronetLoggerImpl";
+
     /**
      * @return The correct CronetLogger to be used for logging.
      */
-    public static CronetLogger createLogger() {
+    public static CronetLogger createLogger(Context ctx, CronetSource source) {
         if (sTestingLogger != null) return sTestingLogger;
-        // TODO(stefanoduo): Add logic to choose different loggers.
+
+        if (!CronetManifest.isAppOptedInForTelemetry(ctx, source)) return sDefaultLogger;
+
+        Class<? extends CronetLogger> cronetLoggerImplClass = fetchLoggerImplClass();
+        if (cronetLoggerImplClass == null) return sDefaultLogger;
+
+        try {
+            return cronetLoggerImplClass.getConstructor().newInstance();
+        } catch (Exception e) {
+            // Pass - since we dont want any failure, catch any exception that might arise.
+        }
         return sDefaultLogger;
     }
 
@@ -48,4 +67,13 @@
             CronetLoggerFactory.setLoggerForTesting(null);
         }
     }
+
+    private static Class<? extends CronetLogger> fetchLoggerImplClass() {
+        ClassLoader loader = CronetLoggerFactory.class.getClassLoader();
+        try {
+            return loader.loadClass(CRONET_LOGGER_IMPL_CLASS).asSubclass(CronetLogger.class);
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
 }
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java
new file mode 100644
index 0000000..cb22cdd3
--- /dev/null
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java
@@ -0,0 +1,41 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net.impl;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import org.chromium.net.impl.CronetLogger.CronetSource;
+
+/**
+ * Utility class for working with the AndroidManifest flags.
+ */
+final class CronetManifest {
+    private CronetManifest() {}
+    // Individual apps can use this meta-data tag in their manifest to opt in for metrics
+    // reporting.
+    // Todo (colibie): Add this to the android documentation
+    static final String METRICS_OPT_IN_META_DATA_STR = "org.chromium.net.CronetMetricsOptIn";
+
+    static boolean isAppOptedInForTelemetry(Context ctx, CronetSource source) {
+        try {
+            // Check if app is opted in
+            ApplicationInfo info = ctx.getPackageManager().getApplicationInfo(
+                    ctx.getPackageName(), PackageManager.GET_META_DATA);
+
+            // TODO(b/226553652): Enable logging if loaded from CRONET_PLAY_SERVICES, after testing
+            //  with select users
+
+            // getBoolean returns false if the key is not found, which is what we want.
+            return info.metaData == null ? false
+                                         : info.metaData.getBoolean(METRICS_OPT_IN_META_DATA_STR);
+        } catch (PackageManager.NameNotFoundException e) {
+            // This should never happen.
+            // The conservative thing is to assume the app HAS opted out.
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
index cd23d9e..f475bb5 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
@@ -203,13 +203,15 @@
                 throw new NullPointerException("Context Adapter creation failed.");
             }
         }
-        mLogger = CronetLoggerFactory.createLogger();
+
+        mLogger = CronetLoggerFactory.createLogger(builder.getContext(), getCronetSource());
+
         // getVersionString()'s output looks like "Cronet/w.x.y.z@hash". CronetVersion only cares
         // about the "w.x.y.z" bit.
         String version = getVersionString();
         version = version.split("/")[1];
         version = version.split("@")[0];
-        // TODO(stefanoduo): Correctly generate the CronetSource parameter.
+
         mLogger.logCronetEngineCreation(getCronetEngineId(), new CronetEngineBuilderInfo(builder),
                 new CronetVersion(version), getCronetSource());
 
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java b/components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java
index b45a9e9..8a1d5aa 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java
@@ -73,7 +73,8 @@
                         });
                     }
                 });
-        mLogger = CronetLoggerFactory.createLogger();
+        mLogger = CronetLoggerFactory.createLogger(
+                builder.getContext(), CronetSource.CRONET_SOURCE_FALLBACK);
         // getVersionString()'s output looks like "Cronet/w.x.y.z@hash". CronetVersion only cares
         // about the "w.x.y.z" bit.
         String version = getVersionString();
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestRule.java b/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestRule.java
index a66fcec..39dcaa7 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestRule.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestRule.java
@@ -113,6 +113,10 @@
         }
     }
 
+    public static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
     int getMaximumAvailableApiLevel() {
         // Prior to M59 the ApiVersion.getMaximumAvailableApiLevel API didn't exist
         if (ApiVersion.getCronetVersion().compareTo("59") < 0) {
@@ -121,10 +125,6 @@
         return ApiVersion.getMaximumAvailableApiLevel();
     }
 
-    public static Context getContext() {
-        return InstrumentationRegistry.getTargetContext();
-    }
-
     @Override
     public Statement apply(final Statement base, final Description desc) {
         return new Statement() {
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetLoggerTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetLoggerTest.java
index 1a7a7963..41f87fbd 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetLoggerTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetLoggerTest.java
@@ -217,7 +217,7 @@
     @SmallTest
     @Feature({"Cronet"})
     public void testSetLoggerForTesting() {
-        CronetLogger logger = CronetLoggerFactory.createLogger();
+        CronetLogger logger = CronetLoggerFactory.createLogger(mContext, null);
         assertEquals(0, mTestLogger.callsToLogCronetTrafficInfo());
         assertEquals(0, mTestLogger.callsToLogCronetEngineCreation());
 
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestTest.java
new file mode 100644
index 0000000..d5d22b3
--- /dev/null
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestTest.java
@@ -0,0 +1,112 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.test.util.Feature;
+import org.chromium.net.CronetTestRule;
+import org.chromium.net.CronetTestRule.OnlyRunNativeCronet;
+import org.chromium.net.impl.CronetLogger.CronetSource;
+
+/**
+ * Tests {@link CronetManifest}
+ */
+@RunWith(AndroidJUnit4.class)
+public class CronetManifestTest {
+    @Rule
+    public final CronetTestRule mTestRule = new CronetTestRule();
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private PackageManager mMockPackageManager;
+    private Bundle mMetadata;
+
+    @Before
+    public void setUp() throws Exception {
+        mMetadata = new Bundle();
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+    }
+
+    @Test
+    @SmallTest
+    @OnlyRunNativeCronet
+    @Feature({"Cronet"})
+    public void testTelemetryOptIn_whenMetadataIsTrue() throws Exception {
+        mMetadata.putBoolean(CronetManifest.METRICS_OPT_IN_META_DATA_STR, true);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.metaData = mMetadata;
+        when(mMockPackageManager.getApplicationInfo(
+                     mMockContext.getPackageName(), PackageManager.GET_META_DATA))
+                .thenReturn(appInfo);
+
+        assertTrue(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_STATICALLY_LINKED));
+        assertTrue(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_PLAY_SERVICES));
+        assertTrue(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_FALLBACK));
+    }
+
+    @Test
+    @SmallTest
+    @OnlyRunNativeCronet
+    @Feature({"Cronet"})
+    public void testTelemetryOptIn_whenNoMetadata() throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        when(mMockPackageManager.getApplicationInfo(
+                     mMockContext.getPackageName(), PackageManager.GET_META_DATA))
+                .thenReturn(appInfo);
+
+        assertFalse(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_STATICALLY_LINKED));
+        assertFalse(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_PLAY_SERVICES));
+        assertFalse(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_FALLBACK));
+    }
+
+    @Test
+    @SmallTest
+    @OnlyRunNativeCronet
+    @Feature({"Cronet"})
+    public void testTelemetryOptIn_whenMetadataIsFalse() throws Exception {
+        mMetadata.putBoolean(CronetManifest.METRICS_OPT_IN_META_DATA_STR, false);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.metaData = mMetadata;
+        when(mMockPackageManager.getApplicationInfo(
+                     mMockContext.getPackageName(), PackageManager.GET_META_DATA))
+                .thenReturn(appInfo);
+
+        assertFalse(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_STATICALLY_LINKED));
+        assertFalse(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_PLAY_SERVICES));
+        assertFalse(CronetManifest.isAppOptedInForTelemetry(
+                mMockContext, CronetSource.CRONET_SOURCE_FALLBACK));
+    }
+}
diff --git a/components/device_reauth/biometric_authenticator.h b/components/device_reauth/biometric_authenticator.h
index c50704c..fdf2baaa 100644
--- a/components/device_reauth/biometric_authenticator.h
+++ b/components/device_reauth/biometric_authenticator.h
@@ -62,8 +62,12 @@
   // Asks the user to authenticate. Invokes |callback| asynchronously when
   // the auth flow returns with the result.
   // |requester| is the filling surface that is asking for authentication.
+  // |use_last_valid_auth| if set to false, ignores the grace 60 seconds
+  // period between the last valid authentication and the current
+  // authentication, and re-invokes system authentication.
   virtual void Authenticate(BiometricAuthRequester requester,
-                            AuthenticateCallback callback) = 0;
+                            AuthenticateCallback callback,
+                            bool use_last_valid_auth) = 0;
 
   // Asks the user to authenticate. Invokes |callback| asynchronously when
   // the auth flow returns with the result.
diff --git a/components/device_reauth/mock_biometric_authenticator.h b/components/device_reauth/mock_biometric_authenticator.h
index f79b5d6..ae47d4f 100644
--- a/components/device_reauth/mock_biometric_authenticator.h
+++ b/components/device_reauth/mock_biometric_authenticator.h
@@ -19,7 +19,7 @@
   MOCK_METHOD(bool, CanAuthenticate, (BiometricAuthRequester), (override));
   MOCK_METHOD(void,
               Authenticate,
-              (BiometricAuthRequester, AuthenticateCallback),
+              (BiometricAuthRequester, AuthenticateCallback, bool),
               (override));
   MOCK_METHOD(void,
               AuthenticateWithMessage,
diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedder_support/user_agent_utils.cc
index 5ceb4b3..fd99a0b9 100644
--- a/components/embedder_support/user_agent_utils.cc
+++ b/components/embedder_support/user_agent_utils.cc
@@ -512,13 +512,11 @@
     const std::vector<std::string> greasey_chars = {
         " ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"};
     const std::vector<std::string> greased_versions = {"8", "99", "24"};
-    // The spec disallows a leading or trailing space, so ensuring the first
-    // char isn't index 0. See the spec:
+    // See the spec:
     // https://wicg.github.io/ua-client-hints/#create-arbitrary-brands-section
     greasey_brand = base::StrCat(
-        {greasey_chars[(seed % (greasey_chars.size() - 1)) + 1], "Not",
-         greasey_chars[(seed + 1) % greasey_chars.size()], "A",
-         greasey_chars[(seed + 2) % greasey_chars.size()], "Brand"});
+        {"Not", greasey_chars[(seed) % greasey_chars.size()], "A",
+         greasey_chars[(seed + 1) % greasey_chars.size()], "Brand"});
     greasey_version = greased_versions[seed % greased_versions.size()];
 
     return GetProcessedGreasedBrandVersion(
diff --git a/components/embedder_support/user_agent_utils_unittest.cc b/components/embedder_support/user_agent_utils_unittest.cc
index 9e01bc7..e539170 100644
--- a/components/embedder_support/user_agent_utils_unittest.cc
+++ b/components/embedder_support/user_agent_utils_unittest.cc
@@ -733,10 +733,10 @@
       blink::UserAgentBrandVersionType::kFullVersion);
   // 1. verify major version
   std::string brand_list = metadata.SerializeBrandMajorVersionList();
-  EXPECT_EQ(R"("/Not=A?Brand";v="8", "Chromium";v="84")", brand_list);
+  EXPECT_EQ(R"("Not;A=Brand";v="8", "Chromium";v="84")", brand_list);
   // 2. verify full version
   std::string brand_list_w_fv = metadata.SerializeBrandFullVersionList();
-  EXPECT_EQ(R"("/Not=A?Brand";v="8.0.0.0", "Chromium";v="84.0.0.0")",
+  EXPECT_EQ(R"("Not;A=Brand";v="8.0.0.0", "Chromium";v="84.0.0.0")",
             brand_list_w_fv);
 }
 
@@ -760,11 +760,11 @@
   // Make sure the lists are different for different seeds (84 vs 85).
   // 1. verify major version
   std::string brand_list_diff = metadata.SerializeBrandMajorVersionList();
-  EXPECT_EQ(R"("Chromium";v="85", ")Not?A_Brand";v="99")", brand_list_diff);
+  EXPECT_EQ(R"("Chromium";v="85", "Not=A?Brand";v="99")", brand_list_diff);
   EXPECT_NE(brand_list, brand_list_diff);
   // 2.verify full version
   std::string brand_list_diff_w_fv = metadata.SerializeBrandFullVersionList();
-  EXPECT_EQ(R"("Chromium";v="85.0.0.0", ")Not?A_Brand";v="99.0.0.0")",
+  EXPECT_EQ(R"("Chromium";v="85.0.0.0", "Not=A?Brand";v="99.0.0.0")",
             brand_list_diff_w_fv);
   EXPECT_NE(brand_list_w_fv, brand_list_diff_w_fv);
 }
@@ -824,12 +824,12 @@
   // 1. verify major version
   std::string brand_version_grease_override =
       metadata.SerializeBrandMajorVersionList();
-  EXPECT_EQ(R"("/Not=A?Brand";v="1024", "Chromium";v="84")",
+  EXPECT_EQ(R"("Not;A=Brand";v="1024", "Chromium";v="84")",
             brand_version_grease_override);
   // 2. verify full version
   std::string brand_version_grease_override_fv =
       metadata.SerializeBrandFullVersionList();
-  EXPECT_EQ(R"("/Not=A?Brand";v="1024.0.0.0", "Chromium";v="84.0.0.0")",
+  EXPECT_EQ(R"("Not;A=Brand";v="1024.0.0.0", "Chromium";v="84.0.0.0")",
             brand_version_grease_override_fv);
 }
 
@@ -844,11 +844,11 @@
   // 1. verify major version
   std::string brand_list_w_brand = metadata.SerializeBrandMajorVersionList();
   EXPECT_EQ(
-      R"("/Not=A?Brand";v="8", "Chromium";v="84", "Totally A Brand";v="84")",
+      R"("Not;A=Brand";v="8", "Chromium";v="84", "Totally A Brand";v="84")",
       brand_list_w_brand);
   // 2. verify full version
   std::string brand_list_w_brand_fv = metadata.SerializeBrandFullVersionList();
-  EXPECT_EQ(base::StrCat({"\"/Not=A?Brand\";v=\"8.0.0.0\", ",
+  EXPECT_EQ(base::StrCat({"\"Not;A=Brand\";v=\"8.0.0.0\", ",
                           "\"Chromium\";v=\"84.0.0.0\", ",
                           "\"Totally A Brand\";v=\"84.0.0.0\""}),
             brand_list_w_brand_fv);
@@ -957,13 +957,13 @@
   blink::UserAgentBrandVersion greased_bv = GetGreasedUserAgentBrandVersion(
       permuted_order, 84, absl::nullopt, absl::nullopt, true,
       blink::UserAgentBrandVersionType::kMajorVersion);
-  EXPECT_EQ(greased_bv.brand, "/Not=A?Brand");
+  EXPECT_EQ(greased_bv.brand, "Not;A=Brand");
   EXPECT_EQ(greased_bv.version, "8");
 
   greased_bv = GetGreasedUserAgentBrandVersion(
       permuted_order, 84, absl::nullopt, absl::nullopt, true,
       blink::UserAgentBrandVersionType::kFullVersion);
-  EXPECT_EQ(greased_bv.brand, "/Not=A?Brand");
+  EXPECT_EQ(greased_bv.brand, "Not;A=Brand");
   EXPECT_EQ(greased_bv.version, "8.0.0.0");
 }
 
@@ -989,13 +989,13 @@
   blink::UserAgentBrandVersion greased_bv = GetGreasedUserAgentBrandVersion(
       permuted_order, 84, absl::nullopt, "1024", true,
       blink::UserAgentBrandVersionType::kMajorVersion);
-  EXPECT_EQ(greased_bv.brand, "/Not=A?Brand");
+  EXPECT_EQ(greased_bv.brand, "Not;A=Brand");
   EXPECT_EQ(greased_bv.version, "1024");
 
   greased_bv = GetGreasedUserAgentBrandVersion(
       permuted_order, 84, absl::nullopt, "1024", true,
       blink::UserAgentBrandVersionType::kFullVersion);
-  EXPECT_EQ(greased_bv.brand, "/Not=A?Brand");
+  EXPECT_EQ(greased_bv.brand, "Not;A=Brand");
   EXPECT_EQ(greased_bv.version, "1024.0.0.0");
 }
 
@@ -1020,26 +1020,26 @@
   blink::UserAgentBrandVersion greased_bv = GetGreasedUserAgentBrandVersion(
       permuted_order, 86, absl::nullopt, absl::nullopt, true,
       blink::UserAgentBrandVersionType::kMajorVersion);
-  EXPECT_EQ(greased_bv.brand, ";Not_A Brand");
+  EXPECT_EQ(greased_bv.brand, "Not?A_Brand");
   EXPECT_EQ(greased_bv.version, "24");
 
   greased_bv = GetGreasedUserAgentBrandVersion(
       permuted_order, 86, absl::nullopt, absl::nullopt, true,
       blink::UserAgentBrandVersionType::kFullVersion);
-  EXPECT_EQ(greased_bv.brand, ";Not_A Brand");
+  EXPECT_EQ(greased_bv.brand, "Not?A_Brand");
   EXPECT_EQ(greased_bv.version, "24.0.0.0");
 
   // Test the greasy input with full version
   greased_bv = GetGreasedUserAgentBrandVersion(
       permuted_order, 84, absl::nullopt, "1024.0.0.0", true,
       blink::UserAgentBrandVersionType::kMajorVersion);
-  EXPECT_EQ(greased_bv.brand, "/Not=A?Brand");
+  EXPECT_EQ(greased_bv.brand, "Not;A=Brand");
   EXPECT_EQ(greased_bv.version, "1024");
 
   greased_bv = GetGreasedUserAgentBrandVersion(
       permuted_order, 84, absl::nullopt, "1024.0.0.0", true,
       blink::UserAgentBrandVersionType::kFullVersion);
-  EXPECT_EQ(greased_bv.brand, "/Not=A?Brand");
+  EXPECT_EQ(greased_bv.brand, "Not;A=Brand");
   EXPECT_EQ(greased_bv.version, "1024.0.0.0");
 }
 
diff --git a/components/mirroring/service/media_remoter.cc b/components/mirroring/service/media_remoter.cc
index 7216624..10b1f7c2 100644
--- a/components/mirroring/service/media_remoter.cc
+++ b/components/mirroring/service/media_remoter.cc
@@ -166,9 +166,9 @@
       base::StringPiece(reinterpret_cast<const char*>(message.data()),
                         message.size()),
       &encoded_rpc);
-  base::Value rpc(base::Value::Type::DICTIONARY);
-  rpc.SetKey("type", base::Value("RPC"));
-  rpc.SetKey("rpc", base::Value(std::move(encoded_rpc)));
+  base::Value::Dict rpc;
+  rpc.Set("type", "RPC");
+  rpc.Set("rpc", std::move(encoded_rpc));
   mojom::CastMessagePtr rpc_message = mojom::CastMessage::New();
   rpc_message->message_namespace = mojom::kRemotingNamespace;
   const bool did_serialize_rpc =
diff --git a/components/mirroring/service/receiver_response_unittest.cc b/components/mirroring/service/receiver_response_unittest.cc
index cbf8d7a2..d8f11665 100644
--- a/components/mirroring/service/receiver_response_unittest.cc
+++ b/components/mirroring/service/receiver_response_unittest.cc
@@ -101,10 +101,10 @@
   ASSERT_TRUE(response->error());
   EXPECT_EQ(42, response->error()->code);
   EXPECT_EQ("it is broken", response->error()->description);
-  std::unique_ptr<base::Value> parsed_details =
-      base::JSONReader::ReadDeprecated(response->error()->details);
+  absl::optional<base::Value> parsed_details =
+      base::JSONReader::Read(response->error()->details);
   ASSERT_TRUE(parsed_details && parsed_details->is_dict());
-  EXPECT_EQ(2u, parsed_details->DictSize());
+  EXPECT_EQ(2u, parsed_details->GetDict().size());
   int fool_value = 0;
   EXPECT_TRUE(GetInt(*parsed_details, "foo", &fool_value));
   EXPECT_EQ(-1, fool_value);
diff --git a/components/mirroring/service/session.cc b/components/mirroring/service/session.cc
index 58843d1..7f86cda 100644
--- a/components/mirroring/service/session.cc
+++ b/components/mirroring/service/session.cc
@@ -164,28 +164,25 @@
                      const std::string& codec_name,
                      const FrameSenderConfig& config,
                      const MirrorSettings& mirror_settings,
-                     base::Value::ListStorage* stream_list) {
-  base::Value stream(base::Value::Type::DICTIONARY);
-  stream.SetKey("index", base::Value(stream_index));
-  stream.SetKey("codecName", base::Value(base::ToLowerASCII(codec_name)));
-  stream.SetKey("rtpProfile", base::Value("cast"));
+                     base::Value::List& stream_list) {
+  base::Value::Dict stream;
+  stream.Set("index", stream_index);
+  stream.Set("codecName", base::ToLowerASCII(codec_name));
+  stream.Set("rtpProfile", "cast");
   const bool is_audio =
       (config.rtp_payload_type <= media::cast::RtpPayloadType::AUDIO_LAST);
-  stream.SetKey("rtpPayloadType",
-                base::Value(is_audio ? kAudioPayloadType : kVideoPayloadType));
-  stream.SetKey("ssrc", base::Value(static_cast<int>(config.sender_ssrc)));
-  stream.SetKey("targetDelay",
-                base::Value(static_cast<int>(
-                    config.animated_playout_delay.InMilliseconds())));
-  stream.SetKey("aesKey", base::Value(base::HexEncode(config.aes_key.data(),
-                                                      config.aes_key.size())));
-  stream.SetKey("aesIvMask",
-                base::Value(base::HexEncode(config.aes_iv_mask.data(),
-                                            config.aes_iv_mask.size())));
-  stream.SetKey("timeBase",
-                base::Value("1/" + std::to_string(config.rtp_timebase)));
-  stream.SetKey("receiverRtcpEventLog", base::Value(true));
-  stream.SetKey("rtpExtensions", base::Value("adaptive_playout_delay"));
+  stream.Set("rtpPayloadType",
+             is_audio ? kAudioPayloadType : kVideoPayloadType);
+  stream.Set("ssrc", static_cast<int>(config.sender_ssrc));
+  stream.Set("targetDelay",
+             static_cast<int>(config.animated_playout_delay.InMilliseconds()));
+  stream.Set("aesKey",
+             base::HexEncode(config.aes_key.data(), config.aes_key.size()));
+  stream.Set("aesIvMask", base::HexEncode(config.aes_iv_mask.data(),
+                                          config.aes_iv_mask.size()));
+  stream.Set("timeBase", "1/" + std::to_string(config.rtp_timebase));
+  stream.Set("receiverRtcpEventLog", true);
+  stream.Set("rtpExtensions", "adaptive_playout_delay");
   if (is_audio) {
     // Note on "AUTO" bitrate calculation: This is based on libopus source
     // at the time of this writing. Internally, it uses the following math:
@@ -199,26 +196,25 @@
                             ? config.max_bitrate
                             : (60 * config.max_frame_rate +
                                config.rtp_timebase * config.channels);
-    stream.SetKey("type", base::Value("audio_source"));
-    stream.SetKey("bitRate", base::Value(bitrate));
-    stream.SetKey("sampleRate", base::Value(config.rtp_timebase));
-    stream.SetKey("channels", base::Value(config.channels));
+    stream.Set("type", "audio_source");
+    stream.Set("bitRate", bitrate);
+    stream.Set("sampleRate", config.rtp_timebase);
+    stream.Set("channels", config.channels);
   } else /* is video */ {
-    stream.SetKey("type", base::Value("video_source"));
-    stream.SetKey("renderMode", base::Value("video"));
-    stream.SetKey("maxFrameRate",
-                  base::Value(std::to_string(static_cast<int>(
-                                  config.max_frame_rate * 1000)) +
-                              "/1000"));
-    stream.SetKey("maxBitRate", base::Value(config.max_bitrate));
-    base::Value::ListStorage resolutions;
-    base::Value resolution(base::Value::Type::DICTIONARY);
-    resolution.SetKey("width", base::Value(mirror_settings.max_width()));
-    resolution.SetKey("height", base::Value(mirror_settings.max_height()));
-    resolutions.emplace_back(std::move(resolution));
-    stream.SetKey("resolutions", base::Value(resolutions));
+    stream.Set("type", "video_source");
+    stream.Set("renderMode", "video");
+    stream.Set("maxFrameRate",
+               std::to_string(static_cast<int>(config.max_frame_rate * 1000)) +
+                   "/1000");
+    stream.Set("maxBitRate", config.max_bitrate);
+    base::Value::List resolutions;
+    base::Value::Dict resolution;
+    resolution.Set("width", mirror_settings.max_width());
+    resolution.Set("height", mirror_settings.max_height());
+    resolutions.Append(std::move(resolution));
+    stream.Set("resolutions", std::move(resolutions));
   }
-  stream_list->emplace_back(std::move(stream));
+  stream_list.Append(std::move(stream));
 }
 
 // Convert the sink capabilities to media::mojom::RemotingSinkMetadata.
@@ -822,7 +818,7 @@
   std::vector<FrameSenderConfig> video_configs;
 
   // Generate stream list with supported audio / video configs.
-  base::Value::ListStorage stream_list;
+  base::Value::List stream_list;
   int stream_index = 0;
   if (session_params_.type != SessionType::VIDEO_ONLY) {
     const int32_t audio_ssrc = base::RandInt(kAudioSsrcMin, kAudioSsrcMax);
@@ -832,14 +828,14 @@
       AddSenderConfig(audio_ssrc, config, aes_key, aes_iv, session_params_,
                       &audio_configs);
       AddStreamObject(stream_index++, "OPUS", audio_configs.back(),
-                      mirror_settings_, &stream_list);
+                      mirror_settings_, stream_list);
     } else /* REMOTING */ {
       FrameSenderConfig config = MirrorSettings::GetDefaultAudioConfig(
           RtpPayloadType::REMOTE_AUDIO, Codec::CODEC_AUDIO_REMOTE);
       AddSenderConfig(audio_ssrc, config, aes_key, aes_iv, session_params_,
                       &audio_configs);
       AddStreamObject(stream_index++, "REMOTE_AUDIO", audio_configs.back(),
-                      mirror_settings_, &stream_list);
+                      mirror_settings_, stream_list);
     }
   }
   if (session_params_.type != SessionType::AUDIO_ONLY) {
@@ -855,7 +851,7 @@
         AddSenderConfig(video_ssrc, config, aes_key, aes_iv, session_params_,
                         &video_configs);
         AddStreamObject(stream_index++, "VP8", video_configs.back(),
-                        mirror_settings_, &stream_list);
+                        mirror_settings_, stream_list);
       }
       if (media::cast::ExternalVideoEncoder::IsRecommended(
               Codec::CODEC_VIDEO_H264, session_params_.receiver_model_name,
@@ -866,7 +862,7 @@
         AddSenderConfig(video_ssrc, config, aes_key, aes_iv, session_params_,
                         &video_configs);
         AddStreamObject(stream_index++, "H264", video_configs.back(),
-                        mirror_settings_, &stream_list);
+                        mirror_settings_, stream_list);
       }
 
       // Then add software AV1 and VP9 if enabled.
@@ -877,7 +873,7 @@
         AddSenderConfig(video_ssrc, config, aes_key, aes_iv, session_params_,
                         &video_configs);
         AddStreamObject(stream_index++, "AV1", video_configs.back(),
-                        mirror_settings_, &stream_list);
+                        mirror_settings_, stream_list);
       }
       if (base::FeatureList::IsEnabled(features::kCastStreamingVp9)) {
         FrameSenderConfig config = MirrorSettings::GetDefaultVideoConfig(
@@ -885,7 +881,7 @@
         AddSenderConfig(video_ssrc, config, aes_key, aes_iv, session_params_,
                         &video_configs);
         AddStreamObject(stream_index++, "VP9", video_configs.back(),
-                        mirror_settings_, &stream_list);
+                        mirror_settings_, stream_list);
       }
 
       // Worst case, default to offering software VP8.
@@ -895,7 +891,7 @@
         AddSenderConfig(video_ssrc, config, aes_key, aes_iv, session_params_,
                         &video_configs);
         AddStreamObject(stream_index++, "VP8", video_configs.back(),
-                        mirror_settings_, &stream_list);
+                        mirror_settings_, stream_list);
       }
 
     } else /* REMOTING */ {
@@ -904,23 +900,22 @@
       AddSenderConfig(video_ssrc, config, aes_key, aes_iv, session_params_,
                       &video_configs);
       AddStreamObject(stream_index++, "REMOTE_VIDEO", video_configs.back(),
-                      mirror_settings_, &stream_list);
+                      mirror_settings_, stream_list);
     }
   }
   DCHECK(!audio_configs.empty() || !video_configs.empty());
 
   // Assemble the OFFER message.
-  base::Value offer(base::Value::Type::DICTIONARY);
-  offer.SetKey("castMode",
-               base::Value(state_ == MIRRORING ? "mirroring" : "remoting"));
-  offer.SetKey("receiverGetStatus", base::Value(true));
-  offer.SetKey("supportedStreams", base::Value(stream_list));
+  base::Value::Dict offer;
+  offer.Set("castMode", state_ == MIRRORING ? "mirroring" : "remoting");
+  offer.Set("receiverGetStatus", true);
+  offer.Set("supportedStreams", std::move(stream_list));
 
   const int32_t sequence_number = message_dispatcher_->GetNextSeqNumber();
-  base::Value offer_message(base::Value::Type::DICTIONARY);
-  offer_message.SetKey("type", base::Value("OFFER"));
-  offer_message.SetKey("seqNum", base::Value(sequence_number));
-  offer_message.SetKey("offer", std::move(offer));
+  base::Value::Dict offer_message;
+  offer_message.Set("type", "OFFER");
+  offer_message.Set("seqNum", sequence_number);
+  offer_message.Set("offer", std::move(offer));
 
   mojom::CastMessagePtr message_to_receiver = mojom::CastMessage::New();
   message_to_receiver->message_namespace = mojom::kWebRtcNamespace;
@@ -963,9 +958,9 @@
 void Session::QueryCapabilitiesForRemoting() {
   DCHECK(!media_remoter_);
   const int32_t sequence_number = message_dispatcher_->GetNextSeqNumber();
-  base::Value query(base::Value::Type::DICTIONARY);
-  query.SetKey("type", base::Value("GET_CAPABILITIES"));
-  query.SetKey("seqNum", base::Value(sequence_number));
+  base::Value::Dict query;
+  query.Set("type", "GET_CAPABILITIES");
+  query.Set("seqNum", sequence_number);
 
   mojom::CastMessagePtr query_message = mojom::CastMessage::New();
   query_message->message_namespace = mojom::kWebRtcNamespace;
diff --git a/components/mirroring/service/session_unittest.cc b/components/mirroring/service/session_unittest.cc
index 036baaa3d..a4c4041 100644
--- a/components/mirroring/service/session_unittest.cc
+++ b/components/mirroring/service/session_unittest.cc
@@ -129,20 +129,19 @@
   void Send(mojom::CastMessagePtr message) override {
     EXPECT_TRUE(message->message_namespace == mojom::kWebRtcNamespace ||
                 message->message_namespace == mojom::kRemotingNamespace);
-    std::unique_ptr<base::Value> value =
-        base::JSONReader::ReadDeprecated(message->json_format_data);
+    absl::optional<base::Value> value =
+        base::JSONReader::Read(message->json_format_data);
     ASSERT_TRUE(value);
     std::string message_type;
     EXPECT_TRUE(GetString(*value, "type", &message_type));
     if (message_type == "OFFER") {
       EXPECT_TRUE(GetInt(*value, "seqNum", &offer_sequence_number_));
-      auto* offer = value->FindKey("offer");
+      base::Value::Dict* offer = value->GetDict().FindDict("offer");
       ASSERT_TRUE(offer);
-      auto* raw_streams = offer->FindKey("supportedStreams");
+      base::Value* raw_streams = offer->Find("supportedStreams");
       if (raw_streams) {
-        base::Value::ListView streams = raw_streams->GetListDeprecated();
-        for (auto it = streams.begin(); it != streams.end(); ++it) {
-          EXPECT_EQ(it->FindKey("targetDelay")->GetInt(),
+        for (auto& value : raw_streams->GetList()) {
+          EXPECT_EQ(*value.GetDict().FindInt("targetDelay"),
                     target_playout_delay_ms_);
         }
       }
diff --git a/components/mirroring/service/value_util.cc b/components/mirroring/service/value_util.cc
index 2815cf85..8c8097e 100644
--- a/components/mirroring/service/value_util.cc
+++ b/components/mirroring/service/value_util.cc
@@ -7,7 +7,7 @@
 namespace mirroring {
 
 bool GetInt(const base::Value& value, const std::string& key, int32_t* result) {
-  auto* found = value.FindKey(key);
+  auto* found = value.GetDict().Find(key);
   if (!found || found->is_none())
     return true;
   if (found->is_int()) {
@@ -20,7 +20,7 @@
 bool GetDouble(const base::Value& value,
                const std::string& key,
                double* result) {
-  auto* found = value.FindKey(key);
+  auto* found = value.GetDict().Find(key);
   if (!found || found->is_none())
     return true;
   if (found->is_double()) {
@@ -37,7 +37,7 @@
 bool GetString(const base::Value& value,
                const std::string& key,
                std::string* result) {
-  auto* found = value.FindKey(key);
+  auto* found = value.GetDict().Find(key);
   if (!found || found->is_none())
     return true;
   if (found->is_string()) {
@@ -48,7 +48,7 @@
 }
 
 bool GetBool(const base::Value& value, const std::string& key, bool* result) {
-  auto* found = value.FindKey(key);
+  auto* found = value.GetDict().Find(key);
   if (!found || found->is_none())
     return true;
   if (found->is_bool()) {
@@ -61,12 +61,12 @@
 bool GetIntArray(const base::Value& value,
                  const std::string& key,
                  std::vector<int32_t>* result) {
-  auto* found = value.FindKey(key);
+  auto* found = value.GetDict().Find(key);
   if (!found || found->is_none())
     return true;
   if (!found->is_list())
     return false;
-  for (const auto& number_value : found->GetListDeprecated()) {
+  for (const auto& number_value : found->GetList()) {
     if (number_value.is_int())
       result->emplace_back(number_value.GetInt());
     else
@@ -78,12 +78,12 @@
 bool GetStringArray(const base::Value& value,
                     const std::string& key,
                     std::vector<std::string>* result) {
-  auto* found = value.FindKey(key);
+  auto* found = value.GetDict().Find(key);
   if (!found || found->is_none())
     return true;
   if (!found->is_list())
     return false;
-  for (const auto& string_value : found->GetListDeprecated()) {
+  for (const auto& string_value : found->GetList()) {
     if (string_value.is_string())
       result->emplace_back(string_value.GetString());
     else
diff --git a/components/omnibox/resources/translations/omnibox_pedal_synonyms_es.xtb b/components/omnibox/resources/translations/omnibox_pedal_synonyms_es.xtb
index fb46d98..53018a1 100644
--- a/components/omnibox/resources/translations/omnibox_pedal_synonyms_es.xtb
+++ b/components/omnibox/resources/translations/omnibox_pedal_synonyms_es.xtb
@@ -47,7 +47,7 @@
 <translation id="7988861522114961979">encuesta, formularios, formulario, cuestionario</translation>
 <translation id="7992725801741093524">cambiar los ajustes de privacidad de Google, gestionar la privacidad de Google, modificar la configuración de privacidad, ajustes de privacidad</translation>
 <translation id="8020024640114692614">abrir enlaces siempre en chrome, usar siempre chrome, usar chrome como navegador predeterminado, navegador predeterminado, borrar safari, eliminar safari, cómo poner chrome como predeterminado, navegador principal, hacer que chrome sea el navegador predeterminado, quiero que chrome sea mi navegador por defecto, abrir enlaces en chrome, elegir chrome como navegador predeterminado, navegador principal, buscar en chrome, configurar chrome como predeterminado</translation>
-<translation id="8319253638505741466">cerrar ventana de incógnito, terminar sesión de Incógnito, salir del modo Incógnito</translation>
+<translation id="8319253638505741466">cerrar ventana de Incógnito, terminar sesión de Incógnito, salir del modo Incógnito</translation>
 <translation id="8483249620579465383">aprovechar, comprender, explorar, entender, aprender, visitar, ver</translation>
 <translation id="8591468627389439293">notas, nota</translation>
 <translation id="8829022445543144664">protección mejorada, protección estándar, configuración de seguridad, llaves de seguridad, certificados, dns seguro, de forma segura</translation>
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index 47557afe..37462f9 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -513,7 +513,8 @@
       authenticator_->Authenticate(
           device_reauth::BiometricAuthRequester::kAutofillSuggestion,
           base::BindOnce(&PasswordAutofillManager::OnBiometricReauthCompleted,
-                         base::Unretained(this), value, frontend_id));
+                         base::Unretained(this), value, frontend_id),
+          /*use_last_valid_auth=*/true);
     }
   }
   autofill_client_->HideAutofillPopup(
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index 7063775..e5a65d3b 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -1626,7 +1626,8 @@
                 CanAuthenticate(BiometricAuthRequester::kAutofillSuggestion))
         .WillOnce(Return(true));
     EXPECT_CALL(*authenticator_.get(),
-                Authenticate(BiometricAuthRequester::kAutofillSuggestion, _))
+                Authenticate(BiometricAuthRequester::kAutofillSuggestion, _,
+                             /*use_last_valid_auth= */ true))
         .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/true));
 
     // Accept the suggestion to start the filing process which tries to
@@ -1682,7 +1683,8 @@
                 CanAuthenticate(BiometricAuthRequester::kAutofillSuggestion))
         .WillOnce(Return(true));
     EXPECT_CALL(*authenticator_.get(),
-                Authenticate(BiometricAuthRequester::kAutofillSuggestion, _))
+                Authenticate(BiometricAuthRequester::kAutofillSuggestion, _,
+                             /*use_last_valid_auth= */ true))
         .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/false));
 
     // Accept the suggestion to start the filing process which tries to
@@ -1725,7 +1727,8 @@
               CanAuthenticate(BiometricAuthRequester::kAutofillSuggestion))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kAutofillSuggestion, _));
+              Authenticate(BiometricAuthRequester::kAutofillSuggestion, _,
+                           /*use_last_valid_auth= */ true));
 
   // Accept the suggestion to start the filing process which tries to
   // reauthenticate the user if possible.
@@ -1769,7 +1772,8 @@
               CanAuthenticate(BiometricAuthRequester::kAutofillSuggestion))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kAutofillSuggestion, _));
+              Authenticate(BiometricAuthRequester::kAutofillSuggestion, _,
+                           /*use_last_valid_auth= */ true));
 
   // Accept the suggestion to start the filing process which tries to
   // reauthenticate the user if possible.
@@ -1814,7 +1818,8 @@
               CanAuthenticate(BiometricAuthRequester::kAutofillSuggestion))
       .WillOnce(Return(true));
   EXPECT_CALL(*authenticator_.get(),
-              Authenticate(BiometricAuthRequester::kAutofillSuggestion, _));
+              Authenticate(BiometricAuthRequester::kAutofillSuggestion, _,
+                           /*use_last_valid_auth= */ true));
 
   // Accept the suggestion to start the filing process which tries to
   // reauthenticate the user if possible.
diff --git a/components/password_manager/core/browser/password_store_backend_metrics_recorder.cc b/components/password_manager/core/browser/password_store_backend_metrics_recorder.cc
index d953bac..6e7f5d5 100644
--- a/components/password_manager/core/browser/password_store_backend_metrics_recorder.cc
+++ b/components/password_manager/core/browser/password_store_backend_metrics_recorder.cc
@@ -98,6 +98,9 @@
   if (backend_error.type == AndroidBackendErrorType::kExternalError) {
     DCHECK(backend_error.api_error_code.has_value());
     RecordApiErrorCode(backend_error.api_error_code.value());
+    LOG(ERROR) << "Password Manager API call for " << metric_infix_
+               << " failed with error code: "
+               << backend_error.api_error_code.value();
   }
 }
 
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
index 239aff3..2081a36 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
@@ -288,7 +288,8 @@
       password_issue.second.is_muted = IsMuted(true);
     }
   }
-  return presenter_->EditSavedCredentials(updated_credential);
+  return presenter_->EditSavedCredentials(updated_credential) ==
+         SavedPasswordsPresenter::EditResult::kSuccess;
 }
 
 bool InsecureCredentialsManager::UnmuteCredential(
@@ -300,7 +301,8 @@
       password_issue.second.is_muted = IsMuted(false);
     }
   }
-  return presenter_->EditSavedCredentials(updated_credential);
+  return presenter_->EditSavedCredentials(updated_credential) ==
+         SavedPasswordsPresenter::EditResult::kSuccess;
 }
 
 bool InsecureCredentialsManager::UpdateCredential(
diff --git a/components/password_manager/core/browser/ui/saved_passwords_presenter.cc b/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
index 782ac14e..1462ccc 100644
--- a/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
+++ b/components/password_manager/core/browser/ui/saved_passwords_presenter.cc
@@ -30,6 +30,7 @@
 using Store = password_manager::PasswordForm::Store;
 using SavedPasswordsView =
     password_manager::SavedPasswordsPresenter::SavedPasswordsView;
+using EditResult = password_manager::SavedPasswordsPresenter::EditResult;
 
 bool IsUsernameAlreadyUsed(SavedPasswordsView all_forms,
                            SavedPasswordsView forms_to_check,
@@ -63,16 +64,36 @@
   return form;
 }
 
-PasswordNoteAction CalculatePasswordNoteAction(bool old_note_empty,
-                                               bool new_note_empty) {
-  if (old_note_empty && !new_note_empty)
+// Check if notes was modified for a specified |form| with |new_note|.
+IsPasswordNoteChanged IsNoteChanged(const password_manager::PasswordForm& form,
+                                    const PasswordNote& new_note) {
+  const auto& old_note_itr = base::ranges::find_if(
+      form.notes, &std::u16string::empty, &PasswordNote::unique_display_name);
+  bool old_note_exists = old_note_itr != form.notes.end();
+  return IsPasswordNoteChanged(
+      (old_note_exists && old_note_itr->value != new_note.value) ||
+      (!old_note_exists && !new_note.value.empty()));
+}
+
+PasswordNoteAction UpdateNoteInPasswordForm(
+    password_manager::PasswordForm& form,
+    const PasswordNote& new_note) {
+  const auto& note_itr = base::ranges::find_if(
+      form.notes, &std::u16string::empty, &PasswordNote::unique_display_name);
+  // if the old note doesn't exist, the note is just created.
+  if (note_itr == form.notes.end()) {
+    form.notes.push_back(new_note);
     return PasswordNoteAction::kNoteAddedInEditDialog;
-  if (!old_note_empty && new_note_empty)
-    return PasswordNoteAction::kNoteRemovedInEditDialog;
-  if (!old_note_empty && !new_note_empty)
-    return PasswordNoteAction::kNoteEditedInEditDialog;
-  NOTREACHED();
-  return PasswordNoteAction::kNoteEditedInEditDialog;
+  }
+  // Note existed, but it was empty.
+  if (note_itr->value.empty()) {
+    note_itr->value = new_note.value;
+    note_itr->date_created = base::Time::Now();
+    return PasswordNoteAction::kNoteAddedInEditDialog;
+  }
+  note_itr->value = new_note.value;
+  return new_note.value.empty() ? PasswordNoteAction::kNoteRemovedInEditDialog
+                                : PasswordNoteAction::kNoteEditedInEditDialog;
 }
 
 }  // namespace
@@ -184,7 +205,7 @@
                                            std::u16string new_password) {
   CredentialUIEntry entry(form);
   entry.password = new_password;
-  return EditSavedCredentials(entry);
+  return EditSavedCredentials(entry) == EditResult::kSuccess;
 }
 
 bool SavedPasswordsPresenter::EditSavedPasswords(
@@ -196,117 +217,85 @@
   CredentialUIEntry entry(form);
   entry.password = new_password;
   entry.username = new_username;
-  return EditSavedCredentials(entry);
+  return EditSavedCredentials(entry) == EditResult::kSuccess;
 }
 
-bool SavedPasswordsPresenter::EditSavedCredentials(
+SavedPasswordsPresenter::EditResult
+SavedPasswordsPresenter::EditSavedCredentials(
     const CredentialUIEntry& credential) {
   std::vector<PasswordForm> forms_to_change =
       GetCorrespondingPasswordForms(credential.key());
   if (forms_to_change.empty())
-    return false;
+    return EditResult::kNotFound;
 
-  const auto& old_note_itr =
-      base::ranges::find_if(forms_to_change[0].notes, &std::u16string::empty,
-                            &PasswordNote::unique_display_name);
+  IsUsernameChanged username_changed(credential.username !=
+                                     forms_to_change[0].username_value);
+  IsPasswordChanged password_changed(credential.password !=
+                                     forms_to_change[0].password_value);
+  IsPasswordNoteChanged note_changed =
+      IsNoteChanged(forms_to_change[0], credential.note);
 
-  // TODO(crbug.com/1184691): Merge into a single method.
-  if (credential.username != forms_to_change[0].username_value ||
-      credential.password != forms_to_change[0].password_value ||
-      (old_note_itr != forms_to_change[0].notes.end() &&
-       credential.note != *old_note_itr)) {
-    return EditSavedPasswords(forms_to_change, credential.username,
-                              credential.password, credential.note.value);
-  } else if (credential.password_issues != forms_to_change[0].password_issues) {
-    for (auto& old_form : forms_to_change) {
-      old_form.password_issues = credential.password_issues;
-      GetStoreFor(old_form).UpdateLogin(old_form);
-    }
-    return true;
-  }
-  return false;
-}
+  bool issues_changed =
+      credential.password_issues != forms_to_change[0].password_issues;
 
-bool SavedPasswordsPresenter::EditSavedPasswords(
-    const SavedPasswordsView forms,
-    const std::u16string& new_username,
-    const std::u16string& new_password,
-    const std::u16string& new_note) {
-  if (forms.empty())
-    return false;
-  IsUsernameChanged username_changed(new_username != forms[0].username_value);
-  IsPasswordChanged password_changed(new_password != forms[0].password_value);
+  // Password can't be empty.
+  if (credential.password.empty())
+    return EditResult::kEmptyPassword;
 
-  const auto& old_note_itr =
-      base::ranges::find_if(forms[0].notes, &std::u16string::empty,
-                            &PasswordNote::unique_display_name);
-  bool old_note_exists = old_note_itr != forms[0].notes.end();
-  IsPasswordNoteChanged note_changed = IsPasswordNoteChanged(
-      (old_note_exists && old_note_itr->value != new_note) ||
-      (!old_note_exists && !new_note.empty()));
-
-  if (new_password.empty())
-    return false;
+  // Username can't be changed to the existing one.
   if (username_changed &&
-      IsUsernameAlreadyUsed(passwords_, forms, new_username)) {
-    return false;
+      IsUsernameAlreadyUsed(passwords_, forms_to_change, credential.username)) {
+    return EditResult::kAlreadyExisits;
   }
 
-  // An updated username implies a change in the primary key, thus we need to
-  // make sure to call the right API. Update every entry in the equivalence
-  // class.
-  if (username_changed || password_changed || note_changed) {
-    for (const auto& old_form : forms) {
-      PasswordStoreInterface& store = GetStoreFor(old_form);
-      PasswordForm new_form = old_form;
+  // Nothing changed.
+  if (!username_changed && !password_changed && !note_changed &&
+      !issues_changed) {
+    password_manager::metrics_util::LogPasswordEditResult(username_changed,
+                                                          password_changed);
+    return EditResult::kNothingChanged;
+  }
 
-      if (password_changed) {
-        new_form.password_value = new_password;
-        new_form.date_password_modified = base::Time::Now();
-        new_form.password_issues.clear();
-      }
+  for (const auto& old_form : forms_to_change) {
+    PasswordStoreInterface& store = GetStoreFor(old_form);
+    PasswordForm new_form = old_form;
 
-      if (note_changed) {
-        bool old_note_empty = false;
-        // if the old note doesn't exist, the note is just created.
-        const auto& note_itr =
-            base::ranges::find_if(new_form.notes, &std::u16string::empty,
-                                  &PasswordNote::unique_display_name);
-        if (note_itr == new_form.notes.end()) {
-          new_form.notes.emplace_back(new_note,
-                                      /*date_created=*/base::Time::Now());
-          old_note_empty = true;
-        } else {
-          if (note_itr->value.empty()) {
-            note_itr->date_created = base::Time::Now();
-            old_note_empty = true;
-          }
-          note_itr->value = new_note;
-        }
-
-        metrics_util::LogPasswordNoteActionInSettings(
-            CalculatePasswordNoteAction(old_note_empty, new_note.empty()));
-      }
-
-      if (username_changed) {
-        new_form.username_value = new_username;
-        // Phished and leaked issues are no longer relevant on username change.
-        // Weak and reused issues are still relevant.
-        new_form.password_issues.erase(InsecureType::kPhished);
-        new_form.password_issues.erase(InsecureType::kLeaked);
-        // Changing username requires deleting old form and adding new one. So
-        // the different API should be called.
-        store.UpdateLoginWithPrimaryKey(new_form, old_form);
-      } else {
-        store.UpdateLogin(new_form);
-      }
-      NotifyEdited(new_form);
+    if (issues_changed) {
+      new_form.password_issues = credential.password_issues;
     }
+
+    if (password_changed) {
+      new_form.password_value = credential.password;
+      new_form.date_password_modified = base::Time::Now();
+      new_form.password_issues.clear();
+    }
+
+    if (note_changed) {
+      PasswordNoteAction note_action =
+          UpdateNoteInPasswordForm(new_form, credential.note);
+      metrics_util::LogPasswordNoteActionInSettings(note_action);
+    }
+
+    // An updated username implies a change in the primary key, thus we need
+    // to make sure to call the right API.
+    if (username_changed) {
+      new_form.username_value = credential.username;
+      // Phished and leaked issues are no longer relevant on username change.
+      // Weak and reused issues are still relevant.
+      new_form.password_issues.erase(InsecureType::kPhished);
+      new_form.password_issues.erase(InsecureType::kLeaked);
+      // Changing username requires deleting old form and adding new one. So
+      // the different API should be called.
+      store.UpdateLoginWithPrimaryKey(new_form, old_form);
+    } else {
+      store.UpdateLogin(new_form);
+    }
+    NotifyEdited(new_form);
   }
 
   password_manager::metrics_util::LogPasswordEditResult(username_changed,
                                                         password_changed);
-  return true;
+  return EditResult::kSuccess;
 }
 
 SavedPasswordsPresenter::SavedPasswordsView
diff --git a/components/password_manager/core/browser/ui/saved_passwords_presenter.h b/components/password_manager/core/browser/ui/saved_passwords_presenter.h
index 5495dc10..4dfd091 100644
--- a/components/password_manager/core/browser/ui/saved_passwords_presenter.h
+++ b/components/password_manager/core/browser/ui/saved_passwords_presenter.h
@@ -57,6 +57,21 @@
     virtual void OnSavedPasswordsChanged(SavedPasswordsView passwords) {}
   };
 
+  // Result of EditSavedCredentials.
+  enum EditResult {
+    // Some credentials were successfully updated.
+    kSuccess,
+    // New credential matches the old one so nothing was changed.
+    kNothingChanged,
+    // Credential couldn't be found in the store.
+    kNotFound,
+    // Credentials with the same username and sign on realm already exists.
+    kAlreadyExisits,
+    // Password was empty.
+    kEmptyPassword,
+    kMaxValue = kEmptyPassword,
+  };
+
   explicit SavedPasswordsPresenter(
       scoped_refptr<PasswordStoreInterface> profile_store,
       scoped_refptr<PasswordStoreInterface> account_store = nullptr);
@@ -99,18 +114,9 @@
                           const std::u16string& new_username,
                           const std::u16string& new_password);
 
-  // Modifies provided password forms, with |new_username|, |new_password| and
-  // |new_note|. |forms| must represent single credential, with its duplicates,
-  // or the same form saved on another store type.
-  // TODO(crbug.com/1330906): Remove in favor of EditSavedCredentials.
-  bool EditSavedPasswords(const SavedPasswordsView forms,
-                          const std::u16string& new_username,
-                          const std::u16string& new_password,
-                          const std::u16string& new_note = std::u16string());
-
   // Modifies all the saved credentials with a matching key. Only username,
   // password and notes are modified.
-  bool EditSavedCredentials(const CredentialUIEntry& credential);
+  EditResult EditSavedCredentials(const CredentialUIEntry& credential);
 
   // Returns a list of the currently saved credentials.
   SavedPasswordsView GetSavedPasswords() const;
diff --git a/components/password_manager/core/browser/ui/saved_passwords_presenter_unittest.cc b/components/password_manager/core/browser/ui/saved_passwords_presenter_unittest.cc
index 9513968..8721f49 100644
--- a/components/password_manager/core/browser/ui/saved_passwords_presenter_unittest.cc
+++ b/components/password_manager/core/browser/ui/saved_passwords_presenter_unittest.cc
@@ -271,8 +271,6 @@
   RunUntilIdle();
   EXPECT_FALSE(store().IsEmpty());
 
-  std::vector<PasswordForm> forms = {form};
-
   const std::u16string new_username = u"new_username";
   // The result of the update should have a new username and no password
   // issues.
@@ -280,13 +278,16 @@
   updated_username.username_value = new_username;
   updated_username.password_issues.clear();
 
+  CredentialUIEntry credential_to_edit(form);
+  credential_to_edit.username = new_username;
+
   // Verify that editing a username triggers the right notifications.
   base::HistogramTester histogram_tester;
 
   EXPECT_CALL(observer, OnEdited(updated_username));
   EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(updated_username)));
-  EXPECT_TRUE(
-      presenter().EditSavedPasswords(forms, new_username, form.password_value));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
   EXPECT_THAT(
       store().stored_passwords(),
@@ -324,21 +325,24 @@
 
   std::vector<PasswordForm> forms = {form};
 
-  const std::u16string new_username = u"new_username";
+  const std::u16string kNewUsername = u"new_username";
   // The result of the update should have a new username and weak and reused
   // password issues.
   PasswordForm updated_username = form;
-  updated_username.username_value = new_username;
+  updated_username.username_value = kNewUsername;
   updated_username.password_issues = {
       {InsecureType::kReused,
        InsecurityMetadata(base::Time::FromTimeT(1), IsMuted(false))},
       {InsecureType::kWeak,
        InsecurityMetadata(base::Time::FromTimeT(1), IsMuted(false))}};
 
+  CredentialUIEntry credential_to_edit(form);
+  credential_to_edit.username = kNewUsername;
+
   EXPECT_CALL(observer, OnEdited(updated_username));
   EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(updated_username)));
-  EXPECT_TRUE(
-      presenter().EditSavedPasswords(forms, new_username, form.password_value));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
   EXPECT_THAT(
       store().stored_passwords(),
@@ -364,8 +368,6 @@
   RunUntilIdle();
   EXPECT_FALSE(store().IsEmpty());
 
-  std::vector<PasswordForm> forms = {form};
-
   const std::u16string new_password = u"new_password";
   PasswordForm updated_password = form;
   // The result of the update should have a new password and no password
@@ -374,12 +376,15 @@
   updated_password.date_password_modified = base::Time::Now();
   updated_password.password_issues.clear();
 
+  CredentialUIEntry credential_to_edit(form);
+  credential_to_edit.password = new_password;
+
   base::HistogramTester histogram_tester;
   // Verify that editing a password triggers the right notifications.
   EXPECT_CALL(observer, OnEdited(updated_password));
   EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(updated_password)));
-  EXPECT_TRUE(
-      presenter().EditSavedPasswords(forms, form.username_value, new_password));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
   EXPECT_THAT(
       store().stored_passwords(),
@@ -402,13 +407,15 @@
 
   store().AddLogin(form);
   RunUntilIdle();
-  std::vector<PasswordForm> forms = {form};
 
   const std::u16string kNewNoteValue = u"new note";
 
+  CredentialUIEntry credential_to_edit(form);
+  credential_to_edit.note = PasswordNote(kNewNoteValue, base::Time::Now());
+
   base::HistogramTester histogram_tester;
-  EXPECT_TRUE(presenter().EditSavedPasswords(
-      forms, form.username_value, form.password_value, kNewNoteValue));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
 
   // The note with the non-empty display name should be untouched. Another note
@@ -434,12 +441,14 @@
 
   store().AddLogin(form);
   RunUntilIdle();
-  std::vector<PasswordForm> forms = {form};
 
   const std::u16string kNewNoteValue = u"new note";
 
-  EXPECT_TRUE(presenter().EditSavedPasswords(
-      forms, form.username_value, form.password_value, kNewNoteValue));
+  CredentialUIEntry credential_to_edit(form);
+  credential_to_edit.note = PasswordNote(kNewNoteValue, base::Time::Now());
+
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
 
   PasswordForm expected_updated_form = form;
@@ -463,9 +472,12 @@
 
   const std::u16string kNewNoteValue = u"new note";
 
+  CredentialUIEntry credential_to_edit(form);
+  credential_to_edit.note = PasswordNote(kNewNoteValue, base::Time::Now());
+
   base::HistogramTester histogram_tester;
-  EXPECT_TRUE(presenter().EditSavedPasswords(
-      forms, form.username_value, form.password_value, kNewNoteValue));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
 
   PasswordForm expected_updated_form = form;
@@ -487,9 +499,13 @@
   store().AddLogin(form);
   RunUntilIdle();
 
+  CredentialUIEntry credential_to_edit(form);
+  credential_to_edit.note = PasswordNote(u"", base::Time::Now());
+
   base::HistogramTester histogram_tester;
-  EXPECT_TRUE(presenter().EditSavedPasswords(forms, form.username_value,
-                                             form.password_value, u""));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
+
   RunUntilIdle();
 
   PasswordForm expected_updated_form = form;
@@ -544,8 +560,6 @@
   RunUntilIdle();
   EXPECT_FALSE(store().IsEmpty());
 
-  std::vector<PasswordForm> forms = {form};
-
   const std::u16string new_username = u"new_username";
   const std::u16string new_password = u"new_password";
 
@@ -557,12 +571,16 @@
   updated_both.date_password_modified = base::Time::Now();
   updated_both.password_issues.clear();
 
+  CredentialUIEntry credential_to_edit(form);
+  credential_to_edit.username = new_username;
+  credential_to_edit.password = new_password;
+
   base::HistogramTester histogram_tester;
   // Verify that editing username and password triggers the right notifications.
   EXPECT_CALL(observer, OnEdited(updated_both));
   EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(updated_both)));
-  EXPECT_TRUE(
-      presenter().EditSavedPasswords(forms, new_username, new_password));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
   EXPECT_THAT(store().stored_passwords(),
               ElementsAre(Pair(form.signon_realm, ElementsAre(updated_both))));
@@ -587,20 +605,21 @@
   RunUntilIdle();
   EXPECT_FALSE(store().IsEmpty());
 
-  std::vector<PasswordForm> forms{form1};
-
+  CredentialUIEntry credential_to_edit(form1);
+  credential_to_edit.username = form2.username_value;
   // Updating the form with the username which is already used for same website
   // fails.
-  const std::u16string new_username = u"test2@gmail.com";
-  EXPECT_FALSE(presenter().EditSavedPasswords(forms, new_username,
-                                              form1.password_value));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kAlreadyExisits,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
   EXPECT_THAT(store().stored_passwords(),
               ElementsAre(Pair(form1.signon_realm, ElementsAre(form1, form2))));
 
+  credential_to_edit = CredentialUIEntry(form1);
+  credential_to_edit.password = u"";
   // Updating the form with the empty password fails.
-  EXPECT_FALSE(presenter().EditSavedPasswords(forms, form1.username_value,
-                                              std::u16string()));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kEmptyPassword,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
   EXPECT_THAT(store().stored_passwords(),
               ElementsAre(Pair(form1.signon_realm, ElementsAre(form1, form2))));
@@ -625,9 +644,9 @@
   base::HistogramTester histogram_tester;
   EXPECT_CALL(observer, OnEdited).Times(0);
   EXPECT_CALL(observer, OnSavedPasswordsChanged).Times(0);
-  std::vector<PasswordForm> forms = {form};
-  EXPECT_TRUE(presenter().EditSavedPasswords(forms, form.username_value,
-                                             form.password_value));
+
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kNothingChanged,
+            presenter().EditSavedCredentials(CredentialUIEntry(form)));
   RunUntilIdle();
   histogram_tester.ExpectBucketCount(
       "PasswordManager.PasswordEditUpdatedValues",
@@ -637,9 +656,10 @@
 }
 
 TEST_F(SavedPasswordsPresenterTest, EditPasswordsEmptyList) {
-  EXPECT_FALSE(presenter().EditSavedPasswords(
-      SavedPasswordsPresenter::SavedPasswordsView(), u"test1@gmail.com",
-      u"password"));
+  CredentialUIEntry credential(
+      CreateTestPasswordForm(PasswordForm::Store::kProfileStore));
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kNotFound,
+            presenter().EditSavedCredentials(credential));
 }
 
 TEST_F(SavedPasswordsPresenterTest, EditUpdatesDuplicates) {
@@ -1018,8 +1038,11 @@
 
   auto new_username = account_store_form.username_value;
   std::vector<PasswordForm> forms_to_edit{profile_store_form};
-  EXPECT_TRUE(presenter().EditSavedPasswords(
-      forms_to_edit, new_username, profile_store_form.password_value));
+  CredentialUIEntry credential_to_edit(profile_store_form);
+  credential_to_edit.username = new_username;
+
+  EXPECT_EQ(SavedPasswordsPresenter::EditResult::kSuccess,
+            presenter().EditSavedCredentials(credential_to_edit));
   RunUntilIdle();
   profile_store_form.username_value = new_username;
   profile_store_form.password_issues.clear();
diff --git a/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.cc b/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.cc
index 95ad65c4..92207af 100644
--- a/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.cc
+++ b/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.cc
@@ -31,54 +31,52 @@
     core_->store()->RemoveObserver(this);
 }
 
-void MachineLevelUserCloudPolicyStatusProvider::GetStatus(
-    base::DictionaryValue* dict) {
+base::Value::Dict MachineLevelUserCloudPolicyStatusProvider::GetStatus() {
   CloudPolicyRefreshScheduler* refresh_scheduler = core_->refresh_scheduler();
 
-  dict->SetStringKey(
-      "refreshInterval",
-      ui::TimeFormat::Simple(
-          ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_SHORT,
-          base::Milliseconds(
-              refresh_scheduler
-                  ? refresh_scheduler->GetActualRefreshDelay()
-                  : CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs)));
-  dict->SetBoolKey(
+  base::Value::Dict dict;
+  dict.Set("refreshInterval",
+           ui::TimeFormat::Simple(
+               ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_SHORT,
+               base::Milliseconds(
+                   refresh_scheduler
+                       ? refresh_scheduler->GetActualRefreshDelay()
+                       : CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs)));
+  dict.Set(
       "policiesPushAvailable",
       refresh_scheduler ? refresh_scheduler->invalidations_available() : false);
 
   if (!context_->enrollmentToken.empty())
-    dict->SetStringKey("enrollmentToken", context_->enrollmentToken);
+    dict.Set("enrollmentToken", context_->enrollmentToken);
 
   if (!context_->deviceId.empty())
-    dict->SetStringKey("deviceId", context_->deviceId);
+    dict.Set("deviceId", context_->deviceId);
 
   CloudPolicyStore* store = core_->store();
   if (store) {
     std::u16string status = GetPolicyStatusFromStore(store, core_->client());
 
-    dict->SetStringKey("status", status);
+    dict.Set("status", status);
 
     const enterprise_management::PolicyData* policy = store->policy();
     if (policy) {
-      dict->SetStringKey(
-          "timeSinceLastRefresh",
-          GetTimeSinceLastActionString(refresh_scheduler
-                                           ? refresh_scheduler->last_refresh()
-                                           : base::Time()));
-      dict->SetStringKey("domain", gaia::ExtractDomainName(policy->username()));
+      dict.Set("timeSinceLastRefresh",
+               GetTimeSinceLastActionString(
+                   refresh_scheduler ? refresh_scheduler->last_refresh()
+                                     : base::Time()));
+      dict.Set("domain", gaia::ExtractDomainName(policy->username()));
     }
   }
-  dict->SetStringKey("machine", GetMachineName());
+  dict.Set("machine", GetMachineName());
 
   if (!context_->lastCloudReportSent.is_null()) {
-    dict->SetStringKey("lastCloudReportSentTimestamp",
-                       base::TimeFormatShortDateAndTimeWithTimeZone(
-                           context_->lastCloudReportSent));
-    dict->SetStringKey(
-        "timeSinceLastCloudReportSent",
-        GetTimeSinceLastActionString(context_->lastCloudReportSent));
+    dict.Set("lastCloudReportSentTimestamp",
+             base::TimeFormatShortDateAndTimeWithTimeZone(
+                 context_->lastCloudReportSent));
+    dict.Set("timeSinceLastCloudReportSent",
+             GetTimeSinceLastActionString(context_->lastCloudReportSent));
   }
+  return dict;
 }
 
 void MachineLevelUserCloudPolicyStatusProvider::OnStoreLoaded(
diff --git a/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h b/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h
index 05f0540..f737150 100644
--- a/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h
+++ b/components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h
@@ -9,14 +9,11 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
+#include "base/values.h"
 #include "components/policy/core/browser/webui/policy_status_provider.h"
 #include "components/policy/core/common/cloud/cloud_policy_store.h"
 #include "components/policy/policy_export.h"
 
-namespace base {
-class DictionaryValue;
-}
-
 namespace policy {
 class CloudPolicyCore;
 
@@ -40,7 +37,7 @@
   ~MachineLevelUserCloudPolicyStatusProvider() override;
 
   // PolicyStatusProvider implementation.
-  void GetStatus(base::DictionaryValue* dict) override;
+  base::Value::Dict GetStatus() override;
 
   // CloudPolicyStore::Observer implementation.
   void OnStoreLoaded(CloudPolicyStore* store) override;
diff --git a/components/policy/core/browser/webui/policy_status_provider.cc b/components/policy/core/browser/webui/policy_status_provider.cc
index 60bf9f7d..65a6ef9 100644
--- a/components/policy/core/browser/webui/policy_status_provider.cc
+++ b/components/policy/core/browser/webui/policy_status_provider.cc
@@ -12,6 +12,7 @@
 #include "base/time/clock.h"
 #include "base/time/default_clock.h"
 #include "base/time/time.h"
+#include "base/values.h"
 #include "components/policy/core/browser/cloud/message_util.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/cloud_policy_core.h"
@@ -68,9 +69,10 @@
 }
 
 // static
-void PolicyStatusProvider::GetStatus(base::DictionaryValue* dict) {
+base::Value::Dict PolicyStatusProvider::GetStatus() {
   // This method is called when the client is not enrolled.
-  // Thus leaving the dict without any changes.
+  // Thus return an empty dictionary.
+  return base::Value::Dict();
 }
 
 void PolicyStatusProvider::NotifyStatusChange() {
@@ -79,8 +81,8 @@
 }
 
 // static
-void PolicyStatusProvider::GetStatusFromCore(const CloudPolicyCore* core,
-                                             base::DictionaryValue* dict) {
+base::Value::Dict PolicyStatusProvider::GetStatusFromCore(
+    const CloudPolicyCore* core) {
   const CloudPolicyStore* store = core->store();
   const CloudPolicyClient* client = core->client();
   const CloudPolicyRefreshScheduler* refresh_scheduler =
@@ -89,7 +91,7 @@
   const std::u16string status = GetPolicyStatusFromStore(store, client);
 
   const em::PolicyData* policy = store->policy();
-  GetStatusFromPolicyData(policy, dict);
+  base::Value::Dict dict = GetStatusFromPolicyData(policy);
 
   base::TimeDelta refresh_interval = base::Milliseconds(
       refresh_scheduler ? refresh_scheduler->GetActualRefreshDelay()
@@ -100,13 +102,13 @@
 
   bool no_error = store->status() == CloudPolicyStore::STATUS_OK && client &&
                   client->status() == DM_STATUS_SUCCESS;
-  dict->SetBoolKey("error", !no_error);
-  dict->SetBoolKey("policiesPushAvailable", is_push_available);
-  dict->SetStringKey("status", status);
+  dict.Set("error", !no_error);
+  dict.Set("policiesPushAvailable", is_push_available);
+  dict.Set("status", status);
   // If push is on, policy update will be done via push. Hide policy fetch
   // interval label to prevent users from misunderstanding.
   if (!is_push_available) {
-    dict->SetStringKey(
+    dict.Set(
         "refreshInterval",
         ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION,
                                ui::TimeFormat::LENGTH_SHORT, refresh_interval));
@@ -115,45 +117,47 @@
       policy && policy->has_timestamp()
           ? base::Time::FromJavaTime(policy->timestamp())
           : base::Time();
-  dict->SetStringKey("timeSinceLastRefresh",
-                     GetTimeSinceLastActionString(last_refresh_time));
+  dict.Set("timeSinceLastRefresh",
+           GetTimeSinceLastActionString(last_refresh_time));
 
   // In case state_keys aren't available, we have no scheduler. See also
   // DeviceCloudPolicyInitializer::TryToCreateClient and b/181140445.
   base::Time last_fetch_attempted_time =
       refresh_scheduler ? refresh_scheduler->last_refresh() : base::Time();
-  dict->SetStringKey("timeSinceLastFetchAttempt",
-                     GetTimeSinceLastActionString(last_fetch_attempted_time));
+  dict.Set("timeSinceLastFetchAttempt",
+           GetTimeSinceLastActionString(last_fetch_attempted_time));
+  return dict;
 }
 
 // static
-void PolicyStatusProvider::GetStatusFromPolicyData(
-    const em::PolicyData* policy,
-    base::DictionaryValue* dict) {
+base::Value::Dict PolicyStatusProvider::GetStatusFromPolicyData(
+    const em::PolicyData* policy) {
   std::string client_id = policy ? policy->device_id() : std::string();
   std::string username = policy ? policy->username() : std::string();
 
+  base::Value::Dict dict;
   if (policy && policy->has_annotated_asset_id())
-    dict->SetStringKey("assetId", policy->annotated_asset_id());
+    dict.Set("assetId", policy->annotated_asset_id());
   if (policy && policy->has_annotated_location())
-    dict->SetStringKey("location", policy->annotated_location());
+    dict.Set("location", policy->annotated_location());
   if (policy && policy->has_directory_api_id())
-    dict->SetStringKey("directoryApiId", policy->directory_api_id());
+    dict.Set("directoryApiId", policy->directory_api_id());
   if (policy && policy->has_gaia_id())
-    dict->SetStringKey("gaiaId", policy->gaia_id());
+    dict.Set("gaiaId", policy->gaia_id());
 
-  dict->SetStringKey("clientId", client_id);
-  dict->SetStringKey("username", username);
+  dict.Set("clientId", client_id);
+  dict.Set("username", username);
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   // Include the "Managed by:" attribute for the user policy legend.
   if (policy->state() == enterprise_management::PolicyData::ACTIVE) {
     if (policy->has_managed_by())
-      dict->SetStringKey("enterpriseDomainManager", policy->managed_by());
+      dict.Set("enterpriseDomainManager", policy->managed_by());
     else if (policy->has_display_domain())
-      dict->SetStringKey("enterpriseDomainManager", policy->display_domain());
+      dict.Set("enterpriseDomainManager", policy->display_domain());
   }
 #endif
+  return dict;
 }
 
 // CloudPolicyStore errors take precedence to show in the status message.
diff --git a/components/policy/core/browser/webui/policy_status_provider.h b/components/policy/core/browser/webui/policy_status_provider.h
index f157704..2048a51 100644
--- a/components/policy/core/browser/webui/policy_status_provider.h
+++ b/components/policy/core/browser/webui/policy_status_provider.h
@@ -10,10 +10,10 @@
 #include "base/callback_helpers.h"
 #include "base/time/clock.h"
 #include "base/time/time.h"
+#include "base/values.h"
 #include "components/policy/policy_export.h"
 
 namespace base {
-class DictionaryValue;
 class Time;
 }
 
@@ -39,15 +39,12 @@
   // Sets a callback to invoke upon status changes.
   virtual void SetStatusChangeCallback(const base::RepeatingClosure& callback);
 
-  // Fills the passed dictionary with metadata about policies.
-  // The passed base::DictionaryValue should be empty.
-  virtual void GetStatus(base::DictionaryValue* dict);
+  // Returns a dictionary with metadata about policies.
+  virtual base::Value::Dict GetStatus();
 
-  static void GetStatusFromCore(const CloudPolicyCore* core,
-                                base::DictionaryValue* dict);
-  static void GetStatusFromPolicyData(
-      const enterprise_management::PolicyData* policy,
-      base::DictionaryValue* dict);
+  static base::Value::Dict GetStatusFromCore(const CloudPolicyCore* core);
+  static base::Value::Dict GetStatusFromPolicyData(
+      const enterprise_management::PolicyData* policy);
 
   // Overrides clock in tests. Returned closure removes the override when
   // destroyed.
diff --git a/components/policy/resources/policy_templates_es.xtb b/components/policy/resources/policy_templates_es.xtb
index c49557b..75b4924 100644
--- a/components/policy/resources/policy_templates_es.xtb
+++ b/components/policy/resources/policy_templates_es.xtb
@@ -4562,7 +4562,7 @@
       Si se le asigna el valor <ph name="BR_UNDER_USER_CONTROL" />, se preguntará a los usuarios si quieren usar los servicios de ubicación de Google. Si los usuarios activan los servicios de ubicación de Google, las aplicaciones Android usarán estos servicios para consultar la ubicación del dispositivo y enviar datos de ubicación anónimos a Google.
 
       Tras la configuración inicial, los usuarios podrán activar y desactivar los servicios de ubicación de Google.</translation>
-<translation id="5908808391744484238">Cuando se habilita o no se establece, el filtro de parámetros de URL puede quitar algunos parámetros cuando un usuario selecciona "Abrir enlace en una ventana de incógnito" desde el menú contextual.
+<translation id="5908808391744484238">Cuando se habilita o no se establece, el filtro de parámetros de URL puede quitar algunos parámetros cuando un usuario selecciona "Abrir enlace en una ventana de Incógnito" desde el menú contextual.
       Cuando se inhabilita, no se aplica ningún filtro.
       Esta política es temporal y podría retirarse en una versión posterior.</translation>
 <translation id="5910810837616572201">Si se utiliza <ph name="PRINTERS_BLACKLIST" /> para la política <ph name="BULK_PRINTERS_ACCESS_MODE_POLICY_NAME" />, al establecer la política <ph name="NATIVE_PRINTERS_BULK_BLACKLIST_POLICY_NAME" />, esta especifica qué impresoras no pueden utilizar los usuarios. El usuario podrá utilizar todas las impresoras, excepto aquellas con los ID indicados en la política. Los ID deben coincidir con los campos <ph name="ID_FIELD" /> o <ph name="GUID_FIELD" /> del archivo definido en la política <ph name="BULK_PRINTERS_POLICY_NAME" />.
diff --git a/components/signin/core/browser/account_reconcilor.cc b/components/signin/core/browser/account_reconcilor.cc
index f48e040..0d9a7f5 100644
--- a/components/signin/core/browser/account_reconcilor.cc
+++ b/components/signin/core/browser/account_reconcilor.cc
@@ -611,7 +611,7 @@
     SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_SCHEDULED);
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(&AccountReconcilor::StartReconcile,
-                                  base::Unretained(this),
+                                  weak_factory_.GetWeakPtr(),
                                   Trigger::kTokenChangeDuringReconcile));
   } else if (error_during_last_reconcile_.state() ==
              GoogleServiceAuthError::NONE) {
diff --git a/components/strings/components_strings_af.xtb b/components/strings/components_strings_af.xtb
index 29244be..0ac72b7 100644
--- a/components/strings/components_strings_af.xtb
+++ b/components/strings/components_strings_af.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Kon nie hierdie webbladsy vertoon nie.</translation>
 <translation id="1586541204584340881">Watter uitbreidings jy geïnstalleer het</translation>
 <translation id="1588438908519853928">Normaal</translation>
-<translation id="1589050138437146318">Installeer ARCore?</translation>
 <translation id="1592005682883173041">Toegang tot plaaslike data</translation>
 <translation id="1594030484168838125">Kies</translation>
 <translation id="1596296697375291157">Rillers, misdaad- en misterieflieks</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Halfgevou</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Programme wat geïnstalleer is en hoe gereeld hulle gebruik word</translation>
-<translation id="8317207217658302555">Dateer ARCore op?</translation>
 <translation id="831997045666694187">Aand</translation>
 <translation id="8321476692217554900">kennisgewings</translation>
 <translation id="8332188693563227489">Toegang tot <ph name="HOST_NAME" /> is geweier</translation>
diff --git a/components/strings/components_strings_am.xtb b/components/strings/components_strings_am.xtb
index 5933dde49..6a543904 100644
--- a/components/strings/components_strings_am.xtb
+++ b/components/strings/components_strings_am.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">ይህን ድረ-ገጽ በማሳየት ላይ ሳለ የሆነ ችግር ተፈጥሯል።</translation>
 <translation id="1586541204584340881">የትኛዎቹ ቅጥያዎች ናቸው እርስዎ የጫኑት</translation>
 <translation id="1588438908519853928">መደበኛ</translation>
-<translation id="1589050138437146318">ARCore ይጫን?</translation>
 <translation id="1592005682883173041">አካባቢያዊ የውሂብ መድረሻ</translation>
 <translation id="1594030484168838125">ምረጥ</translation>
 <translation id="1596296697375291157">አጓጊ፣ ወንጀል እና ሚስጥራዊ ፊልሞች</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">ግማሽ እጠፍ</translation>
 <translation id="8307358339886459768">ትንሽ-ፎቶ</translation>
 <translation id="8307888238279532626">መተግበሪያዎች ተጭነዋል እና ምን ያህል ጊዜ ስራ ላይ የሚውሉ</translation>
-<translation id="8317207217658302555">ARCore ይዘምን?</translation>
 <translation id="831997045666694187">ምሽት</translation>
 <translation id="8321476692217554900">ማሳወቂያዎች</translation>
 <translation id="8332188693563227489">የ<ph name="HOST_NAME" /> መዳረሻ ተከልክሏል</translation>
diff --git a/components/strings/components_strings_ar.xtb b/components/strings/components_strings_ar.xtb
index f9755ee..632693eed 100644
--- a/components/strings/components_strings_ar.xtb
+++ b/components/strings/components_strings_ar.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">حدث خطأ ما أثناء عرض صفحة الويب هذه.</translation>
 <translation id="1586541204584340881">الإضافات التي ثبّتها</translation>
 <translation id="1588438908519853928">عادي</translation>
-<translation id="1589050138437146318">‏هل تريد تثبيت ARCore؟</translation>
 <translation id="1592005682883173041">الوصول إلى البيانات المحلية</translation>
 <translation id="1594030484168838125">اختيار</translation>
 <translation id="1596296697375291157">أفلام تشويق وغموض وجرائم</translation>
@@ -2662,7 +2661,6 @@
 <translation id="830498451218851433">الطي إلى نصفَين</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">التطبيقات المثبَّتة وعدد مرات استخدامها</translation>
-<translation id="8317207217658302555">‏هل تريد تحديث ARCore؟</translation>
 <translation id="831997045666694187">مساءً</translation>
 <translation id="8321476692217554900">الإشعارات</translation>
 <translation id="8332188693563227489">تم رفض الدخول إلى <ph name="HOST_NAME" />.</translation>
diff --git a/components/strings/components_strings_as.xtb b/components/strings/components_strings_as.xtb
index f26fafd..64443ff 100644
--- a/components/strings/components_strings_as.xtb
+++ b/components/strings/components_strings_as.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">এই ৱেবপৃষ্ঠাটো প্ৰদর্শন কৰোঁতে কিবা আসোঁৱাহ হ’ল।</translation>
 <translation id="1586541204584340881">আপুনি ইনষ্টল কৰা এক্সটেনশ্বনসমূহ</translation>
 <translation id="1588438908519853928">সাধাৰণ</translation>
-<translation id="1589050138437146318">ARCore ইনষ্টল কৰিবনে?</translation>
 <translation id="1592005682883173041">স্থানীয় ডেটাৰ এক্সেছ</translation>
 <translation id="1594030484168838125">বাছনি কৰক</translation>
 <translation id="1596296697375291157">থ্ৰিলাৰ, অপৰাধ আৰু ৰহস্যময় কাহিনীৰ চলচ্চিত্ৰ</translation>
@@ -2653,7 +2652,6 @@
 <translation id="830498451218851433">আধা ফ'ল্ড কৰক</translation>
 <translation id="8307358339886459768">সৰু-ফট’</translation>
 <translation id="8307888238279532626">কি এপ্‌ ইনষ্টল কৰা হৈছে আৰু সেইবোৰ কিমান সঘনাই ব্যৱহাৰ কৰা হৈছে</translation>
-<translation id="8317207217658302555">ARCore আপডে’ট কৰিবনে?</translation>
 <translation id="831997045666694187">সন্ধিয়া</translation>
 <translation id="8321476692217554900">জাননী</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" />লৈ এক্সেছ অগ্ৰাহ্য কৰা হৈছিল</translation>
diff --git a/components/strings/components_strings_az.xtb b/components/strings/components_strings_az.xtb
index 2507cef..3be22dd 100644
--- a/components/strings/components_strings_az.xtb
+++ b/components/strings/components_strings_az.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Bu veb səhifəni göstərərkən xəta baş verdi.</translation>
 <translation id="1586541204584340881">Quraşdırdığınız artırmalar</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">ARCore quraşdırılsın?</translation>
 <translation id="1592005682883173041">Yerli Data Girişi</translation>
 <translation id="1594030484168838125">Seçin</translation>
 <translation id="1596296697375291157">Triller, cinayət və sirr filmləri</translation>
@@ -2654,7 +2653,6 @@
 <translation id="830498451218851433">Yarımçıq qatlayın</translation>
 <translation id="8307358339886459768">Kiçik Ölçülü Foto</translation>
 <translation id="8307888238279532626">Quraşdırılmış tətbiqlər və nə zaman istifadə edildikləri</translation>
-<translation id="8317207217658302555">ARCore güncəllənsin?</translation>
 <translation id="831997045666694187">Axşam</translation>
 <translation id="8321476692217554900">bildirişlər</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> hostuna giriş rədd edildi</translation>
diff --git a/components/strings/components_strings_be.xtb b/components/strings/components_strings_be.xtb
index 965fadd..ef5a404 100644
--- a/components/strings/components_strings_be.xtb
+++ b/components/strings/components_strings_be.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Падчас паказу вэб-старонкі нешта пайшло не так.</translation>
 <translation id="1586541204584340881">Якія пашырэнні вы ўсталявалі.</translation>
 <translation id="1588438908519853928">Звычайны</translation>
-<translation id="1589050138437146318">Усталяваць ARCore?</translation>
 <translation id="1592005682883173041">Доступ да лакальных даных</translation>
 <translation id="1594030484168838125">Выбраць</translation>
 <translation id="1596296697375291157">Трылеры, крымінальныя і дэтэктыўныя фільмы</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Згіб напалову</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Усталяваныя праграмы, і як часта яны выкарыстоўваліся</translation>
-<translation id="8317207217658302555">Абнавіць ARCore?</translation>
 <translation id="831997045666694187">Вечар</translation>
 <translation id="8321476692217554900">апавяшчэнні</translation>
 <translation id="8332188693563227489">Доступ да хоста <ph name="HOST_NAME" /> забаронены</translation>
diff --git a/components/strings/components_strings_bg.xtb b/components/strings/components_strings_bg.xtb
index 7840878..81b6b3b 100644
--- a/components/strings/components_strings_bg.xtb
+++ b/components/strings/components_strings_bg.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Възникна проблем при показването на тази уеб страница.</translation>
 <translation id="1586541204584340881">Кои разширения сте инсталирали</translation>
 <translation id="1588438908519853928">Нормално</translation>
-<translation id="1589050138437146318">Да се инсталира ли ARCore?</translation>
 <translation id="1592005682883173041">Достъп до локални данни</translation>
 <translation id="1594030484168838125">Избор</translation>
 <translation id="1596296697375291157">Трилъри, криминални и детективски филми</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Сгъване в средата</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Инсталираните приложения и колко често са използвани</translation>
-<translation id="8317207217658302555">Да се актуализира ли ARCore?</translation>
 <translation id="831997045666694187">Вечерта</translation>
 <translation id="8321476692217554900">известия</translation>
 <translation id="8332188693563227489">Достъпът до <ph name="HOST_NAME" /> бе отказан</translation>
diff --git a/components/strings/components_strings_bn.xtb b/components/strings/components_strings_bn.xtb
index 0488712..06c8b334 100644
--- a/components/strings/components_strings_bn.xtb
+++ b/components/strings/components_strings_bn.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">এই ওয়েবপৃষ্ঠাটি দেখানোর সময় কোনো সমস্যা হয়েছে।</translation>
 <translation id="1586541204584340881">কোন কোন এক্সটেনশন আপনি ইনস্টল করেছেন</translation>
 <translation id="1588438908519853928">সাধারণ</translation>
-<translation id="1589050138437146318">ARCore ইনস্টল করবেন?</translation>
 <translation id="1592005682883173041">স্থানীয় ডেটা অ্যাক্সেস</translation>
 <translation id="1594030484168838125">বেছে নিন</translation>
 <translation id="1596296697375291157">থ্রিলার, ক্রাইম ও রহস্যমূলক সিনেমা</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">অর্ধেক ফোল্ড করুন</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">কোন কোন অ্যাপ ইনস্টল করা আছে এবং কত ঘন ঘন সেগুলি ব্যবহার করা হয়েছে</translation>
-<translation id="8317207217658302555">ARCore আপডেট করবেন?</translation>
 <translation id="831997045666694187">সন্ধ্যা</translation>
 <translation id="8321476692217554900">বিজ্ঞপ্তি</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> এ অ্যাক্সেস অস্বীকার করা হয়েছে</translation>
diff --git a/components/strings/components_strings_bs.xtb b/components/strings/components_strings_bs.xtb
index 474f987..1ffe6a2 100644
--- a/components/strings/components_strings_bs.xtb
+++ b/components/strings/components_strings_bs.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Došlo je do problema prilikom prikazivanja ove web stranice.</translation>
 <translation id="1586541204584340881">Koje ekstenzije ste instalirali</translation>
 <translation id="1588438908519853928">Normalno</translation>
-<translation id="1589050138437146318">Instalirati ARCore?</translation>
 <translation id="1592005682883173041">Pristup lokalnim podacima</translation>
 <translation id="1594030484168838125">Odaberi</translation>
 <translation id="1596296697375291157">Trileri, kriminalistički filmovi i misterije</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Presavijanje napola</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Instalirane aplikacije i koliko često se koriste</translation>
-<translation id="8317207217658302555">Ažurirati ARCore?</translation>
 <translation id="831997045666694187">Večer</translation>
 <translation id="8321476692217554900">obavještenja</translation>
 <translation id="8332188693563227489">Pristup host računaru <ph name="HOST_NAME" /> je zabranjen</translation>
diff --git a/components/strings/components_strings_ca.xtb b/components/strings/components_strings_ca.xtb
index af15f269..388545a 100644
--- a/components/strings/components_strings_ca.xtb
+++ b/components/strings/components_strings_ca.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">S'ha produït un error en mostrar aquesta pàgina web.</translation>
 <translation id="1586541204584340881">Quines extensions has instal·lat</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Vols instal·lar ARCore?</translation>
 <translation id="1592005682883173041">Accés a les dades locals</translation>
 <translation id="1594030484168838125">Tria</translation>
 <translation id="1596296697375291157">Pel·lícules de crims, de misteri i thrillers</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Plegat per la meitat</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Aplicacions instal·lades i freqüència d'utilització</translation>
-<translation id="8317207217658302555">Vols actualitzar ARCore?</translation>
 <translation id="831997045666694187">Tarda</translation>
 <translation id="8321476692217554900">notificacions</translation>
 <translation id="8332188693563227489">S'ha rebutjat l'accés a <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_cs.xtb b/components/strings/components_strings_cs.xtb
index e292423..30dd9982 100644
--- a/components/strings/components_strings_cs.xtb
+++ b/components/strings/components_strings_cs.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Při zobrazování této webové stránky došlo k chybě.</translation>
 <translation id="1586541204584340881">Která rozšíření máte nainstalovaná</translation>
 <translation id="1588438908519853928">Normální</translation>
-<translation id="1589050138437146318">Nainstalovat ARCore?</translation>
 <translation id="1592005682883173041">Přístup k místním datům</translation>
 <translation id="1594030484168838125">Zvolit</translation>
 <translation id="1596296697375291157">Thrillery, krimi a filmy o záhadách</translation>
@@ -2652,7 +2651,6 @@
 <translation id="830498451218851433">Přeložení napůl</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Nainstalované aplikace a četnost jejich používání</translation>
-<translation id="8317207217658302555">Aktualizovat ARCore?</translation>
 <translation id="831997045666694187">Večer</translation>
 <translation id="8321476692217554900">oznámení</translation>
 <translation id="8332188693563227489">Přístup k webu <ph name="HOST_NAME" /> byl odepřen</translation>
diff --git a/components/strings/components_strings_cy.xtb b/components/strings/components_strings_cy.xtb
index daf6c815..9885645 100644
--- a/components/strings/components_strings_cy.xtb
+++ b/components/strings/components_strings_cy.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Aeth rhywbeth o'i le wrth arddangos y dudalen we hon.</translation>
 <translation id="1586541204584340881">Pa estyniadau rydych wedi'u gosod</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Gosod ARCore?</translation>
 <translation id="1592005682883173041">Mynediad Data Lleol</translation>
 <translation id="1594030484168838125">Dewis</translation>
 <translation id="1596296697375291157">Ffilmiau cyffro, trosedd a dirgelwch</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Hanner Plyg</translation>
 <translation id="8307358339886459768">Llun-Bach</translation>
 <translation id="8307888238279532626">Apiau sydd wedi'u gosod a pha mor aml y cânt eu defnyddio</translation>
-<translation id="8317207217658302555">Diweddaru ARCore?</translation>
 <translation id="831997045666694187">Nos</translation>
 <translation id="8321476692217554900">hysbysiadau</translation>
 <translation id="8332188693563227489">Rhwystrwyd mynediad at <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_da.xtb b/components/strings/components_strings_da.xtb
index c38d47d..e09a7b4 100644
--- a/components/strings/components_strings_da.xtb
+++ b/components/strings/components_strings_da.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Noget gik galt ved visningen af denne webside.</translation>
 <translation id="1586541204584340881">Hvilke udvidelser, du har installeret</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Vil du installere ARCore?</translation>
 <translation id="1592005682883173041">Lokal dataadgang</translation>
 <translation id="1594030484168838125">Vælg</translation>
 <translation id="1596296697375291157">Thrillere, kriminal- og mysteriefilm</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Fals halvt</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Installerede apps, og hvor ofte de bruges</translation>
-<translation id="8317207217658302555">Vil du opdatere ARCore?</translation>
 <translation id="831997045666694187">Aften</translation>
 <translation id="8321476692217554900">notifikationer</translation>
 <translation id="8332188693563227489">Adgangen til <ph name="HOST_NAME" /> blev nægtet</translation>
diff --git a/components/strings/components_strings_de.xtb b/components/strings/components_strings_de.xtb
index 31ca9e6..7ca8b23 100644
--- a/components/strings/components_strings_de.xtb
+++ b/components/strings/components_strings_de.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Fehler beim Anzeigen dieser Webseite.</translation>
 <translation id="1586541204584340881">Welche Erweiterungen du installiert hast</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">ARCore installieren?</translation>
 <translation id="1592005682883173041">Zugriff auf lokale Daten</translation>
 <translation id="1594030484168838125">Auswählen</translation>
 <translation id="1596296697375291157">Thriller, Krimis und Mystery-Filme</translation>
@@ -2656,7 +2655,6 @@
 <translation id="830498451218851433">Zur Hälfte falten</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Installierte Apps und Häufigkeit ihrer Verwendung</translation>
-<translation id="8317207217658302555">ARCore aktualisieren?</translation>
 <translation id="831997045666694187">Abends</translation>
 <translation id="8321476692217554900">Benachrichtigungen</translation>
 <translation id="8332188693563227489">Der Zugriff auf <ph name="HOST_NAME" /> wurde verweigert</translation>
diff --git a/components/strings/components_strings_el.xtb b/components/strings/components_strings_el.xtb
index 531505b..913cc5f 100644
--- a/components/strings/components_strings_el.xtb
+++ b/components/strings/components_strings_el.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Παρουσιάστηκε πρόβλημα κατά την εμφάνιση αυτής της ιστοσελίδας.</translation>
 <translation id="1586541204584340881">Ποιες επεκτάσεις έχετε εγκαταστήσει</translation>
 <translation id="1588438908519853928">Κανονική λειτουργία</translation>
-<translation id="1589050138437146318">Εγκατάσταση ARCore;</translation>
 <translation id="1592005682883173041">Πρόσβαση σε τοπικά δεδομένα</translation>
 <translation id="1594030484168838125">Επιλογή</translation>
 <translation id="1596296697375291157">Ταινίες θρίλερ, εγκλημάτων και μυστηρίου</translation>
@@ -2663,7 +2662,6 @@
 <translation id="830498451218851433">Δίπλωση στη μέση</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Εγκατεστημένες εφαρμογές και η συχνότητα χρήσης τους</translation>
-<translation id="8317207217658302555">Ενημέρωση ARCore;</translation>
 <translation id="831997045666694187">Βράδυ</translation>
 <translation id="8321476692217554900">ειδοποιήσεις</translation>
 <translation id="8332188693563227489">Απορρίφθηκε η πρόσβαση στο κεντρικό υπολογιστή <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_en-GB.xtb b/components/strings/components_strings_en-GB.xtb
index 95f9a03..04b5471f 100644
--- a/components/strings/components_strings_en-GB.xtb
+++ b/components/strings/components_strings_en-GB.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Something went wrong while displaying this web page.</translation>
 <translation id="1586541204584340881">Which extensions you have installed</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Install ARCore?</translation>
 <translation id="1592005682883173041">Local Data Access</translation>
 <translation id="1594030484168838125">Choose</translation>
 <translation id="1596296697375291157">Thriller, crime and mystery movies</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Fold half</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Apps installed and how often they are used</translation>
-<translation id="8317207217658302555">Update ARCore?</translation>
 <translation id="831997045666694187">Evening</translation>
 <translation id="8321476692217554900">notifications</translation>
 <translation id="8332188693563227489">Access to <ph name="HOST_NAME" /> was denied</translation>
diff --git a/components/strings/components_strings_es-419.xtb b/components/strings/components_strings_es-419.xtb
index a502efb..fcdda29 100644
--- a/components/strings/components_strings_es-419.xtb
+++ b/components/strings/components_strings_es-419.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Se produjo un error cuando se mostraba la página web.</translation>
 <translation id="1586541204584340881">Las extensiones que instalaste</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">¿Deseas instalar ARCore?</translation>
 <translation id="1592005682883173041">Acceso a datos locales</translation>
 <translation id="1594030484168838125">Seleccionar</translation>
 <translation id="1596296697375291157">Películas de suspenso, crimen y misterio</translation>
@@ -2659,7 +2658,6 @@
 <translation id="830498451218851433">Plegado a la mitad</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Las apps instaladas y su frecuencia de uso</translation>
-<translation id="8317207217658302555">¿Deseas actualizar ARCore?</translation>
 <translation id="831997045666694187">Tarde</translation>
 <translation id="8321476692217554900">notificaciones</translation>
 <translation id="8332188693563227489">Se denegó el acceso a <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_es.xtb b/components/strings/components_strings_es.xtb
index b4a045b..2fe5719 100644
--- a/components/strings/components_strings_es.xtb
+++ b/components/strings/components_strings_es.xtb
@@ -158,7 +158,7 @@
 <translation id="1355754231781595724">Vehículo y seguridad vial</translation>
 <translation id="1357195169723583938">Quién ha utilizado este dispositivo recientemente y cuándo</translation>
 <translation id="1358187717814494928">Crear hoja de cálculo</translation>
-<translation id="1360955481084547712">Abre una nueva ventana de incógnito para navegar en privado</translation>
+<translation id="1360955481084547712">Abre una nueva ventana de Incógnito para navegar en privado</translation>
 <translation id="1363819917331173092">No ofrecer que se traduzcan páginas en <ph name="SOURCE_LANGUAGE" /></translation>
 <translation id="1364822246244961190">Esta política está bloqueada, su valor será ignorado.</translation>
 <translation id="1368318639262510626">Juego del dinosaurio. Un dinosaurio pixelado esquiva cactus y pterodáctilos mientras corre por un paisaje desértico. Cuando escuches una señal sonora, pulsa la barra espaciadora para saltar los obstáculos.</translation>
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Se ha producido un error al mostrar esta página web.</translation>
 <translation id="1586541204584340881">Qué extensiones tienes instaladas</translation>
 <translation id="1588438908519853928">Modo normal</translation>
-<translation id="1589050138437146318">¿Instalar ARCore?</translation>
 <translation id="1592005682883173041">Acceso a datos locales</translation>
 <translation id="1594030484168838125">Seleccionar</translation>
 <translation id="1596296697375291157">Cine de intriga, policíaco y suspense</translation>
@@ -737,7 +736,7 @@
 <translation id="2948083400971632585">Puedes inhabilitar los servidores proxy configurados para una conexión en la página de configuración.</translation>
 <translation id="2949183777371959169">Ignorada porque el equipo no está registrado con Gestión en la nube del navegador Chrome.</translation>
 <translation id="2951588413176968965">Mi buzón de correo</translation>
-<translation id="2952820037279740115">Cerrar todas las ventanas de incógnito</translation>
+<translation id="2952820037279740115">Cerrar todas las ventanas de Incógnito</translation>
 <translation id="295526156371527179">Advertencia: Esta política no se ha combinado como un diccionario tal como se especifica en la política porque no es un diccionario.</translation>
 <translation id="2955913368246107853">Cerrar la barra de búsqueda</translation>
 <translation id="2958544468932521864">Críquet</translation>
@@ -927,7 +926,7 @@
 <translation id="346601286295919445">Química</translation>
 <translation id="3467763166455606212">El nombre del titular de la tarjeta es obligatorio</translation>
 <translation id="3468054117417088249"><ph name="TAB_SWITCH_SUFFIX" />, abierta actualmente; pulsa el tabulador y luego Intro para cambiar a la pestaña abierta</translation>
-<translation id="3470563864795286535"><ph name="CLOSE_INCOGNITO_WINDOWS_FOCUSED_FRIENDLY_MATCH_TEXT" />: pulsa Tabulador y, después, Intro para cerrar todas las ventanas de incógnito que estén abiertas</translation>
+<translation id="3470563864795286535"><ph name="CLOSE_INCOGNITO_WINDOWS_FOCUSED_FRIENDLY_MATCH_TEXT" />: pulsa Tabulador y, después, Intro para cerrar todas las ventanas de Incógnito que estén abiertas</translation>
 <translation id="3477679029130949506">Cartelera y horarios de cines</translation>
 <translation id="3479552764303398839">Ahora no</translation>
 <translation id="3484560055331845446">Podrías perder el acceso a tu cuenta de Google. Chrome te recomienda que cambies la contraseña ahora. Se te pedirá que inicies sesión.</translation>
@@ -952,7 +951,7 @@
 <translation id="3566021033012934673">La conexión no es privada</translation>
 <translation id="3567778190852720481">No te puedes registrar con la cuenta de empresa (la cuenta de empresa no reúne los requisitos).</translation>
 <translation id="3574305903863751447"><ph name="CITY" />, <ph name="STATE" /> <ph name="COUNTRY" /></translation>
-<translation id="3575121482199441727">Permitir para este sitio web</translation>
+<translation id="3575121482199441727">Permitir en este sitio web</translation>
 <translation id="3576616784287504635">Una página insertada en <ph name="SITE" /> dice</translation>
 <translation id="3577902790357386792">Gimnasia</translation>
 <translation id="3581089476000296252">Chrome te avisará cuando la página esté lista. &lt;a&gt;Cancelar&lt;/a&gt;</translation>
@@ -1000,7 +999,7 @@
 <translation id="3676592649209844519">ID del dispositivo:</translation>
 <translation id="3677008721441257057">Quizás quisiste decir: &lt;a href="#" id="dont-proceed-link"&gt;<ph name="DOMAIN" />&lt;/a&gt;</translation>
 <translation id="3678029195006412963">No se ha podido firmar la solicitud</translation>
-<translation id="3678529606614285348">Abre una página en una nueva ventana de incógnito (Ctrl + Mayús + N).</translation>
+<translation id="3678529606614285348">Abre una página en una nueva ventana de Incógnito (Ctrl + Mayús + N).</translation>
 <translation id="3681007416295224113">Datos del certificado</translation>
 <translation id="3701427423622901115">Se han restablecido los ajustes.</translation>
 <translation id="3704162925118123524">La red que estás utilizando puede requerir el acceso a su página de inicio de sesión.</translation>
@@ -1184,7 +1183,7 @@
     &lt;li&gt;En la página de inicio de sesión que se abre, inicia sesión para utilizar la conexión a Internet.&lt;/li&gt;
     &lt;/ol&gt;
     &lt;h4&gt;Paso 2: Abre la página en modo Incógnito (solo en ordenadores)&lt;/h4&gt;
-    &lt;p&gt;Abre la página que estabas visitando en una ventana de incógnito.&lt;/p&gt;
+    &lt;p&gt;Abre la página que estabas visitando en una ventana de Incógnito.&lt;/p&gt;
     &lt;p&gt;Si la página se abre, significa que una extensión de Chrome no funciona correctamente. Para solucionarlo, desactiva esa extensión.&lt;/p&gt;
     &lt;h4&gt;Paso 3: Actualiza tu sistema operativo&lt;/h4&gt;
     &lt;p&gt;Comprueba que tu dispositivo esté actualizado.&lt;/p&gt;
@@ -1430,7 +1429,7 @@
 <translation id="4881695831933465202">Abrir</translation>
 <translation id="4885256590493466218">Paga con <ph name="CARD_DETAIL" /> al tramitar la compra.</translation>
 <translation id="4888600795924685526">Estudios de lenguas extranjeras</translation>
-<translation id="4889420713887366944">Botón Abrir una ventana de incógnito, pulsa Intro para abrir una ventana de incógnito para navegar de forma privada</translation>
+<translation id="4889420713887366944">Botón Abrir una ventana de Incógnito, pulsa Intro para abrir una ventana de Incógnito para navegar de forma privada</translation>
 <translation id="4892518386797173871">Parte trasera</translation>
 <translation id="4895877746940133817"><ph name="TYPE_1" />, <ph name="TYPE_2" /> y <ph name="TYPE_3" /></translation>
 <translation id="4896809202198625921">Olimpiadas</translation>
@@ -1536,7 +1535,7 @@
 <translation id="515292512908731282">C4 (sobre)</translation>
 <translation id="5153314898060540200">Rock duro y progresivo</translation>
 <translation id="5158275234811857234">Portada</translation>
-<translation id="5159010409087891077">Abre una página en una nueva ventana de incógnito (⇧ + ⌘ + N)</translation>
+<translation id="5159010409087891077">Abre una página en una nueva ventana de Incógnito (⇧ + ⌘ + N)</translation>
 <translation id="5161334686036120870">Asunto:</translation>
 <translation id="5161506081086828129">Apilador 9</translation>
 <translation id="5164798890604758545">Se ha introducido texto</translation>
@@ -2224,7 +2223,7 @@
 <translation id="7175401108899573750">{SHIPPING_OPTIONS,plural, =0{<ph name="SHIPPING_OPTION_PREVIEW" />}=1{<ph name="SHIPPING_OPTION_PREVIEW" /> y <ph name="NUMBER_OF_ADDITIONAL_SHIPPING_OPTIONS" /> más}other{<ph name="SHIPPING_OPTION_PREVIEW" /> y <ph name="NUMBER_OF_ADDITIONAL_SHIPPING_OPTIONS" /> más}}</translation>
 <translation id="7179323680825933600">Guardar y autocompletar métodos de pago</translation>
 <translation id="7180611975245234373">Actualizar</translation>
-<translation id="7181261019481237103">Abrir ventana de incógnito</translation>
+<translation id="7181261019481237103">Abrir ventana de Incógnito</translation>
 <translation id="7182878459783632708">No hay políticas establecidas.</translation>
 <translation id="7186367841673660872">Esta página se ha traducido del<ph name="ORIGINAL_LANGUAGE" />al<ph name="LANGUAGE_LANGUAGE" />.</translation>
 <translation id="718872491229180389">Cheerleading</translation>
@@ -2458,7 +2457,7 @@
 <translation id="7740996059027112821">Estándar</translation>
 <translation id="77424286611022110">Este sitio muestra anuncios invasivos o engañosos. <ph name="LEARN_MORE_LINK_TEXT" /></translation>
 <translation id="774634243536837715">Contenido peligroso bloqueado.</translation>
-<translation id="7751971323486164747">Personaliza el tamaño de fuente y los tipos de letra de Chrome</translation>
+<translation id="7751971323486164747">Personaliza los tamaños de fuente y los tipos de letra de Chrome</translation>
 <translation id="7752995774971033316">No administrado</translation>
 <translation id="7755624218968747854">Rollo principal</translation>
 <translation id="7757555340166475417">Dai-Pa-Kai</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Plegado al medio</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Las aplicaciones instaladas y la frecuencia con la que se usan</translation>
-<translation id="8317207217658302555">¿Actualizar ARCore?</translation>
 <translation id="831997045666694187">Tarde</translation>
 <translation id="8321476692217554900">notificaciones</translation>
 <translation id="8332188693563227489">Se ha denegado el acceso a <ph name="HOST_NAME" /></translation>
@@ -2780,7 +2778,7 @@
 <translation id="8730621377337864115">Hecho</translation>
 <translation id="8731544501227493793">Botón Gestionar contraseñas, pulsa Intro para ver y gestionar tus contraseñas desde la configuración de Chrome</translation>
 <translation id="8734529307927223492"><ph name="MANAGER" /> gestiona tu <ph name="DEVICE_TYPE" /></translation>
-<translation id="8737134861345396036"><ph name="LAUNCH_INCOGNITO_FOCUSED_FRIENDLY_MATCH_TEXT" />, pulsa Tabulador y, a continuación, Intro para abrir una ventana de incógnito para navegar de forma privada</translation>
+<translation id="8737134861345396036"><ph name="LAUNCH_INCOGNITO_FOCUSED_FRIENDLY_MATCH_TEXT" />, pulsa Tabulador y, a continuación, Intro para abrir una ventana de Incógnito para navegar de forma privada</translation>
 <translation id="8737685506611670901">Abrir enlaces de <ph name="PROTOCOL" /> en lugar de <ph name="REPLACED_HANDLER_TITLE" /></translation>
 <translation id="8738058698779197622">Para establecer una conexión segura, tu reloj debe estar correctamente configurado. Esto se debe a que los certificados utilizados por los sitios web para identificarse son solo válidos durante períodos específicos de tiempo. Dado que la hora de tu dispositivo no es correcta, Chromium no puede verificar estos certificados.</translation>
 <translation id="8740359287975076522">No se ha podido encontrar la &lt;abbr id="dnsDefinition"&gt;dirección DNS&lt;/abbr&gt; de la página <ph name="HOST_NAME" />. Se está diagnosticando el problema.</translation>
@@ -2793,7 +2791,7 @@
 <translation id="87601671197631245">Este sitio web usa una configuración de seguridad obsoleta y puede que exponga tu información (por ejemplo, las contraseñas, los mensajes o las tarjetas de crédito) cuando se envíe a este sitio web.</translation>
 <translation id="8761567432415473239">La función de Navegación segura de Google <ph name="BEGIN_LINK" />encontró programas dañinos<ph name="END_LINK" /> recientemente en el sitio <ph name="SITE" />.</translation>
 <translation id="8763927697961133303">Dispositivo USB</translation>
-<translation id="8763986294015493060">Cierra todas las ventanas de incógnito que estén abiertas</translation>
+<translation id="8763986294015493060">Cierra todas las ventanas de Incógnito que estén abiertas</translation>
 <translation id="8766943070169463815">La hoja de autenticación de credenciales de pago seguras está abierta</translation>
 <translation id="8767765348545497220">Cerrar cuadro de ayuda</translation>
 <translation id="877985182522063539">A4</translation>
@@ -2812,7 +2810,7 @@
 <translation id="8816395686387277279"><ph name="UPDATE_CHROME_FOCUSED_FRIENDLY_MATCH_TEXT" />, pulsa Tabulador y, a continuación, Intro para actualizar Chrome desde la configuración de Chrome</translation>
 <translation id="8820817407110198400">Marcadores</translation>
 <translation id="882338992931677877">Ranura manual</translation>
-<translation id="8834380158646307944">Botón Cerrar todas las ventanas de incógnito: pulsa Intro para cerrar todas las ventanas de incógnito que estén abiertas</translation>
+<translation id="8834380158646307944">Botón Cerrar todas las ventanas de Incógnito: pulsa Intro para cerrar todas las ventanas de Incógnito que estén abiertas</translation>
 <translation id="883848425547221593">Otros marcadores</translation>
 <translation id="884264119367021077">Dirección de envío</translation>
 <translation id="884923133447025588">No se ha encontrado ningún mecanismo de revocación.</translation>
@@ -2830,7 +2828,7 @@
 <translation id="8870413625673593573">Cerrado recientemente</translation>
 <translation id="8870494189203302833">Mismo orden boca abajo</translation>
 <translation id="8870700989640064057">¿Imprimir archivo confidencial?</translation>
-<translation id="8871553383647848643">Personaliza el diseño de tu navegador</translation>
+<translation id="8871553383647848643">Personaliza el aspecto de tu navegador</translation>
 <translation id="8874824191258364635">Introduce un número de tarjeta válido</translation>
 <translation id="8876033571432926051">Juegos casuales</translation>
 <translation id="8877780815363510165">Pesca</translation>
diff --git a/components/strings/components_strings_et.xtb b/components/strings/components_strings_et.xtb
index d44ee46..2bb36e0f 100644
--- a/components/strings/components_strings_et.xtb
+++ b/components/strings/components_strings_et.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Veebilehe kuvamisel läks midagi valesti.</translation>
 <translation id="1586541204584340881">Millised laiendused olete installinud</translation>
 <translation id="1588438908519853928">Tavaline</translation>
-<translation id="1589050138437146318">Kas installida ARCore?</translation>
 <translation id="1592005682883173041">Juurdepääs kohalikele andmetele</translation>
 <translation id="1594030484168838125">Vali</translation>
 <translation id="1596296697375291157">Põnevus-, krimi- ja müsteeriumfilmid</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Pooleks volditud</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Installitud rakendused ja nende kasutussagedus</translation>
-<translation id="8317207217658302555">Kas värskendada ARCore'i?</translation>
 <translation id="831997045666694187">Õhtu</translation>
 <translation id="8321476692217554900">märguanded</translation>
 <translation id="8332188693563227489">Juurdepääs hostile <ph name="HOST_NAME" /> blokeeriti</translation>
diff --git a/components/strings/components_strings_eu.xtb b/components/strings/components_strings_eu.xtb
index e3ecd41..ffc7836a 100644
--- a/components/strings/components_strings_eu.xtb
+++ b/components/strings/components_strings_eu.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Arazo bat izan da web-orria bistaratzean.</translation>
 <translation id="1586541204584340881">Instalatuta dauzkazun luzapenak.</translation>
 <translation id="1588438908519853928">Arrunta</translation>
-<translation id="1589050138437146318">ARCore instalatu nahi duzu?</translation>
 <translation id="1592005682883173041">Datu lokaletarako sarbidea</translation>
 <translation id="1594030484168838125">Aukeratu</translation>
 <translation id="1596296697375291157">Thrillerrak, krimen-filmak eta misteriozko filmak</translation>
@@ -2653,7 +2652,6 @@
 <translation id="830498451218851433">Erdibitzeko tolestura</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Instalatuta dauden aplikazioak eta zer maiztasunekin erabiltzen diren</translation>
-<translation id="8317207217658302555">ARCore eguneratu nahi duzu?</translation>
 <translation id="831997045666694187">Arratsaldea</translation>
 <translation id="8321476692217554900">jakinarazpenak</translation>
 <translation id="8332188693563227489">Ukatu egin da <ph name="HOST_NAME" /> webgunerako sarbidea</translation>
diff --git a/components/strings/components_strings_fa.xtb b/components/strings/components_strings_fa.xtb
index 1338ab48..9fc3bab 100644
--- a/components/strings/components_strings_fa.xtb
+++ b/components/strings/components_strings_fa.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">هنگام نمایش این صفحه وب مشکلی پیش آمد.</translation>
 <translation id="1586541204584340881">افزونه‌هایی که نصب کرده‌اید</translation>
 <translation id="1588438908519853928">معمولی</translation>
-<translation id="1589050138437146318">‏ARCore نصب شود؟</translation>
 <translation id="1592005682883173041">دسترسی داده محلی</translation>
 <translation id="1594030484168838125">انتخاب</translation>
 <translation id="1596296697375291157">فیلم‌های هیجان‌انگیز، جنایی، و رازآلود</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">تاخوردگی از وسط</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">برنامه‌های نصب‌شده و میزان استفاده از آن‌ها</translation>
-<translation id="8317207217658302555">‏ARCore به‌روزرسانی شود؟</translation>
 <translation id="831997045666694187">شب</translation>
 <translation id="8321476692217554900">اعلان‌ها</translation>
 <translation id="8332188693563227489">دسترسی به <ph name="HOST_NAME" /> رد شد</translation>
diff --git a/components/strings/components_strings_fi.xtb b/components/strings/components_strings_fi.xtb
index 84a6861..df3ae7f 100644
--- a/components/strings/components_strings_fi.xtb
+++ b/components/strings/components_strings_fi.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Jotain meni pieleen. tätä verkkosivua näytettäessä.</translation>
 <translation id="1586541204584340881">Asentamasi laajennukset</translation>
 <translation id="1588438908519853928">Normaali</translation>
-<translation id="1589050138437146318">Asennetaanko ARCore?</translation>
 <translation id="1592005682883173041">Tietojen paikallinen käyttö</translation>
 <translation id="1594030484168838125">Valitse</translation>
 <translation id="1596296697375291157">Trillerit, rikos- ja jännityselokuvat</translation>
@@ -2659,7 +2658,6 @@
 <translation id="830498451218851433">Keskitaite</translation>
 <translation id="8307358339886459768">Pieni kuva</translation>
 <translation id="8307888238279532626">Asennetut sovellukset ja se, miten usein niitä käytetään</translation>
-<translation id="8317207217658302555">Päivitetäänkö ARCore?</translation>
 <translation id="831997045666694187">Ilta</translation>
 <translation id="8321476692217554900">ilmoitukset</translation>
 <translation id="8332188693563227489">Sivuston <ph name="HOST_NAME" /> käyttöoikeus evättiin</translation>
diff --git a/components/strings/components_strings_fil.xtb b/components/strings/components_strings_fil.xtb
index 1f9be8c3..1ff023c2 100644
--- a/components/strings/components_strings_fil.xtb
+++ b/components/strings/components_strings_fil.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Nagkaproblema habang ipinapakita ang webpage na ito.</translation>
 <translation id="1586541204584340881">Aling mga extension ang na-install mo</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">I-install ang ARCore?</translation>
 <translation id="1592005682883173041">Access sa Lokal na Data</translation>
 <translation id="1594030484168838125">Pumili</translation>
 <translation id="1596296697375291157">Mga pelikulang thriller, krimen, at misteryo</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Fold half</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Mga naka-install na app at kung gaano kadalas gamitin ang mga ito</translation>
-<translation id="8317207217658302555">I-update ang ARCore?</translation>
 <translation id="831997045666694187">Gabi</translation>
 <translation id="8321476692217554900">mga notification</translation>
 <translation id="8332188693563227489">Tinanggihan ang access sa <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_fr-CA.xtb b/components/strings/components_strings_fr-CA.xtb
index 7417ae8..3a208a2 100644
--- a/components/strings/components_strings_fr-CA.xtb
+++ b/components/strings/components_strings_fr-CA.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Une erreur s'est produite lors de l'affichage de cette page Web.</translation>
 <translation id="1586541204584340881">Les extensions que vous avez installées</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Installer ARCore?</translation>
 <translation id="1592005682883173041">Accès aux données locales</translation>
 <translation id="1594030484168838125">Choisir</translation>
 <translation id="1596296697375291157">Films à suspense, policiers et mystères</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Plier en deux</translation>
 <translation id="8307358339886459768">Petite photo</translation>
 <translation id="8307888238279532626">Les applications installées et leur fréquence d'utilisation</translation>
-<translation id="8317207217658302555">Mettre à jour ARCore?</translation>
 <translation id="831997045666694187">Soirée</translation>
 <translation id="8321476692217554900">Notifications</translation>
 <translation id="8332188693563227489">Accès à l'adresse <ph name="HOST_NAME" /> refusé</translation>
diff --git a/components/strings/components_strings_fr.xtb b/components/strings/components_strings_fr.xtb
index 59a9263..adad814 100644
--- a/components/strings/components_strings_fr.xtb
+++ b/components/strings/components_strings_fr.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Une erreur s'est produite lors de l'affichage de la page Web.</translation>
 <translation id="1586541204584340881">Les extensions que vous avez installées</translation>
 <translation id="1588438908519853928">Standard</translation>
-<translation id="1589050138437146318">Installer ARCore ?</translation>
 <translation id="1592005682883173041">Accès aux données locales</translation>
 <translation id="1594030484168838125">Sélectionner</translation>
 <translation id="1596296697375291157">Films policiers et thrillers</translation>
@@ -2659,7 +2658,6 @@
 <translation id="830498451218851433">Plier en deux</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Applications installées et leur fréquence d'utilisation</translation>
-<translation id="8317207217658302555">Mettre à jour ARCore ?</translation>
 <translation id="831997045666694187">Soirée</translation>
 <translation id="8321476692217554900">notifications</translation>
 <translation id="8332188693563227489">L'accès à <ph name="HOST_NAME" /> a été refusé</translation>
diff --git a/components/strings/components_strings_gl.xtb b/components/strings/components_strings_gl.xtb
index e7c546f..d302a69 100644
--- a/components/strings/components_strings_gl.xtb
+++ b/components/strings/components_strings_gl.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Produciuse un erro ao mostrar esta páxina web.</translation>
 <translation id="1586541204584340881">As extensións que teñas instaladas</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Queres instalar ARCore?</translation>
 <translation id="1592005682883173041">Acceso aos datos locais</translation>
 <translation id="1594030484168838125">Escoller</translation>
 <translation id="1596296697375291157">Películas de intriga, policiais e de suspense</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Dobrez á metade</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Aplicacións instaladas e frecuencia de uso</translation>
-<translation id="8317207217658302555">Queres actualizar ARCore?</translation>
 <translation id="831997045666694187">Serán</translation>
 <translation id="8321476692217554900">notificacións</translation>
 <translation id="8332188693563227489">Denegouse o acceso a <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_gu.xtb b/components/strings/components_strings_gu.xtb
index 017092ef..8f699fa 100644
--- a/components/strings/components_strings_gu.xtb
+++ b/components/strings/components_strings_gu.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">આ વેબપેજ બતાવતી વખતે કંઈક ખોટું થયું.</translation>
 <translation id="1586541204584340881">તમે કયા એક્સ્ટેંશન ઇન્સ્ટૉલ કર્યા છે</translation>
 <translation id="1588438908519853928">સામાન્ય</translation>
-<translation id="1589050138437146318">ARCore ઇન્સ્ટૉલ કરીએ?</translation>
 <translation id="1592005682883173041">સ્થાનિક ડેટા ઍક્સેસ</translation>
 <translation id="1594030484168838125">પસંદ કરો</translation>
 <translation id="1596296697375291157">થ્રિલર, ક્રાઇમ અને મિસ્ટ્રી મૂવી</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">અડધું વાળો</translation>
 <translation id="8307358339886459768">નાનો-ફોટો</translation>
 <translation id="8307888238279532626">ઇન્સ્ટૉલ થયેલી ઍપ અને તેમનો ઉપયોગ કેટલી વાર કરવામાં આવે છે</translation>
-<translation id="8317207217658302555">ARCore અપડેટ કરીએ?</translation>
 <translation id="831997045666694187">સાંજ</translation>
 <translation id="8321476692217554900">નોટિફિકેશન</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> ની ઍક્સેસ નકારાઈ હતી</translation>
diff --git a/components/strings/components_strings_hi.xtb b/components/strings/components_strings_hi.xtb
index 57e4563..cf4b1996 100644
--- a/components/strings/components_strings_hi.xtb
+++ b/components/strings/components_strings_hi.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">इस वेबपेज को दिखाने के दौरान कुछ गड़बड़ी हुई है.</translation>
 <translation id="1586541204584340881">आपने कौनसे एक्सटेंशन इंस्टॉल किए हैं</translation>
 <translation id="1588438908519853928">सामान्य</translation>
-<translation id="1589050138437146318">ARCore इंस्टॉल करें?</translation>
 <translation id="1592005682883173041">स्थानीय डेटा एक्सेस</translation>
 <translation id="1594030484168838125">चुनें</translation>
 <translation id="1596296697375291157">रोमांचक, अपराध, और रहस्य वाली फ़िल्में</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">फ़ोल्ड हाल्फ़</translation>
 <translation id="8307358339886459768">छोटे आकार वाली फ़ोटो</translation>
 <translation id="8307888238279532626">इंस्टॉल किए गए ऐप्लिकेशन और उनका कितनी बार इस्तेमाल किया जाता है</translation>
-<translation id="8317207217658302555">ARCore को अपडेट करें?</translation>
 <translation id="831997045666694187">शाम</translation>
 <translation id="8321476692217554900">सूचनाएं</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> का एक्सेस देने से मना किया गया था</translation>
diff --git a/components/strings/components_strings_hr.xtb b/components/strings/components_strings_hr.xtb
index 5a72e84..48ca83b 100644
--- a/components/strings/components_strings_hr.xtb
+++ b/components/strings/components_strings_hr.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Nešto nije u redu s prikazivanjem ove web-stranice.</translation>
 <translation id="1586541204584340881">koja ste proširenja instalirali</translation>
 <translation id="1588438908519853928">Uobičajeno</translation>
-<translation id="1589050138437146318">Instalirati ARCore?</translation>
 <translation id="1592005682883173041">Pristup lokalnim podacima</translation>
 <translation id="1594030484168838125">Odaberi</translation>
 <translation id="1596296697375291157">Trileri, kriminalistički filmovi i filmovi o misterijima</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Presavijanje napola</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">koje su aplikacije instalirane i kako se često upotrebljavaju</translation>
-<translation id="8317207217658302555">Ažurirati ARCore?</translation>
 <translation id="831997045666694187">Navečer</translation>
 <translation id="8321476692217554900">obavijesti</translation>
 <translation id="8332188693563227489">Pristup hostu <ph name="HOST_NAME" /> je odbijen</translation>
diff --git a/components/strings/components_strings_hu.xtb b/components/strings/components_strings_hu.xtb
index 20dadfba..d99d2b2 100644
--- a/components/strings/components_strings_hu.xtb
+++ b/components/strings/components_strings_hu.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Valami nem sikerült a weboldal megjelenítése során.</translation>
 <translation id="1586541204584340881">Ön milyen bővítményeket telepített.</translation>
 <translation id="1588438908519853928">Normál</translation>
-<translation id="1589050138437146318">Biztosan telepíti az ARCore platformot?</translation>
 <translation id="1592005682883173041">Helyi adatok elérése</translation>
 <translation id="1594030484168838125">Kiválaszt</translation>
 <translation id="1596296697375291157">Thrillerek, bűnügyi és misztikus filmek</translation>
@@ -2656,7 +2655,6 @@
 <translation id="830498451218851433">Félbehajtás</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Telepített alkalmazások és a használatuk gyakorisága</translation>
-<translation id="8317207217658302555">Biztosan frissíti az ARCore platformot?</translation>
 <translation id="831997045666694187">Este</translation>
 <translation id="8321476692217554900">értesítések</translation>
 <translation id="8332188693563227489">A hozzáférés megtagadva a következőhöz: <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_hy.xtb b/components/strings/components_strings_hy.xtb
index 00c5ab3..ff5e749 100644
--- a/components/strings/components_strings_hy.xtb
+++ b/components/strings/components_strings_hy.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Չհաջողվեց բացել վեբէջը:</translation>
 <translation id="1586541204584340881">Ինչ ընդլայնումներ եք դուք տեղադրել</translation>
 <translation id="1588438908519853928">Սովորական</translation>
-<translation id="1589050138437146318">Տեղադրե՞լ ARCore-ը</translation>
 <translation id="1592005682883173041">Տեղային տվյալների օգտագործում</translation>
 <translation id="1594030484168838125">Ընտրել</translation>
 <translation id="1596296697375291157">Թրիլեր, դետեկտիվ և առեղծվածային ֆիլմեր</translation>
@@ -2659,7 +2658,6 @@
 <translation id="830498451218851433">Ծալում կիսով չափ</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Տեղադրված հավելվածները և դրանց օգտագործման հաճախականությունը</translation>
-<translation id="8317207217658302555">Թարմացնե՞լ ARCore-ը</translation>
 <translation id="831997045666694187">Մինչև երեկո</translation>
 <translation id="8321476692217554900">ծանուցումներ</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> մուտքը մերժվել է</translation>
diff --git a/components/strings/components_strings_id.xtb b/components/strings/components_strings_id.xtb
index d6e1e54..7c840da 100644
--- a/components/strings/components_strings_id.xtb
+++ b/components/strings/components_strings_id.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Terjadi masalah sewaktu menampilkan halaman web ini.</translation>
 <translation id="1586541204584340881">Ekstensi yang telah Anda instal</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Instal ARCore?</translation>
 <translation id="1592005682883173041">Akses Data Lokal</translation>
 <translation id="1594030484168838125">Pilih</translation>
 <translation id="1596296697375291157">Film thriller, kriminal &amp; misteri</translation>
@@ -2655,7 +2654,6 @@
 <translation id="830498451218851433">Lipatan setengah</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Aplikasi yang diinstal dan seberapa sering digunakan</translation>
-<translation id="8317207217658302555">Update ARCore?</translation>
 <translation id="831997045666694187">Sore</translation>
 <translation id="8321476692217554900">notifikasi</translation>
 <translation id="8332188693563227489">Akses ke <ph name="HOST_NAME" /> ditolak</translation>
diff --git a/components/strings/components_strings_is.xtb b/components/strings/components_strings_is.xtb
index e2ca721..3623ec8 100644
--- a/components/strings/components_strings_is.xtb
+++ b/components/strings/components_strings_is.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Eitthvað fór úrskeiðis við að birta þessa vefsíðu.</translation>
 <translation id="1586541204584340881">Hvaða viðbætur þú hefur sett upp</translation>
 <translation id="1588438908519853928">Venjuleg</translation>
-<translation id="1589050138437146318">Setja upp ARCore?</translation>
 <translation id="1592005682883173041">Aðgangur að staðbundnum gögnum</translation>
 <translation id="1594030484168838125">Velja</translation>
 <translation id="1596296697375291157">Spennumyndir, glæpamyndir og morðgátur</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Brotið í miðju</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Uppsett forrit og hversu oft þau eru notuð</translation>
-<translation id="8317207217658302555">Uppfæra ARCore?</translation>
 <translation id="831997045666694187">Kvöld</translation>
 <translation id="8321476692217554900">tilkynningar</translation>
 <translation id="8332188693563227489">Ekki fæst aðgangur að <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_it.xtb b/components/strings/components_strings_it.xtb
index 08dd23b..6748dd5 100644
--- a/components/strings/components_strings_it.xtb
+++ b/components/strings/components_strings_it.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Si è verificato un problema durante la visualizzazione della pagina web.</translation>
 <translation id="1586541204584340881">Le estensioni che hai installato</translation>
 <translation id="1588438908519853928">Normale</translation>
-<translation id="1589050138437146318">Vuoi installare ARCore?</translation>
 <translation id="1592005682883173041">Accesso ai dati locali</translation>
 <translation id="1594030484168838125">Scegli</translation>
 <translation id="1596296697375291157">Film gialli, polizieschi e del mistero</translation>
@@ -2653,7 +2652,6 @@
 <translation id="830498451218851433">Piegatura a metà</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">App installate e frequenza di utilizzo</translation>
-<translation id="8317207217658302555">Vuoi aggiornare ARCore?</translation>
 <translation id="831997045666694187">Sera</translation>
 <translation id="8321476692217554900">notifiche</translation>
 <translation id="8332188693563227489">Accesso a <ph name="HOST_NAME" /> negato</translation>
diff --git a/components/strings/components_strings_iw.xtb b/components/strings/components_strings_iw.xtb
index d202e4cb..a74ab00 100644
--- a/components/strings/components_strings_iw.xtb
+++ b/components/strings/components_strings_iw.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">משהו השתבש בעת הצגת דף אינטרנט זה.</translation>
 <translation id="1586541204584340881">אילו תוספים התקנת.</translation>
 <translation id="1588438908519853928">רגיל</translation>
-<translation id="1589050138437146318">‏להתקין את ARCore?</translation>
 <translation id="1592005682883173041">גישה לנתונים מקומיים</translation>
 <translation id="1594030484168838125">בחירה</translation>
 <translation id="1596296697375291157">סרטי מתח, פשע ומסתורין</translation>
@@ -2663,7 +2662,6 @@
 <translation id="830498451218851433">קיפול באמצע</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">את האפליקציות המותקנות ואת תדירות השימוש בהן.</translation>
-<translation id="8317207217658302555">‏לעדכן את ARCore?</translation>
 <translation id="831997045666694187">ערב</translation>
 <translation id="8321476692217554900">התראות</translation>
 <translation id="8332188693563227489">אין גישה אל <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_ja.xtb b/components/strings/components_strings_ja.xtb
index a6bf99e9..bd306ac 100644
--- a/components/strings/components_strings_ja.xtb
+++ b/components/strings/components_strings_ja.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">このウェブページの表示中に問題が発生しました。</translation>
 <translation id="1586541204584340881">インストールした拡張機能</translation>
 <translation id="1588438908519853928">ノーマル</translation>
-<translation id="1589050138437146318">ARCore をインストールしますか?</translation>
 <translation id="1592005682883173041">ローカルデータへのアクセス</translation>
 <translation id="1594030484168838125">選択</translation>
 <translation id="1596296697375291157">スリラー、犯罪、ミステリー映画</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">2 つ折り</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">インストールしたアプリと使用頻度</translation>
-<translation id="8317207217658302555">ARCore を更新しますか?</translation>
 <translation id="831997045666694187">夕方</translation>
 <translation id="8321476692217554900">通知</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> へのアクセスが拒否されました</translation>
diff --git a/components/strings/components_strings_ka.xtb b/components/strings/components_strings_ka.xtb
index 31444d77..cf5f4bb 100644
--- a/components/strings/components_strings_ka.xtb
+++ b/components/strings/components_strings_ka.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">ამ ვებგვერდის ჩვენებისას პრობლემა წარმოიქმნა.</translation>
 <translation id="1586541204584340881">თქვენ მიერ დაინსტალირებული გაფართოებები</translation>
 <translation id="1588438908519853928">ნორმალური</translation>
-<translation id="1589050138437146318">გსურთ, დააინსტალიროთ ARCore?</translation>
 <translation id="1592005682883173041">ადგილობრივ მონაცემებზე წვდომა</translation>
 <translation id="1594030484168838125">არჩევა</translation>
 <translation id="1596296697375291157">ტრილერის, დეტექტივისა და მისტიკის ჟანრის ფილმები</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">დაკეცვა ნახევრად</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">დაინსტალირებული აპები და მათი გამოყენების სიხშირე</translation>
-<translation id="8317207217658302555">გსურთ, განაახლოთ ARCore?</translation>
 <translation id="831997045666694187">საღამო</translation>
 <translation id="8321476692217554900">შეტყობინებები</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" />-ზე წვდომა აკრძალულია</translation>
diff --git a/components/strings/components_strings_kk.xtb b/components/strings/components_strings_kk.xtb
index ae1217e..280e7a51 100644
--- a/components/strings/components_strings_kk.xtb
+++ b/components/strings/components_strings_kk.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Бұл веб-бетті көрсету кезінде қате кетті.</translation>
 <translation id="1586541204584340881">қай кеңейтімдерді орнатқаныңызды;</translation>
 <translation id="1588438908519853928">Қалыпты</translation>
-<translation id="1589050138437146318">ARCore орнатылсын ба?</translation>
 <translation id="1592005682883173041">Жергілікті деректерге кіру</translation>
 <translation id="1594030484168838125">Таңдау</translation>
 <translation id="1596296697375291157">Триллер, қылмыс және детектив жанрындағы фильмдер</translation>
@@ -2656,7 +2655,6 @@
 <translation id="830498451218851433">Жартылай бүктеу</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Орнатылған қолданбалар және олардың пайдаланылу жиілігі</translation>
-<translation id="8317207217658302555">ARCore жаңартылсын ба?</translation>
 <translation id="831997045666694187">Кеш</translation>
 <translation id="8321476692217554900">хабарландырулар</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> сайтына кіруге тыйым салынған</translation>
diff --git a/components/strings/components_strings_km.xtb b/components/strings/components_strings_km.xtb
index 479f3ab..b6ab328 100644
--- a/components/strings/components_strings_km.xtb
+++ b/components/strings/components_strings_km.xtb
@@ -260,7 +260,6 @@
 <translation id="1583429793053364125">មានបញ្ហាអ្វីមួយកើតឡើង ខណៈពេលកំពុងបង្ហាញគេហទំព័រនេះ</translation>
 <translation id="1586541204584340881">ថាតើកម្មវិធីបន្ថែមណាខ្លះដែលអ្នកបានដំឡើង</translation>
 <translation id="1588438908519853928">ធម្មតា</translation>
-<translation id="1589050138437146318">ដំឡើង ARCore ឬ?</translation>
 <translation id="1592005682883173041">ការចូលប្រើទិន្នន័យមូលដ្ឋាន</translation>
 <translation id="1594030484168838125">ជ្រើសរើស</translation>
 <translation id="1596296697375291157">ភាពយន្ត​បែបរន្ធត់ ឧក្រិដ្ឋកម្ម និង​អាថ៌កំបាំង</translation>
@@ -2664,7 +2663,6 @@
 <translation id="830498451218851433">បត់​ពាក់កណ្ដាល</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">កម្មវិធី​ដែលបានដំឡើង និងភាពញឹកញាប់​នៃការប្រើប្រាស់កម្មវិធីទាំងនោះ</translation>
-<translation id="8317207217658302555">ដំឡើងកំណែ ARCore ឬ?</translation>
 <translation id="831997045666694187">ល្ងាច</translation>
 <translation id="8321476692217554900">ការជូន​ដំណឹង</translation>
 <translation id="8332188693563227489">បានបដិសេធការចូលប្រើ <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_kn.xtb b/components/strings/components_strings_kn.xtb
index b564488..958bcaf 100644
--- a/components/strings/components_strings_kn.xtb
+++ b/components/strings/components_strings_kn.xtb
@@ -258,7 +258,6 @@
 <translation id="1583429793053364125">ಈ ವೆಬ್‌ಪುಟವನ್ನು ಪ್ರದರ್ಶಿಸುವಾಗ ಯಾವುದೋ ತಪ್ಪು ಸಂಭವಿಸಿದೆ.</translation>
 <translation id="1586541204584340881">ನೀವು ಯಾವ ವಿಸ್ತರಣೆಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿದ್ದೀರಿ</translation>
 <translation id="1588438908519853928">ಸಾಮಾನ್ಯ</translation>
-<translation id="1589050138437146318">ARCore ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಬೇಕೆ?</translation>
 <translation id="1592005682883173041">ಸ್ಥಳೀಯ ಡೇಟಾ ಪ್ರವೇಶ</translation>
 <translation id="1594030484168838125">ಆರಿಸಿ</translation>
 <translation id="1596296697375291157">ಥ್ರಿಲ್ಲರ್, ಕ್ರೈಂ ಮತ್ತು ರಹಸ್ಯಮಯ ಚಲನಚಿತ್ರಗಳು</translation>
@@ -2652,7 +2651,6 @@
 <translation id="830498451218851433">ಅರ್ಧ ಮಡಿಸಿ</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">ಇನ್‍‍ಸ್ಟಾಲ್ ಮಾಡಿರುವ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಅವುಗಳನ್ನು ಎಷ್ಟು ಬಾರಿ ಬಳಸಿರುವುದು</translation>
-<translation id="8317207217658302555">ARCore ಅನ್ನು ಅಪ್‌ಡೇಟ್ ಮಾಡಬೇಕೆ?</translation>
 <translation id="831997045666694187">ಸಂಜೆ</translation>
 <translation id="8321476692217554900">ಅಧಿಸೂಚನೆಗಳು</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> ಗೆ ಪ್ರವೇಶವನ್ನು ನಿರಾಕರಿಸಲಾಗಿದೆ</translation>
diff --git a/components/strings/components_strings_ko.xtb b/components/strings/components_strings_ko.xtb
index 31050073..4a11d9c 100644
--- a/components/strings/components_strings_ko.xtb
+++ b/components/strings/components_strings_ko.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">이 웹페이지를 표시하는 도중 문제가 발생했습니다.</translation>
 <translation id="1586541204584340881">내가 설치한 확장 프로그램</translation>
 <translation id="1588438908519853928">일반</translation>
-<translation id="1589050138437146318">ARCore를 설치하시겠습니까?</translation>
 <translation id="1592005682883173041">로컬 데이터 액세스</translation>
 <translation id="1594030484168838125">선택</translation>
 <translation id="1596296697375291157">스릴러, 범죄 및 미스터리 영화</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">폴드 하프</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">설치된 앱 및 앱의 사용 빈도</translation>
-<translation id="8317207217658302555">ARCore를 업데이트하시겠습니까?</translation>
 <translation id="831997045666694187">저녁</translation>
 <translation id="8321476692217554900">알림</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" />에 대한 액세스가 거부됨</translation>
diff --git a/components/strings/components_strings_ky.xtb b/components/strings/components_strings_ky.xtb
index 1adbf31..bfbbd02 100644
--- a/components/strings/components_strings_ky.xtb
+++ b/components/strings/components_strings_ky.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Бул веб-баракча жүктөлүп жатканда бир жерден ката кетти.</translation>
 <translation id="1586541204584340881">Кайсы кеңейтүүлөрдү орнотуп алдыңыз</translation>
 <translation id="1588438908519853928">Кадыресе</translation>
-<translation id="1589050138437146318">ARCore орнотулсунбу?</translation>
 <translation id="1592005682883173041">Жергиликтүү дайындарга мүмкүнчүлүк алуу</translation>
 <translation id="1594030484168838125">Тандоо</translation>
 <translation id="1596296697375291157">Триллер, кылмыш жана сырдуу тасмалар</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Жарымынан бүктөө</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Орнотулган колдонмолор жана алар канчалык көп колдонулат</translation>
-<translation id="8317207217658302555">ARCore жаңыртылсынбы?</translation>
 <translation id="831997045666694187">Кече</translation>
 <translation id="8321476692217554900">билдирмелер</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> сайтына кирүү мүмкүнчүлүгү четке кагылды</translation>
diff --git a/components/strings/components_strings_lo.xtb b/components/strings/components_strings_lo.xtb
index 3598a5c..5a79b11f 100644
--- a/components/strings/components_strings_lo.xtb
+++ b/components/strings/components_strings_lo.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">ມີບາງອັນຜິດພາດ ໃນຂະນະທີ່ກໍາລັງສະແດງໜ້າ​ເວັບນີ້.</translation>
 <translation id="1586541204584340881">ສ່ວນຂະຫຍາຍທີ່ທ່ານຕິດຕັ້ງ</translation>
 <translation id="1588438908519853928">ປົກ​ກະ​ຕິ</translation>
-<translation id="1589050138437146318">ຕິດຕັ້ງ ARCore ບໍ?</translation>
 <translation id="1592005682883173041">ການເຂົ້າເຖິງຂໍ້ມູນພາຍໃນເຄື່ອງ</translation>
 <translation id="1594030484168838125">ເລືອກ</translation>
 <translation id="1596296697375291157">ໜັງເລິກລັບ, ສືບສວນ ແລະ ລະທຶກຂວັນ</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">ພັບເຄິ່ງ</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">ແອັບທີ່ຕິດຕັ້ງ ແລະ ມີການນຳໃຊ້ພວກມັນເລື້ອຍໆສໍ່າໃດ</translation>
-<translation id="8317207217658302555">ອັບເດດ ARCore ບໍ?</translation>
 <translation id="831997045666694187">ຕອນແລງ</translation>
 <translation id="8321476692217554900">ການແຈ້ງເຕືອນ</translation>
 <translation id="8332188693563227489">ການເຂົ້າເຖິງ <ph name="HOST_NAME" /> ຖືກປະຕິເສດແລ້ວ</translation>
diff --git a/components/strings/components_strings_lt.xtb b/components/strings/components_strings_lt.xtb
index 61f3921..43a7832 100644
--- a/components/strings/components_strings_lt.xtb
+++ b/components/strings/components_strings_lt.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Rodant šį tinklalapį įvyko nenumatyta klaida.</translation>
 <translation id="1586541204584340881">Kuriuos plėtinius įdiegėte</translation>
 <translation id="1588438908519853928">Įprastas</translation>
-<translation id="1589050138437146318">Įdiegti „ARCore“?</translation>
 <translation id="1592005682883173041">Prieiga prie vietinių duomenų</translation>
 <translation id="1594030484168838125">Pasirinkti</translation>
 <translation id="1596296697375291157">Trileriai, kriminaliniai ir detektyviniai filmai</translation>
@@ -2659,7 +2658,6 @@
 <translation id="830498451218851433">Perlenkimas per pusę</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Įdiegtos programos ir kaip dažnai jos naudojamos</translation>
-<translation id="8317207217658302555">Atnaujinti „ARCore“?</translation>
 <translation id="831997045666694187">Vakaras</translation>
 <translation id="8321476692217554900">pranešimai</translation>
 <translation id="8332188693563227489">Prieiga prie <ph name="HOST_NAME" /> atmesta</translation>
diff --git a/components/strings/components_strings_lv.xtb b/components/strings/components_strings_lv.xtb
index 9c20c54..057bb85 100644
--- a/components/strings/components_strings_lv.xtb
+++ b/components/strings/components_strings_lv.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Šīs tīmekļa lapas rādīšanas laikā radās kļūda.</translation>
 <translation id="1586541204584340881">Kurus paplašinājumus esat instalējis</translation>
 <translation id="1588438908519853928">Parasts</translation>
-<translation id="1589050138437146318">Vai instalēt ARCore?</translation>
 <translation id="1592005682883173041">Piekļuve lokālajiem datiem</translation>
 <translation id="1594030484168838125">Izvēlēties</translation>
 <translation id="1596296697375291157">Trilleri, noziegumu un šausmu filmas</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Locījums uz pusēm</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Instalētās lietotnes un to izmantošanas biežums</translation>
-<translation id="8317207217658302555">Vai atjaunināt ARCore?</translation>
 <translation id="831997045666694187">Vakarā</translation>
 <translation id="8321476692217554900">paziņojumi</translation>
 <translation id="8332188693563227489">Piekļuve vietnei <ph name="HOST_NAME" /> tika noraidīta</translation>
diff --git a/components/strings/components_strings_mk.xtb b/components/strings/components_strings_mk.xtb
index a9581b49..7f51504 100644
--- a/components/strings/components_strings_mk.xtb
+++ b/components/strings/components_strings_mk.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Нешто тргна наопаку при прикажувањето на оваа веб-страница.</translation>
 <translation id="1586541204584340881">кои екстензии сте ги инсталирале</translation>
 <translation id="1588438908519853928">Нормален</translation>
-<translation id="1589050138437146318">Да се инсталира ARCore?</translation>
 <translation id="1592005682883173041">Локален пристап до податоците</translation>
 <translation id="1594030484168838125">Избери</translation>
 <translation id="1596296697375291157">Трилери, крими и мистерии</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Превиткување на половина</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Инсталирани апликации и колку често се користат</translation>
-<translation id="8317207217658302555">Да се ажурира ARCore?</translation>
 <translation id="831997045666694187">Приквечер</translation>
 <translation id="8321476692217554900">известувања</translation>
 <translation id="8332188693563227489">Пристапот до <ph name="HOST_NAME" /> беше одбиен</translation>
diff --git a/components/strings/components_strings_ml.xtb b/components/strings/components_strings_ml.xtb
index 44bc6ef..65f41ad1 100644
--- a/components/strings/components_strings_ml.xtb
+++ b/components/strings/components_strings_ml.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">ഈ വെബ്‌പേജ് പ്രദർശിപ്പിക്കുമ്പോൾ എന്തോ കുഴപ്പം സംഭവിച്ചു.</translation>
 <translation id="1586541204584340881">ഏതൊക്കെ വിപുലീകരണങ്ങൾ നിങ്ങൾ ഇൻസ്‌റ്റാൾ ചെയ്തിട്ടുണ്ട് എന്നത്</translation>
 <translation id="1588438908519853928">സാധാരണം</translation>
-<translation id="1589050138437146318">ARCore ഇൻസ്റ്റാൾ ചെയ്യണോ?</translation>
 <translation id="1592005682883173041">പ്രാദേശിക ഡാറ്റ ആക്‌സസ്</translation>
 <translation id="1594030484168838125">തിരഞ്ഞെടുക്കുക</translation>
 <translation id="1596296697375291157">ത്രില്ലർ, ക്രൈം, നിഗൂഢ സിനിമകൾ</translation>
@@ -2655,7 +2654,6 @@
 <translation id="830498451218851433">പകുതിയായി മടക്കുക</translation>
 <translation id="8307358339886459768">ചെറിയ-ഫോട്ടോ</translation>
 <translation id="8307888238279532626">ആപ്പുകൾ ഇൻസ്‌റ്റാൾ ചെയ്തിരിക്കുന്നു, അവ എത്ര തവണ ഉപയോഗിക്കുന്നു</translation>
-<translation id="8317207217658302555">ARCore അപ്ഡേറ്റ് ചെയ്യണോ?</translation>
 <translation id="831997045666694187">വൈകുന്നേരം</translation>
 <translation id="8321476692217554900">അറിയിപ്പുകൾ</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> ഹോസ്‌റ്റിലേക്കുള്ള ആക്‌സസ് നിരസിച്ചു</translation>
diff --git a/components/strings/components_strings_mn.xtb b/components/strings/components_strings_mn.xtb
index 70197bc3..6d86e37 100644
--- a/components/strings/components_strings_mn.xtb
+++ b/components/strings/components_strings_mn.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Энэхүү веб хуудсыг харуулахад ямар нэгэн алдаа гарлаа.</translation>
 <translation id="1586541204584340881">Таны ямар өргөтгөл суулгасан болох</translation>
 <translation id="1588438908519853928">Хэвийн</translation>
-<translation id="1589050138437146318">ARCore-г суулгах уу?</translation>
 <translation id="1592005682883173041">Суурин өгөгдлийн хандалт</translation>
 <translation id="1594030484168838125">Сонгох</translation>
 <translation id="1596296697375291157">Онц сонирхолтой, гэмт хэргийн болон нууцлаг кино</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Хагас нугалаа</translation>
 <translation id="8307358339886459768">Жижиг-Зураг</translation>
 <translation id="8307888238279532626">Суулгасан аппууд болон тэдгээрийг ашигладаг давтамж</translation>
-<translation id="8317207217658302555">ARCore-г шинэчлэх үү?</translation>
 <translation id="831997045666694187">Үдэш</translation>
 <translation id="8321476692217554900">мэдэгдэл</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" />-н хандах хүсэлтийг зөвшөөрсөнгүй</translation>
diff --git a/components/strings/components_strings_mr.xtb b/components/strings/components_strings_mr.xtb
index c8b2a60..58c0e595 100644
--- a/components/strings/components_strings_mr.xtb
+++ b/components/strings/components_strings_mr.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">हे वेबपेज प्रदर्शित करताना काहीतरी चूक झाली.</translation>
 <translation id="1586541204584340881">तुम्ही कोणती एक्स्टेंशन इंस्टॉल केली आहेत</translation>
 <translation id="1588438908519853928">सामान्य</translation>
-<translation id="1589050138437146318">ARCore इंस्टॉल करायचे आहे का?</translation>
 <translation id="1592005682883173041">स्थानिक डेटा ॲक्सेस</translation>
 <translation id="1594030484168838125">निवडा</translation>
 <translation id="1596296697375291157">रोमांचक, गुन्हेविषयक आणि गूढ चित्रपट</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">फोल्ड हाफ</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">इंस्टॉल केलेली अ‍ॅप्स आणि ती किती वेळा वापरली जातात</translation>
-<translation id="8317207217658302555">ARCore अपडेट करायचे आहे का?</translation>
 <translation id="831997045666694187">संध्याकाळी</translation>
 <translation id="8321476692217554900">सूचना</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> चा ॲक्सेस नाकारला</translation>
diff --git a/components/strings/components_strings_ms.xtb b/components/strings/components_strings_ms.xtb
index d5967ed..ceb0b3f 100644
--- a/components/strings/components_strings_ms.xtb
+++ b/components/strings/components_strings_ms.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Kesilapan berlaku semasa memaparkan halaman web ini.</translation>
 <translation id="1586541204584340881">Sambungan yang telah anda pasang</translation>
 <translation id="1588438908519853928">Biasa</translation>
-<translation id="1589050138437146318">Pasang ARCore?</translation>
 <translation id="1592005682883173041">Akses Data Setempat</translation>
 <translation id="1594030484168838125">Pilih</translation>
 <translation id="1596296697375291157">Filem ngeri, jenayah &amp; misteri</translation>
@@ -2659,7 +2658,6 @@
 <translation id="830498451218851433">Lipat dua</translation>
 <translation id="8307358339886459768">Foto-Kecil</translation>
 <translation id="8307888238279532626">Apl yang dipasang dan kekerapan apl itu digunakan</translation>
-<translation id="8317207217658302555">Kemas kini ARCore?</translation>
 <translation id="831997045666694187">Petang</translation>
 <translation id="8321476692217554900">pemberitahuan</translation>
 <translation id="8332188693563227489">Akses ke <ph name="HOST_NAME" /> dinafikan</translation>
diff --git a/components/strings/components_strings_my.xtb b/components/strings/components_strings_my.xtb
index 5eacdee..53b75995 100644
--- a/components/strings/components_strings_my.xtb
+++ b/components/strings/components_strings_my.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">ဤဝဘ်ဆိုက်အား ပြသစဉ် တစ်ခုခု မှားသွား၏။</translation>
 <translation id="1586541204584340881">သင်ထည့်သွင်းထားသည့် နောက်ဆက်တွဲများ</translation>
 <translation id="1588438908519853928">ပုံမှန်</translation>
-<translation id="1589050138437146318">ARCore ထည့်သွင်းမလား။</translation>
 <translation id="1592005682883173041">စက်အတွင်းဒေတာ အသုံးပြုမှု</translation>
 <translation id="1594030484168838125">ရွေးချယ်ရန်</translation>
 <translation id="1596296697375291157">သည်းထိတ်ရင်ဖို၊ ရာဇဝတ်နှင့် လျှို့ဝှက်ဆန်းကြယ် ရုပ်ရှင်များ</translation>
@@ -2660,7 +2659,6 @@
 <translation id="830498451218851433">တစ်ဝက် ခေါက်ရန်</translation>
 <translation id="8307358339886459768">ဓာတ်ပုံ အသေး</translation>
 <translation id="8307888238279532626">ထည့်သွင်းထားသော အက်ပ်များနှင့် ၎င်းတို့ကို မည်မျှတစ်ကြိမ် အသုံးပြုပုံ</translation>
-<translation id="8317207217658302555">ARCore ကို အပ်ဒိတ်လုပ်မလား။</translation>
 <translation id="831997045666694187">ညနေ</translation>
 <translation id="8321476692217554900">အကြောင်းကြားချက်များ</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> အားအသုံးပြုခွင့် ပယ်ချခံခဲ့ရပါသည်</translation>
diff --git a/components/strings/components_strings_ne.xtb b/components/strings/components_strings_ne.xtb
index b895875..c94af93 100644
--- a/components/strings/components_strings_ne.xtb
+++ b/components/strings/components_strings_ne.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">यो वेबपृष्ठ प्रदर्शन गर्दा कुनै त्रुटी भयो।</translation>
 <translation id="1586541204584340881">तपाईंले स्थापना गर्नुभएका एक्स्टेन्सनहरू</translation>
 <translation id="1588438908519853928">सामान्य</translation>
-<translation id="1589050138437146318">ARCore इन्स्टल गर्ने हो?</translation>
 <translation id="1592005682883173041">स्थानीय डेटा पहुँच</translation>
 <translation id="1594030484168838125">छनौट गर्नुहोस्</translation>
 <translation id="1596296697375291157">थ्रिलर, अपराध तथा रहस्यमयी कथा भएका चलचित्रहरू</translation>
@@ -2655,7 +2654,6 @@
 <translation id="830498451218851433">आधा फोल्ड</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">इन्स्टल गरिएका एप र ती एप प्रयोग गरिने अन्तराल</translation>
-<translation id="8317207217658302555">ARCore अपडेट गर्ने हो?</translation>
 <translation id="831997045666694187">साँझ</translation>
 <translation id="8321476692217554900">सूचनाहरू</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> मा पहुँच गर्न अस्वीकृत भयो</translation>
diff --git a/components/strings/components_strings_nl.xtb b/components/strings/components_strings_nl.xtb
index 1a20bb2..accbe5c 100644
--- a/components/strings/components_strings_nl.xtb
+++ b/components/strings/components_strings_nl.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Er is iets misgegaan met het bekijken van deze webpagina.</translation>
 <translation id="1586541204584340881">Welke extensies je hebt geïnstalleerd</translation>
 <translation id="1588438908519853928">Normaal</translation>
-<translation id="1589050138437146318">ARCore installeren?</translation>
 <translation id="1592005682883173041">Lokale gegevenstoegang</translation>
 <translation id="1594030484168838125">Kiezen</translation>
 <translation id="1596296697375291157">Thrillers, misdaad- en mysteriefilms</translation>
@@ -2652,7 +2651,6 @@
 <translation id="830498451218851433">Enkele vouw</translation>
 <translation id="8307358339886459768">Kleine foto</translation>
 <translation id="8307888238279532626">Geïnstalleerde apps en hoe vaak ze zijn gebruikt</translation>
-<translation id="8317207217658302555">ARCore updaten?</translation>
 <translation id="831997045666694187">Avond</translation>
 <translation id="8321476692217554900">meldingen</translation>
 <translation id="8332188693563227489">Toegang tot <ph name="HOST_NAME" /> is geweigerd</translation>
diff --git a/components/strings/components_strings_no.xtb b/components/strings/components_strings_no.xtb
index 13140ca..28125af 100644
--- a/components/strings/components_strings_no.xtb
+++ b/components/strings/components_strings_no.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Noe gikk galt da denne nettsiden skulle åpnes.</translation>
 <translation id="1586541204584340881">hvilke utvidelser du har installert</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Vil du installere ARCore?</translation>
 <translation id="1592005682883173041">Tilgang til lokale data</translation>
 <translation id="1594030484168838125">Velg</translation>
 <translation id="1596296697375291157">Thriller-, krim- og mysteriefilmer</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Halvfals</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">hvilke apper som er installert, og hvor ofte de brukes</translation>
-<translation id="8317207217658302555">Vil du oppdatere ARCore?</translation>
 <translation id="831997045666694187">Kveld</translation>
 <translation id="8321476692217554900">varsler</translation>
 <translation id="8332188693563227489">Forsøket på å koble til <ph name="HOST_NAME" /> ble avvist</translation>
diff --git a/components/strings/components_strings_or.xtb b/components/strings/components_strings_or.xtb
index c47e7088..6231726 100644
--- a/components/strings/components_strings_or.xtb
+++ b/components/strings/components_strings_or.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">ଏହି ୱେବ୍ ପୃଷ୍ଠାଟି ପ୍ରଦର୍ଶନ କରୁଥିବା ବେଳେ କିଛି ତ୍ରୁଟି ହୋଇଛି।</translation>
 <translation id="1586541204584340881">ଆପଣ କେଉଁ ଏକ୍ସଟେନସନଗୁଡ଼ିକୁ ଇନଷ୍ଟଲ୍ କରିଛନ୍ତି</translation>
 <translation id="1588438908519853928">ସାଧାରଣ</translation>
-<translation id="1589050138437146318">ARCore ଇନଷ୍ଟଲ କରିବେ କି?</translation>
 <translation id="1592005682883173041">ସ୍ଥାନୀୟ ଡାଟା ଆକ୍ସେସ୍</translation>
 <translation id="1594030484168838125">ବାଛନ୍ତୁ</translation>
 <translation id="1596296697375291157">ଥ୍ରିଲର, ଅପରାଧ ଏବଂ ରହସ୍ୟ ମୁଭିଗୁଡ଼ିକ</translation>
@@ -2655,7 +2654,6 @@
 <translation id="830498451218851433">ଅଧା ଫୋଲ୍ଡ କରନ୍ତୁ</translation>
 <translation id="8307358339886459768">ଛୋଟ-ଫଟୋ</translation>
 <translation id="8307888238279532626">ଇନଷ୍ଟଲ୍ କରାଯାଇଥିବା ଆପଗୁଡ଼ିକ ଏବଂ ସେଗୁଡ଼ିକ କେତେ ଥର ବ୍ୟବହାର କରାଯାଇଛି</translation>
-<translation id="8317207217658302555">ARCore ଅପଡେଟ କରିବେ କି?</translation>
 <translation id="831997045666694187">ସନ୍ଧ୍ୟା</translation>
 <translation id="8321476692217554900">ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" />କୁ ଆକ୍ସେସ୍‌ ପ୍ରତ୍ୟାଖ୍ୟାନ କରାଯାଇଥିଲା</translation>
diff --git a/components/strings/components_strings_pa.xtb b/components/strings/components_strings_pa.xtb
index 4ae496a..1424f51 100644
--- a/components/strings/components_strings_pa.xtb
+++ b/components/strings/components_strings_pa.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">ਇਹ ਵੈਬਸਫ਼ਾ ਪ੍ਰਦਰਸ਼ਿਤ ਕਰਦੇ ਸਮੇਂ ਕੁਝ ਗ਼ਲਤ ਹੋਇਆ ਸੀ।</translation>
 <translation id="1586541204584340881">ਤੁਸੀਂ ਕਿਹੜੀਆਂ ਐਕਸਟੈਂਸ਼ਨਾਂ ਸਥਾਪਤ ਕੀਤੀਆਂ ਹੋਈਆਂ ਹਨ</translation>
 <translation id="1588438908519853928">ਸਧਾਰਨ</translation>
-<translation id="1589050138437146318">ਕੀ ARCore ਨੂੰ ਸਥਾਪਤ ਕਰਨਾ ਹੈ?</translation>
 <translation id="1592005682883173041">ਸਥਾਨਕ  ਡਾਟਾ  ਪਹੁੰਚ</translation>
 <translation id="1594030484168838125">ਚੁਣੋ</translation>
 <translation id="1596296697375291157">ਰੋਮਾਂਚਕ, ਅਪਰਾਧਕ ਅਤੇ ਰਹੱਸਮਈ ਫ਼ਿਲਮਾਂ</translation>
@@ -2654,7 +2653,6 @@
 <translation id="830498451218851433">ਅੱਧੀ ਤਹਿ</translation>
 <translation id="8307358339886459768">ਛੋਟੀ-ਫ਼ੋਟੋ</translation>
 <translation id="8307888238279532626">ਸਥਾਪਤ ਐਪਾਂ ਅਤੇ ਐਪਾਂ ਵਰਤਣ ਦਾ ਸਮਾਂ</translation>
-<translation id="8317207217658302555">ਕੀ ARCore ਨੂੰ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?</translation>
 <translation id="831997045666694187">ਸ਼ਾਮ</translation>
 <translation id="8321476692217554900">ਸੂਚਨਾਵਾਂ</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> 'ਤੇ ਪਹੁੰਚ ਨੂੰ ਇਨਕਾਰਿਆ ਗਿਆ ਸੀ</translation>
diff --git a/components/strings/components_strings_pl.xtb b/components/strings/components_strings_pl.xtb
index b8ab3e1..105098b9 100644
--- a/components/strings/components_strings_pl.xtb
+++ b/components/strings/components_strings_pl.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Podczas wyświetlania strony wystąpił błąd.</translation>
 <translation id="1586541204584340881">Zainstalowane przez Ciebie rozszerzenia</translation>
 <translation id="1588438908519853928">Normalny</translation>
-<translation id="1589050138437146318">Zainstalować ARCore?</translation>
 <translation id="1592005682883173041">Lokalny dostęp do danych</translation>
 <translation id="1594030484168838125">Wybierz</translation>
 <translation id="1596296697375291157">Thrillery i filmy kryminalne</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Składanie w połowie</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Zainstalowane aplikacje i częstotliwość ich użycia</translation>
-<translation id="8317207217658302555">Zaktualizować ARCore?</translation>
 <translation id="831997045666694187">Wieczorem</translation>
 <translation id="8321476692217554900">powiadomienia</translation>
 <translation id="8332188693563227489">Odmowa dostępu do <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_pt-BR.xtb b/components/strings/components_strings_pt-BR.xtb
index 66d09d3..1b2256b 100644
--- a/components/strings/components_strings_pt-BR.xtb
+++ b/components/strings/components_strings_pt-BR.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Algo deu errado ao exibir esta página da Web.</translation>
 <translation id="1586541204584340881">quais extensões você instalou;</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Instalar o ARCore?</translation>
 <translation id="1592005682883173041">Acesso a dados locais</translation>
 <translation id="1594030484168838125">Escolher</translation>
 <translation id="1596296697375291157">Filmes de suspense, crime e mistério</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Dobra simples</translation>
 <translation id="8307358339886459768">Small photo</translation>
 <translation id="8307888238279532626">Os apps instalados e a frequência de uso deles</translation>
-<translation id="8317207217658302555">Atualizar o ARCore?</translation>
 <translation id="831997045666694187">Noite</translation>
 <translation id="8321476692217554900">notificações</translation>
 <translation id="8332188693563227489">O acesso a <ph name="HOST_NAME" /> foi negado</translation>
diff --git a/components/strings/components_strings_pt-PT.xtb b/components/strings/components_strings_pt-PT.xtb
index cb4c090..27238011 100644
--- a/components/strings/components_strings_pt-PT.xtb
+++ b/components/strings/components_strings_pt-PT.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Ocorreu um erro ao apresentar esta página Web.</translation>
 <translation id="1586541204584340881">As extensões que instalou.</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Instalar o ARCore?</translation>
 <translation id="1592005682883173041">Acesso aos dados locais</translation>
 <translation id="1594030484168838125">Escolher</translation>
 <translation id="1596296697375291157">Filmes policiais, de mistério e tríleres</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Dobrar a meio</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Apps instaladas e a frequência com que são utilizadas.</translation>
-<translation id="8317207217658302555">Atualizar o ARCore?</translation>
 <translation id="831997045666694187">Noite</translation>
 <translation id="8321476692217554900">notificações</translation>
 <translation id="8332188693563227489">O acesso a <ph name="HOST_NAME" /> foi recusado</translation>
diff --git a/components/strings/components_strings_ro.xtb b/components/strings/components_strings_ro.xtb
index 4efc143a..cd81945 100644
--- a/components/strings/components_strings_ro.xtb
+++ b/components/strings/components_strings_ro.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">A apărut o eroare la afișarea paginii web.</translation>
 <translation id="1586541204584340881">extensiile instalate;</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Instalezi ARCore?</translation>
 <translation id="1592005682883173041">Accesul la datele locale</translation>
 <translation id="1594030484168838125">Alegeți</translation>
 <translation id="1596296697375291157">Thrillere, filme polițiste și de mister</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Îndoire la jumătate</translation>
 <translation id="8307358339886459768">Fotografie-Mic</translation>
 <translation id="8307888238279532626">Aplicațiile instalate și frecvența de folosire a acestora</translation>
-<translation id="8317207217658302555">Actualizezi ARCore?</translation>
 <translation id="831997045666694187">Seara</translation>
 <translation id="8321476692217554900">Notificări</translation>
 <translation id="8332188693563227489">Accesul la <ph name="HOST_NAME" /> nu este permis</translation>
diff --git a/components/strings/components_strings_ru.xtb b/components/strings/components_strings_ru.xtb
index 7ddf8a4..08515ca 100644
--- a/components/strings/components_strings_ru.xtb
+++ b/components/strings/components_strings_ru.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">При загрузке этой страницы возникли неполадки.</translation>
 <translation id="1586541204584340881">Установленные расширения.</translation>
 <translation id="1588438908519853928">Обычный</translation>
-<translation id="1589050138437146318">Установить ARCore?</translation>
 <translation id="1592005682883173041">Доступ к данным на устройстве</translation>
 <translation id="1594030484168838125">Выбрать</translation>
 <translation id="1596296697375291157">Триллеры, криминальные и детективные фильмы</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Фальцовка книжкой</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">установленные приложения и частоту их использования.</translation>
-<translation id="8317207217658302555">Обновить ARCore?</translation>
 <translation id="831997045666694187">До вечера</translation>
 <translation id="8321476692217554900">Уведомления</translation>
 <translation id="8332188693563227489">Доступ к <ph name="HOST_NAME" /> запрещен</translation>
diff --git a/components/strings/components_strings_si.xtb b/components/strings/components_strings_si.xtb
index e2cd235a..6300b00 100644
--- a/components/strings/components_strings_si.xtb
+++ b/components/strings/components_strings_si.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">මෙම වෙබ් පිටුව සංසංදර්ශනය කිරීමේදී යමක් වැරදී ඇත.</translation>
 <translation id="1586541204584340881">ඔබ ස්ථාපන කර ඇති දිගු මොනවාද</translation>
 <translation id="1588438908519853928">සාමාන්‍ය</translation>
-<translation id="1589050138437146318">ARCore ස්ථාපනය කරන්නද?</translation>
 <translation id="1592005682883173041">ස්ථානීය දත්ත ප්‍රවේශය</translation>
 <translation id="1594030484168838125">තෝරන්න</translation>
 <translation id="1596296697375291157">ත්‍රාසජනක, අපරාධ සහ අභිරහස් චිත්‍රපට</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">භාගයට නවන්න</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">ස්ථාපනය කළ යෙදුම් සහ ඒවා කොතරම් නිතර භාවිත කරන්නේද යන්න</translation>
-<translation id="8317207217658302555">ARCore යාවත්කාලීන කරන්නද?</translation>
 <translation id="831997045666694187">සවස</translation>
 <translation id="8321476692217554900">දැනුම්දීම්</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> වෙත ප්‍රවේශය ප්‍රතික්ෂේප කරන ලදී</translation>
diff --git a/components/strings/components_strings_sk.xtb b/components/strings/components_strings_sk.xtb
index ac1c0df..76c037d 100644
--- a/components/strings/components_strings_sk.xtb
+++ b/components/strings/components_strings_sk.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Pri zobrazovaní webovej stránky sa niečo pokazilo.</translation>
 <translation id="1586541204584340881">Ktoré rozšírenia ste nainštalovali</translation>
 <translation id="1588438908519853928">Normálny</translation>
-<translation id="1589050138437146318">Chcete nainštalovať ARCore?</translation>
 <translation id="1592005682883173041">Prístup k miestnym údajom</translation>
 <translation id="1594030484168838125">Zvoliť</translation>
 <translation id="1596296697375291157">Trilery, kriminálky a mysteriózne filmy</translation>
@@ -2653,7 +2652,6 @@
 <translation id="830498451218851433">Zahnúť v polovici</translation>
 <translation id="8307358339886459768">Malá fotka</translation>
 <translation id="8307888238279532626">Nainštalované aplikácie a ako často sú používané</translation>
-<translation id="8317207217658302555">Chcete aktualizovať ARCore?</translation>
 <translation id="831997045666694187">Večer</translation>
 <translation id="8321476692217554900">upozornenia</translation>
 <translation id="8332188693563227489">Prístup k webu <ph name="HOST_NAME" /> bol zamietnutý</translation>
diff --git a/components/strings/components_strings_sl.xtb b/components/strings/components_strings_sl.xtb
index 7bde43cc..785c2ebd 100644
--- a/components/strings/components_strings_sl.xtb
+++ b/components/strings/components_strings_sl.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Med prikazom spletne strani je prišlo do napake.</translation>
 <translation id="1586541204584340881">Katere razširitve ste namestili</translation>
 <translation id="1588438908519853928">Običajen</translation>
-<translation id="1589050138437146318">Želite namestiti ARCore?</translation>
 <translation id="1592005682883173041">Dostop do lokalnih podatkov</translation>
 <translation id="1594030484168838125">Izberi</translation>
 <translation id="1596296697375291157">Trilerji, kriminalke in detektivke</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Prepogibanje na pol</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Nameščene aplikacije in pogostost njihove uporabe</translation>
-<translation id="8317207217658302555">Želite posodobiti ARCore?</translation>
 <translation id="831997045666694187">Večer</translation>
 <translation id="8321476692217554900">obvestila</translation>
 <translation id="8332188693563227489">Dostop do spletnega mesta <ph name="HOST_NAME" /> je bil zavrnjen</translation>
diff --git a/components/strings/components_strings_sq.xtb b/components/strings/components_strings_sq.xtb
index 657b867..93b7743 100644
--- a/components/strings/components_strings_sq.xtb
+++ b/components/strings/components_strings_sq.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Shkoi diçka keq kur po shfaqej faqja e uebit.</translation>
 <translation id="1586541204584340881">Cilat shtesa ke instaluar</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Të instalohet ARCore?</translation>
 <translation id="1592005682883173041">Qasja te të dhënat lokale</translation>
 <translation id="1594030484168838125">Zgjidh</translation>
 <translation id="1596296697375291157">Filmat melodramë, krim dhe mister</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Palosje në gjysmë</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Aplikacionet e instaluara dhe sa shpesh përdoren</translation>
-<translation id="8317207217658302555">Të përditësohet ARCore?</translation>
 <translation id="831997045666694187">Mbrëmje</translation>
 <translation id="8321476692217554900">njoftimet</translation>
 <translation id="8332188693563227489">Qasja te <ph name="HOST_NAME" /> u refuzua</translation>
diff --git a/components/strings/components_strings_sr-Latn.xtb b/components/strings/components_strings_sr-Latn.xtb
index e6df7fc..0cf1c5a 100644
--- a/components/strings/components_strings_sr-Latn.xtb
+++ b/components/strings/components_strings_sr-Latn.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Došlo je do greške pri prikazivanju ove veb-stranice.</translation>
 <translation id="1586541204584340881">dodatke koje ste instalirali</translation>
 <translation id="1588438908519853928">Normalan</translation>
-<translation id="1589050138437146318">Želite da instalirate ARCore?</translation>
 <translation id="1592005682883173041">Pristup lokalnim podacima</translation>
 <translation id="1594030484168838125">Odaberi</translation>
 <translation id="1596296697375291157">Trileri, kriminalistički filmovi i filmovi misterije</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Presavijanje napola</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Instalirane aplikacije i koliko često se koriste</translation>
-<translation id="8317207217658302555">Želite da ažurirate ARCore?</translation>
 <translation id="831997045666694187">Uveče</translation>
 <translation id="8321476692217554900">obaveštenja</translation>
 <translation id="8332188693563227489">Pristup hostu <ph name="HOST_NAME" /> je odbijen</translation>
diff --git a/components/strings/components_strings_sr.xtb b/components/strings/components_strings_sr.xtb
index b58406d..c791450f 100644
--- a/components/strings/components_strings_sr.xtb
+++ b/components/strings/components_strings_sr.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Дошло је до грешке при приказивању ове веб-странице.</translation>
 <translation id="1586541204584340881">додатке које сте инсталирали</translation>
 <translation id="1588438908519853928">Нормалан</translation>
-<translation id="1589050138437146318">Желите да инсталирате ARCore?</translation>
 <translation id="1592005682883173041">Приступ локалним подацима</translation>
 <translation id="1594030484168838125">Одабери</translation>
 <translation id="1596296697375291157">Трилери, криминалистички филмови и филмови мистерије</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">Пресавијање напола</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Инсталиране апликације и колико често се користе</translation>
-<translation id="8317207217658302555">Желите да ажурирате ARCore?</translation>
 <translation id="831997045666694187">Увече</translation>
 <translation id="8321476692217554900">обавештења</translation>
 <translation id="8332188693563227489">Приступ хосту <ph name="HOST_NAME" /> је одбијен</translation>
diff --git a/components/strings/components_strings_sv.xtb b/components/strings/components_strings_sv.xtb
index 3429882..87e8717 100644
--- a/components/strings/components_strings_sv.xtb
+++ b/components/strings/components_strings_sv.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Ett fel uppstod när webbsidan skulle visas.</translation>
 <translation id="1586541204584340881">Vilka tillägg du har installerat</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">Vill du installera ARCore?</translation>
 <translation id="1592005682883173041">Lokal dataåtkomst</translation>
 <translation id="1594030484168838125">Välj</translation>
 <translation id="1596296697375291157">Thriller, polis och kriminalgåtor, film</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Enkelfalsning</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Vilka appar som har installerats och hur ofta de används</translation>
-<translation id="8317207217658302555">Vill du uppdatera ARCore?</translation>
 <translation id="831997045666694187">kvällen</translation>
 <translation id="8321476692217554900">aviseringar</translation>
 <translation id="8332188693563227489">Åtkomst nekades till <ph name="HOST_NAME" />.</translation>
diff --git a/components/strings/components_strings_sw.xtb b/components/strings/components_strings_sw.xtb
index 3c5a137..443e4a5 100644
--- a/components/strings/components_strings_sw.xtb
+++ b/components/strings/components_strings_sw.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Hitilafu ilitokea wakati wa kuonyesha ukurasa huu wa wavuti.</translation>
 <translation id="1586541204584340881">Viendelezi ambavyo umesakinisha</translation>
 <translation id="1588438908519853928">Ya kawaida</translation>
-<translation id="1589050138437146318">Ungependa kusakinisha ARCore?</translation>
 <translation id="1592005682883173041">Ufikiaji wa Data Iliyo Katika Kifaa Chako</translation>
 <translation id="1594030484168838125">Chagua</translation>
 <translation id="1596296697375291157">Filamu za kutisha, uhalifu na zenye mafumbo</translation>
@@ -2656,7 +2655,6 @@
 <translation id="830498451218851433">Kunja nusu</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Programu zilizosakinishwa na mara ambazo zinatumika</translation>
-<translation id="8317207217658302555">Ungependa kusasisha ARCore?</translation>
 <translation id="831997045666694187">Jioni</translation>
 <translation id="8321476692217554900">arifa</translation>
 <translation id="8332188693563227489">Ufikiaji wa <ph name="HOST_NAME" /> umekataliwa</translation>
diff --git a/components/strings/components_strings_ta.xtb b/components/strings/components_strings_ta.xtb
index b83fe98..293894c 100644
--- a/components/strings/components_strings_ta.xtb
+++ b/components/strings/components_strings_ta.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">இந்த இணையப்பக்கத்தைக் காட்டும்போது ஏதோ தவறு ஏற்பட்டது.</translation>
 <translation id="1586541204584340881">நீங்கள் நிறுவியுள்ள நீட்டிப்புகள்</translation>
 <translation id="1588438908519853928">இயல்பு</translation>
-<translation id="1589050138437146318">ARCoreரை நிறுவவா?</translation>
 <translation id="1592005682883173041">அகத் தரவு அணுகல்</translation>
 <translation id="1594030484168838125">தேர்வுசெய்</translation>
 <translation id="1596296697375291157">திகில், குற்றம் &amp; மர்மத் திரைப்படங்கள்</translation>
@@ -2653,7 +2652,6 @@
 <translation id="830498451218851433">ஃபோல்டு ஹாஃப்</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">நிறுவியுள்ள ஆப்ஸும் அவை எப்போதெல்லாம் பயன்படுத்தப்படுகின்றன என்ற தகவலும்</translation>
-<translation id="8317207217658302555">ARCoreரைப் புதுப்பிக்கவா?</translation>
 <translation id="831997045666694187">மாலை</translation>
 <translation id="8321476692217554900">அறிவிப்புகள்</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> க்கான அணுகல் மறுக்கப்பட்டது</translation>
diff --git a/components/strings/components_strings_te.xtb b/components/strings/components_strings_te.xtb
index 4cfbaa7..440010110 100644
--- a/components/strings/components_strings_te.xtb
+++ b/components/strings/components_strings_te.xtb
@@ -261,7 +261,6 @@
 <translation id="1583429793053364125">ఈ వెబ్ పేజీని ప్రదర్శిస్తున్నప్పుడు ఏదో తప్పు జరిగింది.</translation>
 <translation id="1586541204584340881">మీరు ఏ ఎక్స్‌టెన్షన్‌లను ఇన్ స్టాల్ చేసుకున్నారు</translation>
 <translation id="1588438908519853928">సాధారణ</translation>
-<translation id="1589050138437146318">ARCoreను ఇన్‌స్టాల్ చేయాలా?</translation>
 <translation id="1592005682883173041">స్థానిక డేటా యాక్సెస్</translation>
 <translation id="1594030484168838125">ఎంచుకోండి</translation>
 <translation id="1596296697375291157">థ్రిల్లర్, క్రైమ్ &amp; మిస్టరీ సినిమాలు</translation>
@@ -2660,7 +2659,6 @@
 <translation id="830498451218851433">సగం ఫోల్డ్</translation>
 <translation id="8307358339886459768">చిన్న-ఫోటో</translation>
 <translation id="8307888238279532626">ఇన్‌స్టాల్ చేయబడిన యాప్‌లు అలాగే వాటిని ఎంత తరచుగా ఉపయోగించినది తెలిపే వివరాలు</translation>
-<translation id="8317207217658302555">ARCoreను అప్‌డేట్ చేయాలా?</translation>
 <translation id="831997045666694187">సాయంత్రం</translation>
 <translation id="8321476692217554900">నోటిఫికేషన్‌లు</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" />కి యాక్సెస్ నిరాకరించబడింది</translation>
diff --git a/components/strings/components_strings_th.xtb b/components/strings/components_strings_th.xtb
index c3a56f35..c81e7e9 100644
--- a/components/strings/components_strings_th.xtb
+++ b/components/strings/components_strings_th.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">มีสิ่งผิดปกติเกิดขึ้นในขณะที่แสดงหน้าเว็บนี้</translation>
 <translation id="1586541204584340881">ส่วนขยายที่คุณติดตั้ง</translation>
 <translation id="1588438908519853928">ปกติ</translation>
-<translation id="1589050138437146318">ติดตั้ง ARCore ไหม</translation>
 <translation id="1592005682883173041">การเข้าถึงข้อมูลในเครื่อง</translation>
 <translation id="1594030484168838125">เลือก</translation>
 <translation id="1596296697375291157">ภาพยนตร์ลึกลับ สืบสวน และเขย่าขวัญ</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">พับครึ่ง</translation>
 <translation id="8307358339886459768">รูปภาพขนาดเล็ก</translation>
 <translation id="8307888238279532626">แอปที่ติดตั้งและความถี่ในการใช้</translation>
-<translation id="8317207217658302555">อัปเดต ARCore ไหม</translation>
 <translation id="831997045666694187">ตอนเย็น</translation>
 <translation id="8321476692217554900">การแจ้งเตือน</translation>
 <translation id="8332188693563227489">การเข้าถึง <ph name="HOST_NAME" /> ถูกปฏิเสธ</translation>
diff --git a/components/strings/components_strings_tr.xtb b/components/strings/components_strings_tr.xtb
index a9e0aec..fb465f85 100644
--- a/components/strings/components_strings_tr.xtb
+++ b/components/strings/components_strings_tr.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Bu web sayfasını görüntülerken bir hata oluştu.</translation>
 <translation id="1586541204584340881">Hangi uzantıları yüklediğiniz</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">ARCore yüklensin mi?</translation>
 <translation id="1592005682883173041">Yerel Veri Erişimi</translation>
 <translation id="1594030484168838125">Seç</translation>
 <translation id="1596296697375291157">Gerilim, suç ve gizem filmleri</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Ortadan katlama</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Yüklü uygulamalar ve ne sıklıkla kullanıldıkları</translation>
-<translation id="8317207217658302555">ARCore güncellensin mi?</translation>
 <translation id="831997045666694187">Akşam</translation>
 <translation id="8321476692217554900">bildirimler</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> ana makinesine erişim reddedildi</translation>
diff --git a/components/strings/components_strings_uk.xtb b/components/strings/components_strings_uk.xtb
index 043596e..608d440 100644
--- a/components/strings/components_strings_uk.xtb
+++ b/components/strings/components_strings_uk.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Під час завантаженння цієї сторінки сталася помилка.</translation>
 <translation id="1586541204584340881">встановлені розширення;</translation>
 <translation id="1588438908519853928">Звичайний</translation>
-<translation id="1589050138437146318">Установити ARCore?</translation>
 <translation id="1592005682883173041">Доступ до локальних даних</translation>
 <translation id="1594030484168838125">Вибрати</translation>
 <translation id="1596296697375291157">Трилери, детективи та кримінальні фільми</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Зігнути посередині</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Установлені додатки та як часто ними користуються</translation>
-<translation id="8317207217658302555">Оновити ARCore?</translation>
 <translation id="831997045666694187">Увечері</translation>
 <translation id="8321476692217554900">сповіщення</translation>
 <translation id="8332188693563227489">Відмовлено в доступі до хосту <ph name="HOST_NAME" /></translation>
diff --git a/components/strings/components_strings_ur.xtb b/components/strings/components_strings_ur.xtb
index 82099b3..504652c 100644
--- a/components/strings/components_strings_ur.xtb
+++ b/components/strings/components_strings_ur.xtb
@@ -260,7 +260,6 @@
 <translation id="1583429793053364125">یہ ویب صفحہ ڈسپلے کرتے ہوئے کچھ غلط ہو گیا۔</translation>
 <translation id="1586541204584340881">آپ نے کن ایکسٹینشنز کو انسٹال کیا ہے</translation>
 <translation id="1588438908519853928">حسب معمول</translation>
-<translation id="1589050138437146318">‏ARCore انسٹال کریں؟</translation>
 <translation id="1592005682883173041">مقامی ڈیٹا تک رسائی</translation>
 <translation id="1594030484168838125">منتخب کریں</translation>
 <translation id="1596296697375291157">ہیجانی، جرم اور پُراسرار فلمیں</translation>
@@ -2661,7 +2660,6 @@
 <translation id="830498451218851433">آدھا فولڈ کریں</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">ایپس انسٹال ہو گئی ہیں اور کتنی بار استعمال ہوتی ہیں</translation>
-<translation id="8317207217658302555">‏ARCore کو اپ ڈیٹ کریں؟</translation>
 <translation id="831997045666694187">شام</translation>
 <translation id="8321476692217554900">اطلاعات</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> تک رسائی مسترد کر دی گئی</translation>
diff --git a/components/strings/components_strings_uz.xtb b/components/strings/components_strings_uz.xtb
index 3584d9a..4342998 100644
--- a/components/strings/components_strings_uz.xtb
+++ b/components/strings/components_strings_uz.xtb
@@ -258,7 +258,6 @@
 <translation id="1583429793053364125">Veb-sahifani ochish vaqtida kutilmagan xatolik yuz berdi.</translation>
 <translation id="1586541204584340881">Oʻrnatilgan kengaytmalar</translation>
 <translation id="1588438908519853928">Normal</translation>
-<translation id="1589050138437146318">ARCore oʻrnatilsinmi?</translation>
 <translation id="1592005682883173041">Qurilmadagi mahalliy ma’lumotlarga ruxsat</translation>
 <translation id="1594030484168838125">Tanlang</translation>
 <translation id="1596296697375291157">Triller, kriminal va detektiv filmlar</translation>
@@ -2652,7 +2651,6 @@
 <translation id="830498451218851433">Yarim taxlash</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Oʻrnatilgan ilovalar va ularning qanday ishlatilishi</translation>
-<translation id="8317207217658302555">ARCore yangilansinmi?</translation>
 <translation id="831997045666694187">Kechqurun</translation>
 <translation id="8321476692217554900">bildirishnomalar</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> saytiga kirish taqiqlangan</translation>
diff --git a/components/strings/components_strings_vi.xtb b/components/strings/components_strings_vi.xtb
index fa75230..12385017 100644
--- a/components/strings/components_strings_vi.xtb
+++ b/components/strings/components_strings_vi.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Đã xảy ra lỗi khi hiển thị trang web này.</translation>
 <translation id="1586541204584340881">Các tiện ích bạn đã cài đặt</translation>
 <translation id="1588438908519853928">Bình thường</translation>
-<translation id="1589050138437146318">Cài đặt ARCore?</translation>
 <translation id="1592005682883173041">Quyền truy cập dữ liệu cục bộ</translation>
 <translation id="1594030484168838125">Chọn</translation>
 <translation id="1596296697375291157">Phim kinh dị, hình sự và bí ẩn</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">Gấp đôi</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">Số ứng dụng đã cài đặt và tần suất sử dụng</translation>
-<translation id="8317207217658302555">Cập nhật ARCore?</translation>
 <translation id="831997045666694187">Buổi tối</translation>
 <translation id="8321476692217554900">thông báo</translation>
 <translation id="8332188693563227489">Quyền truy cập <ph name="HOST_NAME" /> bị từ chối</translation>
diff --git a/components/strings/components_strings_zh-CN.xtb b/components/strings/components_strings_zh-CN.xtb
index 59c94af..cc47e1d 100644
--- a/components/strings/components_strings_zh-CN.xtb
+++ b/components/strings/components_strings_zh-CN.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">显示此网页时出了点问题。</translation>
 <translation id="1586541204584340881">您已安装的扩展程序</translation>
 <translation id="1588438908519853928">正常</translation>
-<translation id="1589050138437146318">安装 ARCore?</translation>
 <translation id="1592005682883173041">本地数据访问权限</translation>
 <translation id="1594030484168838125">选择</translation>
 <translation id="1596296697375291157">惊悚片、犯罪片与悬疑片</translation>
@@ -2653,7 +2652,6 @@
 <translation id="830498451218851433">对折</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">已安装的应用及其使用频率</translation>
-<translation id="8317207217658302555">更新 ARCore?</translation>
 <translation id="831997045666694187">晚上</translation>
 <translation id="8321476692217554900">通知</translation>
 <translation id="8332188693563227489">访问 <ph name="HOST_NAME" /> 的请求遭到拒绝</translation>
diff --git a/components/strings/components_strings_zh-HK.xtb b/components/strings/components_strings_zh-HK.xtb
index 618c411..b9eb989 100644
--- a/components/strings/components_strings_zh-HK.xtb
+++ b/components/strings/components_strings_zh-HK.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">顯示此網頁時發生錯誤。</translation>
 <translation id="1586541204584340881">您已安裝的擴充程式</translation>
 <translation id="1588438908519853928">一般</translation>
-<translation id="1589050138437146318">要安裝 ARCore 嗎?</translation>
 <translation id="1592005682883173041">本地資料存取</translation>
 <translation id="1594030484168838125">選擇</translation>
 <translation id="1596296697375291157">恐怖、犯罪和懸疑電影</translation>
@@ -2657,7 +2656,6 @@
 <translation id="830498451218851433">對摺</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">已安裝的應用程式及使用頻率</translation>
-<translation id="8317207217658302555">要更新 ARCore 嗎?</translation>
 <translation id="831997045666694187">傍晚</translation>
 <translation id="8321476692217554900">通知</translation>
 <translation id="8332188693563227489"><ph name="HOST_NAME" /> 的存取被拒</translation>
diff --git a/components/strings/components_strings_zh-TW.xtb b/components/strings/components_strings_zh-TW.xtb
index 09188817..b5adb761 100644
--- a/components/strings/components_strings_zh-TW.xtb
+++ b/components/strings/components_strings_zh-TW.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">顯示這個網頁時發生錯誤。</translation>
 <translation id="1586541204584340881">你安裝的擴充功能</translation>
 <translation id="1588438908519853928">一般</translation>
-<translation id="1589050138437146318">要安裝 ARCore 嗎?</translation>
 <translation id="1592005682883173041">本機資料存取權</translation>
 <translation id="1594030484168838125">選擇</translation>
 <translation id="1596296697375291157">恐怖、犯罪與懸疑片</translation>
@@ -2658,7 +2657,6 @@
 <translation id="830498451218851433">對折</translation>
 <translation id="8307358339886459768">Small-Photo</translation>
 <translation id="8307888238279532626">已安裝的應用程式以及使用頻率</translation>
-<translation id="8317207217658302555">要更新 ARCore 嗎?</translation>
 <translation id="831997045666694187">傍晚</translation>
 <translation id="8321476692217554900">通知</translation>
 <translation id="8332188693563227489">存取 <ph name="HOST_NAME" /> 的要求遭到拒絕</translation>
diff --git a/components/strings/components_strings_zu.xtb b/components/strings/components_strings_zu.xtb
index 432c82d5e..d85d8d5 100644
--- a/components/strings/components_strings_zu.xtb
+++ b/components/strings/components_strings_zu.xtb
@@ -259,7 +259,6 @@
 <translation id="1583429793053364125">Kukhona okungahambanga kahle ngenkathi kuboniswa lesi khasi lewebhu.</translation>
 <translation id="1586541204584340881">Iziphi izandiso ozifakile</translation>
 <translation id="1588438908519853928">Jwayelekile</translation>
-<translation id="1589050138437146318">Faka i-ARCore?</translation>
 <translation id="1592005682883173041">Ukufinyelela kudatha yasendaweni</translation>
 <translation id="1594030484168838125">Khetha</translation>
 <translation id="1596296697375291157">Amafilimu ajabulisayo, obugebengu nawemfihlo</translation>
@@ -2655,7 +2654,6 @@
 <translation id="830498451218851433">Goqa ngohafu</translation>
 <translation id="8307358339886459768">Isithombe esincane</translation>
 <translation id="8307888238279532626">Izinhlelo zokusebenza ezifakiwe nokuthi zisetshenziswa kaningi kangakanani</translation>
-<translation id="8317207217658302555">Buyekeza i-ARCore?</translation>
 <translation id="831997045666694187">Kusihlwa</translation>
 <translation id="8321476692217554900">izaziso</translation>
 <translation id="8332188693563227489">Ukufinyelela ku-<ph name="HOST_NAME" /> kunqatshelwe</translation>
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index f3c0f220..57014395 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -1093,6 +1093,7 @@
   VISIT(arc_play_terms_of_service_consent);
   VISIT(assistant_activity_control_consent);
   VISIT(account_passwords_consent);
+  VISIT(autofill_assistant_consent);
 }
 
 VISIT_PROTO_FIELDS(
@@ -1141,6 +1142,13 @@
   VISIT_ENUM(status);
 }
 
+VISIT_PROTO_FIELDS(
+    const sync_pb::UserConsentTypes::AutofillAssistantConsent& proto) {
+  VISIT_REP(description_grd_ids);
+  VISIT(confirmation_grd_id);
+  VISIT_ENUM(status);
+}
+
 VISIT_PROTO_FIELDS(const sync_pb::UserEventSpecifics& proto) {
   VISIT(event_time_usec);
   VISIT(navigation_id);
diff --git a/components/sync/protocol/user_consent_specifics.proto b/components/sync/protocol/user_consent_specifics.proto
index f990be6f..b07412e 100644
--- a/components/sync/protocol/user_consent_specifics.proto
+++ b/components/sync/protocol/user_consent_specifics.proto
@@ -18,7 +18,7 @@
 
 import "components/sync/protocol/user_consent_types.proto";
 
-// Next id: 14
+// Next id: 17
 message UserConsentSpecifics {
   // ===========================================================================
   // Fields common to all Chrome User Consents.
@@ -59,6 +59,8 @@
         assistant_activity_control_consent = 14;
 
     UserConsentTypes.AccountPasswordsConsent account_passwords_consent = 15;
+
+    UserConsentTypes.AutofillAssistantConsent autofill_assistant_consent = 16;
   }
   reserved "arc_metrics_and_usage_consent";
   reserved 11;
diff --git a/components/sync/protocol/user_consent_types.proto b/components/sync/protocol/user_consent_types.proto
index 71eecc2..b028a642 100644
--- a/components/sync/protocol/user_consent_types.proto
+++ b/components/sync/protocol/user_consent_types.proto
@@ -168,4 +168,18 @@
     // or not given/revoked.
     optional ConsentStatus status = 3;
   }
+
+  // The User Consent for Autofill Assistant (i.e. Duplex technology) used for
+  // performing automated flows on the web.
+  message AutofillAssistantConsent {
+    // Ids of the strings of the consent text presented to the user.
+    repeated int32 description_grd_ids = 1;
+
+    // Id of the string of the UI element the user clicked in order to confirm
+    // the consent dialog.
+    optional int32 confirmation_grd_id = 2;
+
+    // The status of the consent.
+    optional ConsentStatus status = 3;
+  }
 }
diff --git a/components/test/data/web_package/generate-test-wbns.sh b/components/test/data/web_package/generate-test-wbns.sh
index b969084..21efd18 100755
--- a/components/test/data/web_package/generate-test-wbns.sh
+++ b/components/test/data/web_package/generate-test-wbns.sh
@@ -42,4 +42,10 @@
   -date $signature_date \
   -expire 168h \
   -validityUrl https://test.example.org/resource.validity.msg \
-  -o hello_signed.wbn
+  -o hello_vouched_subsets.wbn
+
+sign-bundle \
+  -i simple_b2.wbn \
+  -signType integrityblock \
+  -privateKey signed_web_bundle_private_key.pem \
+  -o simple_b2_signed.wbn
diff --git a/components/test/data/web_package/hello_b2.wbn b/components/test/data/web_package/hello_b2.wbn
index 5364642d..d74e0ce4 100644
--- a/components/test/data/web_package/hello_b2.wbn
+++ b/components/test/data/web_package/hello_b2.wbn
Binary files differ
diff --git a/components/test/data/web_package/signed_web_bundle_private_key.pem b/components/test/data/web_package/signed_web_bundle_private_key.pem
new file mode 100644
index 0000000..d0df956
--- /dev/null
+++ b/components/test/data/web_package/signed_web_bundle_private_key.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIB8nP5PpWU7HiILHSfh5PYzb5GAcIfHZ+bw6tcd/LZXh
+-----END PRIVATE KEY-----
diff --git a/components/test/data/web_package/simple_b2_signed.wbn b/components/test/data/web_package/simple_b2_signed.wbn
new file mode 100644
index 0000000..047790b
--- /dev/null
+++ b/components/test/data/web_package/simple_b2_signed.wbn
Binary files differ
diff --git a/components/web_package/BUILD.gn b/components/web_package/BUILD.gn
index 5383dfd0..18981a6 100644
--- a/components/web_package/BUILD.gn
+++ b/components/web_package/BUILD.gn
@@ -12,6 +12,8 @@
     "signed_web_bundles/ed25519_public_key.h",
     "signed_web_bundles/signed_web_bundle_id.cc",
     "signed_web_bundles/signed_web_bundle_id.h",
+    "signed_web_bundles/signed_web_bundle_utils.cc",
+    "signed_web_bundles/signed_web_bundle_utils.h",
     "web_bundle_builder.cc",
     "web_bundle_builder.h",
     "web_bundle_parser.cc",
@@ -39,6 +41,7 @@
   sources = [
     "signed_web_bundles/ed25519_public_key_unittest.cc",
     "signed_web_bundles/signed_web_bundle_id_unittest.cc",
+    "signed_web_bundles/signed_web_bundle_utils_unittest.cc",
     "web_bundle_builder_unittest.cc",
     "web_bundle_parser_factory_unittest.cc",
     "web_bundle_parser_unittest.cc",
@@ -48,6 +51,7 @@
     ":web_package",
     "//base/test:test_support",
     "//components/cbor",
+    "//components/web_package/test_support",
     "//testing/gtest",
   ]
 }
diff --git a/components/web_package/DEPS b/components/web_package/DEPS
index 8f11009a..d2560d5 100644
--- a/components/web_package/DEPS
+++ b/components/web_package/DEPS
@@ -10,7 +10,10 @@
   "web_bundle_parser_fuzzer\.cc": [
     "+mojo/core/embedder/embedder.h",
   ],
-  "ed25519_public_key\.cc": [
+  "web_bundle_signer.cc": [
+    "+crypto",
+  ],
+  "(ed25519_public_key|web_bundle_signer)\.cc": [
     "+third_party/boringssl",
   ],
 }
diff --git a/components/web_package/signed_web_bundles/signed_web_bundle_utils.cc b/components/web_package/signed_web_bundles/signed_web_bundle_utils.cc
new file mode 100644
index 0000000..4cf884a
--- /dev/null
+++ b/components/web_package/signed_web_bundles/signed_web_bundle_utils.cc
@@ -0,0 +1,39 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/web_package/signed_web_bundles/signed_web_bundle_utils.h"
+
+#include "base/big_endian.h"
+
+namespace web_package {
+
+namespace {
+
+void AddItemToPayload(std::vector<uint8_t>& payload,
+                      base::span<const uint8_t> item) {
+  // Each item that is part of the payload is prefixed with its length encoded
+  // as a 64 bit unsigned integer.
+  std::array<char, sizeof(uint64_t)> length;
+  base::BigEndianWriter writer(length.data(), length.size());
+  CHECK(writer.WriteU64(item.size()));
+
+  payload.insert(payload.end(), length.begin(), length.end());
+  payload.insert(payload.end(), item.begin(), item.end());
+}
+
+}  // namespace
+
+std::vector<uint8_t> CreateSignaturePayload(
+    base::span<const uint8_t> unsigned_bundle_hash,
+    base::span<const uint8_t> integrity_block,
+    base::span<const uint8_t> signature_stack_entry_attributes) {
+  std::vector<uint8_t> payload;
+  AddItemToPayload(payload, unsigned_bundle_hash);
+  AddItemToPayload(payload, integrity_block);
+  AddItemToPayload(payload, signature_stack_entry_attributes);
+
+  return payload;
+}
+
+}  // namespace web_package
diff --git a/components/web_package/signed_web_bundles/signed_web_bundle_utils.h b/components/web_package/signed_web_bundles/signed_web_bundle_utils.h
new file mode 100644
index 0000000..467b0af
--- /dev/null
+++ b/components/web_package/signed_web_bundles/signed_web_bundle_utils.h
@@ -0,0 +1,25 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_WEB_PACKAGE_SIGNED_WEB_BUNDLES_SIGNED_WEB_BUNDLE_UTILS_H_
+#define COMPONENTS_WEB_PACKAGE_SIGNED_WEB_BUNDLES_SIGNED_WEB_BUNDLE_UTILS_H_
+
+#include <vector>
+
+#include "base/containers/span.h"
+
+namespace web_package {
+
+// Helper function to construct and correctly encode the payload from the
+// unsigned web bundle's hash, the integrity block, and the attributes of the
+// signature stack entry. The payload can then be used to verify or calculate
+// the signed web bundle's signature.
+std::vector<uint8_t> CreateSignaturePayload(
+    base::span<const uint8_t> unsigned_bundle_hash,
+    base::span<const uint8_t> integrity_block,
+    base::span<const uint8_t> signature_stack_entry_attributes);
+
+}  // namespace web_package
+
+#endif  // COMPONENTS_WEB_PACKAGE_SIGNED_WEB_BUNDLES_SIGNED_WEB_BUNDLE_UTILS_H_
diff --git a/components/web_package/signed_web_bundles/signed_web_bundle_utils_unittest.cc b/components/web_package/signed_web_bundles/signed_web_bundle_utils_unittest.cc
new file mode 100644
index 0000000..7f529df0
--- /dev/null
+++ b/components/web_package/signed_web_bundles/signed_web_bundle_utils_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/web_package/signed_web_bundles/signed_web_bundle_utils.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace web_package {
+
+namespace {
+
+constexpr uint8_t kFakeUnsignedWebBundleHash[] = {0x01, 0x02, 0x03};
+constexpr uint8_t kFakeIntegrityBlock[] = {0x04, 0x05, 0x06, 0x07};
+constexpr uint8_t kFakeAttributes[] = {0x08, 0x09};
+
+constexpr uint8_t kExpectedPayloadForSigning[] = {
+    // length
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+    // unsigned web bundle hash
+    0x01, 0x02, 0x03,
+    // length
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
+    // integrity block
+    0x04, 0x05, 0x06, 0x07,
+    // length
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+    // attributes
+    0x08, 0x09};
+
+}  // namespace
+
+TEST(SignedWebBundleUtilsTest, BuildSignaturePayload) {
+  auto payload = CreateSignaturePayload(kFakeUnsignedWebBundleHash,
+                                        kFakeIntegrityBlock, kFakeAttributes);
+  EXPECT_EQ(payload, std::vector(std::begin(kExpectedPayloadForSigning),
+                                 std::end(kExpectedPayloadForSigning)));
+}
+
+}  // namespace web_package
diff --git a/components/web_package/test_support/BUILD.gn b/components/web_package/test_support/BUILD.gn
new file mode 100644
index 0000000..b4c070e
--- /dev/null
+++ b/components/web_package/test_support/BUILD.gn
@@ -0,0 +1,32 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("test_support") {
+  testonly = true
+
+  sources = [
+    "signed_web_bundles/web_bundle_signer.cc",
+    "signed_web_bundles/web_bundle_signer.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/cbor",
+    "//components/web_package",
+    "//crypto",
+    "//third_party/boringssl",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "signed_web_bundles/web_bundle_signer_unittest.cc" ]
+
+  deps = [
+    ":test_support",
+    "//base",
+    "//components/web_package",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/web_package/test_support/signed_web_bundles/web_bundle_signer.cc b/components/web_package/test_support/signed_web_bundles/web_bundle_signer.cc
new file mode 100644
index 0000000..d87f077
--- /dev/null
+++ b/components/web_package/test_support/signed_web_bundles/web_bundle_signer.cc
@@ -0,0 +1,144 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/web_package/test_support/signed_web_bundles/web_bundle_signer.h"
+
+#include "components/cbor/values.h"
+#include "components/cbor/writer.h"
+#include "components/web_package/signed_web_bundles/signed_web_bundle_utils.h"
+#include "crypto/secure_hash.h"
+#include "third_party/boringssl/src/include/openssl/curve25519.h"
+
+namespace web_package {
+
+cbor::Value WebBundleSigner::CreateIntegrityBlock(
+    const cbor::Value::ArrayValue& signature_stack) {
+  cbor::Value::ArrayValue integrity_block;
+  // magic bytes
+  integrity_block.push_back(cbor::Value(cbor::Value::BinaryValue(
+      {0xF0, 0x9F, 0x96, 0x8B, 0xF0, 0x9F, 0x93, 0xA6})));
+  // version
+  integrity_block.push_back(
+      cbor::Value(cbor::Value::BinaryValue({'1', 'b', '\0', '\0'})));
+  // signature stack
+  integrity_block.emplace_back(signature_stack);
+
+  return cbor::Value(integrity_block);
+}
+
+cbor::Value WebBundleSigner::CreateSignatureStackEntry(
+    base::span<const uint8_t> public_key,
+    std::vector<uint8_t> signature,
+    ErrorForTesting error_for_testing) {
+  if (error_for_testing == ErrorForTesting::kInvalidSignatureLength) {
+    signature.push_back(42);
+  }
+
+  cbor::Value::ArrayValue entry;
+  entry.push_back(cbor::Value(CreateSignatureStackEntryAttributes(
+      std::vector(public_key.begin(), public_key.end()), error_for_testing)));
+  entry.push_back(cbor::Value(signature));
+
+  if (error_for_testing ==
+      ErrorForTesting::kAdditionalSignatureStackEntryElement) {
+    entry.push_back(cbor::Value("foo"));
+  }
+
+  return cbor::Value(entry);
+}
+
+cbor::Value WebBundleSigner::CreateSignatureStackEntryAttributes(
+    std::vector<uint8_t> public_key,
+    ErrorForTesting error_for_testing) {
+  if (error_for_testing == ErrorForTesting::kInvalidPublicKeyLength) {
+    public_key.push_back(42);
+  }
+
+  cbor::Value::MapValue attributes;
+
+  if (error_for_testing !=
+      ErrorForTesting::kNoPublicKeySignatureStackEntryAttribute) {
+    if (error_for_testing ==
+        ErrorForTesting::kWrongSignatureStackEntryAttributeName) {
+      // Add a typo: "ee" instead of "ed".
+      attributes[cbor::Value("ee25519PublicKey")] = cbor::Value(public_key);
+    } else {
+      attributes[cbor::Value("ed25519PublicKey")] = cbor::Value(public_key);
+    }
+  }
+
+  if (error_for_testing ==
+      ErrorForTesting::kAdditionalSignatureStackEntryAttribute) {
+    attributes[cbor::Value("foo")] = cbor::Value(42);
+  }
+
+  return cbor::Value(attributes);
+}
+
+cbor::Value WebBundleSigner::CreateIntegrityBlockForBundle(
+    base::span<const uint8_t> unsigned_bundle,
+    const std::vector<KeyPair>& key_pairs,
+    ErrorForTesting error_for_testing) {
+  // Calculate the SHA512 hash of the bundle.
+  auto secure_hash = crypto::SecureHash::Create(crypto::SecureHash::SHA512);
+  secure_hash->Update(unsigned_bundle.data(), unsigned_bundle.size());
+  std::vector<uint8_t> unsigned_bundle_hash(secure_hash->GetHashLength());
+  secure_hash->Finish(unsigned_bundle_hash.data(), unsigned_bundle_hash.size());
+
+  std::vector<cbor::Value> signature_stack;
+  for (const KeyPair& key_pair : key_pairs) {
+    // Create an integrity block with all previous signature stack entries.
+    absl::optional<std::vector<uint8_t>> integrity_block =
+        cbor::Writer::Write(CreateIntegrityBlock(signature_stack));
+
+    // Create the attributes map for the current signature stack entry.
+    absl::optional<std::vector<uint8_t>> attributes = cbor::Writer::Write(
+        CreateSignatureStackEntryAttributes(key_pair.public_key));
+
+    // Build the payload to sign and then sign it.
+    std::vector<uint8_t> payload_to_sign = CreateSignaturePayload(
+        unsigned_bundle_hash, *integrity_block, *attributes);
+
+    std::vector<uint8_t> signature(ED25519_SIGNATURE_LEN);
+    CHECK_EQ(key_pair.private_key.size(),
+             static_cast<size_t>(ED25519_PRIVATE_KEY_LEN));
+    CHECK_EQ(ED25519_sign(signature.data(), payload_to_sign.data(),
+                          payload_to_sign.size(), key_pair.private_key.data()),
+             1);
+
+    signature_stack.push_back(CreateSignatureStackEntry(
+        key_pair.public_key, signature, error_for_testing));
+  }
+
+  return CreateIntegrityBlock(signature_stack);
+}
+
+std::vector<uint8_t> WebBundleSigner::SignBundle(
+    base::span<const uint8_t> unsigned_bundle,
+    const std::vector<KeyPair>& key_pairs,
+    ErrorForTesting error_for_testing) {
+  absl::optional<std::vector<uint8_t>> integrity_block =
+      cbor::Writer::Write(CreateIntegrityBlockForBundle(
+          unsigned_bundle, key_pairs, error_for_testing));
+
+  std::vector<uint8_t> signed_web_bundle;
+  signed_web_bundle.insert(signed_web_bundle.end(), integrity_block->begin(),
+                           integrity_block->end());
+  signed_web_bundle.insert(signed_web_bundle.end(), unsigned_bundle.begin(),
+                           unsigned_bundle.end());
+
+  return signed_web_bundle;
+}
+
+WebBundleSigner::KeyPair::KeyPair(base::span<const uint8_t> public_key,
+                                  base::span<const uint8_t> private_key)
+    : public_key(public_key.begin(), public_key.end()),
+      private_key(private_key.begin(), private_key.end()){};
+
+WebBundleSigner::KeyPair::KeyPair(const WebBundleSigner::KeyPair& other) =
+    default;
+
+WebBundleSigner::KeyPair::~KeyPair() = default;
+
+}  // namespace web_package
diff --git a/components/web_package/test_support/signed_web_bundles/web_bundle_signer.h b/components/web_package/test_support/signed_web_bundles/web_bundle_signer.h
new file mode 100644
index 0000000..df280ba
--- /dev/null
+++ b/components/web_package/test_support/signed_web_bundles/web_bundle_signer.h
@@ -0,0 +1,73 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_WEB_PACKAGE_TEST_SUPPORT_SIGNED_WEB_BUNDLES_WEB_BUNDLE_SIGNER_H_
+#define COMPONENTS_WEB_PACKAGE_TEST_SUPPORT_SIGNED_WEB_BUNDLES_WEB_BUNDLE_SIGNER_H_
+
+#include <vector>
+
+#include "base/containers/span.h"
+#include "components/cbor/values.h"
+
+namespace web_package {
+
+// A class for use in tests to create signed web bundles. It can also be used to
+// create wrongly signed bundles by passing `ErrorForTesting` != `kNoError`.
+// Since this class is only intended for use in tests, error handling is
+// implemented in the form of CHECKs. Use this class in conjunction with the
+// `WebBundleBuilder` class to create signed web bundles.
+class WebBundleSigner {
+ public:
+  enum class ErrorForTesting {
+    kNoError,
+    kInvalidSignatureLength,
+    kInvalidPublicKeyLength,
+    kWrongSignatureStackEntryAttributeName,
+    kNoPublicKeySignatureStackEntryAttribute,
+    kAdditionalSignatureStackEntryAttribute,
+    kAdditionalSignatureStackEntryElement,
+  };
+
+  struct KeyPair {
+    KeyPair(base::span<const uint8_t> public_key,
+            base::span<const uint8_t> private_key);
+    KeyPair(const KeyPair& other);
+    ~KeyPair();
+
+    std::vector<uint8_t> public_key;
+    std::vector<uint8_t> private_key;
+  };
+
+  // Creates an integrity block with the given signature stack entries.
+  static cbor::Value CreateIntegrityBlock(
+      const cbor::Value::ArrayValue& signature_stack);
+
+  // Signs an unsigned bundle with the given key pairs, in order. I.e. the first
+  // key pair will sign the unsigned bundle, the second key pair will sign the
+  // bundle signed with the first key pair, and so on.
+  static std::vector<uint8_t> SignBundle(
+      base::span<const uint8_t> unsigned_bundle,
+      const std::vector<KeyPair>& key_pairs,
+      ErrorForTesting error_for_testing = ErrorForTesting::kNoError);
+
+  // Creates a signature stack entry for the given public key and signature.
+  static cbor::Value CreateSignatureStackEntry(
+      base::span<const uint8_t> public_key,
+      std::vector<uint8_t> signature,
+      ErrorForTesting error_for_testing = ErrorForTesting::kNoError);
+
+ private:
+  static cbor::Value CreateSignatureStackEntryAttributes(
+      std::vector<uint8_t> public_key,
+      ErrorForTesting error_for_testing = ErrorForTesting::kNoError);
+
+  static cbor::Value CreateIntegrityBlockForBundle(
+      base::span<const uint8_t> unsigned_bundle,
+      const std::vector<KeyPair>& key_pairs,
+      ErrorForTesting error_for_testing = ErrorForTesting::kNoError);
+};
+
+}  // namespace web_package
+
+#endif  // COMPONENTS_WEB_PACKAGE_TEST_SUPPORT_SIGNED_WEB_BUNDLES_WEB_BUNDLE_SIGNER_H_
diff --git a/components/web_package/test_support/signed_web_bundles/web_bundle_signer_unittest.cc b/components/web_package/test_support/signed_web_bundles/web_bundle_signer_unittest.cc
new file mode 100644
index 0000000..442d0a1
--- /dev/null
+++ b/components/web_package/test_support/signed_web_bundles/web_bundle_signer_unittest.cc
@@ -0,0 +1,70 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/web_package/test_support/signed_web_bundles/web_bundle_signer.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "components/web_package/web_bundle_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace web_package {
+
+namespace {
+
+constexpr uint8_t kTestPublicKey[] = {
+    0xE4, 0xD5, 0x16, 0xC9, 0x85, 0x9A, 0xF8, 0x63, 0x56, 0xA3, 0x51,
+    0x66, 0x7D, 0xBD, 0x00, 0x43, 0x61, 0x10, 0x1A, 0x92, 0xD4, 0x02,
+    0x72, 0xFE, 0x2B, 0xCE, 0x81, 0xBB, 0x3B, 0x71, 0x3F, 0x2D,
+};
+
+constexpr uint8_t kTestPrivateKey[] = {
+    0x1F, 0x27, 0x3F, 0x93, 0xE9, 0x59, 0x4E, 0xC7, 0x88, 0x82, 0xC7, 0x49,
+    0xF8, 0x79, 0x3D, 0x8C, 0xDB, 0xE4, 0x60, 0x1C, 0x21, 0xF1, 0xD9, 0xF9,
+    0xBC, 0x3A, 0xB5, 0xC7, 0x7F, 0x2D, 0x95, 0xE1,
+    // public key (part of the private key)
+    0xE4, 0xD5, 0x16, 0xC9, 0x85, 0x9A, 0xF8, 0x63, 0x56, 0xA3, 0x51, 0x66,
+    0x7D, 0xBD, 0x00, 0x43, 0x61, 0x10, 0x1A, 0x92, 0xD4, 0x02, 0x72, 0xFE,
+    0x2B, 0xCE, 0x81, 0xBB, 0x3B, 0x71, 0x3F, 0x2D};
+
+std::string GetTestFileContents(const base::FilePath& path) {
+  base::FilePath test_data_dir;
+  base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir);
+  test_data_dir = test_data_dir.Append(
+      base::FilePath(FILE_PATH_LITERAL("components/test/data/web_package")));
+
+  std::string contents;
+  EXPECT_TRUE(base::ReadFileToString(test_data_dir.Append(path), &contents));
+  return contents;
+}
+
+std::vector<uint8_t> GetStringAsBytes(base::StringPiece contents) {
+  auto bytes = base::as_bytes(base::make_span(contents));
+  return std::vector<uint8_t>(bytes.begin(), bytes.end());
+}
+
+}  // namespace
+
+TEST(WebBundleSignerTest, SignedWebBundleByteByByteComparison) {
+  WebBundleBuilder builder;
+  builder.AddExchange(
+      "https://test.example.org/",
+      {{":status", "200"}, {"content-type", "text/html; charset=UTF-8"}},
+      "<a href='index.html'>click for web bundles</a>");
+  builder.AddExchange(
+      "https://test.example.org/index.html",
+      {{":status", "200"}, {"content-type", "text/html; charset=UTF-8"}},
+      "<p>Hello Web Bundles!</p>");
+  std::vector<uint8_t> unsigned_bundle = builder.CreateBundle();
+  std::vector<uint8_t> signed_bundle = WebBundleSigner::SignBundle(
+      unsigned_bundle,
+      {WebBundleSigner::KeyPair(kTestPublicKey, kTestPrivateKey)});
+
+  std::vector<uint8_t> expected_bundle = GetStringAsBytes(GetTestFileContents(
+      base::FilePath(FILE_PATH_LITERAL("simple_b2_signed.wbn"))));
+  EXPECT_EQ(signed_bundle, expected_bundle);
+}
+}  // namespace web_package
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index c5bbd72..cd99c32 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -1132,6 +1132,26 @@
   EXPECT_TRUE(HasExistingNotification("Page.frameStartedLoading"));
 }
 
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
+                       NavigationToFileUrlRequiresFileAccess) {
+  Attach();
+
+  std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue());
+  GURL test_url = GetTestUrl("devtools", "navigation.html");
+  params->SetStringKey("url", test_url.spec());
+  ASSERT_TRUE(SendCommand("Page.navigate", params->GetDict().Clone(), true));
+
+  Detach();
+  SetMayReadLocalFiles(false);
+
+  Attach();
+
+  ASSERT_FALSE(SendCommand("Page.navigate", params->GetDict().Clone(), true));
+  EXPECT_THAT(
+      error()->FindInt("code"),
+      testing::Optional(static_cast<int>(crdtp::DispatchCode::SERVER_ERROR)));
+}
+
 IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteNoDetach) {
   content::SetupCrossSiteRedirector(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index 2513d99..0bc2a5a 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -199,12 +199,14 @@
     BrowserHandler* browser_handler,
     bool allow_unsafe_operations,
     bool may_capture_screenshots_not_from_surface,
-    absl::optional<url::Origin> navigation_initiator_origin)
+    absl::optional<url::Origin> navigation_initiator_origin,
+    bool may_read_local_files)
     : DevToolsDomainHandler(Page::Metainfo::domainName),
       allow_unsafe_operations_(allow_unsafe_operations),
       may_capture_screenshots_not_from_surface_(
           may_capture_screenshots_not_from_surface),
       navigation_initiator_origin_(navigation_initiator_origin),
+      may_read_local_files_(may_read_local_files),
       enabled_(false),
       screencast_enabled_(false),
       screencast_quality_(kDefaultScreenshotQuality),
@@ -505,6 +507,11 @@
         Response::ServerError("Cannot navigate to invalid URL"));
     return;
   }
+  if (gurl.SchemeIsFile() && !may_read_local_files_) {
+    callback->sendFailure(
+        Response::ServerError("Navigating to local URL is not allowed"));
+    return;
+  }
 
   if (!host_) {
     callback->sendFailure(Response::InternalError());
diff --git a/content/browser/devtools/protocol/page_handler.h b/content/browser/devtools/protocol/page_handler.h
index 801bf36..add8242 100644
--- a/content/browser/devtools/protocol/page_handler.h
+++ b/content/browser/devtools/protocol/page_handler.h
@@ -67,7 +67,8 @@
               BrowserHandler* browser_handler,
               bool allow_unsafe_operations,
               bool may_capture_screenshots_not_from_surface,
-              absl::optional<url::Origin> navigation_initiator_origin);
+              absl::optional<url::Origin> navigation_initiator_origin,
+              bool may_read_local_files);
 
   PageHandler(const PageHandler&) = delete;
   PageHandler& operator=(const PageHandler&) = delete;
@@ -223,6 +224,7 @@
   const bool allow_unsafe_operations_;
   const bool may_capture_screenshots_not_from_surface_;
   const absl::optional<url::Origin> navigation_initiator_origin_;
+  const bool may_read_local_files_;
 
   bool enabled_;
   bool bypass_csp_ = false;
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index fee664e..0daa83e8 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -351,7 +351,8 @@
       emulation_handler, browser_handler,
       session->GetClient()->AllowUnsafeOperations(),
       session->GetClient()->IsTrusted(),
-      session->GetClient()->GetNavigationInitiatorOrigin());
+      session->GetClient()->GetNavigationInitiatorOrigin(),
+      session->GetClient()->MayReadLocalFiles());
   session->CreateAndAddHandler<protocol::SecurityHandler>();
   if (!frame_tree_node_ || !frame_tree_node_->parent()) {
     session->CreateAndAddHandler<protocol::TracingHandler>(GetIOContext());
diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl.cc b/content/browser/first_party_sets/first_party_sets_handler_impl.cc
index 5f278ebe..2bc42dad 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_impl.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_impl.cc
@@ -26,6 +26,7 @@
 #include "content/browser/first_party_sets/first_party_sets_loader.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/common/content_client.h"
+#include "net/base/schemeful_site.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
@@ -58,6 +59,32 @@
   }
 }
 
+// Converts a list of First-Party Sets from a SingleSet to a FlattenedSet
+// representation.
+FirstPartySetsHandlerImpl::FlattenedSets SetListToFlattenedSets(
+    const std::vector<FirstPartySetParser::SingleSet>& set_list) {
+  FirstPartySetsHandlerImpl::FlattenedSets sets;
+  for (const auto& [owner, members] : set_list) {
+    sets.emplace(owner, owner);
+    for (const net::SchemefulSite& member : members)
+      sets.emplace(member, owner);
+  }
+  return sets;
+}
+
+// Adds all sets in a list of First-Party Sets into `site_to_owner` which maps
+// from a site to its owner.
+void UpdateCustomizationMap(
+    const std::vector<FirstPartySetParser::SingleSet>& set_list,
+    base::flat_map<net::SchemefulSite, absl::optional<net::SchemefulSite>>&
+        site_to_owner) {
+  for (const auto& [owner, members] : set_list) {
+    site_to_owner.emplace(owner, owner);
+    for (const net::SchemefulSite& member : members)
+      site_to_owner.emplace(member, owner);
+  }
+}
+
 }  // namespace
 
 bool FirstPartySetsHandler::PolicyParsingError::operator==(
@@ -90,6 +117,113 @@
       policy, /*out_sets=*/nullptr);
 }
 
+// TODO (https://crbug.com/1325050): Call this function when NetworkContext are
+// created in order to provide the customizations to the
+// FirstPartySetsAccessDelegate.
+FirstPartySetsHandlerImpl::PolicyCustomization
+FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+    const FlattenedSets& sets,
+    const FirstPartySetParser::ParsedPolicySetLists& policy) {
+  // Maps a site to its new owner if it has one.
+  base::flat_map<net::SchemefulSite, absl::optional<net::SchemefulSite>>
+      site_to_owner;
+
+  // Normalize the addition sets to prevent them from affecting the same
+  // existing set.
+  std::vector<FirstPartySetParser::SingleSet> normalized_additions =
+      FirstPartySetsLoader::NormalizeAdditionSets(sets, policy.additions);
+
+  // Create flattened versions of the sets for easier lookup.
+  FlattenedSets flattened_replacements =
+      SetListToFlattenedSets(policy.replacements);
+  FlattenedSets flattened_additions =
+      SetListToFlattenedSets(normalized_additions);
+
+  // All of the policy sets are automatically inserted into site_to_owner.
+  UpdateCustomizationMap(policy.replacements, site_to_owner);
+  UpdateCustomizationMap(normalized_additions, site_to_owner);
+
+  // Maps old owner to new owner.
+  base::flat_map<net::SchemefulSite, net::SchemefulSite>
+      addition_intersected_owners;
+  for (const auto& [new_member, new_owner] : flattened_additions) {
+    if (const auto entry = sets.find(new_member); entry != sets.end()) {
+      // Found an overlap with the existing list of sets.
+      addition_intersected_owners.emplace(entry->second, new_owner);
+    }
+  }
+
+  // Maps an existing owner to the members it lost due to replacement.
+  base::flat_map<net::SchemefulSite, base::flat_set<net::SchemefulSite>>
+      potential_singletons;
+  for (const auto& [member, owner] : flattened_replacements) {
+    if (member == owner)
+      continue;
+    if (auto entry = sets.find(member);
+        entry != sets.end() && entry->second != member) {
+      const net::SchemefulSite& existing_owner = entry->second;
+      if (!addition_intersected_owners.contains(existing_owner) &&
+          !flattened_additions.contains(existing_owner) &&
+          !flattened_replacements.contains(existing_owner)) {
+        auto [it, successful] = potential_singletons.emplace(
+            existing_owner, base::flat_set<net::SchemefulSite>{member});
+        if (!successful)
+          it->second.insert(member);
+      }
+    }
+  }
+
+  // Find the existing owners that have left their existing sets, and whose
+  // existing members should be removed from their set (excl any policy sets
+  // that those members are involved in).
+  base::flat_set<net::SchemefulSite> replaced_existing_owners;
+  for (const auto& [site, unused_owner] : flattened_replacements) {
+    if (const auto entry = sets.find(site);
+        entry != sets.end() && entry->second == site) {
+      // Site was an owner in the existing sets.
+      bool inserted = replaced_existing_owners.emplace(site).second;
+      DCHECK(inserted);
+    }
+  }
+
+  // Find out which potential singletons are actually singletons; delete
+  // members whose owners left; and reparent the sets that intersected with
+  // an addition set.
+  for (const auto& [member, owner] : sets) {
+    // Reparent all sites in any intersecting addition sets.
+    if (auto entry = addition_intersected_owners.find(owner);
+        entry != addition_intersected_owners.end() &&
+        !flattened_replacements.contains(member)) {
+      site_to_owner.emplace(member, entry->second);
+    }
+    if (member == owner)
+      continue;
+    // Remove non-singletons from the potential list.
+    if (auto entry = potential_singletons.find(owner);
+        entry != potential_singletons.end() &&
+        !entry->second.contains(member)) {
+      // This owner lost members, but it still has at least one (`member`),
+      // so it's not a singleton.
+      potential_singletons.erase(entry);
+    }
+    // Remove members from sets whose owner left.
+    if (replaced_existing_owners.contains(owner) &&
+        !flattened_replacements.contains(member) &&
+        !addition_intersected_owners.contains(owner)) {
+      bool inserted = site_to_owner.emplace(member, absl::nullopt).second;
+      DCHECK(inserted);
+    }
+  }
+  // Any owner remaining in `potential_singleton` is a real singleton, so delete
+  // it:
+  for (auto& [owner, members] : potential_singletons) {
+    bool inserted = site_to_owner.emplace(owner, absl::nullopt).second;
+    DCHECK(inserted);
+  }
+
+  return site_to_owner;
+}
+
 FirstPartySetsHandlerImpl::FirstPartySetsHandlerImpl(
     bool enabled,
     bool embedder_will_provide_public_sets)
@@ -100,9 +234,7 @@
       base::BindOnce(&FirstPartySetsHandlerImpl::SetCompleteSets,
                      // base::Unretained(this) is safe here because
                      // this is a static singleton.
-                     base::Unretained(this)),
-      IsEnabled() ? GetContentClient()->browser()->GetFirstPartySetsOverrides()
-                  : base::Value::Dict());
+                     base::Unretained(this)));
 }
 
 FirstPartySetsHandlerImpl::~FirstPartySetsHandlerImpl() = default;
@@ -163,9 +295,7 @@
       base::BindOnce(&FirstPartySetsHandlerImpl::SetCompleteSets,
                      // base::Unretained(this) is safe here because
                      // this is a static singleton.
-                     base::Unretained(this)),
-      IsEnabled() ? GetContentClient()->browser()->GetFirstPartySetsOverrides()
-                  : base::Value::Dict());
+                     base::Unretained(this)));
   on_sets_ready_callbacks_.clear();
   persisted_sets_path_ = base::FilePath();
   sets_ = absl::nullopt;
diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl.h b/content/browser/first_party_sets/first_party_sets_handler_impl.h
index 7d9320e4..b8306556 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_impl.h
+++ b/content/browser/first_party_sets/first_party_sets_handler_impl.h
@@ -19,6 +19,7 @@
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
 #include "base/values.h"
+#include "content/browser/first_party_sets/first_party_set_parser.h"
 #include "content/browser/first_party_sets/first_party_sets_loader.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/first_party_sets_handler.h"
@@ -38,6 +39,12 @@
  public:
   using FlattenedSets = base::flat_map<net::SchemefulSite, net::SchemefulSite>;
   using SetsReadyOnceCallback = base::OnceCallback<void(FlattenedSets)>;
+  // The keys are member sites and the values are their owners in the final
+  // list of First-Party Sets that result from combining the public sets and
+  // the per-profile Overrides policy. Entries of site -> absl::nullopt means
+  // the key site is considered deleted from the existing First-Party Sets.
+  using PolicyCustomization =
+      base::flat_map<net::SchemefulSite, absl::optional<net::SchemefulSite>>;
 
   static FirstPartySetsHandlerImpl* GetInstance();
 
@@ -95,6 +102,13 @@
       const base::flat_map<net::SchemefulSite, net::SchemefulSite>&
           current_sets);
 
+  // Computes information needed by the FirstPartySetsAccessDelegate in order to
+  // update the browser's list of First-Party Sets to respect a profile's
+  // setting for the per-profile FirstPartySetsOverrides policy.
+  static PolicyCustomization ComputeEnterpriseCustomizations(
+      const FlattenedSets& sets,
+      const FirstPartySetParser::ParsedPolicySetLists& policy);
+
  private:
   friend class base::NoDestructor<FirstPartySetsHandlerImpl>;
 
diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc b/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
index 979d4c75..cd914dd 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
@@ -23,18 +23,61 @@
 #include "url/gurl.h"
 
 using ::testing::IsEmpty;
+using ::testing::Optional;
 using ::testing::Pair;
 using ::testing::UnorderedElementsAre;
 
 // Some of these tests overlap with FirstPartySetParser unittests, but
 // overlapping test coverage isn't the worst thing.
 namespace content {
+namespace {
+using PolicyCustomization = FirstPartySetsHandlerImpl::PolicyCustomization;
 
 MATCHER_P(SerializesTo, want, "") {
   const std::string got = arg.Serialize();
   return testing::ExplainMatchResult(testing::Eq(want), got, result_listener);
 }
 
+FirstPartySetsHandlerImpl::FlattenedSets MakeFlattenedSetsFromMap(
+    const base::flat_map<std::string, std::vector<std::string>>&
+        owners_to_members) {
+  FirstPartySetsHandlerImpl::FlattenedSets result;
+  for (const auto& [owner, members] : owners_to_members) {
+    net::SchemefulSite owner_site((GURL(owner)));
+    result.insert(std::make_pair(owner_site, owner_site));
+    for (const std::string& member : members) {
+      net::SchemefulSite member_site((GURL(member)));
+      result.insert(std::make_pair(member_site, owner_site));
+    }
+  }
+  return result;
+}
+
+// Creates a ParsedPolicySetLists with the replacements and additions fields
+// constructed from `replacements` and `additions`.
+FirstPartySetParser::ParsedPolicySetLists MakeParsedPolicyFromMap(
+    const base::flat_map<std::string, std::vector<std::string>>& replacements,
+    const base::flat_map<std::string, std::vector<std::string>>& additions) {
+  FirstPartySetParser::ParsedPolicySetLists result;
+  for (auto& [owner, members] : replacements) {
+    std::vector<net::SchemefulSite> member_sites;
+    for (const std::string& member : members) {
+      member_sites.emplace_back(GURL(member));
+    }
+    result.replacements.emplace_back(net::SchemefulSite(GURL(owner)),
+                                     member_sites);
+  }
+  for (auto& [owner, members] : additions) {
+    std::vector<net::SchemefulSite> member_sites;
+    for (const std::string& member : members) {
+      member_sites.emplace_back(GURL(member));
+    }
+    result.additions.emplace_back(net::SchemefulSite(GURL(owner)),
+                                  member_sites);
+  }
+  return result;
+}
+
 FirstPartySetsHandlerImpl::FlattenedSets ParseSetsFromStream(
     const std::string& sets) {
   std::istringstream stream(sets);
@@ -47,6 +90,7 @@
       FirstPartySetsHandlerImpl::GetInstance()->GetSets(future.GetCallback());
   return result.has_value() ? result.value() : future.Get();
 }
+}  // namespace
 
 TEST(FirstPartySetsHandlerImpl, ComputeSetsDiff_SitesJoined) {
   FirstPartySetsHandlerImpl::FlattenedSets old_sets = {
@@ -539,4 +583,191 @@
                                     SerializesTo("https://example.test")))));
 }
 
-}  // namespace content
\ No newline at end of file
+TEST(FirstPartySetsProfilePolicyCustomizations, EmptyPolicySetLists) {
+  EXPECT_THAT(FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+                  MakeFlattenedSetsFromMap(
+                      {{"https://owner1.test", {"https://member1.test"}}}),
+                  MakeParsedPolicyFromMap({}, {})),
+              FirstPartySetsHandlerImpl::PolicyCustomization());
+}
+
+TEST(FirstPartySetsProfilePolicyCustomizations,
+     Replacements__NoIntersection_NoRemoval) {
+  PolicyCustomization customization =
+      FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+          MakeFlattenedSetsFromMap(
+              {{"https://owner1.test", {"https://member1.test"}}}),
+          MakeParsedPolicyFromMap(
+              /*replacements=*/{{"https://owner2.test",
+                                 {"https://member2.test"}}},
+              /*additions=*/{}));
+  EXPECT_THAT(customization,
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://member2.test"),
+                       Optional(SerializesTo("https://owner2.test"))),
+                  Pair(SerializesTo("https://owner2.test"),
+                       Optional(SerializesTo("https://owner2.test")))));
+}
+
+// The common member between the policy and existing set is removed from its
+// previous set.
+TEST(FirstPartySetsProfilePolicyCustomizations,
+     Replacements_ReplacesExistingMember_RemovedFromFormerSet) {
+  PolicyCustomization customization =
+      FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+          MakeFlattenedSetsFromMap(
+              {{"https://owner1.test",
+                {"https://member1a.test", "https://member1b.test"}}}),
+          MakeParsedPolicyFromMap(
+              /*replacements=*/{{"https://owner2.test",
+                                 {"https://member1b.test"}}},
+              /*additions=*/{}));
+  EXPECT_THAT(customization,
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://member1b.test"),
+                       Optional(SerializesTo("https://owner2.test"))),
+                  Pair(SerializesTo("https://owner2.test"),
+                       Optional(SerializesTo("https://owner2.test")))));
+}
+
+// The common owner between the policy and existing set is removed and its
+// former members are removed since they are now unowned.
+TEST(FirstPartySetsProfilePolicyCustomizations,
+     Replacements_ReplacesExistingOwner_RemovesFormerMembers) {
+  PolicyCustomization customization =
+      FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+          MakeFlattenedSetsFromMap(
+              {{"https://owner1.test",
+                {"https://member1a.test", "https://member1b.test"}}}),
+          MakeParsedPolicyFromMap(
+              /*replacements=*/{{"https://owner1.test",
+                                 {"https://member2.test"}}},
+              /*additions=*/{}));
+  EXPECT_THAT(customization,
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://member2.test"),
+                       Optional(SerializesTo("https://owner1.test"))),
+                  Pair(SerializesTo("https://owner1.test"),
+                       Optional(SerializesTo("https://owner1.test"))),
+                  Pair(SerializesTo("https://member1a.test"), absl::nullopt),
+                  Pair(SerializesTo("https://member1b.test"), absl::nullopt)));
+}
+
+// The common member between the policy and existing set is removed and any
+// leftover singletons are deleted.
+TEST(FirstPartySetsProfilePolicyCustomizations,
+     Replacements_ReplacesExistingMember_RemovesSingletons) {
+  PolicyCustomization customization =
+      FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+          MakeFlattenedSetsFromMap(
+              {{"https://owner1.test", {"https://member1.test"}}}),
+          MakeParsedPolicyFromMap(
+              /*replacements=*/{{"https://owner3.test",
+                                 {"https://member1.test"}}},
+              /*additions=*/{}));
+  EXPECT_THAT(customization,
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://member1.test"),
+                       Optional(SerializesTo("https://owner3.test"))),
+                  Pair(SerializesTo("https://owner3.test"),
+                       Optional(SerializesTo("https://owner3.test"))),
+                  Pair(SerializesTo("https://owner1.test"), absl::nullopt)));
+}
+
+// The policy set and the existing set have nothing in common so the policy set
+// gets added in without updating the existing set.
+TEST(FirstPartySetsProfilePolicyCustomizations,
+     Additions_NoIntersection_AddsWithoutUpdating) {
+  PolicyCustomization customization =
+      FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+          MakeFlattenedSetsFromMap(
+              {{"https://owner1.test", {"https://member1.test"}}}),
+          MakeParsedPolicyFromMap(
+              /*replacements=*/{},
+              /*additions=*/{
+                  {"https://owner2.test", {"https://member2.test"}}}));
+  EXPECT_THAT(customization,
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://member2.test"),
+                       Optional(SerializesTo("https://owner2.test"))),
+                  Pair(SerializesTo("https://owner2.test"),
+                       Optional(SerializesTo("https://owner2.test")))));
+}
+
+// The owner of a policy set is also a member in an existing set.
+// The policy set absorbs all sites in the existing set into its members.
+TEST(FirstPartySetsProfilePolicyCustomizations,
+     Additions_PolicyOwnerIsExistingMember_PolicySetAbsorbsExistingSet) {
+  PolicyCustomization customization =
+      FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+          MakeFlattenedSetsFromMap(
+              {{"https://owner1.test", {"https://member2.test"}}}),
+          MakeParsedPolicyFromMap(
+              /*replacements=*/{},
+              /*additions=*/{
+                  {"https://member2.test",
+                   {"https://member2a.test", "https://member2b.test"}}}));
+  EXPECT_THAT(customization,
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://owner1.test"),
+                       Optional(SerializesTo("https://member2.test"))),
+                  Pair(SerializesTo("https://member2a.test"),
+                       Optional(SerializesTo("https://member2.test"))),
+                  Pair(SerializesTo("https://member2b.test"),
+                       Optional(SerializesTo("https://member2.test"))),
+                  Pair(SerializesTo("https://member2.test"),
+                       Optional(SerializesTo("https://member2.test")))));
+}
+
+// The owner of a policy set is also an owner of an existing set.
+// The policy set absorbs all of its owner's existing members into its members.
+TEST(FirstPartySetsProfilePolicyCustomizations,
+     Additions_PolicyOwnerIsExistingOwner_PolicySetAbsorbsExistingMembers) {
+  PolicyCustomization customization =
+      FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+          MakeFlattenedSetsFromMap(
+              {{"https://owner1.test",
+                {"https://member1.test", "https://member3.test"}}}),
+          MakeParsedPolicyFromMap(
+              /*replacements=*/{},
+              /*additions=*/{
+                  {"https://owner1.test", {"https://member2.test"}}}));
+  EXPECT_THAT(customization,
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://member2.test"),
+                       Optional(SerializesTo("https://owner1.test"))),
+                  Pair(SerializesTo("https://member1.test"),
+                       Optional(SerializesTo("https://owner1.test"))),
+                  Pair(SerializesTo("https://member3.test"),
+                       Optional(SerializesTo("https://owner1.test"))),
+                  Pair(SerializesTo("https://owner1.test"),
+                       Optional(SerializesTo("https://owner1.test")))));
+}
+
+// Existing set overlaps with both replacement and addition set.
+TEST(FirstPartySetsProfilePolicyCustomizations,
+     ReplacementsAndAdditions_SetListsOverlapWithSameExistingSet) {
+  PolicyCustomization customization =
+      FirstPartySetsHandlerImpl::ComputeEnterpriseCustomizations(
+          MakeFlattenedSetsFromMap(
+              {{"https://owner1.test",
+                {"https://member1.test", "https://member2.test"}}}),
+          MakeParsedPolicyFromMap(
+              /*replacements=*/{{"https://owner0.test",
+                                 {"https://member1.test"}}},
+              /*additions=*/{
+                  {"https://owner1.test", {"https://new-member1.test"}}}));
+  EXPECT_THAT(customization,
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://member1.test"),
+                       Optional(SerializesTo("https://owner0.test"))),
+                  Pair(SerializesTo("https://owner0.test"),
+                       Optional(SerializesTo("https://owner0.test"))),
+                  Pair(SerializesTo("https://new-member1.test"),
+                       Optional(SerializesTo("https://owner1.test"))),
+                  Pair(SerializesTo("https://member2.test"),
+                       Optional(SerializesTo("https://owner1.test"))),
+                  Pair(SerializesTo("https://owner1.test"),
+                       Optional(SerializesTo("https://owner1.test")))));
+}
+}  // namespace content
diff --git a/content/browser/first_party_sets/first_party_sets_loader.cc b/content/browser/first_party_sets/first_party_sets_loader.cc
index 717f3b78..2ef1a68 100644
--- a/content/browser/first_party_sets/first_party_sets_loader.cc
+++ b/content/browser/first_party_sets/first_party_sets_loader.cc
@@ -67,17 +67,6 @@
   return base::ReadStreamToString(file.get(), &raw_sets) ? raw_sets : "";
 }
 
-// Creates a set of SchemefulSites present with the given list of SingleSets.
-base::flat_set<net::SchemefulSite> FlattenSingleSetList(
-    const std::vector<content::FirstPartySetsLoader::SingleSet>& sets) {
-  std::vector<net::SchemefulSite> sites;
-  for (const content::FirstPartySetsLoader::SingleSet& set : sets) {
-    sites.push_back(set.first);
-    sites.insert(sites.end(), set.second.begin(), set.second.end());
-  }
-  return sites;
-}
-
 // Populates the `policy_set_overlaps` out-parameter by checking
 // `existing_sets`. If `site` is equal to an existing site e in `sets`, then
 // `policy_set_index` will be added to the list of set indices at
@@ -101,15 +90,8 @@
 }  // namespace
 
 FirstPartySetsLoader::FirstPartySetsLoader(
-    LoadCompleteOnceCallback on_load_complete,
-    base::Value::Dict policy_overrides)
-    : on_load_complete_(std::move(on_load_complete)) {
-  FirstPartySetParser::ParsedPolicySetLists out_sets;
-  auto error = FirstPartySetParser::ParseSetsFromEnterprisePolicy(
-      policy_overrides, &out_sets);
-  if (!error.has_value())
-    policy_overrides_ = out_sets;
-}
+    LoadCompleteOnceCallback on_load_complete)
+    : on_load_complete_(std::move(on_load_complete)) {}
 
 FirstPartySetsLoader::~FirstPartySetsLoader() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -226,89 +208,24 @@
   DCHECK(HasAllInputs());
   if (!manually_specified_set_.value().has_value())
     return;
-  ApplyReplacementOverrides({manually_specified_set_->value()});
-  RemoveAllSingletons();
-}
+  net::SchemefulSite owner = manually_specified_set_->value().first;
+  base::flat_set<net::SchemefulSite> members =
+      manually_specified_set_->value().second;
 
-void FirstPartySetsLoader::ApplyReplacementOverrides(
-    const std::vector<SingleSet>& override_sets) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(HasAllInputs());
-
-  base::flat_set<net::SchemefulSite> all_override_sites =
-      FlattenSingleSetList(override_sets);
-
-  // Erase the intersection between |sets_| and the list of |override_sets| and
+  // Erase the intersection between |sets_| and |manually_specified_set_| and
   // any members whose owner was in the intersection.
   base::EraseIf(
-      sets_, [&all_override_sites](
+      sets_, [&owner, members](
                  const std::pair<net::SchemefulSite, net::SchemefulSite>& p) {
-        return all_override_sites.contains(p.first) ||
-               all_override_sites.contains(p.second);
+        return p.first == owner || p.second == owner ||
+               members.contains(p.first) || members.contains(p.second);
       });
 
-  // Next, we must add each site in the override_sets to |sets_|.
-  for (auto& [owner, members] : override_sets) {
-    sets_.emplace(owner, owner);
-    for (const net::SchemefulSite& member : members) {
-      sets_.emplace(member, owner);
-    }
+  // Next, we must add the manually specified set to |sets_|.
+  sets_.emplace(owner, owner);
+  for (const net::SchemefulSite& member : members) {
+    sets_.emplace(member, owner);
   }
-}
-
-void FirstPartySetsLoader::ApplyAdditionOverrides(
-    const std::vector<SingleSet>& new_sets) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(HasAllInputs());
-
-  if (new_sets.empty())
-    return;
-
-  std::vector<SingleSet> normalized_additions =
-      NormalizeAdditionSets(sets_, new_sets);
-
-  FlattenedSets flattened_additions;
-  for (const auto& [owner, members] : normalized_additions) {
-    for (const net::SchemefulSite& member : members)
-      flattened_additions.emplace(member, owner);
-    flattened_additions.emplace(owner, owner);
-  }
-
-  // Identify intersections between addition sets and existing sets. This will
-  // be used to reparent existing sets if they intersect with an addition set.
-  //
-  // Since we reparent every member of an existing set (regardless of whether
-  // the intersection was via one of its members or its owner), we just keep
-  // track of the set itself, via its owner.
-  base::flat_map<net::SchemefulSite, net::SchemefulSite> owners_in_intersection;
-  for (const auto& [site, owner] : flattened_additions) {
-    // Found an overlap with an existing set. Add the existing owner to the
-    // map.
-    if (auto it = sets_.find(site); it != sets_.end()) {
-      owners_in_intersection[it->second] = owner;
-    }
-  }
-
-  // Update the (site, owner) mappings in sets_ such that if owner is in the
-  // intersection, then the site is mapped to owners_in_intersection[owner].
-  //
-  // This reparents existing sets to their owner given by the normalized
-  // addition sets.
-  for (auto& [site, owner] : sets_) {
-    if (auto owner_entry = owners_in_intersection.find(owner);
-        owner_entry != owners_in_intersection.end()) {
-      owner = owner_entry->second;
-    }
-  }
-
-  // Since the intersection between sets_ and flattened_additions has already
-  // been updated above, we can insert flattened_additions into sets_ without
-  // affecting any existing mappings in sets_.
-  sets_.insert(flattened_additions.begin(), flattened_additions.end());
-}
-
-void FirstPartySetsLoader::RemoveAllSingletons() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Now remove singleton sets, which are sets that just contain sites that
   // *are* owners, but no longer have any (other) members.
   std::set<net::SchemefulSite> owners_with_members;
@@ -321,20 +238,11 @@
   });
 }
 
-void FirstPartySetsLoader::ApplyAllPolicyOverrides() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(HasAllInputs());
-  ApplyReplacementOverrides(policy_overrides_.replacements);
-  ApplyAdditionOverrides(policy_overrides_.additions);
-  RemoveAllSingletons();
-}
-
 void FirstPartySetsLoader::MaybeFinishLoading() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!HasAllInputs())
     return;
   ApplyManuallySpecifiedSet();
-  ApplyAllPolicyOverrides();
   std::move(on_load_complete_).Run(std::move(sets_));
 }
 
diff --git a/content/browser/first_party_sets/first_party_sets_loader.h b/content/browser/first_party_sets/first_party_sets_loader.h
index 681ed53..043c0ea 100644
--- a/content/browser/first_party_sets/first_party_sets_loader.h
+++ b/content/browser/first_party_sets/first_party_sets_loader.h
@@ -12,7 +12,6 @@
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
 #include "base/timer/elapsed_timer.h"
-#include "base/values.h"
 #include "content/browser/first_party_sets/first_party_set_parser.h"
 #include "content/common/content_export.h"
 #include "net/base/schemeful_site.h"
@@ -33,8 +32,7 @@
   using SingleSet =
       std::pair<net::SchemefulSite, base::flat_set<net::SchemefulSite>>;
 
-  FirstPartySetsLoader(LoadCompleteOnceCallback on_load_complete,
-                       base::Value::Dict policy_overrides);
+  explicit FirstPartySetsLoader(LoadCompleteOnceCallback on_load_complete);
 
   ~FirstPartySetsLoader();
 
@@ -80,33 +78,6 @@
   // `SetManuallySpecifiedSet`, and the public sets via `SetComponentSets`.
   void ApplyManuallySpecifiedSet();
 
-  // Removes the intersection between `sets_` and `override_sets` from the
-  // `sets_` member variable, and then adds the `override_sets` into `sets_`.
-  void ApplyReplacementOverrides(const std::vector<SingleSet>& override_sets);
-
-  // Updates the intersection between `sets_` and `override_sets` within the
-  // `sets_` member variable, and then adds the `override_sets` into
-  // `sets_`.
-  //
-  // The applied update ensures that invariants of First-Party Sets are
-  // maintained, and that all sets in sets_ are disjoint.
-  //
-  // This will add in the `override_sets` into `sets_` without removing
-  // any existing sites from the list of First-Party Sets.
-  void ApplyAdditionOverrides(const std::vector<SingleSet>& override_sets);
-
-  // Removes all singletons (owners that have no members) from sets_.
-  void RemoveAllSingletons();
-
-  // Applies the First-Party Sets overrides provided by policy.
-  //
-  // Must not be called until the loader has already received the public sets
-  // via `SetComponentSets` and the CLI-provided sets have been applied to
-  // `sets_`.
-  //
-  // Applies "Replacement" overrides before applying "Addition" overrides.
-  void ApplyAllPolicyOverrides();
-
   // Checks the required inputs have been received, and if so, invokes the
   // callback `on_load_complete_`, after merging sets appropriately.
   void MaybeFinishLoading();
@@ -130,15 +101,6 @@
   absl::optional<absl::optional<SingleSet>> manually_specified_set_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
-  // Contains two (possibly empty) lists of SingleSets which are provided to
-  // override |sets_| either by replacement or addition. The type of override
-  // that a SingleSet should be used for is specified by the
-  // ParsedPolicySetLists field.
-  //
-  // This variable is not set by any methods other than the constructor.
-  FirstPartySetParser::ParsedPolicySetLists policy_overrides_
-      GUARDED_BY_CONTEXT(sequence_checker_);
-
   enum Progress {
     kNotStarted,
     kStarted,
diff --git a/content/browser/first_party_sets/first_party_sets_loader_unittest.cc b/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
index 8c9b98b..fbbac27 100644
--- a/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
+++ b/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
@@ -52,46 +52,6 @@
       base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ));
 }
 
-// Create a base::Value::Dict representation of a First-Party Set that has
-// an owner field equal to |owner| and members field equal to |members|.
-base::Value::Dict MakeFirstPartySetDict(
-    const std::string& owner,
-    const base::flat_set<std::string>& members) {
-  base::Value::Dict dict;
-  base::Value::List member_list;
-
-  dict.Set("owner", owner);
-  for (const std::string& member : members) {
-    member_list.Append(member);
-  }
-  dict.Set("members", std::move(member_list));
-  return dict;
-}
-
-// Converts a map of (owner->members) into a base::Value::List of First-Party
-// Sets, each represented as a base::Value::Dict for ease of testing.
-base::Value::List MakeFirstPartySetsList(
-    const base::flat_map<std::string, std::vector<std::string>>&
-        owners_to_members) {
-  base::Value::List set_list;
-  for (auto& [owner, members] : owners_to_members) {
-    set_list.Append(MakeFirstPartySetDict(owner, members));
-  }
-  return set_list;
-}
-
-// Creates a base::Value::Dict representing a policy input JSON with a
-// 'replacements' field equal to |replacements| and an 'additions' field equal
-// to |additions|.
-base::Value::Dict MakePolicySetInputFromMap(
-    const base::flat_map<std::string, std::vector<std::string>>& replacements,
-    const base::flat_map<std::string, std::vector<std::string>>& additions) {
-  base::Value::Dict result;
-  result.Set("replacements", base::Value(MakeFirstPartySetsList(replacements)));
-  result.Set("additions", base::Value(MakeFirstPartySetsList(additions)));
-  return result;
-}
-
 enum class FirstPartySetsSource { kPublicSets, kCommandLineSet };
 
 FirstPartySetsLoader::FlattenedSets MakeFlattenedSetsFromMap(
@@ -113,45 +73,36 @@
 
 class FirstPartySetsLoaderTest : public ::testing::Test {
  public:
-  FirstPartySetsLoaderTest() = default;
+  FirstPartySetsLoaderTest() : loader_(future_.GetCallback()) {}
+
+  FirstPartySetsLoader& loader() { return loader_; }
 
   base::flat_map<net::SchemefulSite, net::SchemefulSite> WaitAndGetResult() {
     return future_.Get();
   }
 
- protected:
+ private:
   base::test::TaskEnvironment env_;
   base::test::TestFuture<base::flat_map<net::SchemefulSite, net::SchemefulSite>>
       future_;
-};
-
-class FirstPartySetsLoaderTestWithoutPolicySets
-    : public FirstPartySetsLoaderTest {
- public:
-  FirstPartySetsLoaderTestWithoutPolicySets()
-      : loader_(future_.GetCallback(), base::Value::Dict()) {}
-
-  FirstPartySetsLoader& loader() { return loader_; }
-
- private:
   FirstPartySetsLoader loader_;
 };
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets, IgnoresInvalidFile) {
+TEST_F(FirstPartySetsLoaderTest, IgnoresInvalidFile) {
   loader().SetManuallySpecifiedSet("");
   const std::string input = "certainly not valid JSON";
   SetComponentSets(loader(), input);
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets, ParsesComponent) {
+TEST_F(FirstPartySetsLoaderTest, ParsesComponent) {
   SetComponentSets(loader(), "");
   // Set required input to make sure callback gets called.
   loader().SetManuallySpecifiedSet("");
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets, AcceptsMinimal) {
+TEST_F(FirstPartySetsLoaderTest, AcceptsMinimal) {
   const std::string input =
       "{\"owner\": \"https://example.test\",\"members\": "
       "[\"https://aaaa.test\",],}";
@@ -166,7 +117,7 @@
                                         SerializesTo("https://example.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets, AcceptsMultipleSets) {
+TEST_F(FirstPartySetsLoaderTest, AcceptsMultipleSets) {
   const std::string input =
       "{\"owner\": \"https://example.test\",\"members\": "
       "[\"https://member1.test\"]}\n"
@@ -188,7 +139,7 @@
                                         SerializesTo("https://foo.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets, SetComponentSets_Idempotent) {
+TEST_F(FirstPartySetsLoaderTest, SetComponentSets_Idempotent) {
   std::string input =
       R"({"owner": "https://example.test", "members": ["https://member1.test"]}
 {"owner": "https://foo.test", "members": ["https://member2.test"]})";
@@ -214,7 +165,7 @@
                                         SerializesTo("https://foo.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets, OwnerIsOnlyMember) {
+TEST_F(FirstPartySetsLoaderTest, OwnerIsOnlyMember) {
   const std::string input =
       R"({"owner": "https://example.test", "members": ["https://example.test"]}
 {"owner": "https://foo.test", "members": ["https://member2.test"]})";
@@ -226,7 +177,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets, OwnerIsMember) {
+TEST_F(FirstPartySetsLoaderTest, OwnerIsMember) {
   const std::string input =
       R"({"owner": "https://example.test", "members":)"
       R"( ["https://example.test", "https://member1.test"]}
@@ -238,7 +189,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets, RepeatedMember) {
+TEST_F(FirstPartySetsLoaderTest, RepeatedMember) {
   const std::string input =
       R"({"owner": "https://example.test", "members":)"
       R"( ["https://member1.test", "https://member2.test",)"
@@ -252,8 +203,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
-       SetsManuallySpecified_Invalid_TooSmall) {
+TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Invalid_TooSmall) {
   loader().SetManuallySpecifiedSet("https://example.test");
   // Set required input to make sure callback gets called.
   SetComponentSets(loader(), "");
@@ -261,8 +211,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
-       SetsManuallySpecified_Invalid_NotOrigins) {
+TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Invalid_NotOrigins) {
   loader().SetManuallySpecifiedSet("https://example.test,member1");
   // Set required input to make sure callback gets called.
   SetComponentSets(loader(), "");
@@ -270,8 +219,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
-       SetsManuallySpecified_Invalid_NotHTTPS) {
+TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Invalid_NotHTTPS) {
   loader().SetManuallySpecifiedSet("https://example.test,http://member1.test");
   // Set required input to make sure callback gets called.
   SetComponentSets(loader(), "");
@@ -279,7 +227,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
+TEST_F(FirstPartySetsLoaderTest,
        SetsManuallySpecified_Invalid_RegisteredDomain_Owner) {
   loader().SetManuallySpecifiedSet(
       "https://www.example.test..,https://www.member.test");
@@ -289,7 +237,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
+TEST_F(FirstPartySetsLoaderTest,
        SetsManuallySpecified_Invalid_RegisteredDomain_Member) {
   loader().SetManuallySpecifiedSet(
       "https://www.example.test,https://www.member.test..");
@@ -299,8 +247,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
-       SetsManuallySpecified_Valid_SingleMember) {
+TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Valid_SingleMember) {
   loader().SetManuallySpecifiedSet("https://example.test,https://member.test");
   // Set required input to make sure callback gets called.
   SetComponentSets(loader(), "");
@@ -312,7 +259,7 @@
                                         SerializesTo("https://example.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
+TEST_F(FirstPartySetsLoaderTest,
        SetsManuallySpecified_Valid_SingleMember_RegisteredDomain) {
   loader().SetManuallySpecifiedSet(
       "https://www.example.test,https://www.member.test");
@@ -326,8 +273,7 @@
                                         SerializesTo("https://example.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
-       SetsManuallySpecified_Valid_MultipleMembers) {
+TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Valid_MultipleMembers) {
   loader().SetManuallySpecifiedSet(
       "https://example.test,https://member1.test,https://member2.test");
   // Set required input to make sure callback gets called.
@@ -342,7 +288,7 @@
                                         SerializesTo("https://example.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
+TEST_F(FirstPartySetsLoaderTest,
        SetsManuallySpecified_Valid_OwnerIsOnlyMember) {
   loader().SetManuallySpecifiedSet("https://example.test,https://example.test");
   // Set required input to make sure callback gets called.
@@ -351,8 +297,7 @@
   EXPECT_THAT(WaitAndGetResult(), IsEmpty());
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
-       SetsManuallySpecified_Valid_OwnerIsMember) {
+TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Valid_OwnerIsMember) {
   loader().SetManuallySpecifiedSet(
       "https://example.test,https://example.test,https://member1.test");
   // Set required input to make sure callback gets called.
@@ -365,8 +310,7 @@
                                         SerializesTo("https://example.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
-       SetsManuallySpecified_Valid_RepeatedMember) {
+TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Valid_RepeatedMember) {
   loader().SetManuallySpecifiedSet(R"(https://example.test,
 https://member1.test,
 https://member2.test,
@@ -383,8 +327,7 @@
                                         SerializesTo("https://example.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
-       SetsManuallySpecified_DeduplicatesOwnerOwner) {
+TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_DeduplicatesOwnerOwner) {
   const std::string input = R"({"owner": "https://example.test", "members": )"
                             R"(["https://member2.test", "https://member3.test"]}
 {"owner": "https://bar.test", "members": ["https://member4.test"]})";
@@ -405,7 +348,7 @@
                                         SerializesTo("https://bar.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
+TEST_F(FirstPartySetsLoaderTest,
        SetsManuallySpecified_DeduplicatesOwnerMember) {
   const std::string input = R"({"owner": "https://foo.test", "members": )"
                             R"(["https://member1.test", "https://example.test"]}
@@ -427,7 +370,7 @@
                                         SerializesTo("https://bar.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
+TEST_F(FirstPartySetsLoaderTest,
        SetsManuallySpecified_DeduplicatesMemberOwner) {
   const std::string input = R"({"owner": "https://foo.test", "members": )"
                             R"(["https://member1.test", "https://member2.test"]}
@@ -448,7 +391,7 @@
                                         SerializesTo("https://foo.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
+TEST_F(FirstPartySetsLoaderTest,
        SetsManuallySpecified_DeduplicatesMemberMember) {
   const std::string input = R"({"owner": "https://foo.test", "members": )"
                             R"(["https://member2.test", "https://member3.test"]}
@@ -474,7 +417,7 @@
                                         SerializesTo("https://bar.test"))));
 }
 
-TEST_F(FirstPartySetsLoaderTestWithoutPolicySets,
+TEST_F(FirstPartySetsLoaderTest,
        SetsManuallySpecified_PrunesInducedSingletons) {
   const std::string input =
       R"({"owner": "https://foo.test", "members": ["https://member1.test"]})";
@@ -492,292 +435,6 @@
                                         SerializesTo("https://example.test"))));
 }
 
-// These tests verify that the policy sets override the public sets and that the
-// policy sets override the manually-specified set.
-class FirstPartySetsLoaderTestWithPolicySets
-    : public FirstPartySetsLoaderTest,
-      public testing::WithParamInterface<FirstPartySetsSource> {
- public:
-  FirstPartySetsLoaderTestWithPolicySets() = default;
-
- protected:
-  // This method sets either the public sets or the manually specified
-  // set with a {owner: `owner`, members: `members`} First-Party Set.
-  //
-  // This is used to test how the policy sets override either the public or
-  // manually specified set.
-  void SetEitherPublicOrManuallySpecifiedSet(
-      FirstPartySetsLoader& loader,
-      const std::string& owner,
-      const std::vector<std::string>& members) {
-    switch (GetParam()) {
-      case FirstPartySetsSource::kPublicSets: {
-        // Create the JSON representation.
-        base::Value::Dict public_set;
-        public_set.Set("owner", base::Value(owner));
-        base::Value::List member_list;
-        for (const std::string& member : members) {
-          member_list.Append(member);
-        }
-        public_set.Set("members", base::Value(std::move(member_list)));
-        std::string component_input;
-        JSONStringValueSerializer serializer(&component_input);
-        serializer.Serialize(public_set);
-        SetComponentSets(loader, component_input);
-        loader.SetManuallySpecifiedSet("");
-        return;
-      }
-      case FirstPartySetsSource::kCommandLineSet:
-        SetComponentSets(loader, "");
-        loader.SetManuallySpecifiedSet(base::StringPrintf(
-            "%s,%s", owner.c_str(), base::JoinString(members, ",").c_str()));
-        return;
-    }
-  }
-
-  base::OnceCallback<
-      void(base::flat_map<net::SchemefulSite, net::SchemefulSite>)>
-  callback() {
-    return future_.GetCallback();
-  }
-};
-
-TEST_P(FirstPartySetsLoaderTestWithPolicySets, EmptyPolicySetLists) {
-  FirstPartySetsLoader loader(
-      callback(),
-      MakePolicySetInputFromMap(/*replacements=*/{}, /*additions=*/{}));
-  SetEitherPublicOrManuallySpecifiedSet(loader, "https://owner1.test",
-                                        {"https://member1.test"});
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://member1.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://owner1.test"),
-                                        SerializesTo("https://owner1.test"))));
-}
-
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       Replacements__NoIntersection_NoRemoval) {
-  FirstPartySetsLoader loader(
-      callback(),
-      MakePolicySetInputFromMap(
-          /*replacements=*/{{"https://owner2.test", {"https://member2.test"}}},
-          /*additions=*/{}));
-  SetEitherPublicOrManuallySpecifiedSet(loader, "https://owner1.test",
-                                        {"https://member1.test"});
-
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://member1.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://member2.test"),
-                                        SerializesTo("https://owner2.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://owner1.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://owner2.test"),
-                                        SerializesTo("https://owner2.test"))));
-}
-
-// The common member between the policy and existing set is removed from its
-// previous set.
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       Replacements_ReplacesExistingMember_RemovedFromFormerSet) {
-  FirstPartySetsLoader loader(
-      callback(),
-      MakePolicySetInputFromMap(
-          /*replacements=*/{{"https://owner2.test", {"https://member1b.test"}}},
-          /*additions=*/{}));
-  SetEitherPublicOrManuallySpecifiedSet(
-      loader, "https://owner1.test",
-      {"https://member1a.test", "https://member1b.test"});
-
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://member1a.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://member1b.test"),
-                                        SerializesTo("https://owner2.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://owner1.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://owner2.test"),
-                                        SerializesTo("https://owner2.test"))));
-}
-
-// The common owner between the policy and existing set is removed and its
-// former members are removed since they are now unowned.
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       Replacements_ReplacesExistingOwner_RemovesFormerMembers) {
-  FirstPartySetsLoader loader(
-      callback(),
-      MakePolicySetInputFromMap(
-          /*replacements=*/{{"https://owner1.test", {"https://member2.test"}}},
-          /*additions=*/{}));
-  SetEitherPublicOrManuallySpecifiedSet(
-      loader, "https://owner1.test",
-      {"https://member1a.test", "https://member1b.test"});
-
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://member2.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://owner1.test"),
-                                        SerializesTo("https://owner1.test"))));
-}
-
-// The common member between the policy and existing set is removed and any
-// leftover singletons are deleted.
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       Replacements_ReplacesExistingMember_RemovesSingletons) {
-  FirstPartySetsLoader loader(
-      callback(),
-      MakePolicySetInputFromMap(
-          /*replacements=*/{{"https://owner3.test", {"https://member1.test"}}},
-          /*additions=*/{}));
-  SetEitherPublicOrManuallySpecifiedSet(loader, "https://owner1.test",
-                                        {"https://member1.test"});
-
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://member1.test"),
-                                        SerializesTo("https://owner3.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://owner3.test"),
-                                        SerializesTo("https://owner3.test"))));
-}
-
-// The policy set and the existing set have nothing in common so the policy set
-// gets added in without updating the existing set.
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       Additions_NoIntersection_AddsWithoutUpdating) {
-  FirstPartySetsLoader loader(
-      callback(), MakePolicySetInputFromMap(
-                      /*replacements=*/{}, /*additions=*/{
-                          {"https://owner2.test", {"https://member2.test"}}}));
-  SetEitherPublicOrManuallySpecifiedSet(loader, "https://owner1.test",
-                                        {"https://member1.test"});
-
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://member1.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://member2.test"),
-                                        SerializesTo("https://owner2.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://owner1.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://owner2.test"),
-                                        SerializesTo("https://owner2.test"))));
-}
-
-// The owner of a policy set is also a member in an existing set.
-// The policy set absorbs all sites in the existing set into its members.
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       Additions_PolicyOwnerIsExistingMember_PolicySetAbsorbsExistingSet) {
-  FirstPartySetsLoader loader(
-      callback(),
-      MakePolicySetInputFromMap(
-          /*replacements=*/{}, /*additions=*/{
-              {"https://member2.test",
-               {"https://member2a.test", "https://member2b.test"}}}));
-  SetEitherPublicOrManuallySpecifiedSet(loader, "https://owner1.test",
-                                        {"https://member2.test"});
-
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://owner1.test"),
-                                        SerializesTo("https://member2.test")),
-                                   Pair(SerializesTo("https://member2b.test"),
-                                        SerializesTo("https://member2.test")),
-                                   Pair(SerializesTo("https://member2a.test"),
-                                        SerializesTo("https://member2.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://member2.test"),
-                                        SerializesTo("https://member2.test"))));
-}
-
-// The owner of a policy set is also an owner of an existing set.
-// The policy set absorbs all of its owner's existing members into its members.
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       Additions_PolicyOwnerIsExistingOwner_PolicySetAbsorbsExistingMembers) {
-  FirstPartySetsLoader loader(
-      callback(), MakePolicySetInputFromMap(
-                      /*replacements=*/{}, /*additions=*/{
-                          {"https://owner1.test", {"https://member2.test"}}}));
-  SetEitherPublicOrManuallySpecifiedSet(
-      loader, "https://owner1.test",
-      {"https://member1.test", "https://member3.test"});
-
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://member3.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://member2.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://member1.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://owner1.test"),
-                                        SerializesTo("https://owner1.test"))));
-}
-
-// Replacement & Addition policy set lists are required to be disjoint with
-// each other.
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       BothTypes_SetListsIntersect_NoOverridesApplied) {
-  FirstPartySetsLoader loader(
-      callback(), MakePolicySetInputFromMap(
-                      /*replacements=*/{{"https://owner0.test",
-                                         {"https://new-member.test"}}},
-                      /*additions=*/{{"https://owner42.test",
-                                      {"https://new-member.test"}}}));
-  SetEitherPublicOrManuallySpecifiedSet(
-      loader, "https://owner1.test",
-      {"https://member1.test", "https://member2.test"});
-
-  // The policy set overrides are ignored since the set lists were not disjoint,
-  // which violates a requirement of the policy.
-  EXPECT_THAT(WaitAndGetResult(),
-              UnorderedElementsAre(Pair(SerializesTo("https://member2.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   Pair(SerializesTo("https://member1.test"),
-                                        SerializesTo("https://owner1.test")),
-                                   // Below are the owner self mappings.
-                                   Pair(SerializesTo("https://owner1.test"),
-                                        SerializesTo("https://owner1.test"))));
-}
-
-// Replacement & Addition policy set lists are disjoint with each other.
-TEST_P(FirstPartySetsLoaderTestWithPolicySets,
-       BothTypes_SetListsDontIntersect_OverridesApplied) {
-  FirstPartySetsLoader loader(
-      callback(),
-      MakePolicySetInputFromMap(
-          /*replacements=*/{{"https://owner0.test", {"https://member1.test"}}},
-          /*additions=*/{
-              {"https://owner1.test", {"https://new-member1.test"}}}));
-  SetEitherPublicOrManuallySpecifiedSet(
-      loader, "https://owner1.test",
-      {"https://member1.test", "https://member2.test"});
-
-  // While the policy set lists are disjoint, they are able to affect the same
-  // existing set.
-  EXPECT_THAT(
-      WaitAndGetResult(),
-      UnorderedElementsAre(Pair(SerializesTo("https://member2.test"),
-                                SerializesTo("https://owner1.test")),
-                           Pair(SerializesTo("https://new-member1.test"),
-                                SerializesTo("https://owner1.test")),
-                           Pair(SerializesTo("https://member1.test"),
-                                SerializesTo("https://owner0.test")),
-                           // Below are the owner self mappings.
-                           Pair(SerializesTo("https://owner0.test"),
-                                SerializesTo("https://owner0.test")),
-                           Pair(SerializesTo("https://owner1.test"),
-                                SerializesTo("https://owner1.test"))));
-}
-
-INSTANTIATE_TEST_CASE_P(
-    /* no label */,
-    FirstPartySetsLoaderTestWithPolicySets,
-    ::testing::Values(FirstPartySetsSource::kPublicSets,
-                      FirstPartySetsSource::kCommandLineSet));
-
 // There is no overlap between the existing sets and the addition sets, so
 // normalization should be a noop.
 TEST(FirstPartySetsLoaderTestNormalizeAdditionSets,
diff --git a/content/browser/renderer_host/input/fling_controller.h b/content/browser/renderer_host/input/fling_controller.h
index 7dbf039..d790e90 100644
--- a/content/browser/renderer_host/input/fling_controller.h
+++ b/content/browser/renderer_host/input/fling_controller.h
@@ -162,8 +162,7 @@
     return !last_progress_time_.is_null();
   }
 
-  raw_ptr<FlingControllerEventSenderClient, DanglingUntriaged>
-      event_sender_client_;
+  raw_ptr<FlingControllerEventSenderClient> event_sender_client_;
 
   raw_ptr<FlingControllerSchedulerClient, DanglingUntriaged> scheduler_client_;
 
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index e617a44..f63c1aa 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -1323,7 +1323,12 @@
 
   std::unique_ptr<SyntheticGestureController> synthetic_gesture_controller_;
 
+  // Must be declared before `input_router_`. The latter is constructed by
+  // borrowing a reference to this object, so it must be deleted first.
+  std::unique_ptr<FlingSchedulerBase> fling_scheduler_;
+
   // Receives and handles all input events.
+  // Depends on `fling_scheduler` above, so it must be declared last.
   std::unique_ptr<InputRouter> input_router_;
 
   base::OneShotTimer input_event_ack_timeout_;
@@ -1388,8 +1393,6 @@
 
   const viz::FrameSinkId frame_sink_id_;
 
-  std::unique_ptr<FlingSchedulerBase> fling_scheduler_;
-
   bool sent_autoscroll_scroll_begin_ = false;
   gfx::PointF autoscroll_start_position_;
 
diff --git a/content/public/test/test_devtools_protocol_client.cc b/content/public/test/test_devtools_protocol_client.cc
index 17c1946b..55fe5ef 100644
--- a/content/public/test/test_devtools_protocol_client.cc
+++ b/content/public/test/test_devtools_protocol_client.cc
@@ -179,6 +179,10 @@
   return is_trusted_;
 }
 
+bool TestDevToolsProtocolClient::MayReadLocalFiles() {
+  return may_read_local_files_;
+}
+
 absl::optional<url::Origin>
 TestDevToolsProtocolClient::GetNavigationInitiatorOrigin() {
   return navigation_initiator_origin_;
diff --git a/content/public/test/test_devtools_protocol_client.h b/content/public/test/test_devtools_protocol_client.h
index a797fad..fdb958de4 100644
--- a/content/public/test/test_devtools_protocol_client.h
+++ b/content/public/test/test_devtools_protocol_client.h
@@ -89,6 +89,10 @@
     navigation_initiator_origin_ = navigation_initiator_origin;
   }
 
+  void SetMayReadLocalFiles(bool may_read_local_files) {
+    may_read_local_files_ = may_read_local_files;
+  }
+
   const base::Value::Dict* result() const;
   const base::Value::Dict* error() const;
   int received_responses_count() const { return received_responses_count_; }
@@ -105,6 +109,7 @@
   absl::optional<url::Origin> GetNavigationInitiatorOrigin() override;
   bool AllowUnsafeOperations() override;
   bool IsTrusted() override;
+  bool MayReadLocalFiles() override;
 
   int last_sent_id_ = 0;
   int waiting_for_command_result_id_ = 0;
@@ -123,6 +128,7 @@
   bool allow_unsafe_operations_ = true;
   bool is_trusted_ = true;
   absl::optional<url::Origin> navigation_initiator_origin_;
+  bool may_read_local_files_ = true;
 };
 
 }  // namespace content
diff --git a/extensions/browser/api/offscreen/offscreen_api.cc b/extensions/browser/api/offscreen/offscreen_api.cc
index 26d189e..d6b4708 100644
--- a/extensions/browser/api/offscreen/offscreen_api.cc
+++ b/extensions/browser/api/offscreen/offscreen_api.cc
@@ -4,15 +4,54 @@
 
 #include "extensions/browser/api/offscreen/offscreen_api.h"
 
+#include "content/public/browser/browser_context.h"
 #include "extensions/browser/api/offscreen/offscreen_document_manager.h"
+#include "extensions/browser/extension_util.h"
+#include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/offscreen_document_host.h"
 #include "extensions/common/api/offscreen.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
 namespace extensions {
 
+namespace {
+
+// Returns the BrowserContext with which offscreen documents should be
+// associated for the given `extension` and `calling_context`. This may be
+// different from the `calling_context`, as in the case of spanning mode
+// extensions.
+content::BrowserContext& GetBrowserContextToUse(
+    content::BrowserContext& calling_context,
+    const Extension& extension) {
+  // The on-the-record profile always uses itself.
+  if (!calling_context.IsOffTheRecord())
+    return calling_context;
+
+  DCHECK(util::IsIncognitoEnabled(extension.id(), &calling_context))
+      << "Only incognito-enabled extensions should have an incognito context";
+
+  // Split-mode extensions use the incognito (calling) context; spanning mode
+  // extensions fall back to the original profile.
+  bool is_split_mode = IncognitoInfo::IsSplitMode(&extension);
+  return is_split_mode ? calling_context
+                       : *ExtensionsBrowserClient::Get()->GetOriginalContext(
+                             &calling_context);
+}
+
+// Similar to the above, returns the OffscreenDocumentManager to use for the
+// given `extension` and `calling_context`.
+OffscreenDocumentManager* GetManagerToUse(
+    content::BrowserContext& calling_context,
+    const Extension& extension) {
+  return OffscreenDocumentManager::Get(
+      &GetBrowserContextToUse(calling_context, extension));
+}
+
+}  // namespace
+
 OffscreenCreateDocumentFunction::OffscreenCreateDocumentFunction() = default;
 OffscreenCreateDocumentFunction::~OffscreenCreateDocumentFunction() = default;
 
@@ -30,7 +69,7 @@
   CHECK_EQ(extension()->origin(), url::Origin::Create(url));
 
   OffscreenDocumentManager* manager =
-      OffscreenDocumentManager::Get(browser_context());
+      GetManagerToUse(*browser_context(), *extension());
   OffscreenDocumentHost* offscreen_document =
       manager->CreateOffscreenDocument(*extension(), url);
   DCHECK(offscreen_document);
@@ -94,7 +133,7 @@
   EXTENSION_FUNCTION_VALIDATE(extension());
 
   OffscreenDocumentManager* manager =
-      OffscreenDocumentManager::Get(browser_context());
+      GetManagerToUse(*browser_context(), *extension());
   OffscreenDocumentHost* offscreen_document =
       manager->GetOffscreenDocumentForExtension(*extension());
   if (!offscreen_document)
@@ -149,7 +188,7 @@
   EXTENSION_FUNCTION_VALIDATE(extension());
 
   bool has_document =
-      OffscreenDocumentManager::Get(browser_context())
+      GetManagerToUse(*browser_context(), *extension())
           ->GetOffscreenDocumentForExtension(*extension()) != nullptr;
   return RespondNow(OneArgument(base::Value(has_document)));
 }
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index d295762..1bfa8b6e 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1536,8 +1536,8 @@
   TERMINALPRIVATE_OPENWINDOW = 1473,
   AUTOTESTPRIVATE_SETPLUGINVMPOLICY = 1474,
   AUTOTESTPRIVATE_SHOWPLUGINVMINSTALLER = 1475,
-  PASSWORDSPRIVATE_REMOVESAVEDPASSWORDS = 1476,
-  PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTIONS = 1477,
+  DELETED_PASSWORDSPRIVATE_REMOVESAVEDPASSWORDS = 1476,
+  DELETED_PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTIONS = 1477,
   AUTOTESTPRIVATE_WAITFORAMBIENTPHOTOANIMATION = 1478,
   INPUT_IME_SETASSISTIVEWINDOWPROPERTIES = 1479,
   PASSWORDSPRIVATE_MOVEPASSWORDSTOACCOUNT = 1480,
diff --git a/gpu/command_buffer/common/shared_image_usage.cc b/gpu/command_buffer/common/shared_image_usage.cc
index 9eb520d2b..a0415c6 100644
--- a/gpu/command_buffer/common/shared_image_usage.cc
+++ b/gpu/command_buffer/common/shared_image_usage.cc
@@ -62,6 +62,12 @@
   if (usage & SHARED_IMAGE_USAGE_CPU_WRITE) {
     label += "|CpuWrite";
   }
+  if (usage & SHARED_IMAGE_USAGE_RAW_DRAW) {
+    label += "|RawDraw";
+  }
+  if (usage & SHARED_IMAGE_USAGE_RASTER_DELEGATED_COMPOSITING) {
+    label += "|RasterDelegatedCompositing";
+  }
 
   DCHECK(!label.empty());
 
diff --git a/gpu/command_buffer/service/gpu_fence_manager_unittest.cc b/gpu/command_buffer/service/gpu_fence_manager_unittest.cc
index 95f56497..d467b91 100644
--- a/gpu/command_buffer/service/gpu_fence_manager_unittest.cc
+++ b/gpu/command_buffer/service/gpu_fence_manager_unittest.cc
@@ -18,8 +18,9 @@
 #include "ui/gfx/gpu_fence.h"
 #include "ui/gfx/gpu_fence_handle.h"
 #include "ui/gl/egl_mock.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_egl_api_implementation.h"
-#include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 
 #if BUILDFLAG(IS_POSIX)
 #include <unistd.h>
@@ -74,11 +75,13 @@
 
     gl::ClearBindingsEGL();
     gl::InitializeStaticGLBindingsEGL();
-    display_ = gl::GLSurfaceEGL::InitializeOneOffForTesting();
+    display_ = gl::GetDefaultDisplayEGL();
+    display_->InitializeForTesting();
   }
 
   void TeardownMockEGL() {
-    gl::GLSurfaceEGL::ShutdownOneOff(display_);
+    if (display_)
+      display_->Shutdown();
     egl_.reset();
   }
 
diff --git a/gpu/command_buffer/service/shared_image_manager.cc b/gpu/command_buffer/service/shared_image_manager.cc
index a46230e3e..114c629 100644
--- a/gpu/command_buffer/service/shared_image_manager.cc
+++ b/gpu/command_buffer/service/shared_image_manager.cc
@@ -17,6 +17,7 @@
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/common/shared_image_trace_utils.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "gpu/command_buffer/service/shared_image_representation.h"
 #include "ui/gl/trace_util.h"
@@ -429,6 +430,12 @@
   dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
                   base::trace_event::MemoryAllocatorDump::kUnitsBytes,
                   estimated_size);
+  // Usage is optional, but |CreateLabelForSharedImageUsage()| expects one to be
+  // set.
+  if (backing->usage()) {
+    dump->AddString("usage", "",
+                    CreateLabelForSharedImageUsage(backing->usage()));
+  }
   // Add a mailbox guid which expresses shared ownership with the client
   // process.
   // This must match the client-side.
diff --git a/ios/chrome/app/strings/resources/ios_strings_es.xtb b/ios/chrome/app/strings/resources/ios_strings_es.xtb
index fda9e63..cfc8f5f 100644
--- a/ios/chrome/app/strings/resources/ios_strings_es.xtb
+++ b/ios/chrome/app/strings/resources/ios_strings_es.xtb
@@ -437,7 +437,7 @@
 <translation id="4737560986434232178">Buscar en las pestañas recientes</translation>
 <translation id="473775607612524610">Actualizar</translation>
 <translation id="4747097190499141774">El cifrado mediante frase de contraseña no incluye los métodos de pago ni las direcciones de Google Pay. Solo alguien que tenga tu frase de contraseña puede leer tus datos cifrados. La frase de contraseña no se envía a Google, que tampoco la guarda. Si la olvidas o quieres cambiar esta opción, deberás restablecer la sincronización. <ph name="BEGIN_LINK" />Más información<ph name="END_LINK" /></translation>
-<translation id="4751645464639803239">Nueva pestaña incógnito</translation>
+<translation id="4751645464639803239">Nueva pestaña de Incógnito</translation>
 <translation id="4775879719735953715">Navegador predeterminado</translation>
 <translation id="4778644898150334464">Usar otra contraseña</translation>
 <translation id="4802417911091824046">El cifrado mediante frase de contraseña no incluye los métodos de pago ni las direcciones de Google Pay.
diff --git a/ios/chrome/browser/browser_state/BUILD.gn b/ios/chrome/browser/browser_state/BUILD.gn
index 037bf3bd..78d3e3a4 100644
--- a/ios/chrome/browser/browser_state/BUILD.gn
+++ b/ios/chrome/browser/browser_state/BUILD.gn
@@ -130,6 +130,7 @@
     "//ios/chrome/browser/send_tab_to_self",
     "//ios/chrome/browser/sessions",
     "//ios/chrome/browser/signin",
+    "//ios/chrome/browser/signin:trusted_vault_factory",
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/sync",
     "//ios/chrome/browser/sync/glue",
diff --git a/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm b/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
index b23acbe4..6a9a02e 100644
--- a/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
+++ b/ios/chrome/browser/browser_state/browser_state_keyed_service_factories.mm
@@ -61,6 +61,7 @@
 #include "ios/chrome/browser/signin/signin_browser_state_info_updater_factory.h"
 #include "ios/chrome/browser/signin/signin_client_factory.h"
 #include "ios/chrome/browser/signin/signin_error_controller_factory.h"
+#import "ios/chrome/browser/signin/trusted_vault_client_backend_factory.h"
 #include "ios/chrome/browser/sync/consent_auditor_factory.h"
 #include "ios/chrome/browser/sync/ios_user_event_service_factory.h"
 #include "ios/chrome/browser/sync/model_type_store_service_factory.h"
@@ -161,6 +162,7 @@
   UrlLanguageHistogramFactory::GetInstance();
   VerdictCacheManagerFactory::GetInstance();
   PolicyBlocklistServiceFactory::GetInstance();
+  TrustedVaultClientBackendFactory::GetInstance();
 
 #if BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
   CredentialProviderServiceFactory::GetInstance();
diff --git a/ios/chrome/browser/reading_list/offline_page_tab_helper.mm b/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
index 84920472..6a6b647 100644
--- a/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
+++ b/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
@@ -348,8 +348,7 @@
     // If the current navigation was not committed, but it was a new navigation,
     // a new placeholder navigation with a chrome://offline URL can be created
     // which will be replaced by the offline version on load failure.
-    GURL offlineURL = reading_list::OfflineURLForPath(
-        entry->DistilledPath(), entry_url, entry->DistilledURL());
+    GURL offlineURL = reading_list::OfflineURLForURL(entry_url);
 
     web::NavigationManager::WebLoadParams params(offlineURL);
     params.transition_type = navigation_transition_type_;
diff --git a/ios/chrome/browser/reading_list/offline_url_utils.h b/ios/chrome/browser/reading_list/offline_url_utils.h
index 3e48475..f443751 100644
--- a/ios/chrome/browser/reading_list/offline_url_utils.h
+++ b/ios/chrome/browser/reading_list/offline_url_utils.h
@@ -11,15 +11,9 @@
 
 namespace reading_list {
 
-// The distilled URL chrome://offline/... that will load the file at |path|.
-// |entry_url| is the URL of the ReadingListEntry.
-// |virtual_url| is the URL to display in the omnibox. This can be different
-// from |entry_url| if the distillation was done after a redirection.
-// |distilled_path|, |entry_url| and |virtual_url| are required and must not be
-// empty or invalid.
-GURL OfflineURLForPath(const base::FilePath& distilled_path,
-                       const GURL& entry_url,
-                       const GURL& virtual_url);
+// Returns the offline URL for |entry_url|, the URL of the ReadingListEntry
+// which must not be empty or invalid.
+GURL OfflineURLForURL(const GURL& entry_url);
 
 // Create a chrome://offline/ URL that embeds entry_url in a "reload"
 // parameters.
@@ -29,18 +23,6 @@
 // If not, return GURL::EmptyURL().
 GURL EntryURLForOfflineURL(const GURL& offline_url);
 
-// If |offline_url| has a "virtualURL" query params that is a URL, returns it.
-// If not, return GURL::EmptyURL().
-GURL VirtualURLForOfflineURL(const GURL& offline_url);
-
-// The file URL pointing to the local file to load to display |distilled_url|.
-// If |resources_root_url| is not nullptr, it is set to a file URL to the
-// directory conatining all the resources needed by |distilled_url|.
-// |offline_path| is the root path to the directory containing offline files.
-GURL FileURLForDistilledURL(const GURL& distilled_url,
-                            const base::FilePath& offline_path,
-                            GURL* resources_root_url);
-
 // If |offline_url| has a "reload" query params that is a URL, returns it.
 // If not, return GURL::EmptyURL().
 GURL ReloadURLForOfflineURL(const GURL& offline_url);
diff --git a/ios/chrome/browser/reading_list/offline_url_utils.mm b/ios/chrome/browser/reading_list/offline_url_utils.mm
index 73294f9..27b600a 100644
--- a/ios/chrome/browser/reading_list/offline_url_utils.mm
+++ b/ios/chrome/browser/reading_list/offline_url_utils.mm
@@ -22,27 +22,18 @@
 namespace {
 const char kEntryURLQueryParam[] = "entryURL";
 const char kReloadURLQueryParam[] = "reload";
-const char kVirtualURLQueryParam[] = "virtualURL";
-}
+}  // namespace
 
 namespace reading_list {
 
-GURL OfflineURLForPath(const base::FilePath& distilled_path,
-                       const GURL& entry_url,
-                       const GURL& virtual_url) {
-  DCHECK(!distilled_path.empty());
+GURL OfflineURLForURL(const GURL& entry_url) {
   DCHECK(entry_url.is_valid());
-  DCHECK(virtual_url.is_valid());
   GURL page_url(kChromeUIOfflineURL);
   GURL::Replacements replacements;
-  replacements.SetPathStr(distilled_path.value());
   page_url = page_url.ReplaceComponents(replacements);
   page_url = net::AppendQueryParameter(page_url, kEntryURLQueryParam,
                                        entry_url.spec());
 
-  page_url = net::AppendQueryParameter(page_url, kVirtualURLQueryParam,
-                                       virtual_url.spec());
-
   return page_url;
 }
 
@@ -67,34 +58,6 @@
   return GURL::EmptyGURL();
 }
 
-GURL VirtualURLForOfflineURL(const GURL& offline_url) {
-  std::string virtual_url_string;
-  if (net::GetValueForKeyInQuery(offline_url, kVirtualURLQueryParam,
-                                 &virtual_url_string)) {
-    GURL virtual_url = GURL(virtual_url_string);
-    if (virtual_url.is_valid()) {
-      return virtual_url;
-    }
-  }
-  return GURL::EmptyGURL();
-}
-
-GURL FileURLForDistilledURL(const GURL& distilled_url,
-                            const base::FilePath& offline_path,
-                            GURL* resources_root_url) {
-  if (!distilled_url.is_valid()) {
-    return GURL();
-  }
-  DCHECK(distilled_url.SchemeIs(kChromeUIScheme));
-  GURL file_url(base::StringPrintf("%s%s", url::kFileScheme,
-                                   url::kStandardSchemeSeparator) +
-                offline_path.value() + distilled_url.path());
-  if (resources_root_url) {
-    *resources_root_url = file_url.Resolve(".");
-  }
-  return file_url;
-}
-
 GURL ReloadURLForOfflineURL(const GURL& offline_url) {
   std::string reload_url_string;
   if (net::GetValueForKeyInQuery(offline_url, kReloadURLQueryParam,
diff --git a/ios/chrome/browser/reading_list/offline_url_utils_unittest.mm b/ios/chrome/browser/reading_list/offline_url_utils_unittest.mm
index cf9d3db..1365df7 100644
--- a/ios/chrome/browser/reading_list/offline_url_utils_unittest.mm
+++ b/ios/chrome/browser/reading_list/offline_url_utils_unittest.mm
@@ -24,93 +24,40 @@
 using OfflineURLUtilsTest = PlatformTest;
 
 // Checks the distilled URL for the page with an onlineURL is
-// chrome://offline/MD5/page.html?entryURL=...&virtualURL=...
-TEST_F(OfflineURLUtilsTest, OfflineURLForPathWithEntryURLAndVirtualURLTest) {
-  base::FilePath page_path("MD5/page.html");
+// chrome://offline/?entryURL=...
+TEST_F(OfflineURLUtilsTest, OfflineURLForURL) {
   GURL entry_url = GURL("http://foo.bar");
-  GURL virtual_url = GURL("http://foo.bar/virtual");
-  GURL distilled_url =
-      reading_list::OfflineURLForPath(page_path, entry_url, virtual_url);
-  EXPECT_EQ("chrome://offline/MD5/page.html?"
-            "entryURL=http%3A%2F%2Ffoo.bar%2F&"
-            "virtualURL=http%3A%2F%2Ffoo.bar%2Fvirtual",
+  GURL distilled_url = reading_list::OfflineURLForURL(entry_url);
+  EXPECT_EQ("chrome://offline/?"
+            "entryURL=http%3A%2F%2Ffoo.bar%2F",
             distilled_url.spec());
 }
 
 // Checks the parsing of offline URL chrome://offline/MD5/page.html.
-// As entryURL and virtualURL are absent, they should be invalid.
+// As entryURL is absent, it should be invalid.
 TEST_F(OfflineURLUtilsTest, ParseOfflineURLTest) {
   GURL distilled_url("chrome://offline/MD5/page.html");
   GURL entry_url = reading_list::EntryURLForOfflineURL(distilled_url);
   EXPECT_TRUE(entry_url.is_empty());
-  GURL virtual_url = reading_list::VirtualURLForOfflineURL(distilled_url);
-  EXPECT_TRUE(virtual_url.is_empty());
 }
 
 // Checks the parsing of offline URL
 // chrome://offline/MD5/page.html?entryURL=encorded%20URL
 // As entryURL is present, it should be returned correctly.
-// As virtualURL is absent, it should return GURL::EmptyGURL().
 TEST_F(OfflineURLUtilsTest, ParseOfflineURLWithEntryURLTest) {
   GURL offline_url(
       "chrome://offline/MD5/page.html?entryURL=http%3A%2F%2Ffoo.bar%2F");
   GURL entry_url = reading_list::EntryURLForOfflineURL(offline_url);
   EXPECT_EQ("http://foo.bar/", entry_url.spec());
-  GURL virtual_url = reading_list::VirtualURLForOfflineURL(offline_url);
-  EXPECT_TRUE(virtual_url.is_empty());
 }
 
 // Checks the parsing of offline URL
-// chrome://offline/MD5/page.html?virtualURL=encorded%20URL
+// chrome://offline/MD5/page.html
 // As entryURL is absent, it should return the offline URL.
-// As virtualURL is present, it should be returned correctly.
 TEST_F(OfflineURLUtilsTest, ParseOfflineURLWithVirtualURLTest) {
-  GURL offline_url(
-      "chrome://offline/MD5/page.html?virtualURL=http%3A%2F%2Ffoo.bar%2F");
+  GURL offline_url("chrome://offline/MD5/page.html");
   GURL entry_url = reading_list::EntryURLForOfflineURL(offline_url);
   EXPECT_TRUE(entry_url.is_empty());
-  GURL virtual_url = reading_list::VirtualURLForOfflineURL(offline_url);
-  EXPECT_EQ("http://foo.bar/", virtual_url.spec());
-}
-
-// Checks the parsing of offline URL
-// chrome://offline/MD5/page.html?entryURL=...&virtualURL=...
-// As entryURL is present, it should be returned correctly.
-// As virtualURL is present, it should be returned correctly.
-TEST_F(OfflineURLUtilsTest, ParseOfflineURLWithVirtualAndEntryURLTest) {
-  GURL offline_url(
-      "chrome://offline/MD5/"
-      "page.html?virtualURL=http%3A%2F%2Ffoo.bar%2Fvirtual&entryURL=http%3A%2F%"
-      "2Ffoo.bar%2Fentry");
-  GURL entry_url = reading_list::EntryURLForOfflineURL(offline_url);
-  EXPECT_EQ("http://foo.bar/entry", entry_url.spec());
-  GURL virtual_url = reading_list::VirtualURLForOfflineURL(offline_url);
-  EXPECT_EQ("http://foo.bar/virtual", virtual_url.spec());
-}
-
-// Checks the file path for chrome://offline/MD5/page.html is
-// file://profile_path/Offline/MD5/page.html.
-// Checks the resource root for chrome://offline/MD5/page.html is
-// file://profile_path/Offline/MD5
-TEST_F(OfflineURLUtilsTest, FileURLForDistilledURLTest) {
-  base::FilePath offline_path("/profile_path/Offline");
-  GURL file_url =
-      reading_list::FileURLForDistilledURL(GURL(), offline_path, nullptr);
-  EXPECT_FALSE(file_url.is_valid());
-
-  GURL distilled_url("chrome://offline/MD5/page.html");
-  file_url = reading_list::FileURLForDistilledURL(distilled_url, offline_path,
-                                                  nullptr);
-  EXPECT_TRUE(file_url.is_valid());
-  EXPECT_TRUE(file_url.SchemeIsFile());
-  EXPECT_EQ("/profile_path/Offline/MD5/page.html", file_url.path());
-
-  GURL resource_url;
-  file_url = reading_list::FileURLForDistilledURL(distilled_url, offline_path,
-                                                  &resource_url);
-  EXPECT_TRUE(resource_url.is_valid());
-  EXPECT_TRUE(resource_url.SchemeIsFile());
-  EXPECT_EQ("/profile_path/Offline/MD5/", resource_url.path());
 }
 
 // Checks that the offline URLs are correctly detected by |IsOfflineURL|.
diff --git a/ios/chrome/browser/reading_list/reading_list_web_state_observer.mm b/ios/chrome/browser/reading_list/reading_list_web_state_observer.mm
index f16291b..0a645e2 100644
--- a/ios/chrome/browser/reading_list/reading_list_web_state_observer.mm
+++ b/ios/chrome/browser/reading_list/reading_list_web_state_observer.mm
@@ -250,8 +250,7 @@
       reading_list_model_->GetEntryByURL(pending_url_);
   last_load_was_offline_ = true;
   DCHECK(entry->DistilledState() == ReadingListEntry::PROCESSED);
-  GURL url = reading_list::OfflineURLForPath(
-      entry->DistilledPath(), entry->URL(), entry->DistilledURL());
+  GURL url = reading_list::OfflineURLForURL(entry->URL());
   web::NavigationManager* navigationManager =
       web_state_->GetNavigationManager();
   web::NavigationItem* item = navigationManager->GetPendingItem();
diff --git a/ios/chrome/browser/reading_list/reading_list_web_state_observer_unittest.mm b/ios/chrome/browser/reading_list/reading_list_web_state_observer_unittest.mm
index 16eb362..7070023 100644
--- a/ios/chrome/browser/reading_list/reading_list_web_state_observer_unittest.mm
+++ b/ios/chrome/browser/reading_list/reading_list_web_state_observer_unittest.mm
@@ -173,8 +173,7 @@
       url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
       base::Time::FromTimeT(100));
   const ReadingListEntry* entry = reading_list_model()->GetEntryByURL(url);
-  GURL distilled_url = reading_list::OfflineURLForPath(
-      entry->DistilledPath(), entry->URL(), entry->DistilledURL());
+  GURL distilled_url = reading_list::OfflineURLForURL(entry->URL());
 
   FakeNavigationManager* fake_navigation_manager =
       static_cast<FakeNavigationManager*>(
@@ -207,8 +206,7 @@
       url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
       base::Time::FromTimeT(100));
   const ReadingListEntry* entry = reading_list_model()->GetEntryByURL(url);
-  GURL distilled_url = reading_list::OfflineURLForPath(
-      entry->DistilledPath(), entry->URL(), entry->DistilledURL());
+  GURL distilled_url = reading_list::OfflineURLForURL(entry->URL());
 
   FakeNavigationManager* fake_navigation_manager =
       static_cast<FakeNavigationManager*>(
diff --git a/ios/chrome/browser/signin/BUILD.gn b/ios/chrome/browser/signin/BUILD.gn
index 7c9b620..3b203eee 100644
--- a/ios/chrome/browser/signin/BUILD.gn
+++ b/ios/chrome/browser/signin/BUILD.gn
@@ -169,6 +169,23 @@
   ]
 }
 
+source_set("trusted_vault_factory") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "trusted_vault_client_backend_factory.h",
+    "trusted_vault_client_backend_factory.mm",
+  ]
+  deps = [
+    ":trusted_vault",
+    "//base",
+    "//components/keyed_service/ios",
+    "//ios/chrome/browser:application_context",
+    "//ios/chrome/browser/browser_state",
+    "//ios/public/provider/chrome/browser/signin:trusted_vault_api",
+  ]
+  frameworks = [ "Foundation.framework" ]
+}
+
 source_set("test_support") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
diff --git a/ios/chrome/browser/signin/trusted_vault_client_backend_factory.h b/ios/chrome/browser/signin/trusted_vault_client_backend_factory.h
new file mode 100644
index 0000000..3785eb4c
--- /dev/null
+++ b/ios/chrome/browser/signin/trusted_vault_client_backend_factory.h
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_SIGNIN_TRUSTED_VAULT_CLIENT_BACKEND_FACTORY_H_
+#define IOS_CHROME_BROWSER_SIGNIN_TRUSTED_VAULT_CLIENT_BACKEND_FACTORY_H_
+
+#include <memory>
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/ios/browser_state_keyed_service_factory.h"
+
+class ChromeBrowserState;
+class TrustedVaultClientBackend;
+
+// Singleton that owns all TrustedVaultClientBackends and associates them with
+// ChromeBrowserState.
+class TrustedVaultClientBackendFactory
+    : public BrowserStateKeyedServiceFactory {
+ public:
+  static TrustedVaultClientBackend* GetForBrowserState(
+      ChromeBrowserState* browser_state);
+  static TrustedVaultClientBackendFactory* GetInstance();
+
+ private:
+  friend class base::NoDestructor<TrustedVaultClientBackendFactory>;
+
+  TrustedVaultClientBackendFactory();
+  ~TrustedVaultClientBackendFactory() override;
+
+  // BrowserStateKeyedServiceFactory implementation.
+  std::unique_ptr<KeyedService> BuildServiceInstanceFor(
+      web::BrowserState* context) const override;
+};
+
+#endif  // IOS_CHROME_BROWSER_SIGNIN_TRUSTED_VAULT_CLIENT_BACKEND_FACTORY_H_
diff --git a/ios/chrome/browser/signin/trusted_vault_client_backend_factory.mm b/ios/chrome/browser/signin/trusted_vault_client_backend_factory.mm
new file mode 100644
index 0000000..581b5ed5
--- /dev/null
+++ b/ios/chrome/browser/signin/trusted_vault_client_backend_factory.mm
@@ -0,0 +1,50 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/signin/trusted_vault_client_backend_factory.h"
+
+#import "components/keyed_service/ios/browser_state_dependency_manager.h"
+#import "ios/chrome/browser/application_context.h"
+#import "ios/chrome/browser/browser_state/browser_state_otr_helper.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/signin/trusted_vault_client_backend.h"
+#import "ios/chrome/browser/signin/trusted_vault_configuration.h"
+#import "ios/public/provider/chrome/browser/signin/trusted_vault_api.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// static
+TrustedVaultClientBackend* TrustedVaultClientBackendFactory::GetForBrowserState(
+    ChromeBrowserState* browser_state) {
+  return static_cast<TrustedVaultClientBackend*>(
+      GetInstance()->GetServiceForBrowserState(browser_state, true));
+}
+
+// static
+TrustedVaultClientBackendFactory*
+TrustedVaultClientBackendFactory::GetInstance() {
+  static base::NoDestructor<TrustedVaultClientBackendFactory> instance;
+  return instance.get();
+}
+
+TrustedVaultClientBackendFactory::TrustedVaultClientBackendFactory()
+    : BrowserStateKeyedServiceFactory(
+          "TrustedVaultClientBackend",
+          BrowserStateDependencyManager::GetInstance()) {}
+
+TrustedVaultClientBackendFactory::~TrustedVaultClientBackendFactory() = default;
+
+std::unique_ptr<KeyedService>
+TrustedVaultClientBackendFactory::BuildServiceInstanceFor(
+    web::BrowserState* context) const {
+  TrustedVaultConfiguration* configuration =
+      [[TrustedVaultConfiguration alloc] init];
+
+  ApplicationContext* application_context = GetApplicationContext();
+  configuration.ssoService = application_context->GetSSOService();
+
+  return ios::provider::CreateTrustedVaultClientBackend(configuration);
+}
diff --git a/ios/chrome/browser/sync/BUILD.gn b/ios/chrome/browser/sync/BUILD.gn
index 501c8cec..438bfb4 100644
--- a/ios/chrome/browser/sync/BUILD.gn
+++ b/ios/chrome/browser/sync/BUILD.gn
@@ -82,6 +82,8 @@
     "//ios/chrome/browser/search_engines",
     "//ios/chrome/browser/sessions",
     "//ios/chrome/browser/signin",
+    "//ios/chrome/browser/signin:trusted_vault",
+    "//ios/chrome/browser/signin:trusted_vault_factory",
     "//ios/chrome/browser/sync/glue",
     "//ios/chrome/browser/sync/sessions",
     "//ios/chrome/browser/tabs",
@@ -89,8 +91,6 @@
     "//ios/chrome/browser/webdata_services",
     "//ios/chrome/common",
     "//ios/components/webui:url_constants",
-    "//ios/public/provider/chrome/browser",
-    "//ios/public/provider/chrome/browser/signin",
     "//ios/web",
     "//ios/web/public/session",
     "//net",
diff --git a/ios/chrome/browser/sync/ios_chrome_sync_client.mm b/ios/chrome/browser/sync/ios_chrome_sync_client.mm
index c4eb725..67be277 100644
--- a/ios/chrome/browser/sync/ios_chrome_sync_client.mm
+++ b/ios/chrome/browser/sync/ios_chrome_sync_client.mm
@@ -44,6 +44,7 @@
 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
 #import "ios/chrome/browser/signin/chrome_account_manager_service_factory.h"
 #include "ios/chrome/browser/signin/identity_manager_factory.h"
+#import "ios/chrome/browser/signin/trusted_vault_client_backend_factory.h"
 #include "ios/chrome/browser/sync/consent_auditor_factory.h"
 #include "ios/chrome/browser/sync/device_info_sync_service_factory.h"
 #include "ios/chrome/browser/sync/ios_trusted_vault_client.h"
@@ -86,7 +87,8 @@
           ios::BookmarkSyncServiceFactory::GetForBrowserState(browser_state_));
 
   trusted_vault_client_ = std::make_unique<IOSTrustedVaultClient>(
-      ChromeAccountManagerServiceFactory::GetForBrowserState(browser_state_));
+      ChromeAccountManagerServiceFactory::GetForBrowserState(browser_state_),
+      TrustedVaultClientBackendFactory::GetForBrowserState(browser_state_));
 }
 
 IOSChromeSyncClient::~IOSChromeSyncClient() {}
diff --git a/ios/chrome/browser/sync/ios_trusted_vault_client.h b/ios/chrome/browser/sync/ios_trusted_vault_client.h
index 3c6f8d3..9d9c3e0 100644
--- a/ios/chrome/browser/sync/ios_trusted_vault_client.h
+++ b/ios/chrome/browser/sync/ios_trusted_vault_client.h
@@ -7,14 +7,16 @@
 
 #include "components/sync/driver/trusted_vault_client.h"
 
+@class ChromeIdentity;
 class ChromeAccountManagerService;
+class TrustedVaultClientBackend;
 
 // iOS version of TrustedVaultClient. This class uses the Chrome trusted vault
 // service to store the shared keys.
 class IOSTrustedVaultClient : public syncer::TrustedVaultClient {
  public:
-  explicit IOSTrustedVaultClient(
-      ChromeAccountManagerService* account_manager_service);
+  IOSTrustedVaultClient(ChromeAccountManagerService* account_manager_service,
+                        TrustedVaultClientBackend* trusted_vault_service);
   ~IOSTrustedVaultClient() override;
 
   // TrustedVaultClient implementation.
@@ -43,7 +45,11 @@
   IOSTrustedVaultClient& operator=(const IOSTrustedVaultClient&) = delete;
 
  private:
-  ChromeAccountManagerService* account_manager_service_ = nullptr;
+  // Returns the identity for `account_info`.
+  ChromeIdentity* IdentityForAccount(const CoreAccountInfo& account_info);
+
+  ChromeAccountManagerService* const account_manager_service_ = nullptr;
+  TrustedVaultClientBackend* const backend_ = nullptr;
 };
 
 #endif  // IOS_CHROME_BROWSER_SYNC_IOS_TRUSTED_VAULT_CLIENT_H_
diff --git a/ios/chrome/browser/sync/ios_trusted_vault_client.mm b/ios/chrome/browser/sync/ios_trusted_vault_client.mm
index 6d944641..a032f66 100644
--- a/ios/chrome/browser/sync/ios_trusted_vault_client.mm
+++ b/ios/chrome/browser/sync/ios_trusted_vault_client.mm
@@ -6,46 +6,36 @@
 
 #include "components/signin/public/identity_manager/account_info.h"
 #import "ios/chrome/browser/signin/chrome_account_manager_service.h"
-#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
-#include "ios/public/provider/chrome/browser/signin/chrome_trusted_vault_service.h"
+#import "ios/chrome/browser/signin/trusted_vault_client_backend.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
 IOSTrustedVaultClient::IOSTrustedVaultClient(
-    ChromeAccountManagerService* account_manager_service)
-    : account_manager_service_(account_manager_service) {
+    ChromeAccountManagerService* account_manager_service,
+    TrustedVaultClientBackend* trusted_vault_client_backend)
+    : account_manager_service_(account_manager_service),
+      backend_(trusted_vault_client_backend) {
   DCHECK(account_manager_service_);
+  DCHECK(backend_);
 }
 
 IOSTrustedVaultClient::~IOSTrustedVaultClient() = default;
 
 void IOSTrustedVaultClient::AddObserver(Observer* observer) {
-  ios::ChromeTrustedVaultService* trusted_vault_service =
-      ios::GetChromeBrowserProvider().GetChromeTrustedVaultService();
-  if (trusted_vault_service) {
-    trusted_vault_service->AddObserver(observer);
-  }
+  backend_->AddObserver(observer);
 }
 
 void IOSTrustedVaultClient::RemoveObserver(Observer* observer) {
-  ios::ChromeTrustedVaultService* trusted_vault_service =
-      ios::GetChromeBrowserProvider().GetChromeTrustedVaultService();
-  if (trusted_vault_service) {
-    trusted_vault_service->RemoveObserver(observer);
-  }
+  backend_->RemoveObserver(observer);
 }
 
 void IOSTrustedVaultClient::FetchKeys(
     const CoreAccountInfo& account_info,
     base::OnceCallback<void(const std::vector<std::vector<uint8_t>>&)>
         callback) {
-  ChromeIdentity* identity =
-      account_manager_service_->GetIdentityWithGaiaID(account_info.gaia);
-
-  ios::GetChromeBrowserProvider().GetChromeTrustedVaultService()->FetchKeys(
-      identity, std::move(callback));
+  backend_->FetchKeys(IdentityForAccount(account_info), std::move(callback));
 }
 
 void IOSTrustedVaultClient::StoreKeys(
@@ -59,31 +49,18 @@
 void IOSTrustedVaultClient::MarkLocalKeysAsStale(
     const CoreAccountInfo& account_info,
     base::OnceCallback<void(bool)> callback) {
-  ChromeIdentity* identity =
-      account_manager_service_->GetIdentityWithGaiaID(account_info.gaia);
-
-  ios::GetChromeBrowserProvider()
-      .GetChromeTrustedVaultService()
-      ->MarkLocalKeysAsStale(identity,
-                             base::BindOnce(
-                                 [](base::OnceCallback<void(bool)> callback) {
-                                   // Since false positives are allowed in the
-                                   // API, always return true, indicating that
-                                   // something may have changed.
-                                   std::move(callback).Run(true);
-                                 },
-                                 std::move(callback)));
+  backend_->MarkLocalKeysAsStale(
+      IdentityForAccount(account_info),
+      // Since false positives are allowed in the API, always invoke `callback`
+      // with true, indicating that something may have changed.
+      base::BindOnce(std::move(callback), true));
 }
 
 void IOSTrustedVaultClient::GetIsRecoverabilityDegraded(
     const CoreAccountInfo& account_info,
     base::OnceCallback<void(bool)> callback) {
-  ChromeIdentity* identity =
-      account_manager_service_->GetIdentityWithGaiaID(account_info.gaia);
-
-  ios::GetChromeBrowserProvider()
-      .GetChromeTrustedVaultService()
-      ->GetDegradedRecoverabilityStatus(identity, std::move(callback));
+  backend_->GetDegradedRecoverabilityStatus(IdentityForAccount(account_info),
+                                            std::move(callback));
 }
 
 void IOSTrustedVaultClient::AddTrustedRecoveryMethod(
@@ -100,3 +77,8 @@
   // TODO(crbug.com/1273080): decide whether this logic needs to be implemented
   // on iOS.
 }
+
+ChromeIdentity* IOSTrustedVaultClient::IdentityForAccount(
+    const CoreAccountInfo& account_info) {
+  return account_manager_service_->GetIdentityWithGaiaID(account_info.gaia);
+}
diff --git a/ios/chrome/browser/sync/sync_service_factory.mm b/ios/chrome/browser/sync/sync_service_factory.mm
index 4be5148..46915532 100644
--- a/ios/chrome/browser/sync/sync_service_factory.mm
+++ b/ios/chrome/browser/sync/sync_service_factory.mm
@@ -32,6 +32,7 @@
 #include "ios/chrome/browser/signin/about_signin_internals_factory.h"
 #import "ios/chrome/browser/signin/chrome_account_manager_service_factory.h"
 #include "ios/chrome/browser/signin/identity_manager_factory.h"
+#import "ios/chrome/browser/signin/trusted_vault_client_backend_factory.h"
 #include "ios/chrome/browser/sync/consent_auditor_factory.h"
 #include "ios/chrome/browser/sync/device_info_sync_service_factory.h"
 #include "ios/chrome/browser/sync/ios_chrome_sync_client.h"
@@ -120,6 +121,7 @@
   DependsOn(ReadingListModelFactory::GetInstance());
   DependsOn(SessionSyncServiceFactory::GetInstance());
   DependsOn(SyncInvalidationsServiceFactory::GetInstance());
+  DependsOn(TrustedVaultClientBackendFactory::GetInstance());
 }
 
 SyncServiceFactory::~SyncServiceFactory() {}
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
index fa4b72e..c2ca762 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
@@ -155,6 +155,7 @@
                                                               (syncer::
                                                                    TrustedVaultUserActionTriggerForUMA)
                                                                   trigger {
+  DCHECK(!browser->GetBrowserState()->IsOffTheRecord());
   return [[TrustedVaultReauthenticationCoordinator alloc]
       initWithBaseViewController:viewController
                          browser:browser
diff --git a/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/BUILD.gn b/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/BUILD.gn
index 9d09226..3d930f992 100644
--- a/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/BUILD.gn
@@ -16,10 +16,10 @@
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/signin",
+    "//ios/chrome/browser/signin:trusted_vault",
+    "//ios/chrome/browser/signin:trusted_vault_factory",
     "//ios/chrome/browser/ui/alert_coordinator",
     "//ios/chrome/browser/ui/authentication/signin:signin_protected",
-    "//ios/public/provider/chrome/browser",
-    "//ios/public/provider/chrome/browser/signin",
     "//ui/base",
   ]
   public_deps =
@@ -38,7 +38,9 @@
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/signin:test_support",
+    "//ios/chrome/browser/signin:trusted_vault_factory",
     "//ios/chrome/test:test_support",
+    "//ios/chrome/test/providers/signin:trusted_vault",
     "//ios/public/provider/chrome/browser/signin:fake_chrome_identity",
     "//ios/public/provider/chrome/browser/signin:test_support",
     "//ios/web/common:uikit",
diff --git a/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator.mm
index 0f062d4..11d3c584 100644
--- a/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator.mm
@@ -7,14 +7,15 @@
 #import "base/strings/sys_string_conversions.h"
 #import "components/strings/grit/components_strings.h"
 #import "components/sync/driver/sync_service_utils.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/signin/authentication_service.h"
 #import "ios/chrome/browser/signin/authentication_service_factory.h"
+#import "ios/chrome/browser/signin/trusted_vault_client_backend.h"
+#import "ios/chrome/browser/signin/trusted_vault_client_backend_factory.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/authentication/signin/signin_coordinator+protected.h"
 #import "ios/chrome/grit/ios_strings.h"
-#import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
-#import "ios/public/provider/chrome/browser/signin/chrome_trusted_vault_service.h"
 #import "ui/base/l10n/l10n_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -41,6 +42,7 @@
                         intent:(SigninTrustedVaultDialogIntent)intent
                        trigger:(syncer::TrustedVaultUserActionTriggerForUMA)
                                    trigger {
+  DCHECK(!browser->GetBrowserState()->IsOffTheRecord());
   self = [super initWithBaseViewController:viewController browser:browser];
   if (self) {
     _intent = intent;
@@ -60,8 +62,6 @@
 
 - (void)interruptWithAction:(SigninCoordinatorInterruptAction)action
                  completion:(ProceduralBlock)completion {
-  ios::ChromeTrustedVaultService* trustedVaultService =
-      ios::GetChromeBrowserProvider().GetChromeTrustedVaultService();
   BOOL animated;
   switch (action) {
     case SigninCoordinatorInterruptActionNoDismiss:
@@ -87,7 +87,9 @@
       completion();
     }
   };
-  trustedVaultService->CancelDialog(animated, cancelCompletion);
+  TrustedVaultClientBackendFactory::GetForBrowserState(
+      self.browser->GetBrowserState())
+      ->CancelDialog(animated, cancelCompletion);
 }
 
 #pragma mark - ChromeCoordinator
@@ -104,8 +106,6 @@
   // If not, the coordinator can be closed successfuly, by calling
   // -[TrustedVaultReauthenticationCoordinator
   // reauthentificationCompletedWithSuccess:]
-  ios::ChromeTrustedVaultService* trustedVaultService =
-      ios::GetChromeBrowserProvider().GetChromeTrustedVaultService();
   self.identity = AuthenticationServiceFactory::GetForBrowserState(
                       self.browser->GetBrowserState())
                       ->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
@@ -120,12 +120,15 @@
       };
   switch (self.intent) {
     case SigninTrustedVaultDialogIntentFetchKeys:
-      trustedVaultService->Reauthentication(self.identity,
-                                            self.baseViewController, callback);
+      TrustedVaultClientBackendFactory::GetForBrowserState(
+          self.browser->GetBrowserState())
+          ->Reauthentication(self.identity, self.baseViewController, callback);
       break;
     case SigninTrustedVaultDialogIntentDegradedRecoverability:
-      trustedVaultService->FixDegradedRecoverability(
-          self.identity, self.baseViewController, callback);
+      TrustedVaultClientBackendFactory::GetForBrowserState(
+          self.browser->GetBrowserState())
+          ->FixDegradedRecoverability(self.identity, self.baseViewController,
+                                      callback);
       break;
   }
 }
diff --git a/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator_unittest.mm b/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator_unittest.mm
index 28fd132f..ce78dee 100644
--- a/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/trusted_vault_reauthentication/trusted_vault_reauthentication_coordinator_unittest.mm
@@ -10,10 +10,11 @@
 #import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/signin/authentication_service_factory.h"
 #import "ios/chrome/browser/signin/authentication_service_fake.h"
+#import "ios/chrome/browser/signin/trusted_vault_client_backend_factory.h"
 #import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
+#import "ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.h"
 #import "ios/public/provider/chrome/browser/signin/fake_chrome_identity.h"
 #import "ios/public/provider/chrome/browser/signin/fake_chrome_identity_service.h"
-#import "ios/public/provider/chrome/browser/signin/fake_chrome_trusted_vault_service.h"
 #import "ios/web/common/uikit_ui_util.h"
 #import "ios/web/public/test/web_task_environment.h"
 #import "testing/gmock/include/gmock/gmock.h"
@@ -98,8 +99,26 @@
       base::test::ios::kWaitForUIElementTimeout, ^bool() {
         return !base_view_controller_.presentedViewController.beingPresented;
       }));
-  ios::FakeChromeTrustedVaultService::GetInstanceFromChromeProvider()
+
+  // The TrustedVaultClientBackend instance is created by the provider API.
+  // The test implementation returns a `FakeTrustedVaultClientBackend`. The
+  // provider API implementation is selected at link time and since it is a
+  // function, if multiple implementation are linked at the same time, the
+  // linker will fail.
+  //
+  // The class `FakeTrustedVaultClientBackend` is defined in the same target
+  // as the test implementation of the trusted_vault API. This means that if
+  // the current executable succeeded at link time, it is guaranteed to use
+  // the test implementation of the trusted_vault API (as the current target
+  // depends on it, and a binary cannot depend on two version of the API).
+  //
+  // This means that it is safe to cast the `TrustedVaultClientBackend` to
+  // `FakeTrustedVaultClientBackend` at runtime.
+  static_cast<FakeTrustedVaultClientBackend*>(
+      TrustedVaultClientBackendFactory::GetForBrowserState(
+          browser_state_.get()))
       ->SimulateUserCancel();
+
   // Test the completion block.
   EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
       base::test::ios::kWaitForUIElementTimeout, ^bool() {
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index dd3fe48..60d65a0 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -348,6 +348,7 @@
   TabStripCoordinator* _tabStripCoordinator;
   TabStripLegacyCoordinator* _legacyTabStripCoordinator;
   SideSwipeController* _sideSwipeController;
+  FullscreenController* _fullscreenController;
   // The coordinator that shows the Send Tab To Self UI.
   SendTabToSelfCoordinator* _sendTabToSelfCoordinator;
   BookmarkInteractionController* _bookmarkInteractionController;
@@ -581,6 +582,8 @@
     _prerenderService->SetDelegate(self);
   }
 
+  _fullscreenController = FullscreenController::FromBrowser(self.browser);
+
   _primaryToolbarCoordinator =
       [[PrimaryToolbarCoordinator alloc] initWithBrowser:self.browser];
 
@@ -683,6 +686,7 @@
   _viewControllerDependencies.sideSwipeController = _sideSwipeController;
   _viewControllerDependencies.bookmarkInteractionController =
       _bookmarkInteractionController;
+  _viewControllerDependencies.fullscreenController = _fullscreenController;
   _viewControllerDependencies.textZoomHandler = _textZoomHandler;
   _viewControllerDependencies.helpHandler = _helpHandler;
   _viewControllerDependencies.popupMenuCommandsHandler =
@@ -749,6 +753,7 @@
   _bubblePresenter = nil;
 
   _prerenderService = nil;
+  _fullscreenController = nullptr;
 
   [self.popupMenuCoordinator stop];
   self.popupMenuCoordinator = nil;
@@ -1032,7 +1037,7 @@
       initWithScenario:ActivityScenario::TabShareButton];
 
   // Exit fullscreen if needed to make sure that share button is visible.
-  FullscreenController::FromBrowser(self.browser)->ExitFullscreen();
+  _fullscreenController->ExitFullscreen();
 
   UIBarButtonItem* anchor = nil;
   if ([self.viewController.activityServicePositioner
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
index d59ce9c28..302002d 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
@@ -183,10 +183,14 @@
   OCMExpect([mockSharingCoordinator start]);
 
   BrowserCoordinator* browser_coordinator = GetBrowserCoordinator();
+  [browser_coordinator start];
   [browser_coordinator sharePage];
 
   // Check that fullscreen is exited.
   EXPECT_EQ(1.0, controller_ptr->GetProgress());
+
+  [browser_coordinator stop];
+
   // Check that -start has been called.
   EXPECT_OCMOCK_VERIFY(classMock);
 }
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.h b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
index 897888d..485b82f 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.h
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
@@ -37,6 +37,7 @@
 // TODO(crbug.com/1331229): Remove all use of the download manager coordinator
 // from BVC
 @class DownloadManagerCoordinator;
+class FullscreenController;
 @protocol HelpCommands;
 @class KeyCommandsProvider;
 @class NewTabPageCoordinator;
@@ -70,6 +71,7 @@
   TabStripLegacyCoordinator* legacyTabStripCoordinator;
   SideSwipeController* sideSwipeController;
   BookmarkInteractionController* bookmarkInteractionController;
+  FullscreenController* fullscreenController;
   id<TextZoomCommands> textZoomHandler;
   id<HelpCommands> helpHandler;
   id<PopupMenuCommands> popupMenuCommandsHandler;
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index ca8e64a..6e357b7 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -509,9 +509,7 @@
     self.snackbarCommandsHandler = dependencies.snackbarCommandsHandler;
 
     _inNewTabAnimation = NO;
-
-    _fullscreenController = FullscreenController::FromBrowser(browser);
-
+    self.fullscreenController = dependencies.fullscreenController;
     _footerFullscreenProgress = 1.0;
 
     if (browser)
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
index 447f66a..5d4f21b4 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
@@ -39,6 +39,7 @@
 #import "ios/chrome/browser/ui/commands/snackbar_commands.h"
 #import "ios/chrome/browser/ui/commands/text_zoom_commands.h"
 #import "ios/chrome/browser/ui/download/download_manager_coordinator.h"
+#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
 #import "ios/chrome/browser/ui/main/scene_state.h"
 #import "ios/chrome/browser/ui/main/scene_state_browser_agent.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.h"
@@ -234,6 +235,8 @@
     bookmark_interaction_controller_ =
         [[BookmarkInteractionController alloc] initWithBrowser:browser_.get()];
 
+    fullscreen_controller_ = FullscreenController::FromBrowser(browser_.get());
+
     BrowserViewControllerDependencies dependencies;
     dependencies.prerenderService = fake_prerender_service_.get();
     dependencies.bubblePresenter = bubble_presenter_;
@@ -246,6 +249,7 @@
     dependencies.sideSwipeController = side_swipe_controller_;
     dependencies.bookmarkInteractionController =
         bookmark_interaction_controller_;
+    dependencies.fullscreenController = fullscreen_controller_;
 
     bvc_ = [[BrowserViewController alloc] initWithBrowser:browser_.get()
                            browserContainerViewController:container_
@@ -299,6 +303,7 @@
   TabStripLegacyCoordinator* legacy_tab_strip_coordinator_;
   SideSwipeController* side_swipe_controller_;
   BookmarkInteractionController* bookmark_interaction_controller_;
+  FullscreenController* fullscreen_controller_;
 };
 
 TEST_F(BrowserViewControllerTest, TestWebStateSelected) {
diff --git a/ios/chrome/browser/ui/elements/text_view_selection_disabled.h b/ios/chrome/browser/ui/elements/text_view_selection_disabled.h
index 5e57ced..b0df915f 100644
--- a/ios/chrome/browser/ui/elements/text_view_selection_disabled.h
+++ b/ios/chrome/browser/ui/elements/text_view_selection_disabled.h
@@ -10,6 +10,11 @@
 // UITextView subclass is needed to override -canBecomeFirstResponder to prevent
 // text selection while maintaining clickable link.
 @interface TextViewSelectionDisabled : UITextView
+
+// Creates and returns a TextViewSelectionDisabled with workarounds
+// for iOS 16 link tap issues.
++ (TextViewSelectionDisabled*)textView;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_ELEMENTS_TEXT_VIEW_SELECTION_DISABLED_H_
diff --git a/ios/chrome/browser/ui/elements/text_view_selection_disabled.mm b/ios/chrome/browser/ui/elements/text_view_selection_disabled.mm
index 3de9e2e..7989489a 100644
--- a/ios/chrome/browser/ui/elements/text_view_selection_disabled.mm
+++ b/ios/chrome/browser/ui/elements/text_view_selection_disabled.mm
@@ -10,6 +10,17 @@
 
 @implementation TextViewSelectionDisabled
 
++ (TextViewSelectionDisabled*)textView {
+// TODO(crbug.com/1335912): On iOS 16, EG is unable to tap links in
+// TextKit2-based UITextViews. Fall back to TextKit1 until this issue
+// is resolved.
+#if defined(__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
+  if (@available(iOS 16, *))
+    return [TextViewSelectionDisabled textViewUsingTextLayoutManager:NO];
+#endif
+  return [[TextViewSelectionDisabled alloc] init];
+}
+
 - (BOOL)canBecomeFirstResponder {
   return NO;
 }
diff --git a/ios/chrome/browser/ui/first_run/welcome_to_chrome_view.mm b/ios/chrome/browser/ui/first_run/welcome_to_chrome_view.mm
index a084cf8d..04678d7 100644
--- a/ios/chrome/browser/ui/first_run/welcome_to_chrome_view.mm
+++ b/ios/chrome/browser/ui/first_run/welcome_to_chrome_view.mm
@@ -276,7 +276,7 @@
 
 - (TextViewSelectionDisabled*)TOSTextView {
   if (!_TOSTextView) {
-    _TOSTextView = [[TextViewSelectionDisabled alloc] initWithFrame:CGRectZero];
+    _TOSTextView = [TextViewSelectionDisabled textView];
   }
   return _TOSTextView;
 }
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
index 2db9e1d..c52548d 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
@@ -125,6 +125,7 @@
     "overflow_menu_hosting_controller.swift",
     "overflow_menu_item.swift",
     "overflow_menu_model.swift",
+    "overflow_menu_ui_configuration.swift",
     "overflow_menu_view.swift",
     "overflow_menu_view_provider.swift",
   ]
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_hosting_controller.swift b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_hosting_controller.swift
index fd9d092f2..a7271656 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_hosting_controller.swift
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_hosting_controller.swift
@@ -12,6 +12,17 @@
   // This should be the width of the share sheet in compact height environments.
   let compactHeightSheetWidth: CGFloat = 568
 
+  let uiConfiguration: OverflowMenuUIConfiguration
+
+  init(rootView: Content, uiConfiguration: OverflowMenuUIConfiguration) {
+    self.uiConfiguration = uiConfiguration
+    super.init(rootView: rootView)
+  }
+
+  required init(coder aDecoder: NSCoder) {
+    fatalError("Not using storyboards")
+  }
+
   var compactHeightPreferredContentSize: CGSize {
     return CGSize(
       width: compactHeightSheetWidth, height: presentingViewController?.view.bounds.size.height ?? 0
@@ -25,6 +36,12 @@
     // it overrides the default size of the menu on iPad.
     preferredContentSize =
       traitCollection.verticalSizeClass == .compact ? compactHeightPreferredContentSize : .zero
+
+    uiConfiguration.presentingViewControllerHorizontalSizeClass =
+      presentingViewController?.traitCollection.horizontalSizeClass == .regular
+      ? .regular : .compact
+    uiConfiguration.presentingViewControllerVerticalSizeClass =
+      presentingViewController?.traitCollection.verticalSizeClass == .regular ? .regular : .compact
   }
 
   override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
@@ -34,5 +51,11 @@
     // it overrides the default size of the menu on iPad.
     preferredContentSize =
       traitCollection.verticalSizeClass == .compact ? compactHeightPreferredContentSize : .zero
+
+    uiConfiguration.presentingViewControllerHorizontalSizeClass =
+      presentingViewController?.traitCollection.horizontalSizeClass == .regular
+      ? .regular : .compact
+    uiConfiguration.presentingViewControllerVerticalSizeClass =
+      presentingViewController?.traitCollection.verticalSizeClass == .regular ? .regular : .compact
   }
 }
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_ui_configuration.swift b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_ui_configuration.swift
new file mode 100644
index 0000000..0c777bf
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_ui_configuration.swift
@@ -0,0 +1,21 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import SwiftUI
+
+/// Holds UI data necessary to display the overflow menu.
+@objcMembers public class OverflowMenuUIConfiguration: NSObject, ObservableObject {
+  @Published public var presentingViewControllerHorizontalSizeClass: UserInterfaceSizeClass
+  @Published public var presentingViewControllerVerticalSizeClass: UserInterfaceSizeClass
+
+  public init(
+    presentingViewControllerHorizontalSizeClass: UIUserInterfaceSizeClass,
+    presentingViewControllerVerticalSizeClass: UIUserInterfaceSizeClass
+  ) {
+    self.presentingViewControllerHorizontalSizeClass =
+      UserInterfaceSizeClass(presentingViewControllerHorizontalSizeClass) ?? .compact
+    self.presentingViewControllerVerticalSizeClass =
+      UserInterfaceSizeClass(presentingViewControllerVerticalSizeClass) ?? .compact
+  }
+}
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_view.swift b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_view.swift
index c6bb65f..f3b583a 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_view.swift
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_view.swift
@@ -12,6 +12,8 @@
 
   var model: OverflowMenuModel
 
+  var uiConfiguration: OverflowMenuUIConfiguration
+
   weak var metricsHandler: PopupMenuMetricsHandler?
 
   weak var carouselMetricsDelegate: PopupMenuCarouselMetricsDelegate?
@@ -33,6 +35,12 @@
       }.frame(height: Dimensions.destinationListHeight)
       Divider()
       OverflowMenuActionList(actionGroups: model.actionGroups, metricsHandler: metricsHandler)
+      // Add a spacer on iPad to make sure there's space below the list.
+      if uiConfiguration.presentingViewControllerHorizontalSizeClass == .regular
+        && uiConfiguration.presentingViewControllerVerticalSizeClass == .regular
+      {
+        Spacer()
+      }
     }.background(Color(.systemGroupedBackground).edgesIgnoringSafeArea(.all))
   }
 }
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_view_provider.swift b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_view_provider.swift
index 329e84ac..2842c57a1 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_view_provider.swift
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_view_provider.swift
@@ -10,12 +10,15 @@
 @available(iOS 15, *)
 @objcMembers public class OverflowMenuViewProvider: NSObject {
   public static func makeViewController(
-    withModel model: OverflowMenuModel, metricsHandler: PopupMenuMetricsHandler,
+    withModel model: OverflowMenuModel,
+    uiConfiguration: OverflowMenuUIConfiguration,
+    metricsHandler: PopupMenuMetricsHandler,
     carouselMetricsDelegate: PopupMenuCarouselMetricsDelegate
   ) -> UIViewController {
     return OverflowMenuHostingController(
       rootView: OverflowMenuView(
-        model: model, metricsHandler: metricsHandler,
-        carouselMetricsDelegate: carouselMetricsDelegate))
+        model: model, uiConfiguration: uiConfiguration, metricsHandler: metricsHandler,
+        carouselMetricsDelegate: carouselMetricsDelegate),
+      uiConfiguration: uiConfiguration)
   }
 }
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
index 0820d07..cc588e4 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
@@ -404,9 +404,18 @@
 
         self.contentBlockerMediator.consumer = self.overflowMenuMediator;
 
+        OverflowMenuUIConfiguration* uiConfiguration =
+            [[OverflowMenuUIConfiguration alloc]
+                initWithPresentingViewControllerHorizontalSizeClass:
+                    self.baseViewController.traitCollection.horizontalSizeClass
+                          presentingViewControllerVerticalSizeClass:
+                              self.baseViewController.traitCollection
+                                  .verticalSizeClass];
+
         UIViewController* menu = [OverflowMenuViewProvider
             makeViewControllerWithModel:self.overflowMenuMediator
                                             .overflowMenuModel
+                        uiConfiguration:uiConfiguration
                          metricsHandler:self
                 carouselMetricsDelegate:self.overflowMenuMediator];
 
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm b/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
index 5e23337..1c59279 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
@@ -377,8 +377,7 @@
 
   if (entry->DistilledState() == ReadingListEntry::PROCESSED) {
     const GURL entryURL = entry->URL();
-    GURL offlineURL = reading_list::OfflineURLForPath(
-        entry->DistilledPath(), entryURL, entry->DistilledURL());
+    GURL offlineURL = reading_list::OfflineURLForURL(entryURL);
 
     if (web::features::IsLoadSimulatedRequestAPIEnabled()) {
       [self loadEntryURL:entryURL
diff --git a/ios/chrome/browser/ui/webui/policy/policy_ui_handler.mm b/ios/chrome/browser/ui/webui/policy/policy_ui_handler.mm
index 03b4c02..6853bbf3 100644
--- a/ios/chrome/browser/ui/webui/policy/policy_ui_handler.mm
+++ b/ios/chrome/browser/ui/webui/policy/policy_ui_handler.mm
@@ -2,43 +2,44 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ios/chrome/browser/ui/webui/policy/policy_ui_handler.h"
+#import "ios/chrome/browser/ui/webui/policy/policy_ui_handler.h"
 
 #import <UIKit/UIKit.h>
-#include <utility>
-#include <vector>
+#import <algorithm>
+#import <utility>
+#import <vector>
 
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/callback_helpers.h"
+#import "base/bind.h"
+#import "base/callback.h"
+#import "base/callback_helpers.h"
 #import "base/strings/sys_string_conversions.h"
-#include "base/time/time.h"
-#include "base/values.h"
-#include "components/enterprise/browser/controller/browser_dm_token_storage.h"
-#include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
-#include "components/enterprise/browser/reporting/common_pref_names.h"
-#include "components/policy/core/browser/policy_conversions.h"
-#include "components/policy/core/browser/webui/json_generation.h"
-#include "components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h"
-#include "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
-#include "components/policy/core/common/policy_map.h"
-#include "components/policy/core/common/policy_types.h"
-#include "components/policy/core/common/schema.h"
-#include "components/policy/core/common/schema_map.h"
-#include "components/policy/policy_constants.h"
-#include "components/prefs/pref_service.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/version_info/version_info.h"
-#include "ios/chrome/browser/application_context.h"
-#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
-#include "ios/chrome/browser/policy/browser_policy_connector_ios.h"
-#include "ios/chrome/browser/policy/browser_state_policy_connector.h"
-#include "ios/chrome/browser/policy/policy_conversions_client_ios.h"
+#import "base/time/time.h"
+#import "base/values.h"
+#import "components/enterprise/browser/controller/browser_dm_token_storage.h"
+#import "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
+#import "components/enterprise/browser/reporting/common_pref_names.h"
+#import "components/policy/core/browser/policy_conversions.h"
+#import "components/policy/core/browser/webui/json_generation.h"
+#import "components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h"
+#import "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
+#import "components/policy/core/common/policy_map.h"
+#import "components/policy/core/common/policy_types.h"
+#import "components/policy/core/common/schema.h"
+#import "components/policy/core/common/schema_map.h"
+#import "components/policy/policy_constants.h"
+#import "components/prefs/pref_service.h"
+#import "components/strings/grit/components_strings.h"
+#import "components/version_info/version_info.h"
+#import "ios/chrome/browser/application_context.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/policy/browser_policy_connector_ios.h"
+#import "ios/chrome/browser/policy/browser_state_policy_connector.h"
+#import "ios/chrome/browser/policy/policy_conversions_client_ios.h"
 #import "ios/chrome/common/channel_info.h"
-#include "ios/chrome/grit/ios_chromium_strings.h"
-#include "ios/chrome/grit/ios_strings.h"
+#import "ios/chrome/grit/ios_chromium_strings.h"
+#import "ios/chrome/grit/ios_strings.h"
 #import "ui/base/l10n/l10n_util.h"
-#include "ui/base/webui/web_ui_util.h"
+#import "ui/base/webui/web_ui_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -231,23 +232,21 @@
 }
 
 base::Value PolicyUIHandler::GetStatusValue(bool include_box_legend_key) const {
-  std::unique_ptr<base::DictionaryValue> machine_status(
-      new base::DictionaryValue);
-  machine_status_provider_->GetStatus(machine_status.get());
+  base::Value::Dict machine_status = machine_status_provider_->GetStatus();
 
   // Given that it's usual for users to bring their own devices and the fact
   // that device names could expose personal information. We do not show
   // this field in Device Policy Box
-  machine_status->RemoveKey("machine");
+  machine_status.Remove("machine");
 
-  base::DictionaryValue status;
-  if (!machine_status->DictEmpty()) {
+  base::Value::Dict status;
+  if (!machine_status.empty()) {
     if (include_box_legend_key) {
-      machine_status->SetString("boxLegendKey", "statusDevice");
+      machine_status.Set("boxLegendKey", "statusDevice");
     }
     status.Set("machine", std::move(machine_status));
   }
-  return status;
+  return base::Value(std::move(status));
 }
 
 void PolicyUIHandler::SendStatus() {
diff --git a/ios/chrome/browser/web/navigation_egtest.mm b/ios/chrome/browser/web/navigation_egtest.mm
index 355dba5..b1f2c5bf 100644
--- a/ios/chrome/browser/web/navigation_egtest.mm
+++ b/ios/chrome/browser/web/navigation_egtest.mm
@@ -126,6 +126,19 @@
   return std::move(http_response);
 }
 
+CGVector FixCoordinateOffset(CGVector offset) {
+#if TARGET_IPHONE_SIMULATOR
+  // TODO(crbug.com/1342819): For some unknown reason, the XCUICoordinate
+  // space is scaled by the simulator's scale factor when computing offsets
+  // relative to the app or screen.
+  if (@available(iOS 16, *)) {
+    CGFloat scale = UIScreen.mainScreen.scale;
+    return CGVectorMake(offset.dx * scale, offset.dy * scale);
+  }
+#endif
+  return offset;
+}
+
 }  // namespace
 
 // Integration tests for navigating history via JavaScript and the forward and
@@ -624,9 +637,10 @@
   // of zero.
   CGFloat leftEdge = 0;
   XCUICoordinate* leftEdgeCoord =
-      [app coordinateWithNormalizedOffset:CGVectorMake(leftEdge, 0.5)];
-  XCUICoordinate* swipeRight =
-      [leftEdgeCoord coordinateWithOffset:CGVectorMake(600, 0.5)];
+      [app coordinateWithNormalizedOffset:FixCoordinateOffset(
+                                              CGVectorMake(leftEdge, 0.5))];
+  XCUICoordinate* swipeRight = [leftEdgeCoord
+      coordinateWithOffset:FixCoordinateOffset(CGVectorMake(600, 0.5))];
 
   // Swipe back twice.
   [leftEdgeCoord pressForDuration:0.1f thenDragToCoordinate:swipeRight];
@@ -644,9 +658,10 @@
   CGFloat rightEdgeNTP = 0.99;
   CGFloat rightEdge = 1;
   XCUICoordinate* rightEdgeCoordFromNTP =
-      [app coordinateWithNormalizedOffset:CGVectorMake(rightEdgeNTP, 0.5)];
-  XCUICoordinate* swipeLeftFromNTP =
-      [rightEdgeCoordFromNTP coordinateWithOffset:CGVectorMake(-600, 0.5)];
+      [app coordinateWithNormalizedOffset:FixCoordinateOffset(
+                                              CGVectorMake(rightEdgeNTP, 0.5))];
+  XCUICoordinate* swipeLeftFromNTP = [rightEdgeCoordFromNTP
+      coordinateWithOffset:FixCoordinateOffset(CGVectorMake(-600, 0.5))];
 
   // Swiping forward twice and verify each page.
   [rightEdgeCoordFromNTP pressForDuration:0.1f
@@ -655,9 +670,10 @@
   [ChromeEarlGrey waitForWebStateContainingText:"pony"];
 
   XCUICoordinate* rightEdgeCoord =
-      [app coordinateWithNormalizedOffset:CGVectorMake(rightEdge, 0.5)];
-  XCUICoordinate* swipeLeft =
-      [rightEdgeCoord coordinateWithOffset:CGVectorMake(-600, 0.5)];
+      [app coordinateWithNormalizedOffset:FixCoordinateOffset(
+                                              CGVectorMake(rightEdge, 0.5))];
+  XCUICoordinate* swipeLeft = [rightEdgeCoord
+      coordinateWithOffset:FixCoordinateOffset(CGVectorMake(-600, 0.5))];
   [rightEdgeCoord pressForDuration:0.1f thenDragToCoordinate:swipeLeft];
   GREYWaitForAppToIdle(@"App failed to idle");
   [ChromeEarlGrey waitForWebStateContainingText:"onload"];
diff --git a/ios/chrome/common/ui/util/text_view_util.mm b/ios/chrome/common/ui/util/text_view_util.mm
index 24bbb4b..3c19bc7 100644
--- a/ios/chrome/common/ui/util/text_view_util.mm
+++ b/ios/chrome/common/ui/util/text_view_util.mm
@@ -9,6 +9,9 @@
 #endif
 
 UITextView* CreateUITextViewWithTextKit1() {
+// TODO(crbug.com/1335912): On iOS 16, EG is unable to tap links in
+// TextKit2-based UITextViews. Fall back to TextKit1 until this issue
+// is resolved.
 #if defined(__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
   if (@available(iOS 16, *))
     return [UITextView textViewUsingTextLayoutManager:NO];
diff --git a/ios/chrome/test/earl_grey/chrome_xcui_actions.mm b/ios/chrome/test/earl_grey/chrome_xcui_actions.mm
index 62f191f..b037afe 100644
--- a/ios/chrome/test/earl_grey/chrome_xcui_actions.mm
+++ b/ios/chrome/test/earl_grey/chrome_xcui_actions.mm
@@ -17,6 +17,19 @@
 // The splitter is 10 points wide.
 const CGFloat kSplitterWidth = 10.0;
 
+CGVector FixCoordinateOffset(CGVector offset) {
+#if TARGET_IPHONE_SIMULATOR
+  // TODO(crbug.com/1342819): For some unknown reason, the XCUICoordinate
+  // space is scaled by the simulator's scale factor when computing offsets
+  // relative to the app or screen.
+  if (@available(iOS 16, *)) {
+    CGFloat scale = UIScreen.mainScreen.scale;
+    return CGVectorMake(offset.dx * scale, offset.dy * scale);
+  }
+#endif
+  return offset;
+}
+
 // Returns a normalized vector for the given edge.
 CGVector GetNormalizedEdgeVector(GREYContentEdge edge) {
   switch (edge) {
@@ -101,8 +114,8 @@
       app, accessibility_identifier, window_number, XCUIElementTypeCell);
 
   // |app| is still an element, so it can just be passed in directly here.
-  return LongPressAndDragBetweenElements(drag_element, app,
-                                         GetNormalizedEdgeVector(edge));
+  return LongPressAndDragBetweenElements(
+      drag_element, app, FixCoordinateOffset(GetNormalizedEdgeVector(edge)));
 }
 
 BOOL LongPressCellAndDragToOffsetOf(NSString* src_accessibility_identifier,
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc b/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
index a351015..9c1bb54 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
@@ -17,6 +17,7 @@
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/location.h"
 #include "base/notreached.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/rand_util.h"
@@ -30,6 +31,7 @@
 #include "media/base/bind_to_current_loop.h"
 #include "media/capture/video/chromeos/mojom/camera_common.mojom.h"
 #include "media/capture/video/chromeos/mojom/cros_camera_client.mojom.h"
+#include "media/capture/video/chromeos/mojom/cros_camera_service.mojom.h"
 #include "media/capture/video/chromeos/video_capture_features_chromeos.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/platform/named_platform_channel.h"
@@ -151,6 +153,9 @@
 void FailedCameraHalServerCallbacks::CameraPrivacySwitchStateChange(
     cros::mojom::CameraPrivacySwitchState state) {}
 
+void FailedCameraHalServerCallbacks::CameraSWPrivacySwitchStateChange(
+    cros::mojom::CameraPrivacySwitchState state) {}
+
 // static
 CameraHalDispatcherImpl* CameraHalDispatcherImpl::GetInstance() {
   return base::Singleton<CameraHalDispatcherImpl>::get();
@@ -325,17 +330,50 @@
 cros::mojom::CameraPrivacySwitchState
 CameraHalDispatcherImpl::AddCameraPrivacySwitchObserver(
     CameraPrivacySwitchObserver* observer) {
+  base::AutoLock lock(privacy_switch_lock_);
   privacy_switch_observers_->AddObserver(observer);
-
-  base::AutoLock lock(privacy_switch_state_lock_);
   return current_privacy_switch_state_;
 }
 
 void CameraHalDispatcherImpl::RemoveCameraPrivacySwitchObserver(
     CameraPrivacySwitchObserver* observer) {
+  base::AutoLock lock(privacy_switch_lock_);
   privacy_switch_observers_->RemoveObserver(observer);
 }
 
+void CameraHalDispatcherImpl::GetCameraSWPrivacySwitchState(
+    cros::mojom::CameraHalServer::GetCameraSWPrivacySwitchStateCallback
+        callback) {
+  proxy_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &CameraHalDispatcherImpl::GetCameraSWPrivacySwitchStateOnProxyThread,
+          base::Unretained(this), std::move(callback)));
+}
+
+void CameraHalDispatcherImpl::SetCameraSWPrivacySwitchState(
+    cros::mojom::CameraPrivacySwitchState state) {
+  proxy_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &CameraHalDispatcherImpl::SetCameraSWPrivacySwitchStateOnProxyThread,
+          base::Unretained(this), state));
+}
+
+cros::mojom::CameraPrivacySwitchState
+CameraHalDispatcherImpl::AddCameraSWPrivacySwitchObserver(
+    CameraPrivacySwitchObserver* observer) {
+  base::AutoLock lock(sw_privacy_switch_lock_);
+  sw_privacy_switch_observers_->AddObserver(observer);
+  return current_sw_privacy_switch_state_;
+}
+
+void CameraHalDispatcherImpl::RemoveCameraSWPrivacySwitchObserver(
+    CameraPrivacySwitchObserver* observer) {
+  base::AutoLock lock(sw_privacy_switch_lock_);
+  sw_privacy_switch_observers_->RemoveObserver(observer);
+}
+
 void CameraHalDispatcherImpl::RegisterPluginVmToken(
     const base::UnguessableToken& token) {
   token_manager_.RegisterPluginVmToken(token);
@@ -359,7 +397,11 @@
           new base::ObserverListThreadSafe<CameraActiveClientObserver>()),
       current_privacy_switch_state_(
           cros::mojom::CameraPrivacySwitchState::UNKNOWN),
+      current_sw_privacy_switch_state_(
+          cros::mojom::CameraPrivacySwitchState::UNKNOWN),
       privacy_switch_observers_(
+          new base::ObserverListThreadSafe<CameraPrivacySwitchObserver>()),
+      sw_privacy_switch_observers_(
           new base::ObserverListThreadSafe<CameraPrivacySwitchObserver>()) {}
 
 CameraHalDispatcherImpl::~CameraHalDispatcherImpl() {
@@ -512,7 +554,7 @@
     cros::mojom::CameraPrivacySwitchState state) {
   DCHECK(proxy_task_runner_->BelongsToCurrentThread());
 
-  base::AutoLock lock(privacy_switch_state_lock_);
+  base::AutoLock lock(privacy_switch_lock_);
   current_privacy_switch_state_ = state;
   privacy_switch_observers_->Notify(
       FROM_HERE,
@@ -522,6 +564,20 @@
                     << current_privacy_switch_state_;
 }
 
+void CameraHalDispatcherImpl::CameraSWPrivacySwitchStateChange(
+    cros::mojom::CameraPrivacySwitchState state) {
+  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
+
+  base::AutoLock lock(sw_privacy_switch_lock_);
+  current_sw_privacy_switch_state_ = state;
+  sw_privacy_switch_observers_->Notify(
+      FROM_HERE,
+      &CameraPrivacySwitchObserver::OnCameraPrivacySwitchStatusChanged,
+      current_sw_privacy_switch_state_);
+  CAMERA_LOG(EVENT) << "Camera software privacy switch state changed: "
+                    << current_sw_privacy_switch_state_;
+}
+
 base::UnguessableToken CameraHalDispatcherImpl::GetTokenForTrustedClient(
     cros::mojom::CameraClientType type) {
   return token_manager_.GetTokenForTrustedClient(type);
@@ -648,6 +704,28 @@
   }
 }
 
+void CameraHalDispatcherImpl::GetCameraSWPrivacySwitchStateOnProxyThread(
+    cros::mojom::CameraHalServer::GetCameraSWPrivacySwitchStateCallback
+        callback) {
+  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
+  if (!camera_hal_server_) {
+    LOG(ERROR) << "Camera HAL server is not registered";
+    std::move(callback).Run(cros::mojom::CameraPrivacySwitchState::UNKNOWN);
+    return;
+  }
+  camera_hal_server_->GetCameraSWPrivacySwitchState(std::move(callback));
+}
+
+void CameraHalDispatcherImpl::SetCameraSWPrivacySwitchStateOnProxyThread(
+    cros::mojom::CameraPrivacySwitchState state) {
+  DCHECK(proxy_task_runner_->BelongsToCurrentThread());
+  if (!camera_hal_server_) {
+    LOG(ERROR) << "Camera HAL server is not registered";
+    return;
+  }
+  camera_hal_server_->SetCameraSWPrivacySwitchState(state);
+}
+
 void CameraHalDispatcherImpl::RegisterClientWithTokenOnProxyThread(
     mojo::PendingRemote<cros::mojom::CameraHalClient> client,
     cros::mojom::CameraClientType type,
@@ -707,20 +785,22 @@
 
 void CameraHalDispatcherImpl::OnCameraHalServerConnectionError() {
   DCHECK(proxy_task_runner_->BelongsToCurrentThread());
-  base::AutoLock lock(opened_camera_id_map_lock_);
-  CAMERA_LOG(EVENT) << "Camera HAL server connection lost";
-  camera_hal_server_.reset();
-  camera_hal_server_callbacks_.reset();
-  for (auto& [camera_client_type, camera_id_set] : opened_camera_id_map_) {
-    if (!camera_id_set.empty()) {
-      active_client_observers_->Notify(
-          FROM_HERE, &CameraActiveClientObserver::OnActiveClientChange,
-          camera_client_type, /*is_active=*/false);
+  {
+    base::AutoLock lock(opened_camera_id_map_lock_);
+    CAMERA_LOG(EVENT) << "Camera HAL server connection lost";
+    camera_hal_server_.reset();
+    camera_hal_server_callbacks_.reset();
+    for (auto& [camera_client_type, camera_id_set] : opened_camera_id_map_) {
+      if (!camera_id_set.empty()) {
+        active_client_observers_->Notify(
+            FROM_HERE, &CameraActiveClientObserver::OnActiveClientChange,
+            camera_client_type, /*is_active=*/false);
+      }
     }
+    opened_camera_id_map_.clear();
   }
-  opened_camera_id_map_.clear();
 
-  base::AutoLock privacy_lock(privacy_switch_state_lock_);
+  base::AutoLock lock(privacy_switch_lock_);
   current_privacy_switch_state_ =
       cros::mojom::CameraPrivacySwitchState::UNKNOWN;
   privacy_switch_observers_->Notify(
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl.h b/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
index ce80fa5..e6db465 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
@@ -97,6 +97,8 @@
                                   cros::mojom::CameraClientType type) override;
   void CameraPrivacySwitchStateChange(
       cros::mojom::CameraPrivacySwitchState state) override;
+  void CameraSWPrivacySwitchStateChange(
+      cros::mojom::CameraPrivacySwitchState state) override;
 
   mojo::Receiver<cros::mojom::CameraHalServerCallbacks> callbacks_;
 };
@@ -119,6 +121,11 @@
 // establish direct Mojo connections between the CameraHalServer and the
 // CameraHalClients.
 //
+// CameraHalDispatcherImpl owns two threads. blocking_io_thread_ is for
+// communicating with a socket file to listen for Mojo connection buildup
+// request. proxy_thread_ is the thread where the Mojo channel is bound and all
+// communication through Mojo will happen.
+//
 // For general documentation about the CameraHalDispatcher Mojo interface see
 // the comments in mojo/cros_camera_service.mojom.
 //
@@ -168,7 +175,27 @@
   // being destroyed.
   void RemoveCameraPrivacySwitchObserver(CameraPrivacySwitchObserver* observer);
 
-  // Called by vm_permission_service to register the token used for pluginvm.
+  // Gets the current camera software privacy switch state.
+  void GetCameraSWPrivacySwitchState(
+      cros::mojom::CameraHalServer::GetCameraSWPrivacySwitchStateCallback
+          callback);
+
+  // Sets the camera software privacy switch state.
+  void SetCameraSWPrivacySwitchState(
+      cros::mojom::CameraPrivacySwitchState state);
+
+  // Adds an observer to get notified when the camera software privacy switch
+  // status changed.
+  cros::mojom::CameraPrivacySwitchState AddCameraSWPrivacySwitchObserver(
+      CameraPrivacySwitchObserver* observer);
+
+  // Removes the observer. A previously-added observer must be removed before
+  // being destroyed.
+  void RemoveCameraSWPrivacySwitchObserver(
+      CameraPrivacySwitchObserver* observer);
+
+  // Called by vm_permission_service to register the token used for
+  // pluginvm.
   void RegisterPluginVmToken(const base::UnguessableToken& token);
   void UnregisterPluginVmToken(const base::UnguessableToken& token);
 
@@ -206,6 +233,8 @@
                                   cros::mojom::CameraClientType type) final;
   void CameraPrivacySwitchStateChange(
       cros::mojom::CameraPrivacySwitchState state) final;
+  void CameraSWPrivacySwitchStateChange(
+      cros::mojom::CameraPrivacySwitchState state) final;
 
   base::UnguessableToken GetTokenForTrustedClient(
       cros::mojom::CameraClientType type);
@@ -228,6 +257,13 @@
   // Runs on |blocking_io_thread_|.
   void StartServiceLoop(base::ScopedFD socket_fd, base::WaitableEvent* started);
 
+  void GetCameraSWPrivacySwitchStateOnProxyThread(
+      cros::mojom::CameraHalServer::GetCameraSWPrivacySwitchStateCallback
+          callback);
+
+  void SetCameraSWPrivacySwitchStateOnProxyThread(
+      cros::mojom::CameraPrivacySwitchState state);
+
   void RegisterClientWithTokenOnProxyThread(
       mojo::PendingRemote<cros::mojom::CameraHalClient> client,
       cros::mojom::CameraClientType type,
@@ -295,12 +331,21 @@
   scoped_refptr<base::ObserverListThreadSafe<CameraActiveClientObserver>>
       active_client_observers_;
 
-  base::Lock privacy_switch_state_lock_;
+  // current_privacy_switch_state_ and current_sw_privacy_switch_state_ can be
+  // accessed from the UI thread besides proxy_thread_
+  base::Lock privacy_switch_lock_;
   cros::mojom::CameraPrivacySwitchState current_privacy_switch_state_
-      GUARDED_BY(privacy_switch_state_lock_);
+      GUARDED_BY(privacy_switch_lock_);
+
+  base::Lock sw_privacy_switch_lock_;
+  cros::mojom::CameraPrivacySwitchState current_sw_privacy_switch_state_
+      GUARDED_BY(sw_privacy_switch_lock_);
 
   scoped_refptr<base::ObserverListThreadSafe<CameraPrivacySwitchObserver>>
-      privacy_switch_observers_;
+      privacy_switch_observers_ GUARDED_BY(privacy_switch_lock_);
+
+  scoped_refptr<base::ObserverListThreadSafe<CameraPrivacySwitchObserver>>
+      sw_privacy_switch_observers_ GUARDED_BY(sw_privacy_switch_lock_);
 
   bool sensor_enabled_ = true;
   std::map<CameraClientObserver*, std::unique_ptr<CameraClientObserver>>
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl_unittest.cc b/media/capture/video/chromeos/camera_hal_dispatcher_impl_unittest.cc
index 832bf6d4..2abdad3 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl_unittest.cc
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl_unittest.cc
@@ -48,6 +48,14 @@
                     cros::mojom::CameraClientType camera_client_type));
 
   MOCK_METHOD1(SetTracingEnabled, void(bool enabled));
+  MOCK_METHOD1(SetAutoFramingState,
+               void(cros::mojom::CameraAutoFramingState state));
+  MOCK_METHOD1(
+      GetCameraSWPrivacySwitchState,
+      void(cros::mojom::CameraHalServer::GetCameraSWPrivacySwitchStateCallback
+               callback));
+  MOCK_METHOD1(SetCameraSWPrivacySwitchState,
+               void(cros::mojom::CameraPrivacySwitchState state));
 
   mojo::PendingRemote<cros::mojom::CameraHalServer> GetPendingRemote() {
     return receiver_.BindNewPipeAndPassRemote();
diff --git a/media/capture/video/chromeos/mojom/cros_camera_service.mojom b/media/capture/video/chromeos/mojom/cros_camera_service.mojom
index 45c00de..ce66ef9 100644
--- a/media/capture/video/chromeos/mojom/cros_camera_service.mojom
+++ b/media/capture/video/chromeos/mojom/cros_camera_service.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 min version: 7
+// Next min version: 9
 
 module cros.mojom;
 
@@ -17,7 +17,7 @@
 // It should be kept in sync with the ChromeOSCameraClientType enum in
 // tools/metrics/histograms/enums.xml in chromium.
 [Extensible]
-enum CameraClientType{
+enum CameraClientType {
   UNKNOWN = 0,
   TESTING = 1,
   CHROME = 2,
@@ -28,10 +28,13 @@
 };
 
 // CameraPrivacySwitchState indicates the state of the camera privacy switch.
-enum CameraPrivacySwitchState{
-  // For devices which can only read the privacy switch status while the camera
-  // is streaming, it is possible that the state of privacy switch is currently
-  // unknown.
+// This can represent both software and hardware privacy switch statuses.
+enum CameraPrivacySwitchState {
+  // [HW] For devices which can only read the privacy switch status while the
+  // camera is streaming, it is possible that the state of privacy switch is
+  // currently unknown.
+  // [SW] If the CrOS Camera Service is not reachable, UNKNOWN will be returned
+  // as the result of GetCameraSWPrivacySwitchState.
   UNKNOWN = 0,
 
   // State when the privacy switch is on, which means the black frames will be
@@ -43,6 +46,18 @@
   OFF = 2,
 };
 
+// CameraAutoZoomState indicates the state of the camera autozoom feature.
+enum CameraAutoFramingState {
+  // Auto framing is disabled.
+  OFF = 0,
+
+  // Auto framing is enabled and set to single person mode.
+  ON_SINGLE = 1,
+
+  // Auto framing is enabled and set to multi people mode.
+  ON_MULTI = 2,
+};
+
 // The CrOS camera HAL v3 Mojo dispatcher.  The dispatcher acts as a proxy and
 // waits for the server and the clients to register.  There can only be one
 // server registered, with multiple clients requesting connections to the
@@ -113,6 +128,18 @@
   // Enable or disable tracing.
   [MinVersion=3]
   SetTracingEnabled@1(bool enabled);
+
+  // Enable or disable auto framing.
+  [MinVersion=7]
+  SetAutoFramingState@2(CameraAutoFramingState state);
+
+  // Get the current camera software privacy switch state.
+  [MinVersion=8]
+  GetCameraSWPrivacySwitchState@3() => (CameraPrivacySwitchState state);
+
+  // Enable or disable the camera software privacy switch.
+  [MinVersion=8]
+  SetCameraSWPrivacySwitchState@4(CameraPrivacySwitchState state);
 };
 
 // CameraHalServerCallbacks is an interface for CameraHalServer to notify
@@ -133,4 +160,8 @@
   // current status when the callbacks are registered.
   [MinVersion=5]
   CameraPrivacySwitchStateChange@1(CameraPrivacySwitchState state);
+
+  // Fired when the camera software privacy switch status is changed.
+  [MinVersion=8]
+  CameraSWPrivacySwitchStateChange@2(CameraPrivacySwitchState state);
 };
diff --git a/media/gpu/android/codec_image.cc b/media/gpu/android/codec_image.cc
index f86895b..d81393d 100644
--- a/media/gpu/android/codec_image.cc
+++ b/media/gpu/android/codec_image.cc
@@ -10,6 +10,7 @@
 
 #include "base/android/scoped_hardware_buffer_fence_sync.h"
 #include "base/callback_helpers.h"
+#include "base/debug/dump_without_crashing.h"
 #include "gpu/command_buffer/service/gles2_cmd_decoder.h"
 #include "gpu/command_buffer/service/texture_manager.h"
 #include "gpu/config/gpu_finch_features.h"
@@ -117,6 +118,15 @@
     return false;
   }
 
+  // Our hypothesis is that in actuality the rendering to the front buffer and
+  // binding of the image have always already occurred by the time that this
+  // method is called. The below DumpWithoutCrashing() call serves to verify
+  // whether this hypothesis is correct. See crbug.com/1310020 for details.
+  // TODO(crbug.com/1310020): Remove this code as part of removing this entire
+  // function once we have verified that it is indeed no longer needed.
+  if (!output_buffer_renderer_->was_rendered_to_front_buffer())
+    base::debug::DumpWithoutCrashing();
+
   // On some devices GL_TEXTURE_BINDING_EXTERNAL_OES is not supported as
   // glGetIntegerv() parameter. In this case the value of |texture_id| will be
   // zero and we assume that it is properly bound to TextureOwner's texture id.
diff --git a/media/gpu/v4l2/v4l2_device.cc b/media/gpu/v4l2/v4l2_device.cc
index 5ae926d..d205262 100644
--- a/media/gpu/v4l2/v4l2_device.cc
+++ b/media/gpu/v4l2/v4l2_device.cc
@@ -20,6 +20,7 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/numerics/checked_math.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/strings/string_number_conversions.h"
@@ -27,6 +28,7 @@
 #include "build/build_config.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/color_plane_layout.h"
+#include "media/base/media_switches.h"
 #include "media/base/video_types.h"
 #include "media/gpu/chromeos/fourcc.h"
 #include "media/gpu/chromeos/platform_video_frame_utils.h"
@@ -2002,6 +2004,45 @@
   }
 }
 
+VideoEncodeAccelerator::SupportedRateControlMode
+V4L2Device::GetSupportedRateControlMode() {
+  auto rate_control_mode = VideoEncodeAccelerator::kNoMode;
+  v4l2_queryctrl query_ctrl;
+  memset(&query_ctrl, 0, sizeof(query_ctrl));
+  query_ctrl.id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE;
+  if (Ioctl(VIDIOC_QUERYCTRL, &query_ctrl)) {
+    DPLOG(WARNING) << "QUERYCTRL for bitrate mode failed";
+    return rate_control_mode;
+  }
+
+  v4l2_querymenu query_menu;
+  memset(&query_menu, 0, sizeof(query_menu));
+  query_menu.id = query_ctrl.id;
+  for (query_menu.index = query_ctrl.minimum;
+       base::checked_cast<int>(query_menu.index) <= query_ctrl.maximum;
+       query_menu.index++) {
+    if (Ioctl(VIDIOC_QUERYMENU, &query_menu) == 0) {
+      switch (query_menu.index) {
+        case V4L2_MPEG_VIDEO_BITRATE_MODE_CBR:
+          rate_control_mode |= VideoEncodeAccelerator::kConstantMode;
+          break;
+        case V4L2_MPEG_VIDEO_BITRATE_MODE_VBR:
+          if (!base::FeatureList::IsEnabled(kChromeOSHWVBREncoding)) {
+            DVLOGF(3) << "Skip VBR capability";
+            break;
+          }
+          rate_control_mode |= VideoEncodeAccelerator::kVariableMode;
+          break;
+        default:
+          DVLOGF(4) << "Skip bitrate mode: " << query_menu.index;
+          break;
+      }
+    }
+  }
+
+  return rate_control_mode;
+}
+
 std::vector<uint32_t> V4L2Device::EnumerateSupportedPixelformats(
     v4l2_buf_type buf_type) {
   std::vector<uint32_t> pixelformats;
@@ -2063,12 +2104,16 @@
     VideoEncodeAccelerator::SupportedProfile profile;
     profile.max_framerate_numerator = 30;
     profile.max_framerate_denominator = 1;
-    // TODO(b/182240945): remove hard-coding when VBR is supported
-    profile.rate_control_modes = media::VideoEncodeAccelerator::kConstantMode;
+
+    profile.rate_control_modes = GetSupportedRateControlMode();
+    if (profile.rate_control_modes == VideoEncodeAccelerator::kNoMode) {
+      DLOG(ERROR) << "Skipped because no bitrate mode is supported for "
+                  << FourccToString(pixelformat);
+      continue;
+    }
     gfx::Size min_resolution;
     GetSupportedResolution(pixelformat, &min_resolution,
                            &profile.max_resolution);
-
     const auto video_codec_profiles =
         V4L2PixFmtToVideoCodecProfiles(pixelformat);
 
diff --git a/media/gpu/v4l2/v4l2_device.h b/media/gpu/v4l2/v4l2_device.h
index ba0600b5..339c80d 100644
--- a/media/gpu/v4l2/v4l2_device.h
+++ b/media/gpu/v4l2/v4l2_device.h
@@ -731,6 +731,11 @@
                               gfx::Size* min_resolution,
                               gfx::Size* max_resolution);
 
+  // Get the supported bitrate control modes. This function should be called
+  // when V4L2Device opens an encoder driver node.
+  VideoEncodeAccelerator::SupportedRateControlMode
+  GetSupportedRateControlMode();
+
   std::vector<uint32_t> EnumerateSupportedPixelformats(v4l2_buf_type buf_type);
 
   // NOTE: The below methods to query capabilities have a side effect of
diff --git a/media/gpu/v4l2/v4l2_video_encode_accelerator.cc b/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
index 87193cf..bdcc3a74 100644
--- a/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
@@ -32,6 +32,7 @@
 #include "media/base/bitstream_buffer.h"
 #include "media/base/color_plane_layout.h"
 #include "media/base/media_log.h"
+#include "media/base/media_switches.h"
 #include "media/base/scopedfd_helper.h"
 #include "media/base/video_frame_layout.h"
 #include "media/base/video_types.h"
@@ -173,7 +174,6 @@
       native_input_mode_(false),
       output_buffer_byte_size_(0),
       output_format_fourcc_(0),
-      current_bitrate_(0),
       current_framerate_(0),
       encoder_state_(kUninitialized),
       device_(std::move(device)),
@@ -350,6 +350,37 @@
   }
 
   encoder_state_ = kInitialized;
+  uint32_t bitrate_mode = V4L2_MPEG_VIDEO_BITRATE_MODE_CBR;
+  switch (config.bitrate.mode()) {
+    case Bitrate::Mode::kConstant:
+      current_bitrate_ = Bitrate::ConstantBitrate(0u);
+      bitrate_mode = V4L2_MPEG_VIDEO_BITRATE_MODE_CBR;
+      break;
+    case Bitrate::Mode::kVariable:
+      if (!base::FeatureList::IsEnabled(kChromeOSHWVBREncoding)) {
+        VLOGF(1) << "VBR encoding is disabled";
+        NOTIFY_ERROR(kPlatformFailureError);
+        return;
+      }
+      current_bitrate_ = Bitrate::VariableBitrate(0u, 0u);
+      bitrate_mode = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR;
+      break;
+    default:
+      VLOGF(1) << "Invalid bitrate mode: "
+               << base::strict_cast<int>(config.bitrate.mode());
+      NOTIFY_ERROR(kInvalidArgumentError);
+      return;
+  }
+
+  if (!device_->SetExtCtrls(
+          V4L2_CID_MPEG_CLASS,
+          {V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_BITRATE_MODE, bitrate_mode)})) {
+    VLOGF(1) << "Failed to configure bitrate mode: "
+             << base::strict_cast<int>(config.bitrate.mode());
+    NOTIFY_ERROR(kPlatformFailureError);
+    return;
+  }
+
   RequestEncodingParametersChangeTask(
       config.bitrate, config.initial_framerate.value_or(
                           VideoEncodeAccelerator::kDefaultFramerate));
@@ -1492,37 +1523,42 @@
 void V4L2VideoEncodeAccelerator::RequestEncodingParametersChangeTask(
     const Bitrate& bitrate,
     uint32_t framerate) {
-  // If this is changed to use variable bitrate encoding, change this to check
-  // that the mode matches the current mode.
-  if (bitrate.mode() != Bitrate::Mode::kConstant)
-    return;
-
-  // Set bitrate control to CBR
-  // Not all devices support multiple bitrate control algorithms,
-  // so this control can't be mandatory and therefore the return
-  // value is not checked.
-  device_->SetExtCtrls(V4L2_CID_MPEG_CLASS,
-                       {V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
-                                    V4L2_MPEG_VIDEO_BITRATE_MODE_CBR)});
-
-  if (current_bitrate_ == bitrate.target_bps() &&
-      current_framerate_ == framerate) {
-    return;
-  }
-  if (bitrate.target_bps() == 0 || framerate == 0)
+  DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
+  if (current_bitrate_ == bitrate && current_framerate_ == framerate)
     return;
 
   VLOGF(2) << "bitrate=" << bitrate.ToString() << ", framerate=" << framerate;
-  DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
+  if (bitrate.mode() != current_bitrate_.mode()) {
+    VLOGF(1) << "Bitrate mode changed during encoding";
+    NOTIFY_ERROR(kInvalidArgumentError);
+    return;
+  }
+
   TRACE_EVENT2("media,gpu", "V4L2VEA::RequestEncodingParametersChangeTask",
                "bitrate", bitrate.ToString(), "framerate", framerate);
-  if (current_bitrate_ != bitrate.target_bps() &&
-      !device_->SetExtCtrls(
-          V4L2_CTRL_CLASS_MPEG,
-          {V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_BITRATE, bitrate.target_bps())})) {
-    VLOGF(1) << "Failed changing bitrate";
-    NOTIFY_ERROR(kPlatformFailureError);
-    return;
+  if (current_bitrate_ != bitrate) {
+    switch (bitrate.mode()) {
+      case Bitrate::Mode::kVariable:
+        if (!device_->SetExtCtrls(V4L2_CTRL_CLASS_MPEG,
+                                  {V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+                                               bitrate.peak_bps())})) {
+          VLOGF(1) << "Failed to change peak bitrate";
+          NOTIFY_ERROR(kPlatformFailureError);
+          return;
+        }
+
+        // Both the average and peak bitrate are to be set in VBR.
+        // Only the average bitrate are to be set in CBR.
+        [[fallthrough]];
+      case Bitrate::Mode::kConstant:
+        if (!device_->SetExtCtrls(V4L2_CTRL_CLASS_MPEG,
+                                  {V4L2ExtCtrl(V4L2_CID_MPEG_VIDEO_BITRATE,
+                                               bitrate.target_bps())})) {
+          VLOGF(1) << "Failed to change average bitrate";
+          NOTIFY_ERROR(kPlatformFailureError);
+          return;
+        }
+    }
   }
 
   if (current_framerate_ != framerate) {
@@ -1536,7 +1572,7 @@
     IOCTL_OR_ERROR_RETURN(VIDIOC_S_PARM, &parms);
   }
 
-  current_bitrate_ = bitrate.target_bps();
+  current_bitrate_ = bitrate;
   current_framerate_ = framerate;
 }
 
diff --git a/media/gpu/v4l2/v4l2_video_encode_accelerator.h b/media/gpu/v4l2/v4l2_video_encode_accelerator.h
index 793c530..686b3c2 100644
--- a/media/gpu/v4l2/v4l2_video_encode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_video_encode_accelerator.h
@@ -294,7 +294,7 @@
   size_t output_buffer_byte_size_;
   uint32_t output_format_fourcc_;
 
-  uint32_t current_bitrate_;
+  Bitrate current_bitrate_;
   size_t current_framerate_;
 
   // Encoder state, owned and operated by |encoder_task_runner_|.
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 0fb45d5..f77556f 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -8333,15 +8333,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8418,15 +8418,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8843,15 +8843,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8928,15 +8928,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index a7304bf..2bf2ab5 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -46513,15 +46513,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46598,15 +46598,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47023,15 +47023,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47108,15 +47108,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47537,15 +47537,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47622,15 +47622,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48047,15 +48047,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48132,15 +48132,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48629,15 +48629,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48714,15 +48714,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49139,15 +49139,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49224,15 +49224,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49721,15 +49721,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--client-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49806,15 +49806,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--client-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -50231,15 +50231,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M103/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M103/out/Release",
           "--impl-version=103",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -50316,15 +50316,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--implementation-outdir",
-          "../../weblayer_instrumentation_test_M104/out/Release",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
+          "--webview-apk-path=apks/SystemWebView.apk",
+          "--implementation-outdir",
+          "../../weblayer_instrumentation_test_M104/out/Release",
           "--impl-version=104",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 862dcbe..8bec23b 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -529,16 +529,16 @@
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--implementation-outdir',
-      '../../weblayer_instrumentation_test_M104/out/Release',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--impl-version=104',
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
+      '--implementation-outdir',
+      '../../weblayer_instrumentation_test_M104/out/Release',
+      '--impl-version=104'
     ],
     'identifier': 'with_impl_from_104',
     'swarming': {
@@ -546,23 +546,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.40',
+          'revision': 'version:104.0.5112.40'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--implementation-outdir',
-      '../../weblayer_instrumentation_test_M103/out/Release',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--impl-version=103',
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
+      '--implementation-outdir',
+      '../../weblayer_instrumentation_test_M103/out/Release',
+      '--impl-version=103'
     ],
     'identifier': 'with_impl_from_103',
     'swarming': {
@@ -570,10 +570,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.120',
+          'revision': 'version:103.0.5060.120'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -673,16 +673,16 @@
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--implementation-outdir',
-      '../../weblayer_instrumentation_test_M104/out/Release',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--impl-version=104',
+      '--webview-apk-path=apks/SystemWebView.apk',
+      '--implementation-outdir',
+      '../../weblayer_instrumentation_test_M104/out/Release',
+      '--impl-version=104'
     ],
     'identifier': 'with_impl_from_104',
     'swarming': {
@@ -690,23 +690,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.40',
+          'revision': 'version:104.0.5112.40'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--implementation-outdir',
-      '../../weblayer_instrumentation_test_M103/out/Release',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--impl-version=103',
+      '--webview-apk-path=apks/SystemWebView.apk',
+      '--implementation-outdir',
+      '../../weblayer_instrumentation_test_M103/out/Release',
+      '--impl-version=103'
     ],
     'identifier': 'with_impl_from_103',
     'swarming': {
@@ -714,10 +714,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.120',
+          'revision': 'version:103.0.5060.120'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -817,16 +817,16 @@
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M104/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--client-version=104',
+      '--webview-apk-path=apks/SystemWebView.apk',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M104/out/Release',
+      '--client-version=104'
     ],
     'identifier': 'with_client_from_104',
     'swarming': {
@@ -834,23 +834,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.40',
+          'revision': 'version:104.0.5112.40'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M103/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--client-version=103',
+      '--webview-apk-path=apks/SystemWebView.apk',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M103/out/Release',
+      '--client-version=103'
     ],
     'identifier': 'with_client_from_103',
     'swarming': {
@@ -858,10 +858,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.120',
+          'revision': 'version:103.0.5060.120'
         }
-      ],
-    },
+      ]
+    }
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 2e79626..801928e 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -854,6 +854,27 @@
             ]
         }
     ],
+    "AutofillEnableExtendedAddressFormats": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AutofillEnableExtendedAddressFormats"
+                    ]
+                }
+            ]
+        }
+    ],
     "AutofillEnableLabelPrecedenceForTurkishAddresses": [
         {
             "platforms": [
@@ -1646,9 +1667,43 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled",
+                    "name": "DisabledPolicy_20220621",
+                    "disable_features": [
+                        "BFCachePerformanceManagerPolicy",
+                        "BackForwardCacheSize"
+                    ]
+                },
+                {
+                    "name": "EnabledPolicy_CacheSize2_20220621",
+                    "params": {
+                        "cache_size": "2",
+                        "foreground_cache_size": "-1"
+                    },
                     "enable_features": [
-                        "BFCachePerformanceManagerPolicy"
+                        "BFCachePerformanceManagerPolicy",
+                        "BackForwardCacheSize"
+                    ]
+                },
+                {
+                    "name": "EnabledPolicy_CacheSize4_20220621",
+                    "params": {
+                        "cache_size": "4",
+                        "foreground_cache_size": "-1"
+                    },
+                    "enable_features": [
+                        "BFCachePerformanceManagerPolicy",
+                        "BackForwardCacheSize"
+                    ]
+                },
+                {
+                    "name": "EnabledPolicy_CacheSize6_20220621",
+                    "params": {
+                        "cache_size": "6",
+                        "foreground_cache_size": "-1"
+                    },
+                    "enable_features": [
+                        "BFCachePerformanceManagerPolicy",
+                        "BackForwardCacheSize"
                     ]
                 }
             ]
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 01dbdaa..71799182 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -467,6 +467,10 @@
 const base::Feature kCssSelectorFragmentAnchor{
     "CssSelectorFragmentAnchor", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Drop input events before user sees first paint https://crbug.com/1255485
+const base::Feature kDropInputEventsBeforeFirstPaint{
+    "DropInputEventsBeforeFirstPaint", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // File handling integration. https://crbug.com/829689
 const base::Feature kFileHandlingAPI {
   "FileHandlingAPI",
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 79cbf9aa..542b7ce1 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -193,6 +193,7 @@
 BLINK_COMMON_EXPORT extern const base::Feature kStorageAccessAPI;
 BLINK_COMMON_EXPORT extern const base::Feature kTextFragmentAnchor;
 BLINK_COMMON_EXPORT extern const base::Feature kCssSelectorFragmentAnchor;
+BLINK_COMMON_EXPORT extern const base::Feature kDropInputEventsBeforeFirstPaint;
 BLINK_COMMON_EXPORT extern const base::Feature kFontAccess;
 BLINK_COMMON_EXPORT extern const base::Feature kComputePressure;
 BLINK_COMMON_EXPORT extern const base::Feature kFileHandlingAPI;
diff --git a/third_party/blink/renderer/bindings/core/v8/iterable.h b/third_party/blink/renderer/bindings/core/v8/iterable.h
index 6770592..556aed45 100644
--- a/third_party/blink/renderer/bindings/core/v8/iterable.h
+++ b/third_party/blink/renderer/bindings/core/v8/iterable.h
@@ -8,7 +8,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_for_each_iterator_callback.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h"
 #include "third_party/blink/renderer/core/dom/iterator.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index 194a656..30d881c1 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -2810,6 +2810,9 @@
   // callback was requested and when it was resolved by the compositor.
   if (local_root_)
     local_root_->ViewImpl()->DidFirstVisuallyNonEmptyPaint();
+
+  if (widget_base_)
+    widget_base_->DidFirstVisuallyNonEmptyPaint();
 }
 
 void WebFrameWidgetImpl::RequestAnimationAfterDelay(
diff --git a/third_party/blink/renderer/core/html/build.gni b/third_party/blink/renderer/core/html/build.gni
index 141357b..4fb9a62 100644
--- a/third_party/blink/renderer/core/html/build.gni
+++ b/third_party/blink/renderer/core/html/build.gni
@@ -539,6 +539,8 @@
   "track/vtt/buffered_line_reader.h",
   "track/vtt/vtt_cue.cc",
   "track/vtt/vtt_cue.h",
+  "track/vtt/vtt_cue_box.cc",
+  "track/vtt/vtt_cue_box.h",
   "track/vtt/vtt_element.cc",
   "track/vtt/vtt_element.h",
   "track/vtt/vtt_parser.cc",
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.cc b/third_party/blink/renderer/core/html/media/html_video_element.cc
index 9f5f507..72d6dd0c 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element.cc
@@ -499,10 +499,14 @@
   if (!media_video_frame || !video_renderer)
     return nullptr;
 
-  const gfx::Size intrinsic_size = media_video_frame->natural_size();
+  // TODO(https://crbug.com/1341235): The choice of color type, alpha type,
+  // and color space is inappropriate in many circumstances.
+  const auto resource_provider_info =
+      SkImageInfo::Make(gfx::SizeToSkISize(media_video_frame->natural_size()),
+                        kN32_SkColorType, kPremul_SkAlphaType, nullptr);
   if (!resource_provider_ ||
       allow_accelerated_images != resource_provider_->IsAccelerated() ||
-      intrinsic_size != resource_provider_->Size()) {
+      resource_provider_info != resource_provider_->GetSkImageInfo()) {
     viz::RasterContextProvider* raster_context_provider = nullptr;
     if (allow_accelerated_images) {
       if (auto wrapper = SharedGpuContext::ContextProviderWrapper()) {
@@ -512,7 +516,7 @@
     }
     // Providing a null |raster_context_provider| creates a software provider.
     resource_provider_ = CreateResourceProviderForVideoFrame(
-        intrinsic_size, raster_context_provider);
+        resource_provider_info, raster_context_provider);
     if (!resource_provider_)
       return nullptr;
   }
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
index 66ac7bf..22a9925 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
@@ -40,11 +40,11 @@
 #include "third_party/blink/renderer/core/html/html_div_element.h"
 #include "third_party/blink/renderer/core/html/track/text_track.h"
 #include "third_party/blink/renderer/core/html/track/text_track_cue_list.h"
+#include "third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.h"
 #include "third_party/blink/renderer/core/html/track/vtt/vtt_element.h"
 #include "third_party/blink/renderer/core/html/track/vtt/vtt_parser.h"
 #include "third_party/blink/renderer/core/html/track/vtt/vtt_region.h"
 #include "third_party/blink/renderer/core/html/track/vtt/vtt_scanner.h"
-#include "third_party/blink/renderer/core/layout/layout_vtt_cue.h"
 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -128,12 +128,6 @@
   return false;
 }
 
-VTTCueBox::VTTCueBox(Document& document)
-    : HTMLDivElement(document),
-      snap_to_lines_position_(std::numeric_limits<float>::quiet_NaN()) {
-  SetShadowPseudoId(AtomicString("-webkit-media-text-track-display"));
-}
-
 VTTCueBackgroundBox::VTTCueBackgroundBox(Document& document)
     : HTMLDivElement(document) {
   SetShadowPseudoId(TextTrackCue::CueShadowPseudoId());
@@ -148,93 +142,6 @@
   track_ = track;
 }
 
-void VTTCueBox::ApplyCSSProperties(
-    const VTTDisplayParameters& display_parameters) {
-  // http://dev.w3.org/html5/webvtt/#applying-css-properties-to-webvtt-node-objects
-
-  // Initialize the (root) list of WebVTT Node Objects with the following CSS
-  // settings:
-
-  // the 'position' property must be set to 'absolute'
-  SetInlineStyleProperty(CSSPropertyID::kPosition, CSSValueID::kAbsolute);
-
-  //  the 'unicode-bidi' property must be set to 'plaintext'
-  SetInlineStyleProperty(CSSPropertyID::kUnicodeBidi,
-                         CSSValueID::kWebkitPlaintext);
-
-  // the 'direction' property must be set to direction
-  SetInlineStyleProperty(CSSPropertyID::kDirection,
-                         display_parameters.direction);
-
-  // the 'writing-mode' property must be set to writing-mode
-  SetInlineStyleProperty(CSSPropertyID::kWebkitWritingMode,
-                         display_parameters.writing_mode);
-
-  const gfx::PointF& position = display_parameters.position;
-
-  // the 'top' property must be set to top,
-  SetInlineStyleProperty(CSSPropertyID::kTop, position.y(),
-                         CSSPrimitiveValue::UnitType::kPercentage);
-
-  // the 'left' property must be set to left
-  SetInlineStyleProperty(CSSPropertyID::kLeft, position.x(),
-                         CSSPrimitiveValue::UnitType::kPercentage);
-
-  // the 'width' property must be set to width, and the 'height' property  must
-  // be set to height
-  if (display_parameters.writing_mode == CSSValueID::kHorizontalTb) {
-    SetInlineStyleProperty(CSSPropertyID::kWidth, display_parameters.size,
-                           CSSPrimitiveValue::UnitType::kPercentage);
-    SetInlineStyleProperty(CSSPropertyID::kHeight, CSSValueID::kAuto);
-  } else {
-    SetInlineStyleProperty(CSSPropertyID::kWidth, CSSValueID::kAuto);
-    SetInlineStyleProperty(CSSPropertyID::kHeight, display_parameters.size,
-                           CSSPrimitiveValue::UnitType::kPercentage);
-  }
-
-  // The 'text-align' property on the (root) List of WebVTT Node Objects must
-  // be set to the value in the second cell of the row of the table below
-  // whose first cell is the value of the corresponding cue's WebVTT cue
-  // text alignment:
-  SetInlineStyleProperty(CSSPropertyID::kTextAlign,
-                         display_parameters.text_align);
-
-  // TODO(foolip): The position adjustment for non-snap-to-lines cues has
-  // been removed from the spec:
-  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=19178
-  if (std::isnan(display_parameters.snap_to_lines_position)) {
-    // 10.13.1 Set up x and y:
-    // Note: x and y are set through the CSS left and top above.
-    // 10.13.2 Position the boxes in boxes such that the point x% along the
-    // width of the bounding box of the boxes in boxes is x% of the way
-    // across the width of the video's rendering area, and the point y%
-    // along the height of the bounding box of the boxes in boxes is y%
-    // of the way across the height of the video's rendering area, while
-    // maintaining the relative positions of the boxes in boxes to each
-    // other.
-    SetInlineStyleProperty(CSSPropertyID::kTransform,
-                           String::Format("translate(-%.2f%%, -%.2f%%)",
-                                          position.x(), position.y()));
-    SetInlineStyleProperty(CSSPropertyID::kWhiteSpace, CSSValueID::kPre);
-  }
-
-  // The snap-to-lines position is propagated to LayoutVTTCue.
-  snap_to_lines_position_ = display_parameters.snap_to_lines_position;
-}
-
-LayoutObject* VTTCueBox::CreateLayoutObject(const ComputedStyle& style,
-                                            LegacyLayout legacy) {
-  // If WebVTT Regions are used, the regular WebVTT layout algorithm is no
-  // longer necessary, since cues having the region parameter set do not have
-  // any positioning parameters. Also, in this case, the regions themselves
-  // have positioning information.
-  if (style.GetPosition() == EPosition::kRelative)
-    return HTMLDivElement::CreateLayoutObject(style, legacy);
-
-  UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByVTTCue);
-  return MakeGarbageCollected<LayoutVTTCue>(this, snap_to_lines_position_);
-}
-
 VTTCue::VTTCue(Document& document,
                double start_time,
                double end_time,
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
index 1dc0013..227a0466 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
@@ -42,7 +42,7 @@
 class Document;
 class ExecutionContext;
 class V8UnionAutoKeywordOrDouble;
-class VTTCue;
+class VTTCueBox;
 class VTTRegion;
 class VTTScanner;
 
@@ -62,21 +62,6 @@
   double snap_to_lines_position;
 };
 
-class VTTCueBox final : public HTMLDivElement {
- public:
-  explicit VTTCueBox(Document&);
-
-  void ApplyCSSProperties(const VTTDisplayParameters&);
-
- private:
-  LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override;
-
-  // The computed line position for snap-to-lines layout, and NaN for
-  // non-snap-to-lines layout where no adjustment should take place.
-  // This is set in applyCSSProperties and propagated to LayoutVTTCue.
-  float snap_to_lines_position_;
-};
-
 class VTTCueBackgroundBox final : public HTMLDivElement {
  public:
   explicit VTTCueBackgroundBox(Document&);
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.cc
new file mode 100644
index 0000000..72cb31cc
--- /dev/null
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.cc
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2013, Opera Software ASA. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Opera Software ASA nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.h"
+
+#include "third_party/blink/renderer/core/css/css_property_names.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
+#include "third_party/blink/renderer/core/html/track/vtt/vtt_cue.h"
+#include "third_party/blink/renderer/core/layout/layout_vtt_cue.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+
+namespace blink {
+
+VTTCueBox::VTTCueBox(Document& document)
+    : HTMLDivElement(document),
+      snap_to_lines_position_(std::numeric_limits<float>::quiet_NaN()) {
+  SetShadowPseudoId(AtomicString("-webkit-media-text-track-display"));
+}
+
+void VTTCueBox::ApplyCSSProperties(
+    const VTTDisplayParameters& display_parameters) {
+  // http://dev.w3.org/html5/webvtt/#applying-css-properties-to-webvtt-node-objects
+
+  // Initialize the (root) list of WebVTT Node Objects with the following CSS
+  // settings:
+
+  // the 'position' property must be set to 'absolute'
+  SetInlineStyleProperty(CSSPropertyID::kPosition, CSSValueID::kAbsolute);
+
+  //  the 'unicode-bidi' property must be set to 'plaintext'
+  SetInlineStyleProperty(CSSPropertyID::kUnicodeBidi,
+                         CSSValueID::kWebkitPlaintext);
+
+  // the 'direction' property must be set to direction
+  SetInlineStyleProperty(CSSPropertyID::kDirection,
+                         display_parameters.direction);
+
+  // the 'writing-mode' property must be set to writing-mode
+  SetInlineStyleProperty(CSSPropertyID::kWebkitWritingMode,
+                         display_parameters.writing_mode);
+
+  const gfx::PointF& position = display_parameters.position;
+
+  // the 'top' property must be set to top,
+  SetInlineStyleProperty(CSSPropertyID::kTop, position.y(),
+                         CSSPrimitiveValue::UnitType::kPercentage);
+
+  // the 'left' property must be set to left
+  SetInlineStyleProperty(CSSPropertyID::kLeft, position.x(),
+                         CSSPrimitiveValue::UnitType::kPercentage);
+
+  // the 'width' property must be set to width, and the 'height' property  must
+  // be set to height
+  if (display_parameters.writing_mode == CSSValueID::kHorizontalTb) {
+    SetInlineStyleProperty(CSSPropertyID::kWidth, display_parameters.size,
+                           CSSPrimitiveValue::UnitType::kPercentage);
+    SetInlineStyleProperty(CSSPropertyID::kHeight, CSSValueID::kAuto);
+  } else {
+    SetInlineStyleProperty(CSSPropertyID::kWidth, CSSValueID::kAuto);
+    SetInlineStyleProperty(CSSPropertyID::kHeight, display_parameters.size,
+                           CSSPrimitiveValue::UnitType::kPercentage);
+  }
+
+  // The 'text-align' property on the (root) List of WebVTT Node Objects must
+  // be set to the value in the second cell of the row of the table below
+  // whose first cell is the value of the corresponding cue's WebVTT cue
+  // text alignment:
+  SetInlineStyleProperty(CSSPropertyID::kTextAlign,
+                         display_parameters.text_align);
+
+  // TODO(foolip): The position adjustment for non-snap-to-lines cues has
+  // been removed from the spec:
+  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=19178
+  if (std::isnan(display_parameters.snap_to_lines_position)) {
+    // 10.13.1 Set up x and y:
+    // Note: x and y are set through the CSS left and top above.
+    // 10.13.2 Position the boxes in boxes such that the point x% along the
+    // width of the bounding box of the boxes in boxes is x% of the way
+    // across the width of the video's rendering area, and the point y%
+    // along the height of the bounding box of the boxes in boxes is y%
+    // of the way across the height of the video's rendering area, while
+    // maintaining the relative positions of the boxes in boxes to each
+    // other.
+    SetInlineStyleProperty(CSSPropertyID::kTransform,
+                           String::Format("translate(-%.2f%%, -%.2f%%)",
+                                          position.x(), position.y()));
+    SetInlineStyleProperty(CSSPropertyID::kWhiteSpace, CSSValueID::kPre);
+  }
+
+  // The snap-to-lines position is propagated to LayoutVTTCue.
+  snap_to_lines_position_ = display_parameters.snap_to_lines_position;
+}
+
+LayoutObject* VTTCueBox::CreateLayoutObject(const ComputedStyle& style,
+                                            LegacyLayout legacy) {
+  // If WebVTT Regions are used, the regular WebVTT layout algorithm is no
+  // longer necessary, since cues having the region parameter set do not have
+  // any positioning parameters. Also, in this case, the regions themselves
+  // have positioning information.
+  if (style.GetPosition() == EPosition::kRelative)
+    return HTMLDivElement::CreateLayoutObject(style, legacy);
+
+  UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByVTTCue);
+  return MakeGarbageCollected<LayoutVTTCue>(this, snap_to_lines_position_);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.h b/third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.h
new file mode 100644
index 0000000..e9eb6ac
--- /dev/null
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2013, Opera Software ASA. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Opera Software ASA nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_TRACK_VTT_VTT_CUE_BOX_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_TRACK_VTT_VTT_CUE_BOX_H_
+
+#include "third_party/blink/renderer/core/html/html_div_element.h"
+
+namespace blink {
+
+struct VTTDisplayParameters;
+
+// VTTCueBox represents the bounding box of a VTTCue.
+//
+// It is an element in a media element shadow tree, and is always a child of
+// TextTrackContainer. It always has a single VTTCueBackgroundBox child.
+class VTTCueBox final : public HTMLDivElement {
+ public:
+  explicit VTTCueBox(Document&);
+
+  void ApplyCSSProperties(const VTTDisplayParameters&);
+
+ private:
+  LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override;
+
+  // The computed line position for snap-to-lines layout, and NaN for
+  // non-snap-to-lines layout where no adjustment should take place.
+  // This is set in applyCSSProperties and propagated to LayoutVTTCue.
+  float snap_to_lines_position_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_TRACK_VTT_VTT_CUE_BOX_H_
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_region.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_region.cc
index 3fe6c60b..7de385e 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_region.cc
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_region.cc
@@ -36,6 +36,7 @@
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
 #include "third_party/blink/renderer/core/html/html_div_element.h"
+#include "third_party/blink/renderer/core/html/track/vtt/vtt_cue_box.h"
 #include "third_party/blink/renderer/core/html/track/vtt/vtt_parser.h"
 #include "third_party/blink/renderer/core/html/track/vtt/vtt_scanner.h"
 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
diff --git a/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc b/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc
index 1ee9c79..7e98b1d 100644
--- a/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc
+++ b/third_party/blink/renderer/core/layout/ng/custom/css_layout_definition.cc
@@ -17,6 +17,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_intrinsic_sizes_result_options.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_layout_callback.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_no_argument_constructor.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h"
 #include "third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
diff --git a/third_party/blink/renderer/core/script/fetch_client_settings_object_impl.cc b/third_party/blink/renderer/core/script/fetch_client_settings_object_impl.cc
index 224c0ef9..8eda386 100644
--- a/third_party/blink/renderer/core/script/fetch_client_settings_object_impl.cc
+++ b/third_party/blink/renderer/core/script/fetch_client_settings_object_impl.cc
@@ -50,9 +50,6 @@
 
 AllowedByNosniff::MimeTypeCheck
 FetchClientSettingsObjectImpl::MimeTypeCheckForClassicWorkerScript() const {
-  if (RuntimeEnabledFeatures::StrictMimeTypesForWorkersEnabled())
-    return AllowedByNosniff::MimeTypeCheck::kStrict;
-
   if (execution_context_->IsWindow()) {
     // For worker creation on a document, don't impose strict MIME-type checks
     // on the top-level worker script for backward compatibility. Note that
diff --git a/third_party/blink/renderer/core/streams/build.gni b/third_party/blink/renderer/core/streams/build.gni
index 67e60818..adbb23ca 100644
--- a/third_party/blink/renderer/core/streams/build.gni
+++ b/third_party/blink/renderer/core/streams/build.gni
@@ -43,6 +43,7 @@
   "transform_stream_default_controller.cc",
   "transform_stream_default_controller.h",
   "transform_stream_transformer.h",
+  "underlying_byte_source_base.h",
   "underlying_sink_base.cc",
   "underlying_sink_base.h",
   "underlying_source_base.cc",
diff --git a/third_party/blink/renderer/core/streams/readable_stream.cc b/third_party/blink/renderer/core/streams/readable_stream.cc
index 43a3fbc..860cedcf 100644
--- a/third_party/blink/renderer/core/streams/readable_stream.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream.cc
@@ -30,6 +30,7 @@
 #include "third_party/blink/renderer/core/streams/stream_algorithms.h"
 #include "third_party/blink/renderer/core/streams/stream_promise_resolver.h"
 #include "third_party/blink/renderer/core/streams/transferable_streams.h"
+#include "third_party/blink/renderer/core/streams/underlying_byte_source_base.h"
 #include "third_party/blink/renderer/core/streams/underlying_source_base.h"
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_controller.h"
@@ -47,6 +48,89 @@
 
 namespace blink {
 
+// Implements a pull algorithm that delegates to an UnderlyingByteSourceBase.
+// This is used when creating a ReadableByteStream from C++.
+class ReadableStream::PullAlgorithm final : public StreamAlgorithm {
+ public:
+  explicit PullAlgorithm(UnderlyingByteSourceBase* underlying_byte_source)
+      : underlying_byte_source_(underlying_byte_source) {}
+
+  v8::Local<v8::Promise> Run(ScriptState* script_state,
+                             int argc,
+                             v8::Local<v8::Value> argv[]) override {
+    DCHECK_EQ(argc, 0);
+    DCHECK(controller_);
+    ExceptionState exception_state(script_state->GetIsolate(),
+                                   ExceptionState::kUnknownContext, "", "");
+    ScriptPromise promise;
+    {
+      // This is needed because the realm of the underlying source can be
+      // different from the realm of the readable stream.
+      ScriptState::Scope scope(underlying_byte_source_->GetScriptState());
+      promise = underlying_byte_source_->Pull(controller_, exception_state);
+    }
+    if (exception_state.HadException()) {
+      auto exception = exception_state.GetException();
+      exception_state.ClearException();
+      return PromiseReject(script_state, exception);
+    }
+
+    return promise.V8Promise();
+  }
+
+  // SetController() must be called before Run() is.
+  void SetController(ReadableByteStreamController* controller) {
+    controller_ = controller;
+  }
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(underlying_byte_source_);
+    visitor->Trace(controller_);
+    StreamAlgorithm::Trace(visitor);
+  }
+
+ private:
+  Member<UnderlyingByteSourceBase> underlying_byte_source_;
+  Member<ReadableByteStreamController> controller_;
+};
+
+// Implements a cancel algorithm that delegates to an UnderlyingByteSourceBase.
+class ReadableStream::CancelAlgorithm final : public StreamAlgorithm {
+ public:
+  explicit CancelAlgorithm(UnderlyingByteSourceBase* underlying_byte_source)
+      : underlying_byte_source_(underlying_byte_source) {}
+
+  v8::Local<v8::Promise> Run(ScriptState* script_state,
+                             int argc,
+                             v8::Local<v8::Value> argv[]) override {
+    DCHECK_EQ(argc, 1);
+    ExceptionState exception_state(script_state->GetIsolate(),
+                                   ExceptionState::kUnknownContext, "", "");
+    ScriptPromise promise;
+    {
+      // This is needed because the realm of the underlying source can be
+      // different from the realm of the readable stream.
+      ScriptState::Scope scope(underlying_byte_source_->GetScriptState());
+      promise = underlying_byte_source_->Cancel(argv[0], exception_state);
+    }
+    if (exception_state.HadException()) {
+      auto exception = exception_state.GetException();
+      exception_state.ClearException();
+      return PromiseReject(script_state, exception);
+    }
+
+    return promise.V8Promise();
+  }
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(underlying_byte_source_);
+    StreamAlgorithm::Trace(visitor);
+  }
+
+ private:
+  Member<UnderlyingByteSourceBase> underlying_byte_source_;
+};
+
 ReadableStream::PipeOptions::PipeOptions()
     : prevent_close_(false), prevent_abort_(false), prevent_cancel_(false) {}
 
@@ -1201,15 +1285,13 @@
   // 3. Assert: ! IsNonNegativeNumber(highWaterMark) is true.
   DCHECK_GE(high_water_mark, 0);
 
-  // 4. Let stream be ObjectCreate(the original value of ReadableStream's
-  //    prototype property).
+  // 4. Let stream be a new ReadableStream.
   auto* stream = MakeGarbageCollected<ReadableStream>();
 
   // 5. Perform ! InitializeReadableStream(stream).
   Initialize(stream);
 
-  // 6. Let controller be ObjectCreate(the original value of
-  //    ReadableStreamDefaultController's prototype property).
+  // 6. Let controller be a new ReadableStreamDefaultController.
   auto* controller = MakeGarbageCollected<ReadableStreamDefaultController>();
 
   // 7. Perform ? SetUpReadableStreamDefaultController(stream, controller,
@@ -1226,6 +1308,54 @@
   return stream;
 }
 
+// static
+ReadableStream* ReadableStream::CreateByteStream(
+    ScriptState* script_state,
+    UnderlyingByteSourceBase* underlying_byte_source,
+    ExceptionState& exception_state) {
+  auto* pull_algorithm =
+      MakeGarbageCollected<PullAlgorithm>(underlying_byte_source);
+  auto* cancel_algorithm =
+      MakeGarbageCollected<CancelAlgorithm>(underlying_byte_source);
+  auto* stream =
+      CreateByteStream(script_state, CreateTrivialStartAlgorithm(),
+                       pull_algorithm, cancel_algorithm, exception_state);
+  DCHECK(stream);
+  DCHECK(!exception_state.HadException());
+  ReadableStreamController* controller = stream->readable_stream_controller_;
+  pull_algorithm->SetController(To<ReadableByteStreamController>(controller));
+  return stream;
+}
+
+ReadableStream* ReadableStream::CreateByteStream(
+    ScriptState* script_state,
+    StreamStartAlgorithm* start_algorithm,
+    StreamAlgorithm* pull_algorithm,
+    StreamAlgorithm* cancel_algorithm,
+    ExceptionState& exception_state) {
+  // https://streams.spec.whatwg.org/#abstract-opdef-createreadablebytestream
+  // 1. Let stream be a new ReadableStream.
+  auto* stream = MakeGarbageCollected<ReadableStream>();
+
+  // 2. Perform ! InitializeReadableStream(stream).
+  Initialize(stream);
+
+  // 3. Let controller be a new ReadableByteStreamController.
+  auto* controller = MakeGarbageCollected<ReadableByteStreamController>();
+
+  // 4. Perform ? SetUpReadableByteStreamController(stream, controller,
+  // startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, undefined).
+  ReadableByteStreamController::SetUp(script_state, stream, controller,
+                                      start_algorithm, pull_algorithm,
+                                      cancel_algorithm, 0, 0, exception_state);
+  if (exception_state.HadException()) {
+    return nullptr;
+  }
+
+  // 5. Return stream.
+  return stream;
+}
+
 ReadableStream::ReadableStream() = default;
 
 ReadableStream::~ReadableStream() = default;
@@ -1303,6 +1433,17 @@
   return result->GetAsReadableStreamDefaultReader();
 }
 
+ReadableStreamBYOBReader* ReadableStream::GetBYOBReaderForTesting(
+    ScriptState* script_state,
+    ExceptionState& exception_state) {
+  auto* options = ReadableStreamGetReaderOptions::Create();
+  options->setMode("byob");
+  auto* result = getReader(script_state, options, exception_state);
+  if (!result)
+    return nullptr;
+  return result->GetAsReadableStreamBYOBReader();
+}
+
 ReadableStream* ReadableStream::pipeThrough(ScriptState* script_state,
                                             ReadableWritablePair* transform,
                                             ExceptionState& exception_state) {
diff --git a/third_party/blink/renderer/core/streams/readable_stream.h b/third_party/blink/renderer/core/streams/readable_stream.h
index b939293..e70125e 100644
--- a/third_party/blink/renderer/core/streams/readable_stream.h
+++ b/third_party/blink/renderer/core/streams/readable_stream.h
@@ -38,6 +38,7 @@
 class StreamPipeOptions;
 class StreamPromiseResolver;
 class StreamStartAlgorithm;
+class UnderlyingByteSourceBase;
 class UnderlyingSourceBase;
 class WritableStream;
 
@@ -112,6 +113,20 @@
                                 StrategySizeAlgorithm* size_algorithm,
                                 ExceptionState&);
 
+  // Entry point to create a ReadableByteStream from other C++ APIs.
+  static ReadableStream* CreateByteStream(
+      ScriptState*,
+      UnderlyingByteSourceBase* underlying_byte_source,
+      ExceptionState&);
+
+  // CreateReadableByteStream():
+  // https://streams.spec.whatwg.org/#abstract-opdef-createreadablebytestream
+  static ReadableStream* CreateByteStream(ScriptState*,
+                                          StreamStartAlgorithm* start_algorithm,
+                                          StreamAlgorithm* pull_algorithm,
+                                          StreamAlgorithm* cancel_algorithm,
+                                          ExceptionState&);
+
   ReadableStream();
 
   ~ReadableStream() override;
@@ -146,6 +161,9 @@
   ReadableStreamDefaultReader* GetDefaultReaderForTesting(ScriptState*,
                                                           ExceptionState&);
 
+  ReadableStreamBYOBReader* GetBYOBReaderForTesting(ScriptState*,
+                                                    ExceptionState&);
+
   ReadableStream* pipeThrough(ScriptState*,
                               ReadableWritablePair* transform,
                               ExceptionState&);
@@ -269,6 +287,8 @@
   friend class ReadableStreamDefaultReader;
   friend class ReadableStreamGenericReader;
 
+  class PullAlgorithm;
+  class CancelAlgorithm;
   class PipeToEngine;
   class ReadHandleImpl;
   class TeeEngine;
diff --git a/third_party/blink/renderer/core/streams/readable_stream_test.cc b/third_party/blink/renderer/core/streams/readable_stream_test.cc
index fa13a83..9744aef2 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_test.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream_test.cc
@@ -4,9 +4,13 @@
 
 #include "third_party/blink/renderer/core/streams/readable_stream.h"
 
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
@@ -21,6 +25,7 @@
 #include "third_party/blink/renderer/core/streams/readable_stream_transferring_optimizer.h"
 #include "third_party/blink/renderer/core/streams/test_underlying_source.h"
 #include "third_party/blink/renderer/core/streams/test_utils.h"
+#include "third_party/blink/renderer/core/streams/underlying_byte_source_base.h"
 #include "third_party/blink/renderer/core/streams/underlying_source_base.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
@@ -33,6 +38,11 @@
 
 namespace {
 
+using ::testing::_;
+using ::testing::ByMove;
+using ::testing::Mock;
+using ::testing::Return;
+
 // Web platform tests test ReadableStream more thoroughly from scripts.
 class ReadableStreamTest : public testing::Test {
  public:
@@ -578,6 +588,231 @@
   EXPECT_FALSE(weak_underlying_source);
 }
 
+class ReadableByteStreamTest : public testing::Test {
+ public:
+  ReadableByteStreamTest() = default;
+
+  ReadableStream* Stream() const { return stream_; }
+
+  void Init(ScriptState* script_state,
+            UnderlyingByteSourceBase* underlying_byte_source,
+            ExceptionState& exception_state) {
+    stream_ = ReadableStream::CreateByteStream(
+        script_state, underlying_byte_source, exception_state);
+  }
+
+  // This takes the |stream| property of ReadableStream and copies it onto the
+  // global object so it can be accessed by Eval().
+  void CopyStreamToGlobal(const V8TestingScope& scope) {
+    auto* script_state = scope.GetScriptState();
+    ReadableStream* stream = Stream();
+    v8::Local<v8::Object> global = script_state->GetContext()->Global();
+    EXPECT_TRUE(global
+                    ->Set(scope.GetContext(),
+                          V8String(scope.GetIsolate(), "stream"),
+                          ToV8Traits<ReadableStream>::ToV8(script_state, stream)
+                              .ToLocalChecked())
+                    .IsJust());
+  }
+
+ private:
+  Persistent<ReadableStream> stream_;
+};
+
+// A convenient base class to make tests shorter. Subclasses need not implement
+// both Pull() and Cancel(), and can override the void versions to avoid
+// the need to create a promise to return. Not appropriate for use in
+// production.
+class TestUnderlyingByteSource : public UnderlyingByteSourceBase {
+ public:
+  explicit TestUnderlyingByteSource(ScriptState* script_state)
+      : script_state_(script_state) {}
+
+  virtual void PullVoid(ReadableByteStreamController*, ExceptionState&) {}
+
+  ScriptPromise Pull(ReadableByteStreamController* controller,
+                     ExceptionState& exception_state) override {
+    PullVoid(controller, exception_state);
+    return ScriptPromise::CastUndefined(script_state_);
+  }
+
+  virtual void CancelVoid(v8::Local<v8::Value>, ExceptionState&) {}
+
+  ScriptPromise Cancel(ExceptionState& exception_state) override {
+    return Cancel(v8::Undefined(script_state_->GetIsolate()), exception_state);
+  }
+
+  ScriptPromise Cancel(v8::Local<v8::Value> reason,
+                       ExceptionState& exception_state) override {
+    CancelVoid(reason, exception_state);
+    return ScriptPromise::CastUndefined(script_state_);
+  }
+
+  ScriptState* GetScriptState() override { return script_state_; }
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(script_state_);
+    UnderlyingByteSourceBase::Trace(visitor);
+  }
+
+ private:
+  const Member<ScriptState> script_state_;
+};
+
+class MockUnderlyingByteSource : public UnderlyingByteSourceBase {
+ public:
+  explicit MockUnderlyingByteSource(ScriptState* script_state)
+      : script_state_(script_state) {}
+
+  MOCK_METHOD2(Pull,
+               ScriptPromise(ReadableByteStreamController*, ExceptionState&));
+  MOCK_METHOD1(Cancel, ScriptPromise(ExceptionState&));
+  MOCK_METHOD2(Cancel,
+               ScriptPromise(v8::Local<v8::Value> reason, ExceptionState&));
+
+  ScriptState* GetScriptState() override { return script_state_; }
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(script_state_);
+    UnderlyingByteSourceBase::Trace(visitor);
+  }
+
+ private:
+  const Member<ScriptState> script_state_;
+};
+
+TEST_F(ReadableByteStreamTest, Construct) {
+  V8TestingScope scope;
+  Init(scope.GetScriptState(),
+       MakeGarbageCollected<TestUnderlyingByteSource>(scope.GetScriptState()),
+       ASSERT_NO_EXCEPTION);
+  EXPECT_TRUE(Stream());
+}
+
+TEST_F(ReadableByteStreamTest, PullIsCalled) {
+  V8TestingScope scope;
+  auto* mock =
+      MakeGarbageCollected<MockUnderlyingByteSource>(scope.GetScriptState());
+  Init(scope.GetScriptState(), mock, ASSERT_NO_EXCEPTION);
+  // Need to run microtasks so the startAlgorithm promise resolves.
+  v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
+  CopyStreamToGlobal(scope);
+
+  EXPECT_CALL(*mock, Pull(_, _))
+      .WillOnce(
+          Return(ByMove(ScriptPromise::CastUndefined(scope.GetScriptState()))));
+
+  EvalWithPrintingError(
+      &scope, "stream.getReader({ mode: 'byob' }).read(new Uint8Array(1));\n");
+
+  Mock::VerifyAndClear(mock);
+  Mock::AllowLeak(mock);
+}
+
+TEST_F(ReadableByteStreamTest, CancelIsCalled) {
+  V8TestingScope scope;
+  auto* mock =
+      MakeGarbageCollected<MockUnderlyingByteSource>(scope.GetScriptState());
+  Init(scope.GetScriptState(), mock, ASSERT_NO_EXCEPTION);
+  // Need to run microtasks so the startAlgorithm promise resolves.
+  v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
+  CopyStreamToGlobal(scope);
+
+  EXPECT_CALL(*mock, Cancel(_, _))
+      .WillOnce(
+          Return(ByMove(ScriptPromise::CastUndefined(scope.GetScriptState()))));
+
+  EvalWithPrintingError(&scope,
+                        "const reader = stream.getReader({ mode: 'byob' });\n"
+                        "reader.cancel('a');\n");
+
+  Mock::VerifyAndClear(mock);
+  Mock::AllowLeak(mock);
+};
+
+bool IsTypeError(ScriptState* script_state,
+                 ScriptValue value,
+                 const String& message) {
+  v8::Local<v8::Object> object;
+  if (!value.V8Value()->ToObject(script_state->GetContext()).ToLocal(&object)) {
+    return false;
+  }
+  if (!object->IsNativeError())
+    return false;
+
+  const auto& Has = [script_state, object](const String& key,
+                                           const String& value) -> bool {
+    v8::Local<v8::Value> actual;
+    return object
+               ->Get(script_state->GetContext(),
+                     V8AtomicString(script_state->GetIsolate(), key))
+               .ToLocal(&actual) &&
+           ToCoreStringWithUndefinedOrNullCheck(actual) == value;
+  };
+
+  return Has("name", "TypeError") && Has("message", message);
+}
+
+TEST_F(ReadableByteStreamTest, ThrowFromPull) {
+  static constexpr char kMessage[] = "errorInPull";
+  class ThrowFromPullUnderlyingByteSource final
+      : public TestUnderlyingByteSource {
+   public:
+    explicit ThrowFromPullUnderlyingByteSource(ScriptState* script_state)
+        : TestUnderlyingByteSource(script_state) {}
+
+    void PullVoid(ReadableByteStreamController*,
+                  ExceptionState& exception_state) override {
+      exception_state.ThrowTypeError(kMessage);
+    }
+  };
+
+  V8TestingScope scope;
+  auto* script_state = scope.GetScriptState();
+  Init(script_state,
+       MakeGarbageCollected<ThrowFromPullUnderlyingByteSource>(script_state),
+       ASSERT_NO_EXCEPTION);
+
+  auto* reader =
+      Stream()->GetBYOBReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
+  NotShared<DOMArrayBufferView> view =
+      NotShared<DOMUint8Array>(DOMUint8Array::Create(1));
+  ScriptPromiseTester read_tester(
+      script_state, reader->read(script_state, view, ASSERT_NO_EXCEPTION));
+  read_tester.WaitUntilSettled();
+  EXPECT_TRUE(read_tester.IsRejected());
+  EXPECT_TRUE(IsTypeError(script_state, read_tester.Value(), kMessage));
+}
+
+TEST_F(ReadableByteStreamTest, ThrowFromCancel) {
+  static constexpr char kMessage[] = "errorInCancel";
+  class ThrowFromCancelUnderlyingByteSource final
+      : public TestUnderlyingByteSource {
+   public:
+    explicit ThrowFromCancelUnderlyingByteSource(ScriptState* script_state)
+        : TestUnderlyingByteSource(script_state) {}
+
+    void CancelVoid(v8::Local<v8::Value>,
+                    ExceptionState& exception_state) override {
+      exception_state.ThrowTypeError(kMessage);
+    }
+  };
+
+  V8TestingScope scope;
+  auto* script_state = scope.GetScriptState();
+  Init(script_state,
+       MakeGarbageCollected<ThrowFromCancelUnderlyingByteSource>(script_state),
+       ASSERT_NO_EXCEPTION);
+
+  auto* reader =
+      Stream()->GetBYOBReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
+  ScriptPromiseTester read_tester(
+      script_state, reader->cancel(script_state, ASSERT_NO_EXCEPTION));
+  read_tester.WaitUntilSettled();
+  EXPECT_TRUE(read_tester.IsRejected());
+  EXPECT_TRUE(IsTypeError(script_state, read_tester.Value(), kMessage));
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/streams/underlying_byte_source_base.h b/third_party/blink/renderer/core/streams/underlying_byte_source_base.h
new file mode 100644
index 0000000..e7e729c
--- /dev/null
+++ b/third_party/blink/renderer/core/streams/underlying_byte_source_base.h
@@ -0,0 +1,46 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_UNDERLYING_BYTE_SOURCE_BASE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_UNDERLYING_BYTE_SOURCE_BASE_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/heap/visitor.h"
+#include "v8/include/v8.h"
+
+namespace blink {
+
+class ReadableByteStreamController;
+class ScriptPromise;
+class ScriptState;
+
+// Interface to be implemented by C++ code that needs to create a
+// ReadableByteStream. Based on the JavaScript [UnderlyingSource
+// API](https://streams.spec.whatwg.org/#underlying-source-api). Errors should
+// be signalled by exceptions or promise rejections.
+class CORE_EXPORT UnderlyingByteSourceBase
+    : public GarbageCollected<UnderlyingByteSourceBase> {
+ public:
+  UnderlyingByteSourceBase() = default;
+  UnderlyingByteSourceBase(const UnderlyingByteSourceBase&) = delete;
+  UnderlyingByteSourceBase& operator=(const UnderlyingByteSourceBase&) = delete;
+  virtual ~UnderlyingByteSourceBase() = default;
+
+  virtual ScriptPromise Pull(ReadableByteStreamController* controller,
+                             ExceptionState&) = 0;
+
+  virtual ScriptPromise Cancel(ExceptionState&) = 0;
+  virtual ScriptPromise Cancel(v8::Local<v8::Value> reason,
+                               ExceptionState&) = 0;
+
+  // Returns the ScriptState associated with this UnderlyingByteSource.
+  virtual ScriptState* GetScriptState() = 0;
+
+  virtual void Trace(Visitor*) const {}
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_UNDERLYING_BYTE_SOURCE_BASE_H_
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index dd207425..7c098c16 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -1299,8 +1299,11 @@
   // If this is an HTMLFrameOwnerElement (such as an iframe), we may need
   // to embed the ID of the child frame.
   if (!ui::IsChildTreeOwner(RoleValue())) {
-    DCHECK(!IsA<HTMLFrameOwnerElement>(GetElement()) || !IsFrame(GetNode()))
-        << "Element was expected to be a child tree owner: " << GetElement();
+    // TODO(crbug.com/1342603) Determine why these are firing in the wild and,
+    // once fixed, turn into a DCHECK.
+    SANITIZER_CHECK(!IsFrame(GetNode()))
+        << "If this is an iframe, it should also be a child tree owner: "
+        << ToString(true, true);
     return;
   }
 
@@ -1308,7 +1311,9 @@
 
   Frame* child_frame = html_frame_owner_element->ContentFrame();
   if (!child_frame) {
-    DCHECK(IsDisabled());
+    // TODO(crbug.com/1342603) Determine why these are firing in the wild and,
+    // once fixed, turn into a DCHECK.
+    SANITIZER_CHECK(IsDisabled()) << ToString(true, true);
     return;
   }
 
diff --git a/third_party/blink/renderer/modules/sanitizer_api/BUILD.gn b/third_party/blink/renderer/modules/sanitizer_api/BUILD.gn
index b22ddb8..4cb2e58 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/BUILD.gn
+++ b/third_party/blink/renderer/modules/sanitizer_api/BUILD.gn
@@ -2,12 +2,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//third_party/blink/renderer/build/scripts/scripts.gni")
 import("//third_party/blink/renderer/modules/modules.gni")
 import("//third_party/libprotobuf-mutator/fuzzable_proto_library.gni")
 import("//third_party/protobuf/proto_library.gni")
 
 blink_modules_sources("sanitizer_api") {
   sources = [
+    "$target_gen_dir/builtins/sanitizer_attribute_lists.cc",
+    "$target_gen_dir/builtins/sanitizer_attribute_lists_with_namespaces.cc",
     "$target_gen_dir/builtins/sanitizer_builtins.cc",
     "$target_gen_dir/builtins/sanitizer_builtins_with_namespaces.cc",
     "builtins.cc",
@@ -22,6 +25,8 @@
     "sanitizer_config_impl.h",
   ]
   deps = [
+    ":generate_sanitizer_attribute_lists",
+    ":generate_sanitizer_attribute_lists_with_namespaces",
     ":generate_sanitizer_builtins",
     ":generate_sanitizer_builtins_with_namespaces",
   ]
@@ -81,6 +86,46 @@
   ]
 }
 
+blink_python_runner("generate_sanitizer_attribute_lists") {
+  script = "builtins/generate_attribute_lists.py"
+  sources =
+      [ "//third_party/blink/renderer/core/html/html_attribute_names.json5" ]
+  inputs = sources + scripts_for_json5_files
+  outputs = [ "$target_gen_dir/builtins/sanitizer_attribute_lists.cc" ]
+  args = [
+    "--out",
+    rebase_path(outputs[0], root_build_dir),
+  ]
+  foreach(source, sources) {
+    args += [ rebase_path(source, root_build_dir) ]
+  }
+}
+
+blink_python_runner("generate_sanitizer_attribute_lists_with_namespaces") {
+  script = "builtins/generate_attribute_lists.py"
+  sources = [
+    "//third_party/blink/renderer/core/html/html_attribute_names.json5",
+    "//third_party/blink/renderer/core/mathml/mathml_attribute_names.json5",
+    "//third_party/blink/renderer/core/svg/svg_attribute_names.json5",
+    "//third_party/blink/renderer/core/svg/xlink_attribute_names.json5",
+    "//third_party/blink/renderer/core/xml/xml_attribute_names.json5",
+    "//third_party/blink/renderer/core/xml/xmlns_attribute_names.json5",
+  ]
+  inputs = sources + scripts_for_json5_files
+  outputs = [
+    "$target_gen_dir/builtins/sanitizer_attribute_lists_with_namespaces.cc",
+  ]
+  args = [
+    "--out",
+    rebase_path(outputs[0], root_build_dir),
+    "--cxx-namespace",
+    "with_namespace_names",
+  ]
+  foreach(source, sources) {
+    args += [ rebase_path(source, root_build_dir) ]
+  }
+}
+
 if (use_libfuzzer) {
   corpus_out_dir = "$target_gen_dir/corpus"
 
diff --git a/third_party/blink/renderer/modules/sanitizer_api/builtins.cc b/third_party/blink/renderer/modules/sanitizer_api/builtins.cc
index 7d6ca3b..988f97e 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/builtins.cc
+++ b/third_party/blink/renderer/modules/sanitizer_api/builtins.cc
@@ -46,10 +46,12 @@
   config.allow_elements_ = ElementsFromAPI(elements);
   config.allow_attributes_ = AttributesFromAPI(attributes);
   config.allow_custom_elements_ = false;
+  config.allow_unknown_markup_ = false;
   config.allow_comments_ = false;
   config.had_allow_elements_ = true;
   config.had_allow_attributes_ = true;
   config.had_allow_custom_elements_ = true;
+  config.had_allow_unknown_markup_ = true;
   return config;
 }
 
@@ -80,6 +82,12 @@
   return attributes_;
 }
 
+const SanitizerConfigImpl::AttributeList& GetKnownAttributes() {
+  DEFINE_STATIC_LOCAL(SanitizerConfigImpl::AttributeList, attributes_,
+                      (AttributesFromAPI(kKnownAttributes)));
+  return attributes_;
+}
+
 }  // namespace default_config_names
 
 namespace with_namespace_names {
@@ -103,6 +111,12 @@
   return attributes_;
 }
 
+const SanitizerConfigImpl::AttributeList& GetKnownAttributes() {
+  DEFINE_STATIC_LOCAL(SanitizerConfigImpl::AttributeList, attributes_,
+                      (AttributesFromAPI(kKnownAttributes)));
+  return attributes_;
+}
+
 }  // namespace with_namespace_names
 
 bool WithNamespaces() {
@@ -126,4 +140,10 @@
   return WithNamespaces() ? with_namespace_names::GetBaselineAllowAttributes()
                           : default_config_names::GetBaselineAllowAttributes();
 }
+
+const SanitizerConfigImpl::AttributeList& GetKnownAttributes() {
+  return WithNamespaces() ? with_namespace_names::GetKnownAttributes()
+                          : default_config_names::GetKnownAttributes();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/sanitizer_api/builtins.h b/third_party/blink/renderer/modules/sanitizer_api/builtins.h
index 4b6521d..e722752 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/builtins.h
+++ b/third_party/blink/renderer/modules/sanitizer_api/builtins.h
@@ -18,6 +18,7 @@
 const SanitizerConfigImpl& GetDefaultConfig();
 const SanitizerConfigImpl::ElementList& GetBaselineAllowElements();
 const SanitizerConfigImpl::AttributeList& GetBaselineAllowAttributes();
+const SanitizerConfigImpl::AttributeList& GetKnownAttributes();
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/modules/sanitizer_api/builtins/default_configuration.txt b/third_party/blink/renderer/modules/sanitizer_api/builtins/default_configuration.txt
index 6f564698..c2b5977 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/builtins/default_configuration.txt
+++ b/third_party/blink/renderer/modules/sanitizer_api/builtins/default_configuration.txt
@@ -1,5 +1,6 @@
 {
   "allowCustomElements": false,
+  "allowUnknownMarkup": false,
   "allowElements": [
     "a",
     "abbr",
diff --git a/third_party/blink/renderer/modules/sanitizer_api/builtins/generate_attribute_lists.py b/third_party/blink/renderer/modules/sanitizer_api/builtins/generate_attribute_lists.py
new file mode 100755
index 0000000..98d13eb4
--- /dev/null
+++ b/third_party/blink/renderer/modules/sanitizer_api/builtins/generate_attribute_lists.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Generate list of attribute names known to this version of Chromium."""
+
+from pyjson5.src import json5
+import optparse
+import sys
+
+
+def error(context, *infos):
+    """Print a brief error message and return an error code."""
+    messages = ["An error occurred when when " + context + ":"]
+    messages.extend(infos)
+    print("\n\t".join(map(str, messages)))
+    return 1
+
+
+def lstrip(string):
+    """Call str.lstrip on each line of text."""
+    return "\n".join(map(str.lstrip, string.split("\n")))
+
+
+def prolog(out):
+    """Print the beginning of the source file."""
+    print(lstrip("""
+        // Copyright 2022 The Chromium Authors. All rights reserved.
+        // Use of this source code is governed by a BSD-style license that can be
+        // found in the LICENSE file.
+
+        // This file is automatically generated. Do not edit. Just generate.
+        // $ ninja -C ... generate_sanitizer_attribute_lists
+
+        #include "third_party/blink/renderer/modules/sanitizer_api/builtins/sanitizer_builtins.h"
+        """),
+          file=out)
+
+
+def namespace(out, name):
+    if name:
+        print("namespace %s {\n" % name, file=out)
+
+
+def end_namespace(out, name):
+    if name:
+        print("}  // namespace %s\n" % name, file=out)
+
+
+def string_list(out, name, items):
+    """Print a list of strings as a C++ array of const char*."""
+    print(f"const char* const {name}[] = {{", file=out)
+    for item in sorted(items):
+        print(f"  \"{item}\",", file=out)
+    print("  nullptr,", file=out)
+    print("};", file=out)
+    print("", file=out)
+
+
+def main(argv):
+    parser = optparse.OptionParser()
+    parser.add_option("--out")
+    parser.add_option("--cxx-namespace")
+    options, args = parser.parse_args(argv)
+    if not options.out:
+        parser.error("--out is required")
+
+    names = set()
+    try:
+        for source in args:
+            json = json5.load(open(source, "r"))
+            names |= set(json["data"])
+    except BaseException as err:
+        return error("reading input file", source, err)
+
+    try:
+        with open(options.out, "w") as out:
+            prolog(out)
+            namespace(out, "blink")
+            namespace(out, options.cxx_namespace)
+            string_list(out, "kKnownAttributes", names)
+            end_namespace(out, options.cxx_namespace)
+            end_namespace(out, "blink")
+    except BaseException as err:
+        return error("writing output file", options.out, err)
+
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
diff --git a/third_party/blink/renderer/modules/sanitizer_api/builtins/sanitizer_builtins.h b/third_party/blink/renderer/modules/sanitizer_api/builtins/sanitizer_builtins.h
index 5629ce9..bed600f 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/builtins/sanitizer_builtins.h
+++ b/third_party/blink/renderer/modules/sanitizer_api/builtins/sanitizer_builtins.h
@@ -8,7 +8,8 @@
 // Access Sanitizer API constants.
 //
 // The constant values are generated by generate_builtins.py.
-// Generate with: ninja -C ... generate_sanitizer_builtins
+// Generate with:
+//  ninja -C ... generate_sanitizer_builtins generate_sanitizer_attribute_lists
 
 namespace blink {
 
@@ -16,6 +17,7 @@
 extern const char* const kBaselineAttributes[];
 extern const char* const kDefaultElements[];
 extern const char* const kDefaultAttributes[];
+extern const char* const kKnownAttributes[];
 
 // We currently support an alternate set of builtins, enabled via a flag.
 namespace with_namespace_names {
@@ -23,6 +25,7 @@
 extern const char* const kBaselineAttributes[];
 extern const char* const kDefaultElements[];
 extern const char* const kDefaultAttributes[];
+extern const char* const kKnownAttributes[];
 }  // namespace with_namespace_names
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/modules/sanitizer_api/config_util.cc b/third_party/blink/renderer/modules/sanitizer_api/config_util.cc
index c103ef55..9370323 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/config_util.cc
+++ b/third_party/blink/renderer/modules/sanitizer_api/config_util.cc
@@ -21,6 +21,8 @@
 
   impl.allow_custom_elements_ =
       config->hasAllowCustomElements() && config->allowCustomElements();
+  impl.allow_unknown_markup_ =
+      config->hasAllowUnknownMarkup() && config->allowUnknownMarkup();
   impl.allow_comments_ = config->hasAllowComments() && config->allowComments();
 
   // Format dropElements to lower case.
@@ -55,6 +57,7 @@
   impl.had_allow_elements_ = config->hasAllowElements();
   impl.had_allow_attributes_ = config->hasAllowAttributes();
   impl.had_allow_custom_elements_ = config->hasAllowCustomElements();
+  impl.had_allow_unknown_markup_ = config->hasAllowUnknownMarkup();
 
   return impl;
 }
@@ -168,6 +171,9 @@
     config->setDropAttributes(ToAPI(impl.drop_attributes_));
   }
 
+  if (impl.had_allow_unknown_markup_)
+    config->setAllowUnknownMarkup(impl.allow_unknown_markup_);
+
   if (impl.had_allow_custom_elements_)
     config->setAllowCustomElements(impl.allow_custom_elements_);
   return config;
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
index 03f13bfa..082b2d7 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc
@@ -47,7 +47,7 @@
          (!config->hasDropElements() && !config->hasBlockElements() &&
           !config->hasAllowElements() && !config->hasDropAttributes() &&
           !config->hasAllowAttributes() && !config->hasAllowCustomElements() &&
-          !config->hasAllowComments());
+          !config->hasAllowComments() && !config->hasAllowUnknownMarkup());
 }
 
 String FromAPI(Node* node) {
@@ -238,6 +238,11 @@
           node = DropNode(element, fragment);
           UseCounter::Count(window->GetExecutionContext(),
                             WebFeature::kSanitizerAPIActionTaken);
+        } else if (is_unknown_element && !config_.allow_unknown_markup_) {
+          // TODO: No spec yet. https://github.com/WICG/sanitizer-api/pull/159
+          node = DropNode(element, fragment);
+          UseCounter::Count(window->GetExecutionContext(),
+                            WebFeature::kSanitizerAPIActionTaken);
         } else if (Match(name, config_.drop_elements_)) {
           // 5. If |name| is in |config|'s [=element drop list=] then 'drop'.
           node = DropNode(element, fragment);
@@ -325,20 +330,27 @@
                              ContainerNode* fragment,
                              LocalDOMWindow* window) {
   String node_name = FromAPI(element);
-  if (Match(Wildcard(), node_name, config_.allow_attributes_)) {
-  } else if (Match(Wildcard(), node_name, config_.drop_attributes_)) {
+
+  if (Match(Wildcard(), node_name, config_.drop_attributes_)) {
     for (const auto& name : element->getAttributeNames()) {
       element->removeAttribute(name);
       UseCounter::Count(window->GetExecutionContext(),
                         WebFeature::kSanitizerAPIActionTaken);
     }
   } else {
+    bool allow_attributes_wildcard =
+        Match(Wildcard(), node_name, config_.allow_attributes_);
     for (const auto& name : element->getAttributeNames()) {
       // Attributes in drop list or not in allow list while allow list
       // exists will be dropped.
-      bool drop = !Match(name, node_name, GetBaselineAllowAttributes()) ||
+      bool is_unknown = !Match(name, node_name, GetKnownAttributes());
+      bool drop = (is_unknown ? !config_.allow_unknown_markup_
+                              : !Match(name, node_name,
+                                       GetBaselineAllowAttributes())) ||
                   Match(name, node_name, config_.drop_attributes_) ||
-                  !Match(name, node_name, config_.allow_attributes_);
+                  (!allow_attributes_wildcard &&
+                   !Match(name, node_name, config_.allow_attributes_));
+
       // 9. If |element|'s [=element interface=] is {{HTMLAnchorElement}} or
       // {{HTMLAreaElement}} and |element|'s `protocol` property is
       // "javascript:", then remove the `href` attribute from |element|.
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.idl b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.idl
index d05aebb..0694fca 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.idl
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.idl
@@ -11,5 +11,6 @@
   record<DOMString, sequence<DOMString>> allowAttributes;
   record<DOMString, sequence<DOMString>> dropAttributes;
   boolean allowCustomElements;
+  boolean allowUnknownMarkup;
   boolean allowComments;
 };
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.proto b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.proto
index c555303e..cb87b15 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.proto
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config.proto
@@ -16,6 +16,7 @@
   map<string, Elements> drop_attributes = 6;
 
   optional bool allow_custom_elements = 7;
+  optional bool allow_unknown_markup = 10;
   optional bool allow_comments = 8;
 
   enum StringContext {
diff --git a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h
index a745b9a..e01c5a4aa 100644
--- a/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h
+++ b/third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h
@@ -37,6 +37,7 @@
   AttributeList allow_attributes_;
   AttributeList drop_attributes_;
   bool allow_custom_elements_;
+  bool allow_unknown_markup_;
   bool allow_comments_;
 
   // These members store whether the original SanitizerConfig had the
@@ -45,6 +46,7 @@
   bool had_allow_elements_;
   bool had_allow_attributes_;
   bool had_allow_custom_elements_;
+  bool had_allow_unknown_markup_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.cc b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
index 5c8e43a1..75a028d 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
@@ -49,6 +49,7 @@
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
 #include "third_party/blink/renderer/platform/graphics/image.h"
+#include "third_party/blink/renderer/platform/graphics/skia/sk_image_info_hash.h"
 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/graphics/video_frame_image_util.h"
@@ -57,6 +58,7 @@
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/libyuv/include/libyuv/planar_functions.h"
 #include "third_party/skia/include/gpu/GrDirectContext.h"
+#include "ui/gfx/geometry/skia_conversions.h"
 #include "v8/include/v8.h"
 
 namespace blink {
@@ -207,27 +209,26 @@
       delete;
   CanvasResourceProviderCache(const CanvasResourceProviderCache&) = delete;
 
-  CanvasResourceProvider* CreateProvider(gfx::Size size) {
-    if (size_to_provider_.IsEmpty())
+  CanvasResourceProvider* CreateProvider(const SkImageInfo& info) {
+    if (info_to_provider_.IsEmpty())
       PostMonitoringTask();
 
     last_access_time_ = base::TimeTicks::Now();
 
-    gfx::SizeF key(size);
-    auto iter = size_to_provider_.find(key);
-    if (iter != size_to_provider_.end()) {
+    auto iter = info_to_provider_.find(info);
+    if (iter != info_to_provider_.end()) {
       auto* result = iter->value.get();
       if (result && result->IsValid())
         return result;
     }
 
-    if (size_to_provider_.size() >= kMaxSize)
-      size_to_provider_.clear();
+    if (info_to_provider_.size() >= kMaxSize)
+      info_to_provider_.clear();
 
     auto provider = CreateResourceProviderForVideoFrame(
-        size, GetRasterContextProvider().get());
+        info, GetRasterContextProvider().get());
     auto* result = provider.get();
-    size_to_provider_.Set(key, std::move(provider));
+    info_to_provider_.Set(info, std::move(provider));
     return result;
   }
 
@@ -250,15 +251,15 @@
 
   void PurgeIdleFramePool() {
     if (base::TimeTicks::Now() - last_access_time_ > kIdleTimeout) {
-      size_to_provider_.clear();
+      info_to_provider_.clear();
       return;
     }
     PostMonitoringTask();
   }
 
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  HashMap<gfx::SizeF, std::unique_ptr<CanvasResourceProvider>>
-      size_to_provider_;
+  HashMap<SkImageInfo, std::unique_ptr<CanvasResourceProvider>>
+      info_to_provider_;
   base::TimeTicks last_access_time_;
   TaskHandle task_handle_;
 };
@@ -1085,10 +1086,17 @@
   auto* execution_context =
       ExecutionContext::From(v8::Isolate::GetCurrent()->GetCurrentContext());
   auto& provider_cache = CanvasResourceProviderCache::From(*execution_context);
-  auto* resource_provider =
-      provider_cache.CreateProvider(local_handle->frame()->natural_size());
 
-  const auto dest_rect = gfx::Rect(local_handle->frame()->natural_size());
+  // TODO(https://crbug.com/1341235): The choice of color type, alpha type, and
+  // color space is inappropriate in many circumstances.
+  const auto& resource_provider_size = local_handle->frame()->natural_size();
+  const auto resource_provider_info =
+      SkImageInfo::Make(gfx::SizeToSkISize(resource_provider_size),
+                        kN32_SkColorType, kPremul_SkAlphaType, nullptr);
+  auto* resource_provider =
+      provider_cache.CreateProvider(resource_provider_info);
+
+  const auto dest_rect = gfx::Rect(resource_provider_size);
   auto image = CreateImageFromVideoFrame(local_handle->frame(),
                                          /*allow_zero_copy_images=*/true,
                                          resource_provider,
@@ -1194,14 +1202,21 @@
   auto* execution_context =
       ExecutionContext::From(v8::Isolate::GetCurrent()->GetCurrentContext());
   auto& provider_cache = CanvasResourceProviderCache::From(*execution_context);
+
+  // TODO(https://crbug.com/1341235): The choice of color type, alpha type, and
+  // color space is inappropriate in many circumstances.
+  const auto& resource_provider_size = local_handle->frame()->natural_size();
+  const auto resource_provider_info =
+      SkImageInfo::Make(gfx::SizeToSkISize(resource_provider_size),
+                        kN32_SkColorType, kPremul_SkAlphaType, nullptr);
   auto* resource_provider =
-      provider_cache.CreateProvider(local_handle->frame()->natural_size());
+      provider_cache.CreateProvider(resource_provider_info);
 
   // We disable zero copy images since the ImageBitmap spec says created bitmaps
   // are copies. Many other paths can avoid doing this w/o issue, but hardware
   // decoders may have a limited number of outputs, so not making a copy becomes
   // an observable issues to clients.
-  const auto dest_rect = gfx::Rect(local_handle->frame()->natural_size());
+  const auto dest_rect = gfx::Rect(resource_provider_size);
   auto image = CreateImageFromVideoFrame(local_handle->frame(),
                                          /*allow_zero_copy_images=*/false,
                                          resource_provider,
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index 06080a8..34d8dc1 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -119,6 +119,7 @@
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
+#include "third_party/blink/renderer/platform/graphics/skia/sk_image_info_hash.h"
 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/graphics/video_frame_image_util.h"
 #include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_util.h"
@@ -5298,7 +5299,7 @@
 
 // TODO(fmalita): figure why WebGLImageConversion::ImageExtractor can't handle
 // SVG-backed images, and get rid of this intermediate step.
-scoped_refptr<Image> WebGLRenderingContextBase::DrawImageIntoBuffer(
+scoped_refptr<Image> WebGLRenderingContextBase::DrawImageIntoBufferForTexImage(
     scoped_refptr<Image> pass_image,
     int width,
     int height,
@@ -5306,9 +5307,13 @@
   scoped_refptr<Image> image(std::move(pass_image));
   DCHECK(image);
 
-  gfx::Size size(width, height);
+  // TODO(https://crbug.com/1341235): The choice of color type should match the
+  // format of the TexImage function. The choice of alpha type should opaque for
+  // opaque images. The color space should match the unpack color space.
+  const auto resource_provider_info = SkImageInfo::Make(
+      width, height, kN32_SkColorType, kPremul_SkAlphaType, nullptr);
   CanvasResourceProvider* resource_provider =
-      generated_image_cache_.GetCanvasResourceProvider(size);
+      generated_image_cache_.GetCanvasResourceProvider(resource_provider_info);
   if (!resource_provider) {
     SynthesizeGLError(GL_OUT_OF_MEMORY, function_name, "out of memory");
     return nullptr;
@@ -5318,7 +5323,7 @@
     resource_provider->Canvas()->clear(SK_ColorTRANSPARENT);
 
   gfx::Rect src_rect(image->Size());
-  gfx::Rect dest_rect(size);
+  gfx::Rect dest_rect(0, 0, width, height);
   cc::PaintFlags flags;
   // TODO(ccameron): WebGL should produce sRGB images.
   // https://crbug.com/672299
@@ -5532,9 +5537,9 @@
       UseCounter::Count(canvas()->GetDocument(), WebFeature::kSVGInWebGL);
     }
     // DrawImageIntoBuffer always respects orientation
-    image_for_render =
-        DrawImageIntoBuffer(std::move(image_for_render), image->width(),
-                            image->height(), func_name);
+    image_for_render = DrawImageIntoBufferForTexImage(
+        std::move(image_for_render), image->width(), image->height(),
+        func_name);
   }
   if (!image_for_render ||
       !ValidateTexFunc(params, kSourceHTMLImageElement,
@@ -6113,12 +6118,21 @@
     dest_rect.Transpose();
   }
 
+  // TODO(https://crbug.com/1341235): The choice of color type will clamp
+  // higher precision sources to 8 bit per color. The choice of color space
+  // should match the unpack color space.
+  const auto resource_provider_info = SkImageInfo::Make(
+      gfx::SizeToSkISize(dest_rect.size()), kN32_SkColorType,
+      media::IsOpaque(media_video_frame->format()) ? kOpaque_SkAlphaType
+                                                   : kPremul_SkAlphaType,
+      nullptr);
+
   // Since TexImageStaticBitmapImage() and TexImageGPU() don't know how to
   // handle tagged orientation, we set |prefer_tagged_orientation| to false.
   scoped_refptr<StaticBitmapImage> image = CreateImageFromVideoFrame(
       std::move(media_video_frame), kAllowZeroCopyImages,
-      image_cache.GetCanvasResourceProvider(dest_rect.size()), video_renderer,
-      dest_rect, /*prefer_tagged_orientation=*/false);
+      image_cache.GetCanvasResourceProvider(resource_provider_info),
+      video_renderer, dest_rect, /*prefer_tagged_orientation=*/false);
   if (!image)
     return;
 
@@ -8537,13 +8551,13 @@
 
 CanvasResourceProvider* WebGLRenderingContextBase::
     LRUCanvasResourceProviderCache::GetCanvasResourceProvider(
-        const gfx::Size& size) {
+        const SkImageInfo& info) {
   wtf_size_t i;
   for (i = 0; i < resource_providers_.size(); ++i) {
     CanvasResourceProvider* resource_provider = resource_providers_[i].get();
     if (!resource_provider)
       break;
-    if (resource_provider->Size() != size)
+    if (resource_provider->GetSkImageInfo() != info)
       continue;
     BubbleToFront(i);
     return resource_provider;
@@ -8556,12 +8570,11 @@
       if (auto* context_provider = wrapper->ContextProvider())
         raster_context_provider = context_provider->RasterContextProvider();
     }
-    temp = CreateResourceProviderForVideoFrame(size, raster_context_provider);
+    temp = CreateResourceProviderForVideoFrame(info, raster_context_provider);
   } else {
     // TODO(fserb): why is this a BITMAP?
     temp = CanvasResourceProvider::CreateBitmapProvider(
-        SkImageInfo::MakeN32Premul(size.width(), size.height()),
-        cc::PaintFlags::FilterQuality::kLow,
+        info, cc::PaintFlags::FilterQuality::kLow,
         CanvasResourceProvider::ShouldInitialize::kNo);  // TODO: should this
                                                          // use the canvas's
   }
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
index 8e9f2d8..7607cd5 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -810,10 +810,13 @@
   // Restore the client unpack parameters.
   virtual void RestoreUnpackParameters();
 
-  scoped_refptr<Image> DrawImageIntoBuffer(scoped_refptr<Image>,
-                                           int width,
-                                           int height,
-                                           const char* function_name);
+  // Draw the specified image into a new image. Used for a workaround when
+  // uploading SVG images (see the caller).
+  scoped_refptr<Image> DrawImageIntoBufferForTexImage(
+      scoped_refptr<Image>,
+      int width,
+      int height,
+      const char* function_name);
 
   // Structure for rendering to a DrawingBuffer, instead of directly
   // to the back-buffer of m_context.
@@ -890,7 +893,7 @@
     enum class CacheType { kImage, kVideo };
     LRUCanvasResourceProviderCache(wtf_size_t capacity, CacheType type);
     // The pointer returned is owned by the image buffer map.
-    CanvasResourceProvider* GetCanvasResourceProvider(const gfx::Size&);
+    CanvasResourceProvider* GetCanvasResourceProvider(const SkImageInfo&);
 
    private:
     void BubbleToFront(wtf_size_t idx);
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 4f209ae..5dafe4f 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1079,6 +1079,7 @@
     "graphics/scoped_interpolation_quality.h",
     "graphics/scrollbar_theme_settings.cc",
     "graphics/scrollbar_theme_settings.h",
+    "graphics/skia/sk_image_info_hash.h",
     "graphics/skia/sk_size_hash.h",
     "graphics/skia/skia_utils.cc",
     "graphics/skia/skia_utils.h",
diff --git a/third_party/blink/renderer/platform/graphics/skia/sk_image_info_hash.h b/third_party/blink/renderer/platform/graphics/skia/sk_image_info_hash.h
new file mode 100644
index 0000000..e5d0427
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/skia/sk_image_info_hash.h
@@ -0,0 +1,56 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_SKIA_SK_IMAGE_INFO_HASH_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_SKIA_SK_IMAGE_INFO_HASH_H_
+
+#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
+#include "third_party/blink/renderer/platform/wtf/hash_traits.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+
+namespace WTF {
+
+template <>
+struct DefaultHash<SkImageInfo> {
+  STATIC_ONLY(DefaultHash);
+  struct Hash {
+    STATIC_ONLY(Hash);
+    static unsigned GetHash(const SkImageInfo& key) {
+      unsigned result = HashInts(key.width(), key.height());
+      result = HashInts(result, key.colorType());
+      result = HashInts(result, key.alphaType());
+      if (auto* cs = key.colorSpace())
+        result = HashInts(result, static_cast<uint32_t>(cs->hash()));
+      return result;
+    }
+    static bool Equal(const SkImageInfo& a, const SkImageInfo& b) {
+      return a == b;
+    }
+    static const bool safe_to_compare_to_empty_or_deleted = true;
+  };
+};
+
+template <>
+struct HashTraits<SkImageInfo> : GenericHashTraits<SkImageInfo> {
+  STATIC_ONLY(HashTraits);
+  static const bool kEmptyValueIsZero = true;
+  static SkImageInfo EmptyValue() {
+    return SkImageInfo::Make(0, 0, kUnknown_SkColorType, kUnknown_SkAlphaType,
+                             nullptr);
+  }
+  static void ConstructDeletedValue(SkImageInfo& slot, bool) {
+    slot = SkImageInfo::Make(-1, -1, kUnknown_SkColorType, kUnknown_SkAlphaType,
+                             nullptr);
+  }
+  static bool IsDeletedValue(const SkImageInfo& value) {
+    return value.width() == -1 && value.height() == -1 &&
+           value.colorType() == kUnknown_SkColorType &&
+           value.alphaType() == kUnknown_SkAlphaType &&
+           value.colorSpace() == nullptr;
+  }
+};
+
+}  // namespace WTF
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_SKIA_SK_IMAGE_INFO_HASH_H_
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_image_util.cc b/third_party/blink/renderer/platform/graphics/video_frame_image_util.cc
index 977d4e8..e756913 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_image_util.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_image_util.cc
@@ -25,6 +25,7 @@
 #include "third_party/skia/include/gpu/GrDriverBugWorkarounds.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/geometry/skia_conversions.h"
 
 namespace blink {
 
@@ -207,11 +208,15 @@
   }
 
   auto raster_context_provider = GetRasterContextProvider();
-  const auto resource_provider_size = final_dest_rect.size();
+  // TODO(https://crbug.com/1341235): The choice of color type, alpha type,
+  // and color space is inappropriate in many circumstances.
+  const auto resource_provider_info =
+      SkImageInfo::Make(gfx::SizeToSkISize(final_dest_rect.size()),
+                        kN32_SkColorType, kPremul_SkAlphaType, nullptr);
   std::unique_ptr<CanvasResourceProvider> local_resource_provider;
   if (!resource_provider) {
     local_resource_provider = CreateResourceProviderForVideoFrame(
-        resource_provider_size, raster_context_provider.get());
+        resource_provider_info, raster_context_provider.get());
     if (!local_resource_provider) {
       DLOG(ERROR) << "Failed to create CanvasResourceProvider.";
       return nullptr;
@@ -327,18 +332,15 @@
 }
 
 std::unique_ptr<CanvasResourceProvider> CreateResourceProviderForVideoFrame(
-    gfx::Size size,
+    const SkImageInfo& info,
     viz::RasterContextProvider* raster_context_provider) {
   if (!ShouldCreateAcceleratedImages(raster_context_provider)) {
     return CanvasResourceProvider::CreateBitmapProvider(
-        SkImageInfo::MakeN32Premul(size.width(), size.height()),
-        cc::PaintFlags::FilterQuality::kLow,
+        info, cc::PaintFlags::FilterQuality::kLow,
         CanvasResourceProvider::ShouldInitialize::kNo);
   }
-
   return CanvasResourceProvider::CreateSharedImageProvider(
-      SkImageInfo::MakeN32Premul(size.width(), size.height()),
-      cc::PaintFlags::FilterQuality::kLow,
+      info, cc::PaintFlags::FilterQuality::kLow,
       CanvasResourceProvider::ShouldInitialize::kNo,
       SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
       false,  // Origin of GL texture is bottom left on screen
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_image_util.h b/third_party/blink/renderer/platform/graphics/video_frame_image_util.h
index 1a6bd5ef..43dd4c8f 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_image_util.h
+++ b/third_party/blink/renderer/platform/graphics/video_frame_image_util.h
@@ -16,6 +16,7 @@
 
 // Note: Don't include "media/base/video_frame.h" here without good reason,
 // since it includes a lot of non-blink types which can pollute the namespace.
+struct SkImageInfo;
 
 namespace media {
 class PaintCanvasVideoRenderer;
@@ -114,7 +115,7 @@
 // resource provider will be returned.
 PLATFORM_EXPORT std::unique_ptr<CanvasResourceProvider>
 CreateResourceProviderForVideoFrame(
-    gfx::Size size,
+    const SkImageInfo& info,
     viz::RasterContextProvider* raster_context_provider);
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_image_util_test.cc b/third_party/blink/renderer/platform/graphics/video_frame_image_util_test.cc
index 6312c7e..bb9550a 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_image_util_test.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_image_util_test.cc
@@ -25,6 +25,7 @@
 namespace {
 
 constexpr auto kTestSize = gfx::Size(64, 64);
+const auto kTestInfo = SkImageInfo::MakeN32Premul(64, 64);
 
 class ScopedFakeGpuContext {
  public:
@@ -230,7 +231,7 @@
   ASSERT_TRUE(raster_context_provider);
 
   auto provider =
-      CreateResourceProviderForVideoFrame(kTestSize, raster_context_provider);
+      CreateResourceProviderForVideoFrame(kTestInfo, raster_context_provider);
   ASSERT_TRUE(provider);
   EXPECT_TRUE(provider->IsAccelerated());
 
@@ -250,7 +251,7 @@
 TEST(VideoFrameImageUtilTest, SoftwareCreateResourceProviderForVideoFrame) {
   // Creating a provider with a null viz::RasterContextProvider should result in
   // a non-accelerated provider being created.
-  auto provider = CreateResourceProviderForVideoFrame(kTestSize, nullptr);
+  auto provider = CreateResourceProviderForVideoFrame(kTestInfo, nullptr);
   ASSERT_TRUE(provider);
   EXPECT_FALSE(provider->IsAccelerated());
 }
@@ -265,7 +266,7 @@
   // Creating a provider with a null viz::RasterContextProvider should result in
   // a non-accelerated provider being created.
   {
-    auto provider = CreateResourceProviderForVideoFrame(kTestSize, nullptr);
+    auto provider = CreateResourceProviderForVideoFrame(kTestInfo, nullptr);
     ASSERT_TRUE(provider);
     EXPECT_FALSE(provider->IsAccelerated());
   }
@@ -274,7 +275,7 @@
   // an accelerated provider being created.
   {
     auto provider =
-        CreateResourceProviderForVideoFrame(kTestSize, raster_context_provider);
+        CreateResourceProviderForVideoFrame(kTestInfo, raster_context_provider);
     ASSERT_TRUE(provider);
     EXPECT_TRUE(provider->IsAccelerated());
   }
@@ -291,7 +292,7 @@
   // an unaccelerated provider being created due to the workaround.
   {
     auto provider =
-        CreateResourceProviderForVideoFrame(kTestSize, raster_context_provider);
+        CreateResourceProviderForVideoFrame(kTestInfo, raster_context_provider);
     ASSERT_TRUE(provider);
     EXPECT_FALSE(provider->IsAccelerated());
   }
@@ -316,8 +317,8 @@
                                    media::VideoFrame::STORAGE_OWNED_MEMORY,
                                    media::PIXEL_FORMAT_XRGB);
 
-  auto provider =
-      CreateResourceProviderForVideoFrame(gfx::Size(16, 16), nullptr);
+  auto provider = CreateResourceProviderForVideoFrame(
+      SkImageInfo::MakeN32Premul(16, 16), nullptr);
   ASSERT_TRUE(provider);
   EXPECT_FALSE(provider->IsAccelerated());
 
@@ -333,8 +334,8 @@
                                    media::VideoFrame::STORAGE_OWNED_MEMORY,
                                    media::PIXEL_FORMAT_XRGB);
 
-  auto provider =
-      CreateResourceProviderForVideoFrame(gfx::Size(128, 128), nullptr);
+  auto provider = CreateResourceProviderForVideoFrame(
+      SkImageInfo::MakeN32Premul(128, 128), nullptr);
   ASSERT_TRUE(provider);
   EXPECT_FALSE(provider->IsAccelerated());
 
diff --git a/third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc b/third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc
index 5cea934..5e5b916e 100644
--- a/third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc
+++ b/third_party/blink/renderer/platform/loader/allowed_by_nosniff.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
 #include "third_party/blink/renderer/platform/network/http_names.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 
@@ -81,6 +82,13 @@
                            WebFeature& counter) {
   using MimeTypeCheck = AllowedByNosniff::MimeTypeCheck;
 
+  // If strict mime type checking for workers is enabled, we'll treat all
+  // "lax" for worker cases as strict.
+  if (mime_type_check_mode == MimeTypeCheck::kLaxForWorker &&
+      RuntimeEnabledFeatures::StrictMimeTypesForWorkersEnabled()) {
+    mime_type_check_mode = MimeTypeCheck::kStrict;
+  }
+
   // The common case: A proper JavaScript MIME type
   if (MIMETypeRegistry::IsSupportedJavaScriptMIMEType(mime_type))
     return true;
diff --git a/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc b/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
index 3aa1ed2b..9298468dd 100644
--- a/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
+++ b/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
@@ -13,6 +13,8 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
 #include "third_party/blink/renderer/platform/loader/testing/test_loader_factory.h"
 #include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 
 namespace blink {
 
@@ -46,11 +48,16 @@
 
 }  // namespace
 
-class AllowedByNosniffTest : public testing::Test {
+class AllowedByNosniffTest : public testing::TestWithParam<bool> {
  public:
 };
 
-TEST_F(AllowedByNosniffTest, AllowedOrNot) {
+INSTANTIATE_TEST_SUITE_P(All, AllowedByNosniffTest, ::testing::Bool());
+
+TEST_P(AllowedByNosniffTest, AllowedOrNot) {
+  RuntimeEnabledFeaturesTestHelpers::ScopedStrictMimeTypesForWorkers feature(
+      GetParam());
+
   struct {
     const char* mimetype;
     bool allowed;
@@ -117,9 +124,13 @@
     ::testing::Mock::VerifyAndClear(use_counter);
 
     EXPECT_CALL(*use_counter, CountUse(_)).Times(::testing::AnyNumber());
-    if (!testcase.allowed)
+    bool expect_allowed =
+        RuntimeEnabledFeatures::StrictMimeTypesForWorkersEnabled()
+            ? testcase.strict_allowed
+            : testcase.allowed;
+    if (!expect_allowed)
       EXPECT_CALL(*logger, AddConsoleMessageImpl(_, _, _, _, _));
-    EXPECT_EQ(testcase.allowed,
+    EXPECT_EQ(expect_allowed,
               AllowedByNosniff::MimeTypeAsScript(*use_counter, logger, response,
                                                  MimeTypeCheck::kLaxForWorker));
     ::testing::Mock::VerifyAndClear(use_counter);
@@ -134,7 +145,10 @@
   }
 }
 
-TEST_F(AllowedByNosniffTest, Counters) {
+TEST_P(AllowedByNosniffTest, Counters) {
+  RuntimeEnabledFeaturesTestHelpers::ScopedStrictMimeTypesForWorkers feature(
+      GetParam());
+
   constexpr auto kBasic = network::mojom::FetchResponseType::kBasic;
   constexpr auto kOpaque = network::mojom::FetchResponseType::kOpaque;
   constexpr auto kCors = network::mojom::FetchResponseType::kCors;
@@ -206,15 +220,27 @@
                                        MimeTypeCheck::kLaxForElement);
     ::testing::Mock::VerifyAndClear(use_counter);
 
-    EXPECT_CALL(*use_counter, CountUse(testcase.expected));
+    // kLaxForWorker should (by default) behave the same as kLaxForElement,
+    // but should behave like kStrict if StrictMimeTypesForWorkersEnabled().
+    // So in the strict case we'll expect the counter calls only if it's a
+    // legitimate script.
+    bool expect_worker_lax =
+        (testcase.expected == WebFeature::kCrossOriginTextScript ||
+         testcase.expected == WebFeature::kSameOriginTextScript) ||
+        !RuntimeEnabledFeatures::StrictMimeTypesForWorkersEnabled();
+    EXPECT_CALL(*use_counter, CountUse(testcase.expected))
+        .Times(expect_worker_lax);
     EXPECT_CALL(*use_counter, CountUse(::testing::Ne(testcase.expected)))
         .Times(::testing::AnyNumber());
     AllowedByNosniff::MimeTypeAsScript(*use_counter, logger, response,
                                        MimeTypeCheck::kLaxForWorker);
     ::testing::Mock::VerifyAndClear(use_counter);
 
+    // The kStrictMimeTypeChecksWouldBlockWorker counter should only be active
+    // is "lax" checking for workers is enabled.
     EXPECT_CALL(*use_counter,
-                CountUse(WebFeature::kStrictMimeTypeChecksWouldBlockWorker));
+                CountUse(WebFeature::kStrictMimeTypeChecksWouldBlockWorker))
+        .Times(!RuntimeEnabledFeatures::StrictMimeTypesForWorkersEnabled());
     EXPECT_CALL(*use_counter,
                 CountUse(::testing::Ne(
                     WebFeature::kStrictMimeTypeChecksWouldBlockWorker)))
@@ -225,7 +251,10 @@
   }
 }
 
-TEST_F(AllowedByNosniffTest, AllTheSchemes) {
+TEST_P(AllowedByNosniffTest, AllTheSchemes) {
+  RuntimeEnabledFeaturesTestHelpers::ScopedStrictMimeTypesForWorkers feature(
+      GetParam());
+
   // We test various URL schemes.
   // To force a decision based on the scheme, we give all responses an
   // invalid Content-Type plus a "nosniff" header. That way, all Content-Type
diff --git a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
index 4298558..d816db6 100644
--- a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
+++ b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
@@ -21,11 +21,13 @@
 #include "components/power_scheduler/power_mode_arbiter.h"
 #include "components/power_scheduler/power_mode_voter.h"
 #include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/input/web_coalesced_input_event.h"
 #include "third_party/blink/public/common/input/web_input_event_attribution.h"
 #include "third_party/blink/public/common/input/web_keyboard_event.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/agent_group_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/widget_scheduler.h"
@@ -197,6 +199,8 @@
           std::move(widget), std::move(frame_widget_input_handler),
           never_composited, compositor_thread_scheduler,
           std::move(widget_scheduler), allow_scroll_resampling);
+
+  manager->DidNavigate();
   if (uses_input_handler)
     manager->InitInputHandler();
 
@@ -250,6 +254,11 @@
 #endif
 }
 
+void WidgetInputHandlerManager::DidFirstVisuallyNonEmptyPaint() {
+  suppressing_input_events_state_ &=
+      ~static_cast<uint16_t>(SuppressingInputEventsBits::kHasNotPainted);
+}
+
 void WidgetInputHandlerManager::InitInputHandler() {
   bool sync_compositing = false;
 #if BUILDFLAG(IS_ANDROID)
@@ -459,10 +468,10 @@
 void WidgetInputHandlerManager::LogInputTimingUMA() {
   if (!have_emitted_uma_) {
     InitialInputTiming lifecycle_state = InitialInputTiming::kBeforeLifecycle;
-    if (!(renderer_deferral_state_ &
-          (unsigned)RenderingDeferralBits::kDeferMainFrameUpdates)) {
-      if (renderer_deferral_state_ &
-          (unsigned)RenderingDeferralBits::kDeferCommits) {
+    if (!(suppressing_input_events_state_ &
+          (unsigned)SuppressingInputEventsBits::kDeferMainFrameUpdates)) {
+      if (suppressing_input_events_state_ &
+          (unsigned)SuppressingInputEventsBits::kDeferCommits) {
         lifecycle_state = InitialInputTiming::kBeforeCommit;
       } else {
         lifecycle_state = InitialInputTiming::kAfterCommit;
@@ -475,7 +484,7 @@
 
 void WidgetInputHandlerManager::DispatchScrollGestureToCompositor(
     std::unique_ptr<WebGestureEvent> event) {
-  DCHECK(base::FeatureList::IsEnabled(features::kScrollUnification));
+  DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
   std::unique_ptr<WebCoalescedInputEvent> web_scoped_gesture_event =
       std::make_unique<WebCoalescedInputEvent>(std::move(event),
                                                ui::LatencyInfo());
@@ -490,7 +499,7 @@
 void WidgetInputHandlerManager::
     HandleInputEventWithLatencyOnInputHandlingThread(
         std::unique_ptr<WebCoalescedInputEvent> event) {
-  DCHECK(base::FeatureList::IsEnabled(features::kScrollUnification));
+  DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
   DCHECK(input_handler_proxy_);
   input_handler_proxy_->HandleInputEventWithLatencyInfo(
       std::move(event), nullptr, base::DoNothing());
@@ -513,7 +522,7 @@
     LogInputTimingUMA();
 
   // Drop input if we are deferring a rendering pipeline phase, unless it's a
-  // move event.
+  // move event, or we are waiting for first visually non empty paint.
   // We don't want users interacting with stuff they can't see, so we drop it.
   // We allow moves because we need to keep the current pointer location up
   // to date. Tests and other code can allow pre-commit input through the
@@ -521,7 +530,15 @@
   // TODO(schenney): Also allow scrolls? This would make some tests not flaky,
   // it seems, because they sometimes crash on seeing a scroll update/end
   // without a begin. Scrolling, pinch-zoom etc. don't seem dangerous.
-  if (renderer_deferral_state_ && !allow_pre_commit_input_ && !event_is_move) {
+
+  uint16_t suppress_input = suppressing_input_events_state_;
+  if (!base::FeatureList::IsEnabled(
+          blink::features::kDropInputEventsBeforeFirstPaint)) {
+    suppress_input &=
+        ~static_cast<uint16_t>(SuppressingInputEventsBits::kHasNotPainted);
+  }
+
+  if (suppress_input && !allow_pre_commit_input_ && !event_is_move) {
     if (callback) {
       std::move(callback).Run(
           mojom::blink::InputEventResultSource::kMainThread, ui::LatencyInfo(),
@@ -670,17 +687,18 @@
 }
 
 void WidgetInputHandlerManager::DidNavigate() {
-  renderer_deferral_state_ = 0;
+  suppressing_input_events_state_ =
+      static_cast<uint16_t>(SuppressingInputEventsBits::kHasNotPainted);
   have_emitted_uma_ = false;
 }
 
 void WidgetInputHandlerManager::OnDeferMainFrameUpdatesChanged(bool status) {
   if (status) {
-    renderer_deferral_state_ |=
-        static_cast<uint16_t>(RenderingDeferralBits::kDeferMainFrameUpdates);
+    suppressing_input_events_state_ |= static_cast<uint16_t>(
+        SuppressingInputEventsBits::kDeferMainFrameUpdates);
   } else {
-    renderer_deferral_state_ &=
-        ~static_cast<uint16_t>(RenderingDeferralBits::kDeferMainFrameUpdates);
+    suppressing_input_events_state_ &= ~static_cast<uint16_t>(
+        SuppressingInputEventsBits::kDeferMainFrameUpdates);
   }
 }
 
@@ -688,11 +706,11 @@
     bool status,
     cc::PaintHoldingReason reason) {
   if (status && reason == cc::PaintHoldingReason::kFirstContentfulPaint) {
-    renderer_deferral_state_ |=
-        static_cast<uint16_t>(RenderingDeferralBits::kDeferCommits);
+    suppressing_input_events_state_ |=
+        static_cast<uint16_t>(SuppressingInputEventsBits::kDeferCommits);
   } else {
-    renderer_deferral_state_ &=
-        ~static_cast<uint16_t>(RenderingDeferralBits::kDeferCommits);
+    suppressing_input_events_state_ &=
+        ~static_cast<uint16_t>(SuppressingInputEventsBits::kDeferCommits);
   }
 }
 
diff --git a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h
index 10f0cfd..4c41d2e 100644
--- a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h
+++ b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h
@@ -60,13 +60,18 @@
     kMaxValue = kAfterCommit
   };
 
-  // For use in bitfields to keep track of what, if anything, the rendering
-  // pipeline is currently deferring. Input is suppressed if anything is
-  // being deferred, and we use the combination of states to correctly report
-  // UMA for input that is suppressed.
-  enum class RenderingDeferralBits {
-    kDeferMainFrameUpdates = 1,
-    kDeferCommits = 2
+  // For use in bitfields to keep track of why we should keep suppressing input
+  // events. Maybe the rendering pipeline is currently deferring something, or
+  // we are still waiting for the user to see some non empty paint. And we use
+  // the combination of states to correctly report UMA for input that is
+  // suppressed.
+  enum class SuppressingInputEventsBits {
+    // if set, suppress events because pipeline is deferring main frame updates
+    kDeferMainFrameUpdates = 1 << 0,
+    // if set, suppress events because pipeline is deferring commits
+    kDeferCommits = 1 << 1,
+    // if set, we have not painted a main frame from the current navigation yet
+    kHasNotPainted = 1 << 2,
   };
 
  public:
@@ -97,6 +102,8 @@
   void InputEventsDispatched(bool raf_aligned) override;
   void SetNeedsMainFrame() override;
 
+  void DidFirstVisuallyNonEmptyPaint();
+
   // InputHandlerProxyClient overrides.
   void WillShutdown() override;
   void DispatchNonBlockingEventToMainThread(
@@ -147,7 +154,7 @@
   void WaitForInputProcessed(base::OnceClosure callback);
 
   // Called when the WidgetBase is notified of a navigation. Resets
-  // the renderer pipeline deferral status, and resets the UMA recorder for
+  // the suppressing of input events state, and resets the UMA recorder for
   // time of first input.
   void DidNavigate();
 
@@ -314,18 +321,19 @@
   // we definitely don't have a compositor thread.
   bool uses_input_handler_ = false;
 
-  // State tracking which parts of the rendering pipeline are currently
-  // deferred. We use this state to suppress all events until the user can see
-  // the content; that is, while rendering stages are being deferred and
-  // this value is zero.
+  // State tracking why we should keep suppressing input events, keeps track of
+  // which parts of the rendering pipeline are currently deferred, or whether
+  // we are waiting for the first non empty paint. We use this state to suppress
+  // all events while user has not seen first paint or rendering pipeline is
+  // deferring something.
   // Move events are still processed to allow tracking of mouse position.
   // Metrics also report the lifecycle state when the first non-move event is
   // seen.
-  // This is a bitfield, using the bit values from RenderingDeferralBits.
+  // This is a bitfield, using the bit values from SuppressingInputEventsBits.
   // The compositor thread accesses this value when processing input (to decide
   // whether to suppress input) and the renderer thread accesses it when the
   // status of deferrals changes, so it needs to be thread safe.
-  std::atomic<uint16_t> renderer_deferral_state_{0};
+  std::atomic<uint16_t> suppressing_input_events_state_;
 
   // Allow input suppression to be disabled for tests and non-browser uses
   // of chromium that do not wait for the first commit, or that may never
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index 9dd62a6e..e37b294 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -246,6 +246,11 @@
   initialized_ = true;
 }
 
+void WidgetBase::DidFirstVisuallyNonEmptyPaint() {
+  if (widget_input_handler_manager_)
+    widget_input_handler_manager_->DidFirstVisuallyNonEmptyPaint();
+}
+
 void WidgetBase::Shutdown() {
   // The |input_event_queue_| is refcounted and will live while an event is
   // being handled. This drops the connection back to this WidgetBase which
diff --git a/third_party/blink/renderer/platform/widget/widget_base.h b/third_party/blink/renderer/platform/widget/widget_base.h
index 791e998..2dd98c03 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.h
+++ b/third_party/blink/renderer/platform/widget/widget_base.h
@@ -107,6 +107,8 @@
   // Shutdown the compositor.
   void Shutdown();
 
+  void DidFirstVisuallyNonEmptyPaint();
+
   // Set the compositor as visible. If |visible| is true, then the compositor
   // will request a new layer frame sink, begin producing frames from the
   // compositor scheduler, and in turn will update the document lifecycle.
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 1adea608..c7088c6 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -1781,7 +1781,7 @@
 # Some Sanitizer API tests test behaviour for MathML, which in Chromium depends
 # on LayoutNG. We accept failures of those specific tests when LayoutNG is
 # disabled.
-crbug.com/1225606 external/wpt/sanitizer-api/sanitizer-names.https.html [ Failure Pass ]
+crbug.com/1225606 external/wpt/sanitizer-api/sanitizer-names.https.tentative.html [ Failure Pass ]
 
 # broken by https://chromium-review.googlesource.com/c/chromium/src/+/3376745
 crbug.com/1283025 external/wpt/css/css-tables/fractional-percent-width.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0628f5f2..de090db 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2520,9 +2520,6 @@
 # crbug.com/1218723 This test fails when SplitCacheByNetworkIsolationKey is enabled.
 crbug.com/1218723 http/tests/devtools/network/network-prefetch.js [ Failure ]
 
-# Method needs to be renamed.
-crbug.com/1253323 http/tests/devtools/network/network-persistence-filename-safety.js [ Skip ]
-
 crbug.com/1051044 external/wpt/css/filter-effects/effect-reference-feimage-001.html [ Failure Pass ]
 crbug.com/1051044 external/wpt/css/filter-effects/effect-reference-feimage-003.html [ Failure Pass ]
 
@@ -6712,7 +6709,7 @@
 crbug.com/1286619 [ Mac ] external/wpt/storage-access-api/storageAccess.testdriver.sub.html [ Failure Pass ]
 
 # Sanitizer API: Namespace-based tests will partially fail until namespace support is complete.
-crbug.com/1225606 external/wpt/sanitizer-api/sanitizer-names.https.html [ Failure ]
+crbug.com/1225606 external/wpt/sanitizer-api/sanitizer-names.https.tentative.html [ Failure ]
 crbug.com/1225606 virtual/mathml-disabled/sanitizer-api-math-namespace-designator.html [ Failure ]
 
 # Temporarily disabled to unblock https://crrev.com/c/3657449
diff --git a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-names.https.html b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-names.https.html
index 635b797..4994992 100644
--- a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-names.https.html
+++ b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-names.https.html
@@ -13,22 +13,21 @@
 
   // Element names:
   const elems_valid = [
-    "p", "template", "span", "custom-elements", "svg", "svg:svg", "potato",
+    "p", "template", "span", "custom-elements", "potato",
 
     // Arguments will be stringified, so anything that stringifies to a valid
     // name is also valid. (E.g. null => "null")
     null, undefined, 123
   ];
   const elems_invalid = [
-    "", "svg svg", "potato:svg", [], ["*"], ["p"]
+    "", [], ["*"], ["p"]
   ];
 
   // Attribute names:
   const attrs_valid = [
-    "href", "span", "xlink:href"
+    "href", "span",
   ];
   const attrs_invalid = [
-    "svg:href", "svg href", "xlink:span"
   ];
 
   const all_elems = elems_valid.concat(elems_invalid);
@@ -48,73 +47,6 @@
                         attrs_valid.map(x => "" + x));
     }, `Attribute names in config item: ${item}`);
   }
-
-  // Quick sanity tests for namespaced elements.
-  // Each test case is a duo or triplet:
-  // - a Sanitizer config string for an element.
-  // - an HTML probe string.
-  // - the expected result. (If different from the probe.)
-  [
-    [ "p", "<p>Hello</p>" ],
-    [ "svg", "<svg>Hello</svg>", "Hello" ],
-    [ "svg:svg", "<svg>Hello</svg>" ],
-    [ "math", "<math>Hello</math>", "Hello" ],
-    [ "svg:math", "<math>Hello</math>", "Hello" ],
-    [ "math:math", "<math>Hello</math>" ],
-    [ "potato:math", "<math>Hello</math>", "Hello" ],
-    [ "potato:math", "<potato:math>Hello</potato:math>", "Hello" ],
-  ].forEach(([elem, probe, expected], index) => {
-    test(t => {
-      const sanitizer = new Sanitizer({allowElements: [elem]});
-      assert_equals(sanitizer.sanitizeFor("template", probe).innerHTML,
-                    expected ?? probe);
-    }, `Namespaced elements #${index}: allowElements: ["${elem}"]`);
-  });
-
-  // Same for attributes:
-  [
-    [ "style", "<p style=\"bla\"></p>" ],
-    [ "href", "<p href=\"bla\"></p>" ],
-    [ "xlink:href", "<p xlink:href=\"bla\"></p>" ],
-    [ "potato:href", "<p potato:href='bla'></p>", "<p></p>" ],
-    [ "xlink:href", "<p href='bla'></p>", "<p></p>" ],
-    [ "href", "<p xlink:href='bla'></p>", "<p></p>" ],
-  ].forEach(([attr, probe, expected], index) => {
-    test(t => {
-      const sanitizer = new Sanitizer({allowAttributes: {[attr]: ["*"]}});
-      assert_equals(sanitizer.sanitizeFor("template", probe).innerHTML,
-                    expected ?? probe);
-    }, `Namespaced attributes #${index}: allowAttributes: {"${attr}": ["*"]}`);
-  });
-
-  // Most element and attribute names are lower-cased, but "foreign content"
-  // like SVG and MathML have some mixed-cased names.
-  [
-    [ "svg:feBlend", "<feBlend></feBlend>" ],
-    [ "svg:feColorMatrix", "<feColorMatrix></feColorMatrix>" ],
-    [ "svg:textPath", "<textPath></textPath>" ],
-  ].forEach(([elem, probe], index) => {
-    const sanitize = (elem, probe) => {
-      return new Sanitizer({allowElements: ["svg:svg", elem]}).
-          sanitizeFor("template", `<svg>${probe}</svg`).
-          content.firstElementChild.innerHTML;
-    };
-    test(t => {
-      assert_equals(sanitize(elem, probe), probe);
-    }, `Mixed-case element names #${index}: "${elem}"`);
-    test(t => {
-      assert_not_equals(sanitize(elem.toLowerCase(), probe), probe);
-    }, `Mixed-case element names #${index}: "${elem.toLowerCase()}"`);
-    test(t => {
-      assert_not_equals(sanitize(elem.toUpperCase(), probe), probe);
-    }, `Mixed-case element names #${index}: "${elem.toUpperCase()}"`);
-    test(t => {
-      const elems = [elem];
-      assert_array_equals(
-        new Sanitizer({allowElements: elems}).getConfiguration().allowElements,
-        elems);
-    }, `Mixed case element names #${index}: "${elem}" is preserved in config.`);
-  });
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-names.https.tentative.html b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-names.https.tentative.html
new file mode 100644
index 0000000..635b797
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-names.https.tentative.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+  // Like assert_array_equals, but disregard element order.
+  function assert_array_same(actual, expected) {
+    assert_array_equals(actual.sort(), expected.sort());
+  }
+
+  // Element names:
+  const elems_valid = [
+    "p", "template", "span", "custom-elements", "svg", "svg:svg", "potato",
+
+    // Arguments will be stringified, so anything that stringifies to a valid
+    // name is also valid. (E.g. null => "null")
+    null, undefined, 123
+  ];
+  const elems_invalid = [
+    "", "svg svg", "potato:svg", [], ["*"], ["p"]
+  ];
+
+  // Attribute names:
+  const attrs_valid = [
+    "href", "span", "xlink:href"
+  ];
+  const attrs_invalid = [
+    "svg:href", "svg href", "xlink:span"
+  ];
+
+  const all_elems = elems_valid.concat(elems_invalid);
+  const all_attrs = attrs_valid.concat(attrs_invalid);
+  for (const item of ["allowElements", "dropElements", "blockElements"]) {
+    test(t => {
+      const sanitizer = new Sanitizer({[item]: all_elems});
+      assert_array_same(sanitizer.getConfiguration()[item],
+                          elems_valid.map(x => "" + x));
+    }, `Element names in config item: ${item}`);
+  }
+  for (const item of ["allowAttributes", "dropAttributes"]) {
+    test(t => {
+      const sanitizer = new Sanitizer(
+          {[item]: Object.fromEntries(all_attrs.map(x => [x, ["*"]]))});
+      assert_array_same(Object.keys(sanitizer.getConfiguration()[item]),
+                        attrs_valid.map(x => "" + x));
+    }, `Attribute names in config item: ${item}`);
+  }
+
+  // Quick sanity tests for namespaced elements.
+  // Each test case is a duo or triplet:
+  // - a Sanitizer config string for an element.
+  // - an HTML probe string.
+  // - the expected result. (If different from the probe.)
+  [
+    [ "p", "<p>Hello</p>" ],
+    [ "svg", "<svg>Hello</svg>", "Hello" ],
+    [ "svg:svg", "<svg>Hello</svg>" ],
+    [ "math", "<math>Hello</math>", "Hello" ],
+    [ "svg:math", "<math>Hello</math>", "Hello" ],
+    [ "math:math", "<math>Hello</math>" ],
+    [ "potato:math", "<math>Hello</math>", "Hello" ],
+    [ "potato:math", "<potato:math>Hello</potato:math>", "Hello" ],
+  ].forEach(([elem, probe, expected], index) => {
+    test(t => {
+      const sanitizer = new Sanitizer({allowElements: [elem]});
+      assert_equals(sanitizer.sanitizeFor("template", probe).innerHTML,
+                    expected ?? probe);
+    }, `Namespaced elements #${index}: allowElements: ["${elem}"]`);
+  });
+
+  // Same for attributes:
+  [
+    [ "style", "<p style=\"bla\"></p>" ],
+    [ "href", "<p href=\"bla\"></p>" ],
+    [ "xlink:href", "<p xlink:href=\"bla\"></p>" ],
+    [ "potato:href", "<p potato:href='bla'></p>", "<p></p>" ],
+    [ "xlink:href", "<p href='bla'></p>", "<p></p>" ],
+    [ "href", "<p xlink:href='bla'></p>", "<p></p>" ],
+  ].forEach(([attr, probe, expected], index) => {
+    test(t => {
+      const sanitizer = new Sanitizer({allowAttributes: {[attr]: ["*"]}});
+      assert_equals(sanitizer.sanitizeFor("template", probe).innerHTML,
+                    expected ?? probe);
+    }, `Namespaced attributes #${index}: allowAttributes: {"${attr}": ["*"]}`);
+  });
+
+  // Most element and attribute names are lower-cased, but "foreign content"
+  // like SVG and MathML have some mixed-cased names.
+  [
+    [ "svg:feBlend", "<feBlend></feBlend>" ],
+    [ "svg:feColorMatrix", "<feColorMatrix></feColorMatrix>" ],
+    [ "svg:textPath", "<textPath></textPath>" ],
+  ].forEach(([elem, probe], index) => {
+    const sanitize = (elem, probe) => {
+      return new Sanitizer({allowElements: ["svg:svg", elem]}).
+          sanitizeFor("template", `<svg>${probe}</svg`).
+          content.firstElementChild.innerHTML;
+    };
+    test(t => {
+      assert_equals(sanitize(elem, probe), probe);
+    }, `Mixed-case element names #${index}: "${elem}"`);
+    test(t => {
+      assert_not_equals(sanitize(elem.toLowerCase(), probe), probe);
+    }, `Mixed-case element names #${index}: "${elem.toLowerCase()}"`);
+    test(t => {
+      assert_not_equals(sanitize(elem.toUpperCase(), probe), probe);
+    }, `Mixed-case element names #${index}: "${elem.toUpperCase()}"`);
+    test(t => {
+      const elems = [elem];
+      assert_array_equals(
+        new Sanitizer({allowElements: elems}).getConfiguration().allowElements,
+        elems);
+    }, `Mixed case element names #${index}: "${elem}" is preserved in config.`);
+  });
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-query-config.https.html b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-query-config.https.html
index 8824ba7..60cba2d 100644
--- a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-query-config.https.html
+++ b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-query-config.https.html
@@ -35,6 +35,7 @@
     assert_false("blockElements" in Sanitizer.getDefaultConfiguration());
     assert_false("dropAttributes" in Sanitizer.getDefaultConfiguration());
     assert_false(Sanitizer.getDefaultConfiguration().allowCustomElements);
+    assert_false(Sanitizer.getDefaultConfiguration().allowUnknownMarkup);
   }, "SanitizerAPI getDefaultConfiguration()");
 
   test(t => {
@@ -49,10 +50,13 @@
       allowAttributes: { "class": ["*"], "color": ["span", "div"],
                          "onclick": ["*"] },
       allowCustomElements: true,
+      allowUnknownMarkup: true,
     },{
       blockElements: ["table", "tbody", "th", "td"],
     }, {
       allowCustomElements: false,
+    }, {
+      allowUnknownMarkup: false,
     }];
     for (const config of configs)
       assert_deep_equals(config, new Sanitizer(config).getConfiguration());
@@ -64,6 +68,7 @@
       allowAttributes: { "class": ["*"], "color": ["sPAn", "div"],
                          "onclick": ["*"] },
       allowCustomElements: true,
+      allowUnknownMarkup: true,
     };
     assert_deep_equals(config_0_mixed,
                        new Sanitizer(config_0_mixed).getConfiguration());
diff --git a/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-unknown.https.html b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-unknown.https.html
new file mode 100644
index 0000000..a703f42
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/sanitizer-api/sanitizer-unknown.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+test(t => {
+  d = document.createElement("div")
+  d.setHTML("<hello><world>",
+      { sanitizer: new Sanitizer({allowElements: ["hello", "world"]}) });
+  assert_equals(d.innerHTML, "");
+}, "Unknown element names get blocked without allowUnknownMarkup.");
+
+test(t => {
+  d = document.createElement("div")
+  d.setHTML("<hello><world>",
+      { sanitizer: new Sanitizer({allowUnknownMarkup: true,
+                                 allowElements: ["hello", "world"]}) });
+  assert_equals(d.innerHTML, "<hello><world></world></hello>");
+}, "Unknown element names pass with allowUnknownMarkup.");
+
+test(t => {
+  d = document.createElement("div")
+  d.setHTML("<b hello='1' world>", { sanitizer:
+      new Sanitizer({allowAttributes: {"hello": ["*"], "world": ["*"]}}) });
+  assert_equals(d.innerHTML, "<b></b>");
+}, "Unknown attributes names get blocked without allowUnknownMarkup.");
+
+test(t => {
+  d = document.createElement("div")
+  d.setHTML("<b hello='1' world>", { sanitizer:
+      new Sanitizer({allowUnknownMarkup: true,
+                     allowAttributes: {"hello": ["*"], "world": ["*"]}}) });
+  assert_equals(d.innerHTML, `<b hello="1" world=""></b>`);
+}, "Unknown attribute names pass with allowUnknownMarkup.");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/sanitizer-api/support/testcases.sub.js b/third_party/blink/web_tests/external/wpt/sanitizer-api/support/testcases.sub.js
index c54d4461..1a5989fb 100644
--- a/third_party/blink/web_tests/external/wpt/sanitizer-api/support/testcases.sub.js
+++ b/third_party/blink/web_tests/external/wpt/sanitizer-api/support/testcases.sub.js
@@ -27,10 +27,7 @@
   {config_input: {dropElements: ["script"]}, value: "<script>alert('i am a test')<\/script>", result: "", message: "test script with [\"script\"] as dropElements list"},
   {config_input: {dropElements: ["test-element", "i"]}, value: "<div>balabala<i>test</i></div><test-element>t</test-element>", result: "<div>balabala</div>", message: "dropElements list [\"test-element\", \"i\"]}"},
   {config_input: {dropElements: ["dl", "p"]}, value: "<div>balabala<i>i</i><p>t</p></div>", result: "<div>balabala<i>i</i></div>", message: "dropElements list [\"dl\", \"p\"]}"},
-  {config_input: {dropElements: [123, [], "test", "i", "custom-element"]}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabala</div>", message: "dropElements list with invalid values"},
-  {config_input: {blockElements: [123, [], "test", "i", "custom-element"]}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabalatest</div>t", message: "blockElements list with invalid values"},
   {config_input: {allowElements: ["p"]}, value: "<div>test<div>p</div>tt<p>div</p></div>", result: "testptt<p>div</p>", message: "allowElements list [\"p\"]"},
-  {config_input: {allowElements: ["p", "test"]}, value: "<div>test<div>p</div>tt<p>div</p><test>test</test></div>", result: "testptt<p>div</p><test>test</test>", message: "allowElements list [\"p\", \"test\"]"},
   {config_input: {dropElements: ["div"], allowElements: ["div"]}, value: "<div>test</div><p>bla", result: "bla", message: "allowElements list has no influence to dropElements"},
   {config_input: {dropAttributes: {"style": ["p"]}}, value: "<p style='color: black'>Click.</p><div style='color: white'>div</div>", result: "<p>Click.</p><div style=\"color: white\">div</div>", message: "dropAttributes list {\"style\": [\"p\"]} with style attribute"},
   {config_input: {dropAttributes: {"*": ["a"]}}, value: "<a id='a' style='color: black'>Click.</a><div style='color: white'>div</div>", result: "<a>Click.</a><div style=\"color: white\">div</div>", message: "dropAttributes list {\"*\": [\"a\"]} with style attribute"},
@@ -74,4 +71,18 @@
   {config_input: {dropAttributes: {"ID": ["*"]}}, value: "<p id=\"test\">Click.</p>", result: "<p id=\"test\">Click.</p>", message: "dropAttributes list {\"ID\": [\"*\"]} with id attribute"},
   {config_input: {dropAttributes: {"ID": ["*"]}}, value: "<p ID=\"test\">Click.</p>", result: "<p id=\"test\">Click.</p>", message: "dropAttributes list {\"ID\": [\"*\"]} with ID attribute"},
   {config_input: {dropAttributes: {"id": ["*"]}}, value: "<p ID=\"test\">Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list {\"id\": [\"*\"]} with ID attribute"},
+
+  // allowUnknownMarkup for elements (with and without)
+  {config_input: {dropElements: [123, [], "test", "i", "custom-element"]}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabala</div>", message: "dropElements with unknown elements and without allowUnknownMarkup"},
+  {config_input: {blockElements: [123, [], "test", "i", "custom-element"]}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabalatest</div>", message: "blockElements with unknown elements and without allowUnknownMarkup"},
+  {config_input: {allowElements: ["p", "test"]}, value: "<div>test<div>p</div>tt<p>div</p></div><test>test</test>", result: "testptt<p>div</p>", message: "allowElements with unknown elements and without allowUnknownMarkup"},
+  {config_input: {dropElements: [123, [], "test", "i", "custom-element"], allowUnknownMarkup: true}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabala</div>", message: "dropElements with unknown elements and with allowUnknownMarkup"},
+  {config_input: {blockElements: [123, [], "test", "i", "custom-element"], allowUnknownMarkup: true}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabalatest</div>t", message: "blockElements with unknown elements and with allowUnknownMarkup"},
+  {config_input: {allowElements: ["p", "test"], allowUnknownMarkup: true}, value: "<div>test<div>p</div>tt<p>div</p><test>test</test></div>", result: "testptt<p>div</p><test>test</test>", message: "allowElements with unknown elements and with allowUnknownMarkup"},
+
+  // allowUnknownMarkup for attributes (with and without)
+  {config_input: {allowAttributes: {"hello": ["*"], "world": ["b"]}}, value: "<div hello='1' world='2'><b hello='3' world='4'>", result: "<div><b></b></div>", message: "allowAttributes unknown attributes and without allowUnknownMarkup"},
+  {config_input: {allowAttributes: {"hello": ["*"], "world": ["b"]}, allowUnknownMarkup: true}, value: "<div hello='1' world='2'><b hello='3' world='4'>", result: "<div hello=\"1\"><b hello=\"3\" world=\"4\"></b></div>", message: "allowAttributes unknown attributes and with allowUnknownMarkup"},
+  {config_input: {dropAttributes: {"hello": ["*"], "world": ["b"]}}, value: "<div hello='1' world='2'><b hello='3' world='4'>", result: "<div><b></b></div>", message: "dropAttributes unknown attributes and without allowUnknownMarkup"},
+  {config_input: {dropAttributes: {"hello": ["*"], "world": ["b"]}, allowUnknownMarkup: true}, value: "<div hello='1' world='2'><b hello='3' world='4'>", result: "<div><b></b></div>", message: "dropAttributes unknown attributes and with allowUnknownMarkup"},
 ];
diff --git a/third_party/blink/web_tests/external/wpt/workers/Worker_script_mimetype.htm b/third_party/blink/web_tests/external/wpt/workers/Worker_script_mimetype.htm
index 29cc5d33..4768fe7 100644
--- a/third_party/blink/web_tests/external/wpt/workers/Worker_script_mimetype.htm
+++ b/third_party/blink/web_tests/external/wpt/workers/Worker_script_mimetype.htm
@@ -7,11 +7,17 @@
 <script>
 async_test(t => {
   const worker = new Worker('./support/WorkerText.txt');
-  worker.onmessage = t.unreached_func("Worker should not recieve messages");
+  worker.onmessage = t.unreached_func("Worker should not receive messages");
   worker.onerror = () => t.done();
 }, "HTTP(S) URLs which respond with text/plain MIME type must not work");
 
 async_test(t => {
+  const worker = new SharedWorker('./support/WorkerText.txt');
+  worker.onmessage = t.unreached_func("Worker should not receive messages");
+  worker.onerror = () => t.done();
+}, "HTTP(S) URLs which respond with text/plain MIME type must not work on SharedWorkers");
+
+async_test(t => {
   const url = URL.createObjectURL(new Blob(['postMessage("PASS")'])); // no MIME type parameter
   const worker = new Worker(url);
   worker.onmessage = () => t.done();
diff --git a/third_party/blink/web_tests/http/tests/devtools/network/network-persistence-filename-safety.js b/third_party/blink/web_tests/http/tests/devtools/network/network-persistence-filename-safety.js
deleted file mode 100644
index 4de8bb32..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/network/network-persistence-filename-safety.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// 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.
-
-(async function() {
-  TestRunner.addResult(`To make sure that filenames are encoded safely for Network Persistence.\n`);
-
-  await TestRunner.loadTestModule('bindings_test_runner');
-
-  var {project} = await BindingsTestRunner.createOverrideProject('file:///tmp/');
-  BindingsTestRunner.setOverridesEnabled(true);
-
-  // Simple tests.
-  log('www.example.com/');
-  log('www.example.com/simple');
-  log('www.example.com/hello/foo/bar');
-  log('www.example.com/.');
-
-  // Reserved names on windows.
-  log('example.com/CON');
-  log('example.com/cOn');
-  log('example.com/cOn/hello');
-  log('example.com/PRN');
-  log('example.com/AUX');
-  log('example.com/NUL');
-  log('example.com/COM1');
-  log('example.com/COM2');
-  log('example.com/COM3');
-  log('example.com/COM4');
-  log('example.com/COM5');
-  log('example.com/COM6');
-  log('example.com/COM7');
-  log('example.com/COM8');
-  log('example.com/COM9');
-  log('example.com/LPT1');
-  log('example.com/LPT2');
-  log('example.com/LPT3');
-  log('example.com/LPT4');
-  log('example.com/LPT5');
-  log('example.com/LPT6');
-  log('example.com/LPT7');
-  log('example.com/LPT8');
-  log('example.com/LPT9');
-
-  // Query params
-  log('example.com/fo?o/bar');
-  log('example.com/foo?/bar');
-  log('example.com/foo/?bar');
-  log('example.com/foo/?bar');
-  log('example.com/?foo/bar/3');
-
-  // Hash params
-  log('example.com/?foo/bar/3#hello/bar');
-  log('example.com/#foo/bar/3hello/bar');
-  log('example.com/foo/bar/?3hello/bar');
-  log('example.com/foo/bar/#?3hello/bar');
-  log('example.com/foo.js#');
-
-  // Windows cannot end in . (period).
-  log('example.com/foo.js.');
-  // Windows cannot end in (space).
-  log('example.com/foo.js ');
-
-  // Others
-  log('example.com/foo .js');
-  log('example.com///foo.js');
-  log('example.com///');
-
-  // Very long file names
-  log('example.com' + '/THIS/PATH/IS_MORE_THAN/200/Chars'.repeat(8));
-  log('example.com' + '/THIS/PATH/IS_LESS_THAN/200/Chars'.repeat(5));
-
-  TestRunner.completeTest();
-
-  function log(url) {
-    TestRunner.addResult(url + ' -> ' + Persistence.networkPersistenceManager.encodedPathFromUrl(url));
-  }
-})();
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/initiator-minified.js b/third_party/blink/web_tests/http/tests/inspector-protocol/network/initiator-minified.js
index 20de8f44..dede9ce 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/network/initiator-minified.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/initiator-minified.js
@@ -1,7 +1,7 @@
 (async function(testRunner) {
-  const {page, session, dp} = await testRunner.startBlank(
-    `Tests that the initiator position is correct even when that initiator is minified.`);
-  testRunner.startDumpingProtocolMessages();
+  const {page, session, dp} = await testRunner.startURL(
+      'resources/minified.html',
+      `Tests that the initiator position is correct even when that initiator is minified.`);
   window.onerror = (msg) => testRunner.log('onerror: ' + msg);
   window.onunhandledrejection = (e) => testRunner.log('onunhandledrejection: ' + e.reason);
   let errorForLog = new Error();
@@ -9,7 +9,7 @@
 
   dp.Network.enable();
   dp.Page.enable();
-  dp.Page.navigate({url: testRunner.url('resources/minified.html')});
+  dp.Page.reload({ignoreCache: true});
 
   let requests = [];
   dp.Network.onLoadingFailed(e => testRunnler.log(JSON.stringify(e)));
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/window-open-effective-opener.js b/third_party/blink/web_tests/http/tests/inspector-protocol/window-open-effective-opener.js
index ebc8fd4..ef61f68 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/window-open-effective-opener.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/window-open-effective-opener.js
@@ -14,8 +14,17 @@
         testRunner.log(`FAIL: Incorrect ${keyName}`);
     }
 
+    async function evaluate(expression) {
+      const response = await dp.Runtime.evaluate({expression});
+      if (response.error &&
+          response.error.message != 'Inspected target navigated or closed') {
+        testRunner.log(`Error while evaluating async ${expression}: ${
+            response.error.message || response.error}`);
+      }
+    }
+
     testRunner.log(`\nOpening without new browsing context`);
-    session.evaluate(`window.open('./resources/test-page.html', '_blank'); undefined`);
+    evaluate(`window.open('./resources/test-page.html', '_blank')`);
     response = await dp.Target.onceTargetCreated();
     testRunner.log(response.params.targetInfo);
     compareTargetIds(targetId, response.params.targetInfo.openerFrameId, 'openerFrameId');
@@ -26,7 +35,7 @@
     compareTargetIds(targetId, response.params.targetInfo.openerId, 'openerId');
 
     testRunner.log(`\nOpening with new browsing context`);
-    session.evaluate(`window.open('./resources/test-page.html', '_blank', 'noopener'); undefined`);
+    evaluate(`window.open('./resources/test-page.html', '_blank', 'noopener')`);
     response = await dp.Target.onceTargetCreated();
     testRunner.log(response.params.targetInfo);
     compareTargetIds(targetId, response.params.targetInfo.openerFrameId, 'openerFrameId');
@@ -38,7 +47,7 @@
 
     testRunner.log(`\nOpening with COOP header`);
     await dp.Page.navigate({ url: testRunner.url('https://127.0.0.1:8443/inspector-protocol/resources/coop.php')});
-    session.evaluate(`window.open('./resources/test-page.html', '_blank'); undefined`);
+    evaluate(`window.open('./resources/test-page.html', '_blank')`);
     response = await dp.Target.onceTargetCreated();
     testRunner.log(response.params.targetInfo);
     compareTargetIds(targetId, response.params.targetInfo.openerFrameId, 'openerFrameId');
@@ -49,4 +58,4 @@
     compareTargetIds(targetId, response.params.targetInfo.openerId, 'openerId');
 
     testRunner.completeTest();
-  })
\ No newline at end of file
+  })
diff --git a/third_party/blink/web_tests/platform/generic/http/tests/devtools/network/network-persistence-filename-safety-expected.txt b/third_party/blink/web_tests/platform/generic/http/tests/devtools/network/network-persistence-filename-safety-expected.txt
deleted file mode 100644
index 9ed2c51d..0000000
--- a/third_party/blink/web_tests/platform/generic/http/tests/devtools/network/network-persistence-filename-safety-expected.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-To make sure that filenames are encoded safely for Network Persistence.
-
-www.example.com/ -> www.example.com/index.html
-www.example.com/simple -> www.example.com/simple
-www.example.com/hello/foo/bar -> www.example.com/hello/foo/bar
-www.example.com/. -> www.example.com/%2e
-example.com/CON -> example.com/%43%4f%4e
-example.com/cOn -> example.com/%63%4f%6e
-example.com/cOn/hello -> example.com/%63%4f%6e/hello
-example.com/PRN -> example.com/%50%52%4e
-example.com/AUX -> example.com/%41%55%58
-example.com/NUL -> example.com/%4e%55%4c
-example.com/COM1 -> example.com/%43%4f%4d%31
-example.com/COM2 -> example.com/%43%4f%4d%32
-example.com/COM3 -> example.com/%43%4f%4d%33
-example.com/COM4 -> example.com/%43%4f%4d%34
-example.com/COM5 -> example.com/%43%4f%4d%35
-example.com/COM6 -> example.com/%43%4f%4d%36
-example.com/COM7 -> example.com/%43%4f%4d%37
-example.com/COM8 -> example.com/%43%4f%4d%38
-example.com/COM9 -> example.com/%43%4f%4d%39
-example.com/LPT1 -> example.com/%4c%50%54%31
-example.com/LPT2 -> example.com/%4c%50%54%32
-example.com/LPT3 -> example.com/%4c%50%54%33
-example.com/LPT4 -> example.com/%4c%50%54%34
-example.com/LPT5 -> example.com/%4c%50%54%35
-example.com/LPT6 -> example.com/%4c%50%54%36
-example.com/LPT7 -> example.com/%4c%50%54%37
-example.com/LPT8 -> example.com/%4c%50%54%38
-example.com/LPT9 -> example.com/%4c%50%54%39
-example.com/fo?o/bar -> example.com/fo%3fo%2fbar
-example.com/foo?/bar -> example.com/foo%3f%2fbar
-example.com/foo/?bar -> example.com/foo/%3fbar
-example.com/foo/?bar -> example.com/foo/%3fbar
-example.com/?foo/bar/3 -> example.com/%3ffoo%2fbar%2f3
-example.com/?foo/bar/3#hello/bar -> example.com/%3ffoo%2fbar%2f3
-example.com/#foo/bar/3hello/bar -> example.com/index.html
-example.com/foo/bar/?3hello/bar -> example.com/foo/bar/%3f3hello%2fbar
-example.com/foo/bar/#?3hello/bar -> example.com/foo/bar/index.html
-example.com/foo.js# -> example.com/foo.js
-example.com/foo.js. -> example.com/foo.js%2e
-example.com/foo.js  -> example.com/foo.js%20
-example.com/foo .js -> example.com/foo%20.js
-example.com///foo.js -> example.com/foo.js
-example.com/// -> example.com/index.html
-example.com/THIS/PATH/IS_MORE_THAN/200/Chars/THIS/PATH/IS_MORE_THAN/200/Chars/THIS/PATH/IS_MORE_THAN/200/Chars/THIS/PATH/IS_MORE_THAN/200/Chars/THIS/PATH/IS_MORE_THAN/200/Chars/THIS/PATH/IS_MORE_THAN/200/Chars/THIS/PATH/IS_MORE_THAN/200/Chars/THIS/PATH/IS_MORE_THAN/200/Chars -> example.com/longurls/Chars-141a715a
-example.com/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars -> example.com/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars
-
diff --git a/third_party/blink/web_tests/wpt_internal/attribution-reporting/debug-key.sub.https.html b/third_party/blink/web_tests/wpt_internal/attribution-reporting/debug-key.sub.https.html
index d6abb0d..ff4cdc46b 100644
--- a/third_party/blink/web_tests/wpt_internal/attribution-reporting/debug-key.sub.https.html
+++ b/third_party/blink/web_tests/wpt_internal/attribution-reporting/debug-key.sub.https.html
@@ -10,7 +10,7 @@
 <script>
 const debug_key_promise_test = (testCase, forSource) =>
   attribution_reporting_promise_test(async t => {
-    registerAttributionSrc(t, {
+    await registerAttributionSrc(t, {
       source: {
         source_event_id: '1',
         destination: `https://{{host}}`,
@@ -19,7 +19,7 @@
       cookie: testCase.cookie,
     });
 
-    registerAttributionSrc(t, {trigger: {
+    await registerAttributionSrc(t, {trigger: {
       event_trigger_data: [{trigger_data: '0'}],
       debug_key: forSource ? undefined : testCase.debugKey,
     }});
diff --git a/third_party/blink/web_tests/wpt_internal/attribution-reporting/simple-aggregatable-report.sub.https.html b/third_party/blink/web_tests/wpt_internal/attribution-reporting/simple-aggregatable-report.sub.https.html
index 9628660..c0b259f4 100644
--- a/third_party/blink/web_tests/wpt_internal/attribution-reporting/simple-aggregatable-report.sub.https.html
+++ b/third_party/blink/web_tests/wpt_internal/attribution-reporting/simple-aggregatable-report.sub.https.html
@@ -30,8 +30,8 @@
     },
   };
 
-  registerAttributionSrc(t, {source, cookie: 'foo=bar;Secure;HttpOnly;Path=/'});
-  registerAttributionSrc(t, {trigger});
+  await registerAttributionSrc(t, {source, cookie: 'foo=bar;Secure;HttpOnly;Path=/'});
+  await registerAttributionSrc(t, {trigger});
 
   const payload = await pollAggregatableReports();
   assert_equals(payload.reports.length, 1);
diff --git a/third_party/blink/web_tests/wpt_internal/attribution-reporting/simple-event-level-report.sub.https.html b/third_party/blink/web_tests/wpt_internal/attribution-reporting/simple-event-level-report.sub.https.html
index bb47e301..0826560 100644
--- a/third_party/blink/web_tests/wpt_internal/attribution-reporting/simple-event-level-report.sub.https.html
+++ b/third_party/blink/web_tests/wpt_internal/attribution-reporting/simple-event-level-report.sub.https.html
@@ -11,8 +11,8 @@
   };
   const trigger = {event_trigger_data: [{trigger_data: '2'}]};
 
-  registerAttributionSrc(t, {source, cookie: 'foo=bar;Secure;HttpOnly;Path=/'});
-  registerAttributionSrc(t, {trigger});
+  await registerAttributionSrc(t, {source, cookie: 'foo=bar;Secure;HttpOnly;Path=/'});
+  await registerAttributionSrc(t, {trigger});
 
   const payload = await pollEventLevelReports();
   assert_equals(payload.reports.length, 1);
diff --git a/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-priority.sub.https.html b/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-priority.sub.https.html
index 4e4b47c..87a21854 100644
--- a/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-priority.sub.https.html
+++ b/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-priority.sub.https.html
@@ -5,23 +5,25 @@
 <script src="resources/helpers.js"></script>
 <script>
 attribution_reporting_promise_test(async t => {
-  registerAttributionSrc(t, {source: {
-    source_event_id: '1',
-    destination: `https://{{host}}`,
-    priority: '11',
-  }});
-  registerAttributionSrc(t, {source: {
-    source_event_id: '2',
-    destination: `https://{{host}}`,
-    priority: '13',
-  }});
-  registerAttributionSrc(t, {source: {
-    source_event_id: '3',
-    destination: `https://{{host}}`,
-    priority: '12',
-  }});
+  await Promise.all([
+    registerAttributionSrc(t, {source: {
+      source_event_id: '1',
+      destination: `https://{{host}}`,
+      priority: '11',
+    }}),
+    registerAttributionSrc(t, {source: {
+      source_event_id: '2',
+      destination: `https://{{host}}`,
+      priority: '13',
+    }}),
+    registerAttributionSrc(t, {source: {
+      source_event_id: '3',
+      destination: `https://{{host}}`,
+      priority: '12',
+    }}),
+  ]);
 
-  registerAttributionSrc(t, {trigger: {
+  await registerAttributionSrc(t, {trigger: {
     event_trigger_data: [{trigger_data: '0'}],
   }});
 
diff --git a/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-registration.sub.https.html b/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-registration.sub.https.html
index bb5be903..a9fde608 100644
--- a/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-registration.sub.https.html
+++ b/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-registration.sub.https.html
@@ -26,7 +26,7 @@
     method: 'variant',
   });
 
-  registerAttributionSrc(t, {trigger: {
+  await registerAttributionSrc(t, {trigger: {
     event_trigger_data: [{trigger_data: '0'}],
   }});
 
diff --git a/third_party/blink/web_tests/wpt_internal/attribution-reporting/trigger-data-sanitization.sub.https.html b/third_party/blink/web_tests/wpt_internal/attribution-reporting/trigger-data-sanitization.sub.https.html
index 3945f85..d3f228f 100644
--- a/third_party/blink/web_tests/wpt_internal/attribution-reporting/trigger-data-sanitization.sub.https.html
+++ b/third_party/blink/web_tests/wpt_internal/attribution-reporting/trigger-data-sanitization.sub.https.html
@@ -34,7 +34,7 @@
       method,
     });
 
-    registerAttributionSrc(t, {trigger: {
+    await registerAttributionSrc(t, {trigger: {
       event_trigger_data: [{trigger_data: `${value}`}],
     }});
 
diff --git a/third_party/blink/web_tests/wpt_internal/attribution-reporting/trigger-registration.sub.https.html b/third_party/blink/web_tests/wpt_internal/attribution-reporting/trigger-registration.sub.https.html
new file mode 100644
index 0000000..f46a85fc
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/attribution-reporting/trigger-registration.sub.https.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name=variant content="?method=fetch">
+<meta name=variant content="?method=fetch&eligible=trigger">
+<meta name=variant content="?method=img">
+<meta name=variant content="?method=img&eligible">
+<meta name=variant content="?method=script">
+<meta name=variant content="?method=script&eligible">
+<meta name=variant content="?method=xhr">
+<meta name=variant content="?method=xhr&eligible=trigger">
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/helpers.js"></script>
+<body>
+<script>
+attribution_reporting_promise_test(async t => {
+  await registerAttributionSrc(t, {source: {
+    source_event_id: '1',
+    destination: `https://{{host}}`,
+  }});
+
+  await registerAttributionSrc(t, {
+    trigger: {
+      event_trigger_data: [{trigger_data: '0'}],
+    },
+    method: 'variant',
+  });
+
+  const payload = await pollEventLevelReports();
+  assert_equals(payload.reports.length, 1);
+
+  const report = JSON.parse(payload.reports[0].body);
+  assert_equals(report.trigger_data, '0');
+}, 'Trigger registration succeeds.');
+</script>
diff --git a/third_party/closure_compiler/externs/passwords_private.js b/third_party/closure_compiler/externs/passwords_private.js
index 92285c9..a2666c1 100644
--- a/third_party/closure_compiler/externs/passwords_private.js
+++ b/third_party/closure_compiler/externs/passwords_private.js
@@ -46,6 +46,15 @@
 /**
  * @enum {string}
  */
+chrome.passwordsPrivate.PasswordStoreSet = {
+  DEVICE: 'DEVICE',
+  ACCOUNT: 'ACCOUNT',
+  DEVICE_AND_ACCOUNT: 'DEVICE_AND_ACCOUNT',
+};
+
+/**
+ * @enum {string}
+ */
 chrome.passwordsPrivate.PasswordCheckState = {
   IDLE: 'IDLE',
   RUNNING: 'RUNNING',
@@ -181,38 +190,23 @@
 chrome.passwordsPrivate.changeSavedPassword = function(ids, params, callback) {};
 
 /**
- * Removes the saved password corresponding to |id|. If no saved password for
- * this pair exists, this function is a no-op.
+ * Removes the saved password corresponding to |id| in |fromStores|. If no saved
+ * password for this pair exists, this function is a no-op.
  * @param {number} id The id for the password entry being removed.
+ * @param {!chrome.passwordsPrivate.PasswordStoreSet} fromStores The store(s)
+ *     from which the password entry is being removed.
  */
-chrome.passwordsPrivate.removeSavedPassword = function(id) {};
-
-/**
- * Removes the saved password corresponding to |ids|. If no saved password
- * exists for a certain id, that id is ignored. Undoing this operation via
- * undoRemoveSavedPasswordOrException will restore all the removed passwords in
- * the batch.
- * @param {!Array<number>} ids
- */
-chrome.passwordsPrivate.removeSavedPasswords = function(ids) {};
+chrome.passwordsPrivate.removeSavedPassword = function(id, fromStores) {};
 
 /**
  * Removes the saved password exception corresponding to |id|. If no exception
- * with this id exists, this function is a no-op.
- * @param {number} id The id for the exception url entry being removed.
+ * with this id exists, this function is a no-op. This will remove exception
+ * from both stores.
+ * @param {number} id The id for the exception url entry is being removed.
  */
 chrome.passwordsPrivate.removePasswordException = function(id) {};
 
 /**
- * Removes the saved password exceptions corresponding to |ids|. If no exception
- * exists for a certain id, that id is ignored. Undoing this operation via
- * undoRemoveSavedPasswordOrException will restore all the removed exceptions in
- * the batch.
- * @param {!Array<number>} ids
- */
-chrome.passwordsPrivate.removePasswordExceptions = function(ids) {};
-
-/**
  * Undoes the last removal of saved password(s) or exception(s).
  */
 chrome.passwordsPrivate.undoRemoveSavedPasswordOrException = function() {};
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 9c5a77d..de25e92 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-12-1-48-g7c151abb6
-Revision: 7c151abb6903ddb9c9ed5a1c7b962819c1b2d36f
+Version: VER-2-12-1-49-g3414fef74
+Revision: 3414fef74f9dd1858aca521f93ebadca9ab36f96
 CPEPrefix: cpe:/a:freetype:freetype:2.11.1
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/ipcz/src/BUILD.gn b/third_party/ipcz/src/BUILD.gn
index 85a4b98..75aef4ad 100644
--- a/third_party/ipcz/src/BUILD.gn
+++ b/third_party/ipcz/src/BUILD.gn
@@ -218,7 +218,6 @@
     "ipcz/driver_transport.h",
     "ipcz/fragment.h",
     "ipcz/fragment_descriptor.h",
-    "ipcz/fragment_ref.h",
     "ipcz/link_side.h",
     "ipcz/link_type.h",
     "ipcz/message.h",
@@ -230,7 +229,6 @@
     "ipcz/parcel.h",
     "ipcz/parcel_queue.h",
     "ipcz/portal.h",
-    "ipcz/ref_counted_fragment.h",
     "ipcz/remote_router_link.h",
     "ipcz/router.h",
     "ipcz/sequence_number.h",
@@ -252,7 +250,6 @@
     "ipcz/driver_transport.cc",
     "ipcz/fragment.cc",
     "ipcz/fragment_descriptor.cc",
-    "ipcz/fragment_ref.cc",
     "ipcz/handle_type.h",
     "ipcz/link_side.cc",
     "ipcz/link_type.cc",
@@ -278,7 +275,6 @@
     "ipcz/parcel.cc",
     "ipcz/parcel_queue.cc",
     "ipcz/portal.cc",
-    "ipcz/ref_counted_fragment.cc",
     "ipcz/remote_router_link.cc",
     "ipcz/router.cc",
     "ipcz/router_descriptor.cc",
@@ -334,7 +330,6 @@
     "ipcz/node_connector_test.cc",
     "ipcz/node_link_test.cc",
     "ipcz/parcel_queue_test.cc",
-    "ipcz/ref_counted_fragment_test.cc",
     "ipcz/sequenced_queue_test.cc",
     "reference_drivers/sync_reference_driver_test.cc",
     "remote_portal_test.cc",
diff --git a/third_party/ipcz/src/ipcz/fragment_ref.cc b/third_party/ipcz/src/ipcz/fragment_ref.cc
deleted file mode 100644
index e586924..0000000
--- a/third_party/ipcz/src/ipcz/fragment_ref.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ipcz/fragment_ref.h"
-
-#include <algorithm>
-#include <utility>
-
-#include "ipcz/fragment.h"
-#include "ipcz/node_link_memory.h"
-#include "ipcz/ref_counted_fragment.h"
-#include "util/ref_counted.h"
-
-namespace ipcz::internal {
-
-GenericFragmentRef::GenericFragmentRef() = default;
-
-GenericFragmentRef::GenericFragmentRef(Ref<NodeLinkMemory> memory,
-                                       const Fragment& fragment)
-    : memory_(std::move(memory)), fragment_(fragment) {}
-
-GenericFragmentRef::~GenericFragmentRef() {
-  reset();
-}
-
-void GenericFragmentRef::reset() {
-  Ref<NodeLinkMemory> memory = std::move(memory_);
-  if (fragment_.is_null()) {
-    return;
-  }
-
-  Fragment fragment;
-  std::swap(fragment, fragment_);
-  if (!fragment.is_addressable()) {
-    return;
-  }
-
-  auto* ref_counted = static_cast<RefCountedFragment*>(fragment.address());
-  if (ref_counted->ReleaseRef() > 1 || !memory) {
-    return;
-  }
-
-  memory->buffer_pool().FreeFragment(fragment);
-}
-
-Fragment GenericFragmentRef::release() {
-  Fragment fragment;
-  std::swap(fragment_, fragment);
-  memory_.reset();
-  return fragment;
-}
-
-}  // namespace ipcz::internal
diff --git a/third_party/ipcz/src/ipcz/fragment_ref.h b/third_party/ipcz/src/ipcz/fragment_ref.h
deleted file mode 100644
index e54135b..0000000
--- a/third_party/ipcz/src/ipcz/fragment_ref.h
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IPCZ_SRC_IPCZ_FRAGMENT_REF_H_
-#define IPCZ_SRC_IPCZ_FRAGMENT_REF_H_
-
-#include <algorithm>
-#include <type_traits>
-#include <utility>
-
-#include "ipcz/fragment.h"
-#include "ipcz/fragment_descriptor.h"
-#include "ipcz/ref_counted_fragment.h"
-#include "third_party/abseil-cpp/absl/base/macros.h"
-#include "util/ref_counted.h"
-
-namespace ipcz {
-
-class NodeLinkMemory;
-
-namespace internal {
-
-// Base class for any FragmentRef<T>, implementing common behavior for managing
-// the underlying RefCountedFragment.
-class GenericFragmentRef {
- public:
-  GenericFragmentRef();
-
-  // Does not increase the ref count of the underlying RefCountedFragment,
-  // effectively assuming ownership of a previously acquired ref.
-  GenericFragmentRef(Ref<NodeLinkMemory> memory, const Fragment& fragment);
-
-  ~GenericFragmentRef();
-
-  const Ref<NodeLinkMemory>& memory() const { return memory_; }
-  const Fragment& fragment() const { return fragment_; }
-
-  bool is_null() const { return fragment_.is_null(); }
-  bool is_addressable() const { return fragment_.is_addressable(); }
-  bool is_pending() const { return fragment_.is_pending(); }
-
-  void reset();
-  Fragment release();
-
-  int32_t ref_count_for_testing() const {
-    return AsRefCountedFragment()->ref_count_for_testing();
-  }
-
- protected:
-  RefCountedFragment* AsRefCountedFragment() const {
-    return static_cast<RefCountedFragment*>(fragment_.address());
-  }
-
-  // The NodeLinkMemory who ultimately owns this fragment's memory. May be null
-  // if the FragmentRef is unmanaged.
-  Ref<NodeLinkMemory> memory_;
-
-  Fragment fragment_;
-};
-
-}  // namespace internal
-
-// Holds a reference to a RefCountedFragment. When this object is destroyed, the
-// underlying ref count is decreased. If the ref count is decreased to zero, the
-// underlying Fragment is returned to its NodeLinkMemory.
-//
-// Some FragmentRefs may be designated as "unmanaged", meaning that they will
-// never attempt to free the underlying Fragment. These refs are used to
-// preserve type compatibility with other similar (but managed) FragmentRefs
-// when the underlying Fragment isn't dynamically allocated and can't be freed.
-//
-// For example most RouterLinkState fragments are dynamically allocated and
-// managed by FragmentRefs, but some instances are allocated at fixed locations
-// within the NodeLinkMemory and cannot be freed or reused. In both cases, ipcz
-// can refer to these objects using a FragmentRef<RouterLinkState>.
-template <typename T>
-class FragmentRef : public internal::GenericFragmentRef {
- public:
-  static_assert(std::is_base_of<RefCountedFragment, T>::value,
-                "T must inherit RefCountedFragment for FragmentRef<T>");
-
-  constexpr FragmentRef() = default;
-  constexpr FragmentRef(std::nullptr_t) : FragmentRef() {}
-
-  // Adopts an existing ref to the RefCountedFragment located at the beginning
-  // of `fragment`, which is a Fragment owned by `memory.
-  FragmentRef(decltype(RefCountedFragment::kAdoptExistingRef),
-              Ref<NodeLinkMemory> memory,
-              const Fragment& fragment)
-      : GenericFragmentRef(std::move(memory), fragment) {
-    ABSL_ASSERT(memory_);
-    ABSL_ASSERT(fragment_.is_null() || fragment_.size() >= sizeof(T));
-  }
-
-  // Constructs an unmanaged FragmentRef, which references `fragment` and
-  // updates its refcount, but which never attempts to release `fragment` back
-  // to its NodeLinkMemory. This is only safe to use with Fragments which cannot
-  // be freed.
-  FragmentRef(decltype(RefCountedFragment::kUnmanagedRef),
-              const Fragment& fragment)
-      : GenericFragmentRef(nullptr, fragment) {
-    ABSL_ASSERT(fragment_.is_null() || fragment_.size() >= sizeof(T));
-  }
-
-  FragmentRef(const FragmentRef<T>& other)
-      : GenericFragmentRef(other.memory(), other.fragment()) {
-    if (!fragment_.is_null()) {
-      ABSL_ASSERT(fragment_.is_addressable());
-      AsRefCountedFragment()->AddRef();
-    }
-  }
-
-  FragmentRef(FragmentRef<T>&& other) noexcept
-      : GenericFragmentRef(std::move(other.memory_), other.fragment_) {
-    other.release();
-  }
-
-  FragmentRef<T>& operator=(const FragmentRef<T>& other) {
-    reset();
-    memory_ = other.memory();
-    fragment_ = other.fragment();
-    if (!fragment_.is_null()) {
-      ABSL_ASSERT(fragment_.is_addressable());
-      AsRefCountedFragment()->AddRef();
-    }
-    return *this;
-  }
-
-  FragmentRef<T>& operator=(FragmentRef<T>&& other) {
-    reset();
-    memory_ = std::move(other.memory_);
-    fragment_ = other.release();
-    return *this;
-  }
-
-  T* get() const { return static_cast<T*>(fragment_.address()); }
-  T* operator->() const { return get(); }
-  T& operator*() const { return *get(); }
-};
-
-}  // namespace ipcz
-
-#endif  // IPCZ_SRC_IPCZ_FRAGMENT_REF_H_
diff --git a/third_party/ipcz/src/ipcz/node_link_memory.cc b/third_party/ipcz/src/ipcz/node_link_memory.cc
index 012f26c..5a8aba0 100644
--- a/third_party/ipcz/src/ipcz/node_link_memory.cc
+++ b/third_party/ipcz/src/ipcz/node_link_memory.cc
@@ -25,11 +25,12 @@
 // Fixed allocation size for each NodeLink's primary shared buffer.
 constexpr size_t kPrimaryBufferSize = 65536;
 
-// The front of the primary buffer is reserved for special current and future
-// uses which require synchronous availability throughout a link's lifetime.
-constexpr size_t kPrimaryBufferReservedHeaderSize = 256;
+}  // namespace
 
-struct IPCZ_ALIGN(8) PrimaryBufferHeader {
+// This structure always sits at offset 0 in the primary buffer and has a fixed
+// layout according to the NodeLink's agreed upon protocol version. This is the
+// layout for version 0 (currently the only version.)
+struct IPCZ_ALIGN(8) NodeLinkMemory::PrimaryBuffer {
   // Atomic generator for new unique BufferIds to use across the associated
   // NodeLink. This allows each side of a NodeLink to generate new BufferIds
   // spontaneously without synchronization or risk of collisions.
@@ -41,51 +42,6 @@
   std::atomic<uint64_t> next_sublink_id;
 };
 
-static_assert(sizeof(PrimaryBufferHeader) < kPrimaryBufferReservedHeaderSize);
-
-constexpr size_t kPrimaryBufferHeaderPaddingSize =
-    kPrimaryBufferReservedHeaderSize - sizeof(PrimaryBufferHeader);
-
-}  // namespace
-
-// This structure always sits at offset 0 in the primary buffer and has a fixed
-// layout according to the NodeLink's agreed upon protocol version. This is the
-// layout for version 0 (currently the only version.)
-struct IPCZ_ALIGN(8) NodeLinkMemory::PrimaryBuffer {
-  // Header + padding occupies the first 256 bytes.
-  PrimaryBufferHeader header;
-  uint8_t reserved_header_padding[kPrimaryBufferHeaderPaddingSize];
-
-  // Reserved memory for a series of fixed block allocators. Additional
-  // allocators may be adopted by a NodeLinkMemory over its lifetime, but these
-  // ones remain fixed within the primary buffer.
-  std::array<uint8_t, 4096> mem_for_64_byte_blocks;
-  std::array<uint8_t, 12288> mem_for_256_byte_blocks;
-  std::array<uint8_t, 15360> mem_for_512_byte_blocks;
-  std::array<uint8_t, 11264> mem_for_1024_byte_blocks;
-  std::array<uint8_t, 16384> mem_for_2048_byte_blocks;
-
-  BlockAllocator block_allocator_64() {
-    return BlockAllocator(absl::MakeSpan(mem_for_64_byte_blocks), 64);
-  }
-
-  BlockAllocator block_allocator_256() {
-    return BlockAllocator(absl::MakeSpan(mem_for_256_byte_blocks), 256);
-  }
-
-  BlockAllocator block_allocator_512() {
-    return BlockAllocator(absl::MakeSpan(mem_for_512_byte_blocks), 512);
-  }
-
-  BlockAllocator block_allocator_1024() {
-    return BlockAllocator(absl::MakeSpan(mem_for_1024_byte_blocks), 1024);
-  }
-
-  BlockAllocator block_allocator_2048() {
-    return BlockAllocator(absl::MakeSpan(mem_for_2048_byte_blocks), 2048);
-  }
-};
-
 NodeLinkMemory::NodeLinkMemory(Ref<Node> node,
                                DriverMemoryMapping primary_buffer_memory)
     : node_(std::move(node)),
@@ -96,17 +52,8 @@
   static_assert(sizeof(PrimaryBuffer) <= kPrimaryBufferSize,
                 "PrimaryBuffer structure is too large.");
 
-  buffer_pool_.AddBuffer(kPrimaryBufferId, std::move(primary_buffer_memory));
-  buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
-                                      primary_buffer_.block_allocator_64());
-  buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
-                                      primary_buffer_.block_allocator_256());
-  buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
-                                      primary_buffer_.block_allocator_512());
-  buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
-                                      primary_buffer_.block_allocator_1024());
-  buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
-                                      primary_buffer_.block_allocator_2048());
+  buffer_pool_.AddBuffer(BufferId{kPrimaryBufferId},
+                         std::move(primary_buffer_memory));
 }
 
 NodeLinkMemory::~NodeLinkMemory() = default;
@@ -124,21 +71,15 @@
   PrimaryBuffer& primary_buffer = memory->primary_buffer_;
 
   // The first allocable BufferId is 1, because the primary buffer uses 0.
-  primary_buffer.header.next_buffer_id.store(1, std::memory_order_relaxed);
+  primary_buffer.next_buffer_id.store(1, std::memory_order_relaxed);
 
   // The first allocable SublinkId is kMaxInitialPortals. This way it doesn't
   // matter whether the two ends of a NodeLink initiate their connection with a
   // different initial portal count: neither can request more than
   // kMaxInitialPortals, so neither will be assuming initial ownership of any
   // SublinkIds at or above this value.
-  primary_buffer.header.next_sublink_id.store(kMaxInitialPortals,
-                                              std::memory_order_release);
-
-  primary_buffer.block_allocator_64().InitializeRegion();
-  primary_buffer.block_allocator_256().InitializeRegion();
-  primary_buffer.block_allocator_512().InitializeRegion();
-  primary_buffer.block_allocator_1024().InitializeRegion();
-  primary_buffer.block_allocator_2048().InitializeRegion();
+  primary_buffer.next_sublink_id.store(kMaxInitialPortals,
+                                       std::memory_order_release);
 
   return {
       .node_link_memory = std::move(memory),
@@ -154,12 +95,12 @@
 }
 
 BufferId NodeLinkMemory::AllocateNewBufferId() {
-  return BufferId{primary_buffer_.header.next_buffer_id.fetch_add(
-      1, std::memory_order_relaxed)};
+  return BufferId{
+      primary_buffer_.next_buffer_id.fetch_add(1, std::memory_order_relaxed)};
 }
 
 SublinkId NodeLinkMemory::AllocateSublinkIds(size_t count) {
-  return SublinkId{primary_buffer_.header.next_sublink_id.fetch_add(
+  return SublinkId{primary_buffer_.next_sublink_id.fetch_add(
       count, std::memory_order_relaxed)};
 }
 
diff --git a/third_party/ipcz/src/ipcz/node_link_memory.h b/third_party/ipcz/src/ipcz/node_link_memory.h
index 014a78ef..b4d65a6c 100644
--- a/third_party/ipcz/src/ipcz/node_link_memory.h
+++ b/third_party/ipcz/src/ipcz/node_link_memory.h
@@ -63,7 +63,7 @@
   // Exposes the underlying BufferPool which owns all shared buffers for this
   // NodeLinkMemory and which facilitates dynamic allocation of the fragments
   // within.
-  BufferPool& buffer_pool() { return buffer_pool_; }
+  BufferPool& buffer_pool();
 
   // Returns a new BufferId which should still be unused by any buffer in this
   // NodeLinkMemory's BufferPool, or that of its peer NodeLinkMemory. When
diff --git a/third_party/ipcz/src/ipcz/ref_counted_fragment.cc b/third_party/ipcz/src/ipcz/ref_counted_fragment.cc
deleted file mode 100644
index 14b21cf..0000000
--- a/third_party/ipcz/src/ipcz/ref_counted_fragment.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ipcz/ref_counted_fragment.h"
-
-#include "third_party/abseil-cpp/absl/base/macros.h"
-
-namespace ipcz {
-
-RefCountedFragment::RefCountedFragment() = default;
-
-RefCountedFragment::~RefCountedFragment() = default;
-
-void RefCountedFragment::AddRef() {
-  ref_count_.fetch_add(1, std::memory_order_relaxed);
-}
-
-int32_t RefCountedFragment::ReleaseRef() {
-  return ref_count_.fetch_sub(1, std::memory_order_acq_rel);
-}
-
-}  // namespace ipcz
diff --git a/third_party/ipcz/src/ipcz/ref_counted_fragment.h b/third_party/ipcz/src/ipcz/ref_counted_fragment.h
deleted file mode 100644
index ff69c45..0000000
--- a/third_party/ipcz/src/ipcz/ref_counted_fragment.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IPCZ_SRC_IPCZ_REF_COUNTED_FRAGMENT_H_
-#define IPCZ_SRC_IPCZ_REF_COUNTED_FRAGMENT_H_
-
-#include <atomic>
-
-#include "ipcz/ipcz.h"
-#include "util/ref_counted.h"
-
-namespace ipcz {
-
-// A RefCountedFragment is an object allocated within a shared Fragment from
-// NodeLinkMemory, and which is automatially freed when its last reference is
-// released. Consumers can hold onto references to RefCountedFragment objects
-// by holding a FragmentRef.
-struct IPCZ_ALIGN(4) RefCountedFragment {
-  enum { kAdoptExistingRef };
-  enum { kUnmanagedRef };
-
-  RefCountedFragment();
-  ~RefCountedFragment();
-
-  int32_t ref_count_for_testing() const { return ref_count_; }
-
-  // Increments the reference count for this object.
-  void AddRef();
-
-  // Releases a reference and returns the previous reference count. If this
-  // returns 1, the underlying Fragment can be safely freed.
-  int32_t ReleaseRef();
-
- private:
-  std::atomic<int32_t> ref_count_{1};
-};
-
-}  // namespace ipcz
-
-#endif  // IPCZ_SRC_IPCZ_REF_COUNTED_FRAGMENT_H_
diff --git a/third_party/ipcz/src/ipcz/ref_counted_fragment_test.cc b/third_party/ipcz/src/ipcz/ref_counted_fragment_test.cc
deleted file mode 100644
index 5bb0db4..0000000
--- a/third_party/ipcz/src/ipcz/ref_counted_fragment_test.cc
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ipcz/ref_counted_fragment.h"
-
-#include <atomic>
-#include <tuple>
-
-#include "ipcz/fragment.h"
-#include "ipcz/fragment_ref.h"
-#include "ipcz/node.h"
-#include "ipcz/node_link_memory.h"
-#include "reference_drivers/sync_reference_driver.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "util/ref_counted.h"
-
-namespace ipcz {
-namespace {
-
-const IpczDriver& kTestDriver = reference_drivers::kSyncReferenceDriver;
-
-using RefCountedFragmentTest = testing::Test;
-
-using TestObject = RefCountedFragment;
-
-TEST_F(RefCountedFragmentTest, NullRef) {
-  FragmentRef<TestObject> ref;
-  EXPECT_TRUE(ref.is_null());
-  EXPECT_FALSE(ref.is_addressable());
-
-  ref.reset();
-  EXPECT_TRUE(ref.is_null());
-  EXPECT_FALSE(ref.is_addressable());
-
-  FragmentRef<TestObject> other1 = ref;
-  EXPECT_TRUE(ref.is_null());
-  EXPECT_FALSE(ref.is_addressable());
-  EXPECT_TRUE(other1.is_null());
-  EXPECT_FALSE(other1.is_addressable());
-
-  FragmentRef<TestObject> other2 = std::move(ref);
-  EXPECT_TRUE(ref.is_null());
-  EXPECT_FALSE(ref.is_addressable());
-  EXPECT_TRUE(other2.is_null());
-  EXPECT_FALSE(other2.is_addressable());
-
-  ref = other1;
-  EXPECT_TRUE(ref.is_null());
-  EXPECT_FALSE(ref.is_addressable());
-  EXPECT_TRUE(other1.is_null());
-  EXPECT_FALSE(other1.is_addressable());
-
-  ref = std::move(other2);
-  EXPECT_TRUE(ref.is_null());
-  EXPECT_FALSE(ref.is_addressable());
-  EXPECT_TRUE(other1.is_null());
-  EXPECT_FALSE(other1.is_addressable());
-}
-
-TEST_F(RefCountedFragmentTest, SimpleRef) {
-  TestObject object;
-
-  FragmentRef<TestObject> ref(
-      RefCountedFragment::kUnmanagedRef,
-      Fragment(FragmentDescriptor(BufferId(0), 0, 0), &object));
-  EXPECT_EQ(1, object.ref_count_for_testing());
-  ref.reset();
-  EXPECT_EQ(0, object.ref_count_for_testing());
-}
-
-TEST_F(RefCountedFragmentTest, Copy) {
-  TestObject object1;
-
-  FragmentRef<TestObject> ref1(
-      RefCountedFragment::kUnmanagedRef,
-      Fragment(FragmentDescriptor(BufferId(0), 0, 0), &object1));
-  EXPECT_EQ(1, object1.ref_count_for_testing());
-
-  FragmentRef<TestObject> other1 = ref1;
-  EXPECT_EQ(2, object1.ref_count_for_testing());
-  other1.reset();
-  EXPECT_EQ(1, object1.ref_count_for_testing());
-  EXPECT_TRUE(other1.is_null());
-  EXPECT_FALSE(other1.is_addressable());
-
-  TestObject object2;
-  auto ref2 = FragmentRef<TestObject>(
-      RefCountedFragment::kUnmanagedRef,
-      Fragment(FragmentDescriptor(BufferId(0), 0, 0), &object2));
-  EXPECT_EQ(1, object1.ref_count_for_testing());
-  EXPECT_EQ(1, object2.ref_count_for_testing());
-  ref2 = ref1;
-  EXPECT_EQ(2, object1.ref_count_for_testing());
-  EXPECT_EQ(0, object2.ref_count_for_testing());
-  EXPECT_FALSE(ref1.is_null());
-  EXPECT_TRUE(ref1.is_addressable());
-  EXPECT_FALSE(ref2.is_null());
-  EXPECT_TRUE(ref2.is_addressable());
-  ref1.reset();
-  EXPECT_EQ(1, object1.ref_count_for_testing());
-  EXPECT_EQ(0, object2.ref_count_for_testing());
-  EXPECT_TRUE(ref1.is_null());
-  EXPECT_FALSE(ref1.is_addressable());
-  ref2.reset();
-  EXPECT_EQ(0, object1.ref_count_for_testing());
-  EXPECT_EQ(0, object2.ref_count_for_testing());
-  EXPECT_TRUE(ref2.is_null());
-  EXPECT_FALSE(ref2.is_addressable());
-}
-
-TEST_F(RefCountedFragmentTest, Move) {
-  TestObject object1;
-
-  FragmentRef<TestObject> ref1(
-      RefCountedFragment::kUnmanagedRef,
-      Fragment(FragmentDescriptor(BufferId(0), 0, 0), &object1));
-  EXPECT_EQ(1, ref1.ref_count_for_testing());
-
-  FragmentRef<TestObject> other1 = std::move(ref1);
-  EXPECT_EQ(1, object1.ref_count_for_testing());
-  EXPECT_FALSE(other1.is_null());
-  EXPECT_TRUE(other1.is_addressable());
-  EXPECT_TRUE(ref1.is_null());
-  EXPECT_FALSE(ref1.is_addressable());
-  other1.reset();
-  EXPECT_TRUE(other1.is_null());
-  EXPECT_FALSE(other1.is_addressable());
-  EXPECT_EQ(0, object1.ref_count_for_testing());
-
-  TestObject object2;
-  TestObject object3;
-  FragmentRef<TestObject> ref2(
-      RefCountedFragment::kUnmanagedRef,
-      Fragment(FragmentDescriptor(BufferId(0), 0, 0), &object2));
-  FragmentRef<TestObject> ref3(
-      RefCountedFragment::kUnmanagedRef,
-      Fragment(FragmentDescriptor(BufferId(0), 0, 0), &object3));
-
-  EXPECT_FALSE(ref2.is_null());
-  EXPECT_TRUE(ref2.is_addressable());
-  EXPECT_FALSE(ref3.is_null());
-  EXPECT_TRUE(ref3.is_addressable());
-  EXPECT_EQ(1, object2.ref_count_for_testing());
-  EXPECT_EQ(1, object3.ref_count_for_testing());
-  ref3 = std::move(ref2);
-  EXPECT_EQ(1, object2.ref_count_for_testing());
-  EXPECT_EQ(0, object3.ref_count_for_testing());
-  EXPECT_TRUE(ref2.is_null());
-  EXPECT_FALSE(ref2.is_addressable());
-  EXPECT_FALSE(ref3.is_null());
-  EXPECT_TRUE(ref3.is_addressable());
-  ref3.reset();
-  EXPECT_TRUE(ref3.is_null());
-  EXPECT_FALSE(ref3.is_addressable());
-  EXPECT_EQ(0, object2.ref_count_for_testing());
-  EXPECT_EQ(0, object3.ref_count_for_testing());
-}
-
-TEST_F(RefCountedFragmentTest, Free) {
-  auto node = MakeRefCounted<Node>(Node::Type::kNormal, kTestDriver,
-                                   IPCZ_INVALID_DRIVER_HANDLE);
-  auto memory = NodeLinkMemory::Allocate(std::move(node)).node_link_memory;
-
-  // Allocate a ton of fragments and let them be released by FragmentRef on
-  // destruction. If the fragments aren't freed properly, allocations will fail
-  // and so will the test.
-  constexpr size_t kNumAllocations = 100000;
-  for (size_t i = 0; i < kNumAllocations; ++i) {
-    Fragment fragment =
-        memory->buffer_pool().AllocateFragment(sizeof(TestObject));
-    EXPECT_TRUE(fragment.is_addressable());
-    FragmentRef<TestObject> ref(RefCountedFragment::kAdoptExistingRef, memory,
-                                fragment);
-  }
-}
-
-}  // namespace
-}  // namespace ipcz
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f428a33..0677ad5 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -58608,6 +58608,7 @@
   <int value="32242305" label="NtpRecipeTasksModule:enabled"/>
   <int value="32488630" label="EphemeralTab:enabled"/>
   <int value="32557641" label="HTTPAuthCommittedInterstitials:disabled"/>
+  <int value="32701080" label="DropInputEventsBeforeFirstPaint:enabled"/>
   <int value="33588676" label="TranslateAssistContent:enabled"/>
   <int value="33778663" label="OriginTrials:enabled"/>
   <int value="34399969" label="SplitPartiallyOccludedQuads:enabled"/>
@@ -61597,6 +61598,7 @@
   <int value="1980648371" label="PointerEventV1SpecCapturing:enabled"/>
   <int value="1985584246" label="PageInfoHistory:enabled"/>
   <int value="1986031201" label="SwipingFromLeftEdgeToGoBack:disabled"/>
+  <int value="1988099368" label="DropInputEventsBeforeFirstPaint:disabled"/>
   <int value="1988506961" label="EnableManualSaving:enabled"/>
   <int value="1988679118" label="DesktopPWAsSubApps:disabled"/>
   <int value="1988810119" label="AutofillDropdownLayout:enabled"/>
@@ -85364,7 +85366,7 @@
   <int value="4" label="Completed the prefetch"/>
   <int value="5" label="Request failed"/>
   <int value="6" label="Request cancelled"/>
-  <int value="7" label="Request served"/>
+  <int value="7" label="Prefetch request served for real navigation"/>
   <int value="8" label="Was served to prerender navigation stack"/>
   <int value="9"
       label="Serving to prerender navigation stack and user navigated to
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index a750f4b3..e372bd7 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -2384,6 +2384,7 @@
     <variant name=".GetAutofillableLoginsAsync"
         summary="for GetAutofillableLoginsAsync"/>
     <variant name=".GetLoginsAsync" summary="for GetLoginsAsync"/>
+    <variant name=".InitialListAsync" summary="for InitialListAsync"/>
     <variant name=".RemoveLoginAsync" summary="for RemoveLoginAsync"/>
     <variant name=".UpdateLoginAsync" summary="for UpdateLoginAsync"/>
   </token>
@@ -2407,6 +2408,7 @@
         summary="retrieving autofillable logins from"/>
     <variant name=".GetLoginsAsync"
         summary="retrieving logins filtered by signon realm from"/>
+    <variant name=".InitialListAsync" summary="initially pinging"/>
     <variant name=".RemoveLoginAsync" summary="removing a login from"/>
     <variant name=".UpdateLoginAsync" summary="updating a login in"/>
   </token>
@@ -2525,6 +2527,7 @@
     <variant name=".GetAutofillableLoginsAsync"
         summary="GetAutofillableLoginsAsync()"/>
     <variant name=".GetLoginsAsync" summary="GetLoginsAsync()"/>
+    <variant name=".InitialListAsync" summary="InitialListAsync()"/>
     <variant name=".RemoveLoginAsync" summary="RemoveLoginAsync()"/>
     <variant name=".RemoveLoginsByURLAndTimeAsync"
         summary="RemoveLoginsByURLAndTimeAsync()"/>
@@ -2560,6 +2563,7 @@
         summary="retrieve autofillable logins from"/>
     <variant name=".GetLoginsAsync"
         summary="retrieve logins filtered by signon realm from"/>
+    <variant name=".InitialListAsync" summary="ping interest in data to"/>
     <variant name=".RemoveLoginAsync" summary="remove a login from"/>
     <variant name=".RemoveLoginsByURLAndTimeAsync"
         summary="remove logins filtered by URL and creation time from"/>
@@ -2601,6 +2605,8 @@
                  autofillable logins from"/>
     <variant name=".GetLoginsAsync"
         summary="succeeded in retrieving logins filtered by signon realm from"/>
+    <variant name=".InitialListAsync"
+        summary="InitialListAsync() completed without errors when pinging"/>
     <variant name=".RemoveLoginAsync"
         summary="RemoveLoginAsync() succeeded in removing a login from"/>
     <variant name=".RemoveLoginsByURLAndTimeAsync"
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 0761af3..b1b7641 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "69c1ac1a94c9a17f492086a1962cf1630db27f2e",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/f56e4abd18a85f6719dcff6fcdc305673d0f3f4e/trace_processor_shell.exe"
+            "hash": "50b445c2634855c04fdbce70f5b3f4513ad4616b",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/f610ed74ff65e7501a8d1e5162afceb68e083e71/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v25.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "3dcbf036a1a9b47a0f80716f0c1adde728b084cb",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/f56e4abd18a85f6719dcff6fcdc305673d0f3f4e/trace_processor_shell"
+            "hash": "2107671c346109940eaf5bbc563a12414c438daa",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/9bad4b45b023dcf07fed418a0b62ef6cf18f44ae/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/results_processor/profiling_util.py b/tools/perf/core/results_processor/profiling_util.py
new file mode 100644
index 0000000..852045b
--- /dev/null
+++ b/tools/perf/core/results_processor/profiling_util.py
@@ -0,0 +1,124 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Helper functions for Perfetto profiling (i.e. callstack sampling) in Telemetry
+# benchmarks.
+
+import logging
+import os
+import shutil
+import subprocess
+import threading
+
+# traceconv is used for symbolization and generating pprof profiles.
+_TRACECONV_PATH = os.path.normpath(
+    os.path.join(os.path.abspath(__file__),
+                 '../../../../../third_party/perfetto/tools/traceconv'))
+# traceconv is not guaranteed to be thread-safe.
+_TRACECONV_LOCK = threading.Lock()
+
+
+def _ConcatenateFiles(files_to_concatenate, output_path):
+  """Concatenates files in the order provided.
+
+  Args:
+    files_to_concatenate: Paths for input files to concatenate.
+    output_path: Path to the resultant output file.
+  """
+  with open(output_path, 'wb') as output_file:
+    for input_path in files_to_concatenate:
+      with open(input_path, 'rb') as input_file:
+        shutil.copyfileobj(input_file, output_file)
+
+
+def _CopyFiles(source_directory_path, destination_directory_path):
+  """Copies all files in from the source to the destination directory."""
+  file_names = os.listdir(source_directory_path)
+  if file_names is None:
+    return
+  for file_name in file_names:
+    shutil.copy(os.path.join(source_directory_path, file_name),
+                destination_directory_path)
+
+
+def SymbolizeTrace(trace_path):
+  """Attempts symbolization of a Perfetto proto trace, if symbols are available.
+
+  If symbolization is successful, the original trace file is replace with the
+  symbolized one.
+
+  Args:
+    trace_path: The path to the trace file.
+  """
+  binary_path = os.getenv('PERFETTO_BINARY_PATH')
+  if binary_path is None:
+    logging.warning(
+        'Not symbolizing trace at %s since PERFETTO_BINARY_PATH is not set.',
+        trace_path)
+    return
+
+  symbols = None
+  # Symbolize the trace.
+  with _TRACECONV_LOCK:
+    popen = subprocess.Popen([_TRACECONV_PATH, 'symbolize', trace_path],
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+    stdout, stderr = popen.communicate()
+
+    if popen.return_code == 0:
+      symbols = stdout
+    else:
+      logging.error('Failed to symbolize trace at %s: %s', trace_path,
+                    stderr.decode('utf-8'))
+  if symbols is None:
+    return
+
+  parent_dir = os.path.dirname(trace_path)
+  symbols_path = os.path.join(parent_dir, 'symbols')
+  symbolized_trace_path = os.path.join(
+      parent_dir, 'symbolized_{}'.format(os.path.basename(trace_path)))
+
+  # We write the symbols to a file in case they are useful for debugging, etc.
+  with open(symbols_path, 'wb') as symbols_file:
+    symbols_file.write(symbols)
+
+  # Add symbols to the trace file.
+  _ConcatenateFiles([trace_path, symbols_path], symbolized_trace_path)
+  # Replace the original trace file.
+  os.remove(trace_path)
+  shutil.move(symbolized_trace_path, trace_path)
+  logging.info('Successfully symbolized trace at %s', trace_path)
+
+
+def GenerateProfiles(trace_path):
+  """Generates pprof profiles from a Perfetto proto trace.
+
+  Args:
+    trace_path: The path to the potentially symbolized Perfetto trace file.
+  """
+  traceconv_output = None
+  with _TRACECONV_LOCK:
+    popen = subprocess.Popen([_TRACECONV_PATH, 'profile', '--perf', trace_path],
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+    stdout, stderr = popen.communicate()
+    if popen.return_code == 0:
+      traceconv_output = stdout.decode('utf-8')
+    else:
+      logging.error('Unable to extract pprof profiles from trace at %s: %s',
+                    trace_path, stderr.decode('utf-8'))
+  if traceconv_output is None:
+    return
+
+  # Copy profiles to the same directory as the source trace.
+  profiles_output_directory = None
+  for word in traceconv_output.split():
+    if 'perf_profile-' in word:
+      profiles_output_directory = word
+  if profiles_output_directory is None:
+    logging.error('No profiles were extracted from trace at %s.', trace_path)
+  else:
+    _CopyFiles(profiles_output_directory, os.path.dirname(trace_path))
+    logging.info('Successfully generated pprof profiles from trace at %s',
+                 trace_path)
diff --git a/tools/perf/core/results_processor/profiling_util_unittest.py b/tools/perf/core/results_processor/profiling_util_unittest.py
new file mode 100644
index 0000000..05b262a
--- /dev/null
+++ b/tools/perf/core/results_processor/profiling_util_unittest.py
@@ -0,0 +1,123 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import shutil
+import tempfile
+import unittest
+
+import mock
+from core.results_processor import profiling_util
+
+
+class MockPopen(object):
+  """Helper class for unit tests that mock subprocess.Popen."""
+
+  def __init__(self, return_code, stdout=None, stderr=None):
+    self.return_code = return_code
+    self._stdout = stdout
+    self._stderr = stderr
+
+  def communicate(self):
+    return self._stdout, self._stderr
+
+
+class ProfilingUtilTests(unittest.TestCase):
+  def __init__(self, *args, **kwargs):
+    super(ProfilingUtilTests, self).__init__(*args, **kwargs)
+    # Working directory for testing.
+    self._temp_directory = None
+    # Placeholder paths used for testing.
+    self._trace_path = None
+    self._symbols_path = None
+    self._profile_file_name = None
+    # The directory where the profile is generated.
+    self._profile_source_dir = None
+    # The directory where the profile should eventually be copied to.
+    self._profile_path = None
+
+  def setUp(self):
+    self._temp_directory = tempfile.mkdtemp()
+    self.addCleanup(self.cleanup)
+
+    self._trace_path = os.path.join(self._temp_directory, 'trace')
+    with open(self._trace_path, 'w') as trace_file:
+      trace_file.write('placeholder_trace')
+
+    self._symbols_path = os.path.join(self._temp_directory, 'symbols')
+
+    self._profile_file_name = 'pprof'
+    # This needs to include 'perf_profile-', since this is what `traceconv`
+    # outputs, too.
+    self._profile_source_dir = os.path.join(self._temp_directory,
+                                            'perf_profile-dir')
+    self._profile_path = os.path.join(self._temp_directory,
+                                      self._profile_file_name)
+
+    os.environ['PERFETTO_BINARY_PATH'] = 'placeholder_binary_path'
+
+  def cleanup(self):
+    shutil.rmtree(self._temp_directory)
+    os.environ.pop('PERFETTO_BINARY_PATH', None)
+
+  # *args and **kwargs are needed for the mock to be able to accept extra
+  # arguments.
+  # pylint: disable=unused-argument
+  def writePlaceholderProfile(self, *args, **kwargs):
+    os.makedirs(self._profile_source_dir)
+    with open(os.path.join(self._profile_source_dir, self._profile_file_name),
+              'w') as profile_file:
+      profile_file.write('placeholder_pprof')
+    # Return output needs to include self._profile_source_dir, since this is
+    # what `traceconv` does, too.
+    return MockPopen(return_code=0,
+                     stdout='Outputting to {}'.format(
+                         self._profile_source_dir).encode('utf-8'))
+
+  @mock.patch('core.results_processor.profiling_util.logging.warning')
+  def testSymbolizeTraceWithoutBinaryPath(self, mock_warning):
+    os.environ.pop('PERFETTO_BINARY_PATH', None)
+    profiling_util.SymbolizeTrace(self._trace_path)
+    mock_warning.assert_called()
+
+  @mock.patch('core.results_processor.profiling_util.logging.error')
+  @mock.patch('core.results_processor.profiling_util.subprocess.Popen')
+  def testSymbolizeTraceWithTraceconvError(self, mock_popen, mock_error):
+    mock_popen.return_value = MockPopen(
+        return_code=-1, stderr='placeholder_error'.encode('utf-8'))
+    profiling_util.SymbolizeTrace(self._trace_path)
+    mock_error.assert_called()
+
+  @mock.patch('core.results_processor.profiling_util.subprocess.Popen')
+  def testSymbolizeTraceWithTraceconvSuccess(self, mock_popen):
+    mock_popen.return_value = MockPopen(
+        return_code=0, stdout='placeholder_symbols'.encode('utf-8'))
+    profiling_util.SymbolizeTrace(self._trace_path)
+    with open(self._trace_path, 'r') as trace_file:
+      self.assertEqual(trace_file.read(),
+                       'placeholder_traceplaceholder_symbols')
+
+  @mock.patch('core.results_processor.profiling_util.logging.error')
+  @mock.patch('core.results_processor.profiling_util.subprocess.Popen')
+  def testGenerateProfilesWithTraceconvError(self, mock_popen, mock_error):
+    mock_popen.return_value = MockPopen(
+        return_code=-1, stderr='placeholder_error'.encode('utf-8'))
+    profiling_util.GenerateProfiles(self._trace_path)
+    mock_error.assert_called()
+
+  @mock.patch('core.results_processor.profiling_util.logging.error')
+  @mock.patch('core.results_processor.profiling_util.subprocess.Popen')
+  def testGenerateProfilesWithInvalidTraceconvOutput(self, mock_popen,
+                                                     mock_error):
+    mock_popen.return_value = MockPopen(
+        return_code=0, stdout='placeholder_output'.encode('utf-8'))
+    profiling_util.GenerateProfiles(self._trace_path)
+    mock_error.assert_called()
+
+  @mock.patch('core.results_processor.profiling_util.subprocess.Popen')
+  def testGenerateProfilesWithTraceconvSuccess(self, mock_popen):
+    mock_popen.side_effect = self.writePlaceholderProfile
+    profiling_util.GenerateProfiles(self._trace_path)
+    with open(self._profile_path, 'r') as profile_file:
+      self.assertEqual(profile_file.read(), 'placeholder_pprof')
diff --git a/tools/perf/core/tbmv3/trace_processor.py b/tools/perf/core/tbmv3/trace_processor.py
index 0bcaa2b2..094e6b5 100644
--- a/tools/perf/core/tbmv3/trace_processor.py
+++ b/tools/perf/core/tbmv3/trace_processor.py
@@ -17,11 +17,10 @@
 from tracing.value import histogram_set
 from tracing.value.histogram import Histogram
 
-
 TP_BINARY_NAME = 'trace_processor_shell'
 EXPORT_JSON_QUERY_TEMPLATE = 'select export_json(%s)\n'
-METRICS_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__),
-                                             'metrics'))
+METRICS_PATH = os.path.realpath(
+    os.path.join(os.path.dirname(__file__), 'metrics'))
 POWER_PROFILE_SQL = 'power_profile.sql'
 
 MetricFiles = namedtuple('MetricFiles', ('sql', 'proto', 'internal_metric'))
@@ -30,6 +29,7 @@
 class InvalidTraceProcessorOutput(Exception):
   pass
 
+
 # These will be set to respective paths once the files have been fetched
 # to avoid downloading several times during one Results Processor run.
 _fetched_trace_processor = None
@@ -73,7 +73,15 @@
 
 def _RunTraceProcessor(*args):
   """Run trace processor shell with given command line arguments."""
-  p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  # Unset environment variables that are not needed for current trace processor
+  # use cases, but, if set, can interfere with its execution.
+  custom_environment = os.environ.copy()
+  custom_environment.pop('PERFETTO_BINARY_PATH', None)
+  custom_environment.pop('PERFETTO_SYMBOLIZER_MODE', None)
+  p = subprocess.Popen(args,
+                       stdout=subprocess.PIPE,
+                       stderr=subprocess.PIPE,
+                       env=custom_environment)
   stdout, stderr = p.communicate()
   stdout = stdout.decode('utf-8')
   stderr = stderr.decode('utf-8')
@@ -120,6 +128,7 @@
 
 
 class ProtoFieldInfo(object):
+
   def __init__(self, name, parent, repeated, field_options):
     self.name = name
     self.parent = parent
@@ -134,11 +143,12 @@
       return self.parent.path_from_root + [self]
 
   def __repr__(self):
-    return 'ProtoFieldInfo("%s", repeated=%s)' %(self.name, self.repeated)
+    return 'ProtoFieldInfo("%s", repeated=%s)' % (self.name, self.repeated)
 
 
 def _LeafFieldAnnotations(annotations, parent=None):
   """Yields leaf fields in the annotations tree, yielding a proto field info
+
   each time. Given the following annotation:
   __annotations: {
     a: {
@@ -155,7 +165,7 @@
 
   """
   for (name, field_value) in annotations.items():
-    if name[:2] == "__":
+    if name[:2] == '__':
       continue  # internal fields.
     current_field = ProtoFieldInfo(
         name=name,
@@ -192,7 +202,7 @@
     field_values = json_dict[path_head.name]
     if not isinstance(field_values, list):
       raise InvalidTraceProcessorOutput(
-          "Field marked as repeated but json value is not list")
+          'Field marked as repeated but json value is not list')
     output = []
     for field_value in field_values:
       output.extend(_PluckField(field_value, path_tail))
@@ -201,7 +211,7 @@
     field_value = json_dict[path_head.name]
     if isinstance(field_value, list):
       raise InvalidTraceProcessorOutput(
-          "Field not marked as repeated but json value is list")
+          'Field not marked as repeated but json value is list')
     return _PluckField(field_value, path_tail)
 
 
@@ -253,7 +263,7 @@
   # Write query to temporary file because trace processor accepts
   # SQL query in a file.
   tp_output = None
-  with tempfile_ext.NamedTemporaryFile(mode="w+") as sql_file:
+  with tempfile_ext.NamedTemporaryFile(mode='w+') as sql_file:
     sql_file.write(sql_command)
     sql_file.close()
     # Run Trace Processor
@@ -271,7 +281,7 @@
   # object inconveniently requires open csv file to access data.
   csv_output = []
   # tempfile creates and opens the file
-  with tempfile.NamedTemporaryFile(mode="w+") as csv_file:
+  with tempfile.NamedTemporaryFile(mode='w+') as csv_file:
     csv_file.write(tp_output)
     csv_file.flush()
     csv_file.seek(0)
@@ -299,7 +309,7 @@
     trace_processor_path: path to the trace_processor executable.
     trace_file: path to the trace file.
     metric_names: a list of metric names (the corresponding files must exist in
-        tbmv3/metrics directory).
+      tbmv3/metrics directory).
 
   Returns:
     A HistogramSet with metric results.
@@ -314,8 +324,10 @@
       metric_name_args.append(metric_files.sql)
   command_args = [
       trace_processor_path,
-      '--run-metrics', ','.join(metric_name_args),
-      '--metrics-output', 'json',
+      '--run-metrics',
+      ','.join(metric_name_args),
+      '--metrics-output',
+      'json',
       trace_file,
   ]
   if fetch_power_profile:
@@ -331,10 +343,10 @@
     annotations = root_annotations.get(full_metric_name, None)
     metric_proto = measurements.get(full_metric_name, None)
     if metric_proto is None:
-      logging.warning("Metric not found in the output: %s", metric_name)
+      logging.warning('Metric not found in the output: %s', metric_name)
       continue
     elif annotations is None:
-      logging.info("Skipping metric %s because it has no field with unit.",
+      logging.info('Skipping metric %s because it has no field with unit.',
                    metric_name)
       continue
 
@@ -342,7 +354,7 @@
       unit = field.field_options.get('unit', None)
       if unit is None:
         logging.debug('Skipping field %s to histograms because it has no unit',
-            field.name)
+                      field.name)
         continue
       histogram_name = ':'.join([field.name for field in field.path_from_root])
       samples = _PluckField(metric_proto, field.path_from_root)
@@ -384,7 +396,8 @@
     query_file.close()
     _RunTraceProcessor(
         trace_processor_path,
-        '-q', query_file.name,
+        '-q',
+        query_file.name,
         proto_file,
     )
 
diff --git a/tools/perf/core/tbmv3/trace_processor_unittest.py b/tools/perf/core/tbmv3/trace_processor_unittest.py
index 89c1dee..00206e6 100644
--- a/tools/perf/core/tbmv3/trace_processor_unittest.py
+++ b/tools/perf/core/tbmv3/trace_processor_unittest.py
@@ -15,6 +15,7 @@
 
 
 class TraceProcessorTestCase(unittest.TestCase):
+
   def setUp(self):
     self.temp_dir = tempfile.mkdtemp()
     self.trace_path = self.CreateEmptyProtoTrace()
@@ -39,8 +40,8 @@
 
   def testConvertProtoTraceToJson(self):
     with mock.patch(RUN_METHOD):
-      trace_processor.ConvertProtoTraceToJson(
-          self.tp_path, '/path/to/proto', '/path/to/json')
+      trace_processor.ConvertProtoTraceToJson(self.tp_path, '/path/to/proto',
+                                              '/path/to/json')
 
   def testRunMetricNoRepeated(self):
     metric_output = """
@@ -63,8 +64,8 @@
     with mock.patch('core.tbmv3.trace_processor.METRICS_PATH', self.temp_dir):
       with mock.patch(RUN_METHOD) as run_patch:
         run_patch.return_value = metric_output
-        histograms = trace_processor.RunMetric(
-            self.tp_path, '/path/to/proto', 'dummy_metric')
+        histograms = trace_processor.RunMetric(self.tp_path, '/path/to/proto',
+                                               'dummy_metric')
 
     foo_hist = histograms.GetHistogramNamed('dummy::foo')
     self.assertEqual(foo_hist.unit, 'count_biggerIsBetter')
@@ -73,7 +74,6 @@
     bar_hists = histograms.GetHistogramsNamed('dummy::bar')
     self.assertEqual(len(bar_hists), 0)
 
-
   def testRunMetricRepeated(self):
     metric_output = """
     {
@@ -104,8 +104,8 @@
     with mock.patch('core.tbmv3.trace_processor.METRICS_PATH', self.temp_dir):
       with mock.patch(RUN_METHOD) as run_patch:
         run_patch.return_value = metric_output
-        histograms = trace_processor.RunMetric(
-            self.tp_path, '/path/to/proto', 'dummy_metric')
+        histograms = trace_processor.RunMetric(self.tp_path, '/path/to/proto',
+                                               'dummy_metric')
 
     foo_hist = histograms.GetHistogramNamed('dummy::foo')
     self.assertEqual(foo_hist.unit, 'count_biggerIsBetter')
@@ -137,8 +137,8 @@
       with mock.patch(RUN_METHOD) as run_patch:
         run_patch.return_value = metric_output
         with self.assertRaises(trace_processor.InvalidTraceProcessorOutput):
-          trace_processor.RunMetric(
-              self.tp_path, '/path/to/proto', 'dummy_metric')
+          trace_processor.RunMetric(self.tp_path, '/path/to/proto',
+                                    'dummy_metric')
 
   def testMarkedNotRepeatedButValueIsList(self):
     metric_output = """
@@ -161,8 +161,8 @@
       with mock.patch(RUN_METHOD) as run_patch:
         run_patch.return_value = metric_output
         with self.assertRaises(trace_processor.InvalidTraceProcessorOutput):
-          trace_processor.RunMetric(
-              self.tp_path, '/path/to/proto', 'dummy_metric')
+          trace_processor.RunMetric(self.tp_path, '/path/to/proto',
+                                    'dummy_metric')
 
   def testRunMetricEmpty(self):
     metric_output = '{}'
@@ -323,3 +323,16 @@
 
     expected_output = [{'int_value': '0', 'str_value': None}]
     self.assertEqual(query_output, expected_output)
+
+  def testWithInterferingEnvironmentVariables(self):
+    os.environ['PERFETTO_SYMBOLIZER_MODE'] = 'placeholder'
+    os.environ['PERFETTO_BINARY_PATH'] = 'placeholder'
+
+    sql_query = 'SELECT int_value, str_value FROM metadata LIMIT 1'
+    try:
+      trace_processor.RunQuery(None, self.trace_path, sql_query)
+    except Exception as error:
+      self.fail('Unexpected trace_processor error: {}'.format(error))
+    finally:
+      os.environ.pop('PERFETTO_BINARY_PATH', None)
+      os.environ.pop('PERFETTO_SYMBOLIZER_MODE', None)
diff --git a/tools/typescript/definitions/passwords_private.d.ts b/tools/typescript/definitions/passwords_private.d.ts
index 17b51b8..e9ee71c 100644
--- a/tools/typescript/definitions/passwords_private.d.ts
+++ b/tools/typescript/definitions/passwords_private.d.ts
@@ -30,6 +30,12 @@
         PHISHED_AND_LEAKED = 'PHISHED_AND_LEAKED',
       }
 
+      export enum PasswordStoreSet {
+        DEVICE = 'DEVICE',
+        ACCOUNT = 'ACCOUNT',
+        DEVICE_AND_ACCOUNT = 'DEVICE_AND_ACCOUNT',
+      }
+
       export enum PasswordCheckState {
         IDLE = 'IDLE',
         RUNNING = 'RUNNING',
@@ -117,12 +123,10 @@
 
       export function recordPasswordsPageAccessInSettings(): void;
       export function changeSavedPassword(
-          ids: Array<number>, params: ChangeSavedPasswordParams,
+          ids: number[], params: ChangeSavedPasswordParams,
           callback?: (newIds: CredentialIds) => void): void;
-      export function removeSavedPassword(id: number): void;
-      export function removeSavedPasswords(ids: Array<number>): void;
+      export function removeSavedPassword(id: number, fromStores: PasswordStoreSet): void;
       export function removePasswordException(id: number): void;
-      export function removePasswordExceptions(ids: Array<number>): void;
       export function undoRemoveSavedPasswordOrException(): void;
       export function requestPlaintextPassword(
           id: number, reason: PlaintextReason,
diff --git a/ui/android/java/src/org/chromium/ui/base/OWNERS b/ui/android/java/src/org/chromium/ui/base/OWNERS
index e5ad189..fdf73a8 100644
--- a/ui/android/java/src/org/chromium/ui/base/OWNERS
+++ b/ui/android/java/src/org/chromium/ui/base/OWNERS
@@ -1,3 +1,5 @@
 # Implementation of the new in-app file picker.
+per-file PhotoPicker*.java=finnur@chromium.org
+per-file PhotoPicker*.java=peter@chromium.org
 per-file SelectFileDialog.java=finnur@chromium.org
 per-file SelectFileDialog.java=peter@chromium.org
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 8531d4a..7fc550e 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -297,6 +297,8 @@
       "cocoa/nsmenu_additions.mm",
       "cocoa/nsmenuitem_additions.h",
       "cocoa/nsmenuitem_additions.mm",
+      "cocoa/nswindow_test_util.cc",
+      "cocoa/nswindow_test_util.h",
       "cocoa/permissions_utils.h",
       "cocoa/permissions_utils.mm",
       "cocoa/quartz_util.h",
@@ -745,8 +747,6 @@
       "test/menu_test_observer.mm",
       "test/ns_ax_tree_validator.h",
       "test/ns_ax_tree_validator.mm",
-      "test/nswindow_fullscreen_notification_waiter.h",
-      "test/nswindow_fullscreen_notification_waiter.mm",
       "test/scoped_fake_full_keyboard_access.h",
       "test/scoped_fake_full_keyboard_access.mm",
       "test/scoped_fake_nswindow_focus.h",
diff --git a/ui/base/cocoa/nswindow_test_util.cc b/ui/base/cocoa/nswindow_test_util.cc
new file mode 100644
index 0000000..d96f687
--- /dev/null
+++ b/ui/base/cocoa/nswindow_test_util.cc
@@ -0,0 +1,66 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/cocoa/nswindow_test_util.h"
+
+namespace ui {
+
+NSWindowFullscreenNotificationWaiter::NSWindowFullscreenNotificationWaiter(
+    gfx::NativeWindow window)
+    : window_(window) {
+  // We only support one NSWindowFullscreenNotificationWaiter instance at once.
+  DCHECK(!instance_);
+  instance_ = this;
+  ;
+}
+
+NSWindowFullscreenNotificationWaiter::~NSWindowFullscreenNotificationWaiter() {
+  DCHECK(instance_ == this);
+  instance_ = nullptr;
+}
+
+void NSWindowFullscreenNotificationWaiter::WaitForEnterAndExitCount(
+    int target_enter_count,
+    int target_exit_count) {
+  target_enter_count_ = target_enter_count;
+  target_exit_count_ = target_exit_count;
+
+  // Do not wait if the targets are already met.
+  if (enter_count_ >= target_enter_count_ &&
+      exit_count_ >= target_exit_count_) {
+    return;
+  }
+
+  run_loop_ = std::make_unique<base::RunLoop>();
+  run_loop_->Run();
+  run_loop_.reset();
+}
+
+void NSWindowFullscreenNotificationWaiter::NotifyFullscreenTransitionComplete(
+    bool fullscreen) {
+  if (fullscreen)
+    enter_count_++;
+  else
+    exit_count_++;
+
+  if (run_loop_ && enter_count_ >= target_enter_count_ &&
+      exit_count_ >= target_exit_count_) {
+    run_loop_->Quit();
+  }
+}
+
+// static
+void NSWindowFullscreenNotificationWaiter::NotifyFullscreenTransitionComplete(
+    gfx::NativeWindow window,
+    bool fullscreen) {
+  if (!instance_ || instance_->window_ != window)
+    return;
+  instance_->NotifyFullscreenTransitionComplete(fullscreen);
+}
+
+// static
+NSWindowFullscreenNotificationWaiter*
+    NSWindowFullscreenNotificationWaiter::instance_ = nullptr;
+
+}  // namespace ui
diff --git a/ui/base/cocoa/nswindow_test_util.h b/ui/base/cocoa/nswindow_test_util.h
new file mode 100644
index 0000000..a9f80aa
--- /dev/null
+++ b/ui/base/cocoa/nswindow_test_util.h
@@ -0,0 +1,44 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_COCOA_NSWINDOW_TEST_UTIL_H_
+#define UI_BASE_COCOA_NSWINDOW_TEST_UTIL_H_
+
+#include <memory>
+
+#include "base/component_export.h"
+#include "base/run_loop.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace ui {
+
+class COMPONENT_EXPORT(UI_BASE) NSWindowFullscreenNotificationWaiter {
+ public:
+  explicit NSWindowFullscreenNotificationWaiter(gfx::NativeWindow window);
+  ~NSWindowFullscreenNotificationWaiter();
+
+  void WaitForEnterAndExitCount(int target_enter_count, int target_exit_count);
+  int enter_count() const { return enter_count_; }
+  int exit_count() const { return exit_count_; }
+
+  static void NotifyFullscreenTransitionComplete(gfx::NativeWindow window,
+                                                 bool fullscreen);
+
+ private:
+  void NotifyFullscreenTransitionComplete(bool fullscreen);
+
+  std::unique_ptr<base::RunLoop> run_loop_;
+  const gfx::NativeWindow window_;
+
+  int enter_count_ = 0;
+  int exit_count_ = 0;
+  int target_enter_count_ = 0;
+  int target_exit_count_ = 0;
+
+  static NSWindowFullscreenNotificationWaiter* instance_;
+};
+
+}  // namespace ui
+
+#endif  // UI_BASE_COCOA_NSWINDOW_TEST_UTIL_H_
diff --git a/ui/base/test/nswindow_fullscreen_notification_waiter.h b/ui/base/test/nswindow_fullscreen_notification_waiter.h
deleted file mode 100644
index 6de80f3..0000000
--- a/ui/base/test/nswindow_fullscreen_notification_waiter.h
+++ /dev/null
@@ -1,37 +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 UI_BASE_TEST_NSWINDOW_FULLSCREEN_NOTIFICATION_WAITER_H_
-#define UI_BASE_TEST_NSWINDOW_FULLSCREEN_NOTIFICATION_WAITER_H_
-
-#import <Cocoa/Cocoa.h>
-
-#include <memory>
-
-#import "base/mac/scoped_nsobject.h"
-#include "base/run_loop.h"
-
-// Waits for fullscreen transitions to complete.
-@interface NSWindowFullscreenNotificationWaiter : NSObject {
- @private
-  std::unique_ptr<base::RunLoop> _runLoop;
-  base::scoped_nsobject<NSWindow> _window;
-  int _enterCount;
-  int _exitCount;
-  int _targetEnterCount;
-  int _targetExitCount;
-}
-
-@property(readonly, nonatomic) int enterCount;
-@property(readonly, nonatomic) int exitCount;
-
-// Initialize for the given window and start tracking notifications.
-- (instancetype)initWithWindow:(NSWindow*)window;
-
-// Keep spinning a run loop until the enter and exit counts match.
-- (void)waitForEnterCount:(int)enterCount exitCount:(int)exitCount;
-
-@end
-
-#endif  // UI_BASE_TEST_NSWINDOW_FULLSCREEN_NOTIFICATION_WAITER_H_
diff --git a/ui/base/test/nswindow_fullscreen_notification_waiter.mm b/ui/base/test/nswindow_fullscreen_notification_waiter.mm
deleted file mode 100644
index 2258820..0000000
--- a/ui/base/test/nswindow_fullscreen_notification_waiter.mm
+++ /dev/null
@@ -1,69 +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 "ui/base/test/nswindow_fullscreen_notification_waiter.h"
-
-@interface NSWindowFullscreenNotificationWaiter ()
-// Exit the RunLoop if there is one and the counts being tracked match.
-- (void)maybeQuitForChangedArg:(int*)changedArg;
-- (void)onEnter:(NSNotification*)notification;
-- (void)onExit:(NSNotification*)notification;
-@end
-
-@implementation NSWindowFullscreenNotificationWaiter
-
-@synthesize enterCount = _enterCount;
-@synthesize exitCount = _exitCount;
-
-- (instancetype)initWithWindow:(NSWindow*)window {
-  if ((self = [super init])) {
-    _window.reset([window retain]);
-    NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
-    [defaultCenter addObserver:self
-                      selector:@selector(onEnter:)
-                          name:NSWindowDidEnterFullScreenNotification
-                        object:window];
-    [defaultCenter addObserver:self
-                      selector:@selector(onExit:)
-                          name:NSWindowDidExitFullScreenNotification
-                        object:window];
-  }
-  return self;
-}
-
-- (void)dealloc {
-  DCHECK(!_runLoop);
-  [[NSNotificationCenter defaultCenter] removeObserver:self];
-  [super dealloc];
-}
-
-- (void)waitForEnterCount:(int)enterCount exitCount:(int)exitCount {
-  if (_enterCount >= enterCount && _exitCount >= exitCount)
-    return;
-
-  _targetEnterCount = enterCount;
-  _targetExitCount = exitCount;
-  _runLoop = std::make_unique<base::RunLoop>();
-  _runLoop->Run();
-  _runLoop.reset();
-}
-
-- (void)maybeQuitForChangedArg:(int*)changedArg {
-  ++*changedArg;
-  if (!_runLoop)
-    return;
-
-  if (_enterCount >= _targetEnterCount && _exitCount >= _targetExitCount)
-    _runLoop->Quit();
-}
-
-- (void)onEnter:(NSNotification*)notification {
-  [self maybeQuitForChangedArg:&_enterCount];
-}
-
-- (void)onExit:(NSNotification*)notification {
-  [self maybeQuitForChangedArg:&_exitCount];
-}
-
-@end
diff --git a/ui/chromeos/translations/ui_chromeos_strings_as.xtb b/ui/chromeos/translations/ui_chromeos_strings_as.xtb
index c73207ae..b52a7539 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_as.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_as.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158"><ph name="NUMBER_OF_ITEMS" /> টা বস্তু <ph name="FOLDER_NAME" />লৈ স্থানান্তৰ কৰি থকা হৈছে</translation>
 <translation id="509429900233858213">কোনো আসোঁৱাহ হৈছে।</translation>
 <translation id="5098629044894065541">হিব্ৰু</translation>
+<translation id="5102922915594634436">ছিংক কৰা ফ’ল্ডাৰ পৰিচালনা কৰক</translation>
 <translation id="5109254780565519649">কিবা আসোঁৱাহ হৈছে। কিছুমান বস্তু পুনঃস্থাপন কৰা নহ’ব পাৰে।</translation>
 <translation id="5110329002213341433">ইংৰাজী (কানাডা)</translation>
 <translation id="5123433949759960244">বাস্কেটবল</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_be.xtb b/ui/chromeos/translations/ui_chromeos_strings_be.xtb
index 023084c..fb0db106 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_be.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_be.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Ідзе перамяшчэнне элементаў (<ph name="NUMBER_OF_ITEMS" />) у папку "<ph name="FOLDER_NAME" />"</translation>
 <translation id="509429900233858213">Адбылася памылка.</translation>
 <translation id="5098629044894065541">Іўрыт</translation>
+<translation id="5102922915594634436">Кіраваць сінхранізаванымі папкамі</translation>
 <translation id="5109254780565519649">Адбылася памылка. Некаторыя элементы маглі не аднавіцца.</translation>
 <translation id="5110329002213341433">Англійская (Канада)</translation>
 <translation id="5123433949759960244">Баскетбол</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_es-419.xtb b/ui/chromeos/translations/ui_chromeos_strings_es-419.xtb
index 113e196..012e844 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_es-419.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_es-419.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Moviendo <ph name="NUMBER_OF_ITEMS" /> elementos a <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">Se ha producido un error.</translation>
 <translation id="5098629044894065541">Hebreo</translation>
+<translation id="5102922915594634436">Administrar carpetas sincronizadas</translation>
 <translation id="5109254780565519649">Se produjo un error. Es posible que no se hayan restablecido algunos elementos.</translation>
 <translation id="5110329002213341433">Inglés (Canadá)</translation>
 <translation id="5123433949759960244">Básquet</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_fr-CA.xtb b/ui/chromeos/translations/ui_chromeos_strings_fr-CA.xtb
index 508b25c..de0b9c6 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_fr-CA.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_fr-CA.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Déplacement de <ph name="NUMBER_OF_ITEMS" /> éléments vers le dossier <ph name="FOLDER_NAME" /> en cours…</translation>
 <translation id="509429900233858213">Une erreur s'est produite.</translation>
 <translation id="5098629044894065541">Hébreu</translation>
+<translation id="5102922915594634436">Gérer les dossiers synchronisés</translation>
 <translation id="5109254780565519649">Une erreur s'est produite. Il est possible que certains éléments n'aient pas été restaurés.</translation>
 <translation id="5110329002213341433">Anglais (Canada)</translation>
 <translation id="5123433949759960244">Basketball</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_fr.xtb b/ui/chromeos/translations/ui_chromeos_strings_fr.xtb
index 2c71544..ce00dcf9 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_fr.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_fr.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Transfert de <ph name="NUMBER_OF_ITEMS" /> éléments vers <ph name="FOLDER_NAME" />…</translation>
 <translation id="509429900233858213">Une erreur s'est produite.</translation>
 <translation id="5098629044894065541">Hébreu</translation>
+<translation id="5102922915594634436">Gérer les dossiers synchronisés</translation>
 <translation id="5109254780565519649">Une erreur s'est produite. Certains éléments peuvent ne pas avoir été restaurés.</translation>
 <translation id="5110329002213341433">Anglais (Canada)</translation>
 <translation id="5123433949759960244">Basket-ball</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_it.xtb b/ui/chromeos/translations/ui_chromeos_strings_it.xtb
index 1d0dedf..5d3bbbf 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_it.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_it.xtb
@@ -470,6 +470,7 @@
 <translation id="508423945471810158">Spostamento di <ph name="NUMBER_OF_ITEMS" /> elementi in <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">Si è verificato un errore.</translation>
 <translation id="5098629044894065541">Ebraico</translation>
+<translation id="5102922915594634436">Gestisci cartelle sincronizzate</translation>
 <translation id="5109254780565519649">Si è verificato un errore. Alcuni elementi potrebbero non essere stati ripristinati.</translation>
 <translation id="5110329002213341433">Inglese (Canada)</translation>
 <translation id="5123433949759960244">Basket</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_iw.xtb b/ui/chromeos/translations/ui_chromeos_strings_iw.xtb
index 4e4e733..e4580e8 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_iw.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_iw.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">מתבצעת העברה של <ph name="NUMBER_OF_ITEMS" /> פריטים אל <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">אירעה שגיאה.</translation>
 <translation id="5098629044894065541">עברית</translation>
+<translation id="5102922915594634436">ניהול תיקיות מסונכרנות</translation>
 <translation id="5109254780565519649">אירעה שגיאה. ייתכן שפריטים מסוימים לא שוחזרו.</translation>
 <translation id="5110329002213341433">אנגלית (קנדה)</translation>
 <translation id="5123433949759960244">כדורסל</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_kk.xtb b/ui/chromeos/translations/ui_chromeos_strings_kk.xtb
index d6e092d..5bde997 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_kk.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_kk.xtb
@@ -470,6 +470,7 @@
 <translation id="508423945471810158"><ph name="NUMBER_OF_ITEMS" /> элемент <ph name="FOLDER_NAME" /> қалтасына тасымалдануда.</translation>
 <translation id="509429900233858213">Қате орын алды.</translation>
 <translation id="5098629044894065541">иврит</translation>
+<translation id="5102922915594634436">Синхрондалған қалталарды басқару</translation>
 <translation id="5109254780565519649">Қате пайда болды. Кейбір элементтер қалпына келтірілмеген болуы мүмкін.</translation>
 <translation id="5110329002213341433">Ағылшын (Канада)</translation>
 <translation id="5123433949759960244">Баскетбол</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_lo.xtb b/ui/chromeos/translations/ui_chromeos_strings_lo.xtb
index c2be270..b11fd8a6 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_lo.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_lo.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">ກຳລັງຍ້າຍ <ph name="NUMBER_OF_ITEMS" /> ລາຍການໄປຍັງ <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">ເກີດຄວາມຜິດພາດຂຶ້ນ.</translation>
 <translation id="5098629044894065541">ພາສາຮິບຣູ</translation>
+<translation id="5102922915594634436">ຈັດການໂຟນເດີທີ່ຊິ້ງຂໍ້ມູນ</translation>
 <translation id="5109254780565519649">ມີຄວາມຜິດພາດເກີດຂຶ້ນ. ລະບົບອາດຈະບໍ່ໄດ້ກູ້ຄືນບາງລາຍການ.</translation>
 <translation id="5110329002213341433">ພາສາອັງກິດ (ຄານາດາ)</translation>
 <translation id="5123433949759960244">ບານບ້ວງ</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_lv.xtb b/ui/chromeos/translations/ui_chromeos_strings_lv.xtb
index ffbb2ff..54623dfe 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_lv.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_lv.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Notiek pārvietošana uz mapi <ph name="FOLDER_NAME" />. Pārvietojamo vienumu skaits: <ph name="NUMBER_OF_ITEMS" />.</translation>
 <translation id="509429900233858213">Radās kļūda.</translation>
 <translation id="5098629044894065541">Ebreju valoda</translation>
+<translation id="5102922915594634436">Pārvaldīt sinhronizētās mapes</translation>
 <translation id="5109254780565519649">Radās kļūda. Iespējams, daži vienumi netika atjaunoti.</translation>
 <translation id="5110329002213341433">Angļu (Kanāda)</translation>
 <translation id="5123433949759960244">Basketbols</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_mr.xtb b/ui/chromeos/translations/ui_chromeos_strings_mr.xtb
index 90b7426..9217ce0c 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_mr.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_mr.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158"><ph name="NUMBER_OF_ITEMS" /> आयटम <ph name="FOLDER_NAME" /> वर हलवत आहे</translation>
 <translation id="509429900233858213">एक एरर आली आहे.</translation>
 <translation id="5098629044894065541">हिब्रू</translation>
+<translation id="5102922915594634436">सिंक केलेली फोल्डर व्यवस्थापित करा</translation>
 <translation id="5109254780565519649">एरर आली. काही आयटम रिस्टोअर झाले नसतील.</translation>
 <translation id="5110329002213341433">इंग्रजी (कॅनडा)</translation>
 <translation id="5123433949759960244">बास्केटबॉल</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_or.xtb b/ui/chromeos/translations/ui_chromeos_strings_or.xtb
index d17df87..c22ab63 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_or.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_or.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158"><ph name="FOLDER_NAME" />କୁ <ph name="NUMBER_OF_ITEMS" />ଟି ଆଇଟମ୍ ମୁଭ୍ କରାଯାଉଛି</translation>
 <translation id="509429900233858213">ଏକ ତ୍ରୁଟି ଦେଖାଦେଲା।</translation>
 <translation id="5098629044894065541">ହେବ୍ର୍ୟୁ</translation>
+<translation id="5102922915594634436">ସିଙ୍କ କରାଯାଇଥିବା ଫୋଲ୍ଡରଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ</translation>
 <translation id="5109254780565519649">ଏକ ତ୍ରୁଟି ହୋଇଛି। କିଛି ଆଇଟମକୁ ରିଷ୍ଟୋର କରାଯାଇନପାରେ।</translation>
 <translation id="5110329002213341433">ଇଂରାଜୀ (କାନାଡ଼ା)</translation>
 <translation id="5123433949759960244">ବାସ୍କେଟବଲ୍</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_si.xtb b/ui/chromeos/translations/ui_chromeos_strings_si.xtb
index e458b0b..8418f52b 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_si.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_si.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">අයිතම <ph name="NUMBER_OF_ITEMS" /> ක් <ph name="FOLDER_NAME" /> වෙත ගෙන යාම</translation>
 <translation id="509429900233858213">දෝශයක් ඇතිවිය.</translation>
 <translation id="5098629044894065541">හීබෲ</translation>
+<translation id="5102922915594634436">සමමුහුර්ත ෆෝල්ඩර කළමනාකරණය කරන්න</translation>
 <translation id="5109254780565519649">දෝෂයක් ඇති විය. සමහර අයිතම ප්‍රතිසාධනය කර නොතිබිය හැකිය</translation>
 <translation id="5110329002213341433">ඉංග්‍රීසි (කැනඩාව)</translation>
 <translation id="5123433949759960244">පැසිපන්දු</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_sk.xtb b/ui/chromeos/translations/ui_chromeos_strings_sk.xtb
index f5432e22..0bb7a5c6 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_sk.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_sk.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Presúvajú sa položky (<ph name="NUMBER_OF_ITEMS" />) do priečinka <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">Vyskytla sa chyba.</translation>
 <translation id="5098629044894065541">Hebrejčina</translation>
+<translation id="5102922915594634436">Spravovať synchronizované priečinky</translation>
 <translation id="5109254780565519649">Vyskytla sa chyba. Niektoré položky sa možno nepodarilo obnoviť.</translation>
 <translation id="5110329002213341433">Anglická (Kanada) klávesnica</translation>
 <translation id="5123433949759960244">Basketbal</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_sq.xtb b/ui/chromeos/translations/ui_chromeos_strings_sq.xtb
index ca52213f..3c335c9 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_sq.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_sq.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Po zhvendos <ph name="NUMBER_OF_ITEMS" /> artikuj në <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">Ndodhi një gabim.</translation>
 <translation id="5098629044894065541">Hebraisht</translation>
+<translation id="5102922915594634436">Menaxho dosjet e sinkronizuara</translation>
 <translation id="5109254780565519649">Ndodhi një gabim. Disa artikuj mund të mos jenë restauruar.</translation>
 <translation id="5110329002213341433">Anglisht (Kanada)</translation>
 <translation id="5123433949759960244">Basketboll</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_sv.xtb b/ui/chromeos/translations/ui_chromeos_strings_sv.xtb
index 29ee50ce..84f9a78 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_sv.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_sv.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158"><ph name="NUMBER_OF_ITEMS" /> objekt flyttas till <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">Det har uppstått ett fel.</translation>
 <translation id="5098629044894065541">hebreiska</translation>
+<translation id="5102922915594634436">Hantera synkroniserade mappar</translation>
 <translation id="5109254780565519649">Ett fel har uppstått. Vissa objekt kanske inte har återställts.</translation>
 <translation id="5110329002213341433">engelska (Kanada)</translation>
 <translation id="5123433949759960244">Basketboll</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_sw.xtb b/ui/chromeos/translations/ui_chromeos_strings_sw.xtb
index 76cb162..877c51db 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_sw.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_sw.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Inahamishia vipengee <ph name="NUMBER_OF_ITEMS" /> kwenye <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">Hitilafu fulani imetokea.</translation>
 <translation id="5098629044894065541">Kiyahudi</translation>
+<translation id="5102922915594634436">Dhibiti folda zilizosawazishwa</translation>
 <translation id="5109254780565519649">Hitilafu fulani imetokea. Huenda baadhi ya vipengee havijarejeshwa.</translation>
 <translation id="5110329002213341433">Kiingereza (Kanada)</translation>
 <translation id="5123433949759960244">Mpira wa kikapu</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_ta.xtb b/ui/chromeos/translations/ui_chromeos_strings_ta.xtb
index 7f2e5d2f4c..c2d1c94 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_ta.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_ta.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158"><ph name="NUMBER_OF_ITEMS" /> ஃபைல்கள் /ஃபோல்டர்களை <ph name="FOLDER_NAME" />க்கு நகர்த்துகிறது</translation>
 <translation id="509429900233858213">பிழை ஏற்பட்டது.</translation>
 <translation id="5098629044894065541">ஹீப்ரு</translation>
+<translation id="5102922915594634436">ஒத்திசைத்த ஃபோல்டர்களை நிர்வகி</translation>
 <translation id="5109254780565519649">பிழை ஏற்பட்டது. இதில் சில மீட்டெடுக்கப்படாமல் இருக்கக்கூடும்.</translation>
 <translation id="5110329002213341433">ஆங்கிலம் (கனடா)</translation>
 <translation id="5123433949759960244">கூடைப்பந்து</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_th.xtb b/ui/chromeos/translations/ui_chromeos_strings_th.xtb
index b3175af..5fd2d432 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_th.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_th.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">กำลังย้าย <ph name="NUMBER_OF_ITEMS" /> รายการไปยัง <ph name="FOLDER_NAME" /></translation>
 <translation id="509429900233858213">มีข้อผิดพลาดเกิดขึ้น</translation>
 <translation id="5098629044894065541">ฮิบรู</translation>
+<translation id="5102922915594634436">จัดการโฟลเดอร์ที่ซิงค์</translation>
 <translation id="5109254780565519649">เกิดข้อผิดพลาด ระบบอาจไม่ได้คืนค่าบางรายการ</translation>
 <translation id="5110329002213341433">อังกฤษ (แคนาดา)</translation>
 <translation id="5123433949759960244">บาสเกตบอล</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_uk.xtb b/ui/chromeos/translations/ui_chromeos_strings_uk.xtb
index 0db9524..91c38c4 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_uk.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_uk.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">Переміщення елементів (<ph name="NUMBER_OF_ITEMS" />) у папку "<ph name="FOLDER_NAME" />"</translation>
 <translation id="509429900233858213">Сталася помилка.</translation>
 <translation id="5098629044894065541">Іврит</translation>
+<translation id="5102922915594634436">Керувати синхронізованими папками</translation>
 <translation id="5109254780565519649">Сталася помилка. Можливо, відновлено не всі елементи.</translation>
 <translation id="5110329002213341433">Англійська (Канада)</translation>
 <translation id="5123433949759960244">Баскетбол</translation>
diff --git a/ui/chromeos/translations/ui_chromeos_strings_ur.xtb b/ui/chromeos/translations/ui_chromeos_strings_ur.xtb
index b002bba..2efbd06 100644
--- a/ui/chromeos/translations/ui_chromeos_strings_ur.xtb
+++ b/ui/chromeos/translations/ui_chromeos_strings_ur.xtb
@@ -473,6 +473,7 @@
 <translation id="508423945471810158">‫<ph name="NUMBER_OF_ITEMS" /> آئٹمز کو <ph name="FOLDER_NAME" /> میں منتقل کیا جا رہا ہے</translation>
 <translation id="509429900233858213">ایک خرابی پیش آگئی۔</translation>
 <translation id="5098629044894065541">عبرانی</translation>
+<translation id="5102922915594634436">سِنک کردہ فولڈرز کا نظم کریں</translation>
 <translation id="5109254780565519649">ایک خرابی پیش آ گئی۔ ہو سکتا ہے کچھ آئٹمز بحال نہ ہوئے ہوں۔</translation>
 <translation id="5110329002213341433">انگریزی (کینیڈا)</translation>
 <translation id="5123433949759960244">باسکٹ بال</translation>
diff --git a/ui/gl/generate_bindings.py b/ui/gl/generate_bindings.py
index 7839ed73..898e471 100755
--- a/ui/gl/generate_bindings.py
+++ b/ui/gl/generate_bindings.py
@@ -3124,9 +3124,6 @@
 class GLContext;
 """ % {'name': set_name.upper()})
 
-  if set_name == 'egl':
-    file.write("class GLDisplayEGL;\n")
-
   # Write typedefs for function pointer types. Always use the GL name for the
   # typedef.
   file.write('\n')
@@ -3161,10 +3158,10 @@
     file.write(
 """
 
-  void InitializeExtensionSettings(GLDisplayEGL* display);
-  void UpdateConditionalExtensionSettings(GLDisplayEGL* display);
+  void InitializeExtensionSettings(EGLDisplay display);
+  void UpdateConditionalExtensionSettings(EGLDisplay display);
 
-  static std::string GetPlatformExtensions(GLDisplayEGL* display);
+  static std::string GetPlatformExtensions(EGLDisplay display);
 """)
   file.write('};\n')
   file.write('\n')
@@ -3328,8 +3325,6 @@
                    'ui/gl/gl_implementation.h',
                    'ui/gl/gl_version_info.h',
                    set_header_name ]
-  if set_name == 'egl':
-    include_list.append('ui/gl/gl_display.h')
 
   includes_string = "\n".join(["#include \"{0}\"".format(h)
                                for h in sorted(include_list)])
@@ -3496,7 +3491,7 @@
     file.write("""\
 }
 
-void DisplayExtensionsEGL::InitializeExtensionSettings(GLDisplayEGL* display) {
+void DisplayExtensionsEGL::InitializeExtensionSettings(EGLDisplay display) {
   std::string platform_extensions(GetPlatformExtensions(display));
   [[maybe_unused]] gfx::ExtensionSet extensions(
       gfx::MakeExtensionSet(platform_extensions));
diff --git a/ui/gl/gl_bindings.cc b/ui/gl/gl_bindings.cc
index 8092fa1..42b1f6ecb 100644
--- a/ui/gl/gl_bindings.cc
+++ b/ui/gl/gl_bindings.cc
@@ -24,7 +24,7 @@
 
 #if defined(USE_EGL)
 void DisplayExtensionsEGL::UpdateConditionalExtensionSettings(
-    GLDisplayEGL* display) {
+    EGLDisplay display) {
   // For the moment, only two extensions can be conditionally disabled
   // through GPU driver bug workarounds mechanism:
   //   EGL_KHR_fence_sync
@@ -43,12 +43,10 @@
 }
 
 // static
-std::string DisplayExtensionsEGL::GetPlatformExtensions(GLDisplayEGL* display) {
-  DCHECK(display);
-  EGLDisplay egl_display = display->GetDisplay();
-  if (egl_display == EGL_NO_DISPLAY)
+std::string DisplayExtensionsEGL::GetPlatformExtensions(EGLDisplay display) {
+  if (display == EGL_NO_DISPLAY)
     return "";
-  const char* str = eglQueryString(egl_display, EGL_EXTENSIONS);
+  const char* str = eglQueryString(display, EGL_EXTENSIONS);
   return str ? std::string(str) : "";
 }
 
diff --git a/ui/gl/gl_bindings_autogen_egl.cc b/ui/gl/gl_bindings_autogen_egl.cc
index 7db07227..e203c35 100644
--- a/ui/gl/gl_bindings_autogen_egl.cc
+++ b/ui/gl/gl_bindings_autogen_egl.cc
@@ -13,7 +13,6 @@
 #include "base/trace_event/trace_event.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
-#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_egl_api_implementation.h"
 #include "ui/gl/gl_enums.h"
 #include "ui/gl/gl_implementation.h"
@@ -266,7 +265,7 @@
       gfx::HasExtension(extensions, "EGL_MESA_platform_surfaceless");
 }
 
-void DisplayExtensionsEGL::InitializeExtensionSettings(GLDisplayEGL* display) {
+void DisplayExtensionsEGL::InitializeExtensionSettings(EGLDisplay display) {
   std::string platform_extensions(GetPlatformExtensions(display));
   [[maybe_unused]] gfx::ExtensionSet extensions(
       gfx::MakeExtensionSet(platform_extensions));
diff --git a/ui/gl/gl_bindings_autogen_egl.h b/ui/gl/gl_bindings_autogen_egl.h
index 0c192dd3..2fdffb0 100644
--- a/ui/gl/gl_bindings_autogen_egl.h
+++ b/ui/gl/gl_bindings_autogen_egl.h
@@ -16,7 +16,6 @@
 namespace gl {
 
 class GLContext;
-class GLDisplayEGL;
 
 typedef EGLBoolean(GL_BINDING_CALL* eglBindAPIProc)(EGLenum api);
 typedef EGLBoolean(GL_BINDING_CALL* eglBindTexImageProc)(EGLDisplay dpy,
@@ -395,10 +394,10 @@
   bool b_GL_CHROMIUM_egl_android_native_fence_sync_hack;
   bool b_GL_CHROMIUM_egl_khr_fence_sync_hack;
 
-  void InitializeExtensionSettings(GLDisplayEGL* display);
-  void UpdateConditionalExtensionSettings(GLDisplayEGL* display);
+  void InitializeExtensionSettings(EGLDisplay display);
+  void UpdateConditionalExtensionSettings(EGLDisplay display);
 
-  static std::string GetPlatformExtensions(GLDisplayEGL* display);
+  static std::string GetPlatformExtensions(EGLDisplay display);
 };
 
 struct ProcsEGL {
diff --git a/ui/gl/gl_display.cc b/ui/gl/gl_display.cc
index e3a2466..9fc104a2 100644
--- a/ui/gl/gl_display.cc
+++ b/ui/gl/gl_display.cc
@@ -666,6 +666,17 @@
 GLDisplay::~GLDisplay() = default;
 
 #if defined(USE_EGL)
+GLDisplayEGL::EGLGpuSwitchingObserver::EGLGpuSwitchingObserver(
+    EGLDisplay display)
+    : display_(display) {
+  DCHECK(display != EGL_NO_DISPLAY);
+}
+
+void GLDisplayEGL::EGLGpuSwitchingObserver::OnGpuSwitched(
+    GpuPreference active_gpu_heuristic) {
+  eglHandleGPUSwitchANGLE(display_);
+}
+
 GLDisplayEGL::GLDisplayEGL(uint64_t system_device_id)
     : GLDisplay(system_device_id) {
   ext = std::make_unique<DisplayExtensionsEGL>();
@@ -678,6 +689,26 @@
   return display_;
 }
 
+void GLDisplayEGL::Shutdown() {
+  if (display_ == EGL_NO_DISPLAY)
+    return;
+
+  if (gpu_switching_observer_.get()) {
+    ui::GpuSwitchingManager::GetInstance()->RemoveObserver(
+        gpu_switching_observer_.get());
+    gpu_switching_observer_.reset();
+  }
+
+  angle::ResetPlatform(display_);
+  DCHECK(g_driver_egl.fn.eglTerminateFn);
+  eglTerminate(display_);
+
+  display_ = EGL_NO_DISPLAY;
+  egl_surfaceless_context_supported_ = false;
+  egl_context_priority_supported_ = false;
+  egl_android_native_fence_sync_supported_ = false;
+}
+
 void GLDisplayEGL::SetDisplay(EGLDisplay display) {
   display_ = display;
 }
@@ -712,6 +743,36 @@
   return this->ext->b_EGL_ANGLE_external_context_and_surface;
 }
 
+bool GLDisplayEGL::Initialize(EGLDisplayPlatform native_display) {
+  if (display_ != EGL_NO_DISPLAY)
+    return true;
+
+  if (!InitializeDisplay(native_display))
+    return false;
+  InitializeCommon();
+
+  if (ext->b_EGL_ANGLE_power_preference) {
+    gpu_switching_observer_ =
+        std::make_unique<EGLGpuSwitchingObserver>(display_);
+    ui::GpuSwitchingManager::GetInstance()->AddObserver(
+        gpu_switching_observer_.get());
+  }
+  return true;
+}
+
+void GLDisplayEGL::InitializeForTesting() {
+  display_ = eglGetCurrentDisplay();
+  ext->InitializeExtensionSettings(display_);
+  InitializeCommon();
+}
+
+bool GLDisplayEGL::InitializeExtensionSettings() {
+  if (display_ == EGL_NO_DISPLAY)
+    return false;
+  ext->UpdateConditionalExtensionSettings(display_);
+  return true;
+}
+
 // InitializeDisplay is necessary because the static binding code
 // needs a full Display init before it can query the Display extensions.
 bool GLDisplayEGL::InitializeDisplay(EGLDisplayPlatform native_display) {
@@ -837,7 +898,7 @@
                               DISPLAY_TYPE_MAX);
     display_ = display;
     display_type_ = display_type;
-    ext->InitializeExtensionSettings(this);
+    ext->InitializeExtensionSettings(display);
     return true;
   }
 
@@ -917,27 +978,6 @@
   }
 #endif
 }
-
-bool GLDisplayEGL::InitializeExtensionSettings() {
-  if (display_ == EGL_NO_DISPLAY)
-    return false;
-  ext->UpdateConditionalExtensionSettings(this);
-  return true;
-}
-
-void GLDisplayEGL::Shutdown() {
-  if (display_ == EGL_NO_DISPLAY)
-    return;
-
-  angle::ResetPlatform(display_);
-  DCHECK(g_driver_egl.fn.eglTerminateFn);
-  eglTerminate(display_);
-
-  display_ = EGL_NO_DISPLAY;
-  egl_surfaceless_context_supported_ = false;
-  egl_context_priority_supported_ = false;
-  egl_android_native_fence_sync_supported_ = false;
-}
 #endif  // defined(USE_EGL)
 
 #if defined(USE_GLX)
@@ -949,6 +989,8 @@
 void* GLDisplayX11::GetDisplay() {
   return x11::Connection::Get()->GetXlibDisplay();
 }
+
+void GLDisplayX11::Shutdown() {}
 #endif  // defined(USE_GLX)
 
 }  // namespace gl
diff --git a/ui/gl/gl_display.h b/ui/gl/gl_display.h
index 46030cf..5d0a6e8 100644
--- a/ui/gl/gl_display.h
+++ b/ui/gl/gl_display.h
@@ -14,6 +14,8 @@
 
 #if defined(USE_EGL)
 #include <EGL/egl.h>
+
+#include "ui/gl/gpu_switching_manager.h"
 #endif  // defined(USE_EGL)
 
 namespace base {
@@ -90,6 +92,7 @@
   virtual ~GLDisplay();
 
   virtual void* GetDisplay() = 0;
+  virtual void Shutdown() = 0;
 
  protected:
   explicit GLDisplay(uint64_t system_device_id);
@@ -108,6 +111,8 @@
   static GLDisplayEGL* GetDisplayForCurrentContext();
 
   EGLDisplay GetDisplay() override;
+  void Shutdown() override;
+
   void SetDisplay(EGLDisplay display);
   EGLDisplayPlatform GetNativeDisplay() const;
   DisplayType GetDisplayType() const;
@@ -117,18 +122,30 @@
   bool IsAndroidNativeFenceSyncSupported();
   bool IsANGLEExternalContextAndSurfaceSupported();
 
-  bool InitializeDisplay(EGLDisplayPlatform native_display);
-  void InitializeCommon();
+  bool Initialize(EGLDisplayPlatform native_display);
+  void InitializeForTesting();
   bool InitializeExtensionSettings();
-  void Shutdown();
 
   std::unique_ptr<DisplayExtensionsEGL> ext;
 
  private:
   friend class GLDisplayManager<GLDisplayEGL>;
+  friend class EGLApiTest;
+
+  class EGLGpuSwitchingObserver final : public ui::GpuSwitchingObserver {
+   public:
+    explicit EGLGpuSwitchingObserver(EGLDisplay display);
+    void OnGpuSwitched(GpuPreference active_gpu_heuristic) override;
+
+   private:
+    EGLDisplay display_ = EGL_NO_DISPLAY;
+  };
 
   explicit GLDisplayEGL(uint64_t system_device_id);
 
+  bool InitializeDisplay(EGLDisplayPlatform native_display);
+  void InitializeCommon();
+
   EGLDisplay display_ = EGL_NO_DISPLAY;
   EGLDisplayPlatform native_display_ = EGLDisplayPlatform(EGL_DEFAULT_DISPLAY);
   DisplayType display_type_ = DisplayType::DEFAULT;
@@ -136,6 +153,8 @@
   bool egl_surfaceless_context_supported_ = false;
   bool egl_context_priority_supported_ = false;
   bool egl_android_native_fence_sync_supported_ = false;
+
+  std::unique_ptr<EGLGpuSwitchingObserver> gpu_switching_observer_;
 };
 #endif  // defined(USE_EGL)
 
@@ -148,6 +167,7 @@
   ~GLDisplayX11() override;
 
   void* GetDisplay() override;
+  void Shutdown() override;
 
  private:
   friend class GLDisplayManager<GLDisplayX11>;
diff --git a/ui/gl/gl_surface_egl.cc b/ui/gl/gl_surface_egl.cc
index ba56996c..0ecfa9a 100644
--- a/ui/gl/gl_surface_egl.cc
+++ b/ui/gl/gl_surface_egl.cc
@@ -30,7 +30,6 @@
 #include "ui/gl/gl_surface_presentation_helper.h"
 #include "ui/gl/gl_surface_stub.h"
 #include "ui/gl/gl_utils.h"
-#include "ui/gl/gpu_switching_manager.h"
 #include "ui/gl/scoped_make_current.h"
 #include "ui/gl/sync_control_vsync_provider.h"
 
@@ -84,10 +83,6 @@
 
 namespace {
 
-class EGLGpuSwitchingObserver;
-
-EGLGpuSwitchingObserver* g_egl_gpu_switching_observer = nullptr;
-
 constexpr const char kSwapEventTraceCategories[] = "gpu";
 
 constexpr size_t kMaxTimestampsSupportable = 9;
@@ -155,21 +150,6 @@
   raw_ptr<GLDisplayEGL> display_;
 };
 
-class EGLGpuSwitchingObserver final : public ui::GpuSwitchingObserver {
- public:
-  explicit EGLGpuSwitchingObserver(GLDisplayEGL* display) : display_(display) {
-    DCHECK(display_);
-  }
-
-  void OnGpuSwitched(gl::GpuPreference active_gpu_heuristic) override {
-    DCHECK(display_->ext->b_EGL_ANGLE_power_preference);
-    eglHandleGPUSwitchANGLE(display_->GetDisplay());
-  }
-
- private:
-  raw_ptr<GLDisplayEGL> display_ = nullptr;
-};
-
 bool ValidateEglConfig(EGLDisplay display,
                        const EGLint* config_attribs,
                        EGLint* num_configs) {
@@ -381,50 +361,6 @@
       GpuPreference::kDefault);
 }
 
-// static
-GLDisplayEGL* GLSurfaceEGL::InitializeOneOff(EGLDisplayPlatform native_display,
-                                             uint64_t system_device_id) {
-  GLDisplayEGL* display =
-      GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
-  if (display->GetDisplay() == EGL_NO_DISPLAY) {
-    if (!display->InitializeDisplay(native_display))
-      return nullptr;
-    display->InitializeCommon();
-    if (display->ext->b_EGL_ANGLE_power_preference) {
-      g_egl_gpu_switching_observer = new EGLGpuSwitchingObserver(display);
-      ui::GpuSwitchingManager::GetInstance()->AddObserver(
-          g_egl_gpu_switching_observer);
-    }
-  }
-  return display;
-}
-
-// static
-GLDisplayEGL* GLSurfaceEGL::InitializeOneOffForTesting() {
-  GLDisplayEGL* display =
-      GLDisplayManagerEGL::GetInstance()->GetDisplay(GpuPreference::kDefault);
-  display->SetDisplay(eglGetCurrentDisplay());
-  display->ext->InitializeExtensionSettings(display);
-  display->InitializeCommon();
-  return display;
-}
-
-// static
-void GLSurfaceEGL::ShutdownOneOff(GLDisplayEGL* display) {
-  if (!display || display->GetDisplay() == EGL_NO_DISPLAY) {
-    return;
-  }
-
-  if (g_egl_gpu_switching_observer) {
-    ui::GpuSwitchingManager::GetInstance()->RemoveObserver(
-        g_egl_gpu_switching_observer);
-    delete g_egl_gpu_switching_observer;
-    g_egl_gpu_switching_observer = nullptr;
-  }
-
-  display->Shutdown();
-}
-
 GLSurfaceEGL::~GLSurfaceEGL() = default;
 
 NativeViewGLSurfaceEGL::NativeViewGLSurfaceEGL(
@@ -451,7 +387,8 @@
   format_ = format;
 
   if (display_->GetDisplay() == EGL_NO_DISPLAY) {
-    LOG(ERROR) << "Trying to create surface with invalid display.";
+    LOG(ERROR) << "Trying to create NativeViewGLSurfaceEGL with invalid "
+               << "display.";
     return false;
   }
 
@@ -1124,6 +1061,12 @@
 }
 
 bool PbufferGLSurfaceEGL::Initialize(GLSurfaceFormat format) {
+  if (display_->GetDisplay() == EGL_NO_DISPLAY) {
+    LOG(ERROR) << "Trying to create PbufferGLSurfaceEGL with invalid "
+               << "display.";
+    return false;
+  }
+
   EGLSurface old_surface = surface_;
 
 #if BUILDFLAG(IS_ANDROID)
diff --git a/ui/gl/gl_surface_egl.h b/ui/gl/gl_surface_egl.h
index ffb507c..31c94912 100644
--- a/ui/gl/gl_surface_egl.h
+++ b/ui/gl/gl_surface_egl.h
@@ -50,17 +50,6 @@
 
   static GLDisplayEGL* GetGLDisplayEGL();
 
-  // |system_device_id| specifies which GPU to use on a multi-GPU system.
-  // If its value is 0, use the default GPU of the system.
-  // Calling this functionm a second time on the same |system_device_id|
-  // is a no-op and returns the same GLDisplayEGL.
-  // TODO(https://crbug.com/1251724): This will be called once per display
-  // when Chrome begins to support multi-gpu rendering.
-  static GLDisplayEGL* InitializeOneOff(EGLDisplayPlatform native_display,
-                                        uint64_t system_device_id);
-  static GLDisplayEGL* InitializeOneOffForTesting();
-  static void ShutdownOneOff(GLDisplayEGL* display);
-
  protected:
   ~GLSurfaceEGL() override;
 
diff --git a/ui/gl/gl_utils.cc b/ui/gl/gl_utils.cc
index db3bb3b..c3e2cb43 100644
--- a/ui/gl/gl_utils.cc
+++ b/ui/gl/gl_utils.cc
@@ -216,6 +216,10 @@
   return GLDisplayManagerEGL::GetInstance()->GetDisplay(
       GpuPreference::kDefault);
 }
+
+GLDisplayEGL* GetDisplayEGL(uint64_t system_device_id) {
+  return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+}
 #endif  // USE_EGL
 
 #if defined(USE_GLX)
diff --git a/ui/gl/gl_utils.h b/ui/gl/gl_utils.h
index 1118348..89a01817 100644
--- a/ui/gl/gl_utils.h
+++ b/ui/gl/gl_utils.h
@@ -79,6 +79,9 @@
 
 // Query the default GLDisplayEGL.
 GL_EXPORT GLDisplayEGL* GetDefaultDisplayEGL();
+
+// Query the GLDisplayEGL by |system_device_id|.
+GL_EXPORT GLDisplayEGL* GetDisplayEGL(uint64_t system_device_id);
 #endif  // USE_EGL
 
 #if defined(USE_GLX)
diff --git a/ui/gl/init/create_gr_gl_interface.cc b/ui/gl/init/create_gr_gl_interface.cc
index cdf895d..7c9cc0c 100644
--- a/ui/gl/init/create_gr_gl_interface.cc
+++ b/ui/gl/init/create_gr_gl_interface.cc
@@ -11,8 +11,9 @@
 #include "base/traits_bag.h"
 #include "build/build_config.h"
 #include "ui/gl/gl_bindings.h"
-#include "ui/gl/gl_display_manager.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_implementation.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/gl_version_info.h"
 #include "ui/gl/progress_reporter.h"
 
@@ -81,9 +82,7 @@
   DCHECK(timeout == GL_TIMEOUT_IGNORED);
   DCHECK(flags == 0);
 
-  if (!GLDisplayManagerEGL::GetInstance()
-           ->GetDisplay(GpuPreference::kDefault)
-           ->ext->b_EGL_KHR_wait_sync) {
+  if (!GetDefaultDisplayEGL()->ext->b_EGL_KHR_wait_sync) {
     eglClientWaitSyncKHR(data->display, data->sync, 0, EGL_FOREVER_KHR);
     return;
   }
@@ -299,8 +298,8 @@
       gl->gl##chrome_name##Fn, progress_reporter, version_info.is_angle)
 #define BIND(fname, ...) BIND_EXTENSION(fname, fname, __VA_ARGS__)
 
-  GrGLInterface* interface = new GrGLInterface();
-  GrGLInterface::Functions* functions = &interface->fFunctions;
+  GrGLInterface* gl_interface = new GrGLInterface();
+  GrGLInterface::Functions* functions = &gl_interface->fFunctions;
   BIND(ActiveTexture);
   BIND(AttachShader);
   BIND(BindAttribLocation);
@@ -729,9 +728,7 @@
       BIND_EXTENSION(DeleteSync, DeleteSyncAPPLE);
     }
 #else
-    if (GLDisplayManagerEGL::GetInstance()
-            ->GetDisplay(GpuPreference::kDefault)
-            ->ext->b_EGL_KHR_fence_sync) {
+    if (GetDefaultDisplayEGL()->ext->b_EGL_KHR_fence_sync) {
       // Emulate APPLE_sync via egl
       extensions.add("GL_APPLE_sync");
 
@@ -761,9 +758,9 @@
 #undef BIND
 #undef BIND_EXTENSION
 
-  interface->fStandard = standard;
-  interface->fExtensions.swap(&extensions);
-  sk_sp<GrGLInterface> returned(interface);
+  gl_interface->fStandard = standard;
+  gl_interface->fExtensions.swap(&extensions);
+  sk_sp<GrGLInterface> returned(gl_interface);
   return returned;
 }
 
diff --git a/ui/gl/init/gl_factory.cc b/ui/gl/init/gl_factory.cc
index 70fdfb35..ff574eb 100644
--- a/ui/gl/init/gl_factory.cc
+++ b/ui/gl/init/gl_factory.cc
@@ -15,7 +15,6 @@
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/gl/gl_display_manager.h"
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/gl_utils.h"
@@ -154,8 +153,7 @@
   if (!InitializeStaticGLBindingsOneOff())
     return nullptr;
   if (GetGLImplementation() == kGLImplementationDisabled) {
-    return GLDisplayManagerEGL::GetInstance()->GetDisplay(
-        GpuPreference::kDefault);
+    return GetDefaultDisplayEGL();
   }
 
   return InitializeGLOneOffPlatformHelper(true, system_device_id);
@@ -169,8 +167,7 @@
     if (!InitializeStaticGLBindingsOneOff())
       return nullptr;
     if (GetGLImplementation() == kGLImplementationDisabled) {
-      return GLDisplayManagerEGL::GetInstance()->GetDisplay(
-          GpuPreference::kDefault);
+      return GetDefaultDisplayEGL();
     }
   }
 
diff --git a/ui/gl/init/gl_initializer_android.cc b/ui/gl/init/gl_initializer_android.cc
index 5abd2d9..294b031 100644
--- a/ui/gl/init/gl_initializer_android.cc
+++ b/ui/gl/init/gl_initializer_android.cc
@@ -10,11 +10,10 @@
 #include "base/logging.h"
 #include "base/native_library.h"
 #include "ui/gl/gl_bindings.h"
-#include "ui/gl/gl_display_manager.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_egl_api_implementation.h"
 #include "ui/gl/gl_gl_api_implementation.h"
-#include "ui/gl/gl_surface_egl.h"
-#include "ui/gl/init/gl_initializer.h"
+#include "ui/gl/gl_utils.h"
 
 namespace gl {
 namespace init {
@@ -78,20 +77,19 @@
 }  // namespace
 
 GLDisplay* InitializeGLOneOffPlatform(uint64_t system_device_id) {
+  GLDisplayEGL* display = GetDisplayEGL(system_device_id);
   switch (GetGLImplementation()) {
     case kGLImplementationEGLGLES2:
-    case kGLImplementationEGLANGLE: {
-      GLDisplay* display = GLSurfaceEGL::InitializeOneOff(
-          EGLDisplayPlatform(EGL_DEFAULT_DISPLAY), system_device_id);
-      if (!display) {
-        LOG(ERROR) << "GLSurfaceEGL::InitializeOneOff failed.";
+    case kGLImplementationEGLANGLE:
+      if (!display->Initialize(EGLDisplayPlatform(EGL_DEFAULT_DISPLAY))) {
+        LOG(ERROR) << "GLDisplayEGL::Initialize failed.";
         return nullptr;
       }
-      return display;
-    }
+      break;
     default:
-      return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+      break;
   }
+  return display;
 }
 
 bool InitializeStaticGLBindings(GLImplementationParts implementation) {
@@ -117,7 +115,8 @@
 }
 
 void ShutdownGLPlatform(GLDisplay* display) {
-  GLSurfaceEGL::ShutdownOneOff(static_cast<GLDisplayEGL*>(display));
+  if (display)
+    display->Shutdown();
   ClearBindingsEGL();
   ClearBindingsGL();
 }
diff --git a/ui/gl/init/gl_initializer_mac.cc b/ui/gl/init/gl_initializer_mac.cc
index edbbaed..bc29406 100644
--- a/ui/gl/init/gl_initializer_mac.cc
+++ b/ui/gl/init/gl_initializer_mac.cc
@@ -18,16 +18,15 @@
 #include "base/threading/thread_restrictions.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
-#include "ui/gl/gl_display_manager.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_gl_api_implementation.h"
 #include "ui/gl/gl_implementation.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/gpu_switching_manager.h"
-#include "ui/gl/init/gl_initializer.h"
 
 #if defined(USE_EGL)
 #include "ui/gl/gl_egl_api_implementation.h"
-#include "ui/gl/gl_surface_egl.h"
 #endif  // defined(USE_EGL)
 
 namespace gl {
@@ -167,28 +166,27 @@
 }  // namespace
 
 GLDisplay* InitializeGLOneOffPlatform(uint64_t system_device_id) {
+  GLDisplayEGL* display = GetDisplayEGL(system_device_id);
   switch (GetGLImplementation()) {
     case kGLImplementationDesktopGL:
     case kGLImplementationDesktopGLCoreProfile:
       if (!InitializeOneOffForSandbox()) {
         LOG(ERROR) << "GLSurfaceCGL::InitializeOneOff failed.";
       }
-      return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+      break;
 #if defined(USE_EGL)
     case kGLImplementationEGLGLES2:
-    case kGLImplementationEGLANGLE: {
-      GLDisplay* display = GLSurfaceEGL::InitializeOneOff(EGLDisplayPlatform(0),
-                                                          system_device_id);
-      if (!display) {
-        LOG(ERROR) << "GLSurfaceEGL::InitializeOneOff failed.";
+    case kGLImplementationEGLANGLE:
+      if (!display->Initialize(EGLDisplayPlatform(0))) {
+        LOG(ERROR) << "GLDisplayEGL::Initialize failed.";
         return nullptr;
       }
-      return display;
-    }
+      break;
 #endif  // defined(USE_EGL)
     default:
-      return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+      break;
   }
+  return display;
 }
 
 bool InitializeStaticGLBindings(GLImplementationParts implementation) {
@@ -227,7 +225,8 @@
 void ShutdownGLPlatform(GLDisplay* display) {
   ClearBindingsGL();
 #if defined(USE_EGL)
-  GLSurfaceEGL::ShutdownOneOff(static_cast<GLDisplayEGL*>(display));
+  if (display)
+    display->Shutdown();
   ClearBindingsEGL();
 #endif  // defined(USE_EGL)
 }
diff --git a/ui/gl/init/gl_initializer_ozone.cc b/ui/gl/init/gl_initializer_ozone.cc
index 36c73023..ea0deb1 100644
--- a/ui/gl/init/gl_initializer_ozone.cc
+++ b/ui/gl/init/gl_initializer_ozone.cc
@@ -7,9 +7,9 @@
 #include "base/check_op.h"
 #include "base/notreached.h"
 #include "ui/gl/gl_bindings.h"
-#include "ui/gl/gl_display_manager.h"
 #include "ui/gl/gl_gl_api_implementation.h"
 #include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
 
 #if defined(USE_OZONE)
 #include "ui/gl/init/gl_display_egl_util_ozone.h"
@@ -29,7 +29,7 @@
   switch (GetGLImplementation()) {
     case kGLImplementationMockGL:
     case kGLImplementationStubGL:
-      return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+      return GetDisplayEGL(system_device_id);
     default:
       NOTREACHED();
   }
diff --git a/ui/gl/init/gl_initializer_win.cc b/ui/gl/init/gl_initializer_win.cc
index 960065c..3b4579fd 100644
--- a/ui/gl/init/gl_initializer_win.cc
+++ b/ui/gl/init/gl_initializer_win.cc
@@ -18,10 +18,10 @@
 #include "base/trace_event/trace_event.h"
 #include "base/win/windows_version.h"
 #include "ui/gl/gl_bindings.h"
-#include "ui/gl/gl_display_manager.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_egl_api_implementation.h"
 #include "ui/gl/gl_gl_api_implementation.h"
-#include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/gl/vsync_provider_win.h"
 
 namespace gl {
@@ -126,24 +126,22 @@
 GLDisplay* InitializeGLOneOffPlatform(uint64_t system_device_id) {
   VSyncProviderWin::InitializeOneOff();
 
+  GLDisplayEGL* display = GetDisplayEGL(system_device_id);
   switch (GetGLImplementation()) {
-    case kGLImplementationEGLANGLE: {
-      GLDisplayEGL* display = GLSurfaceEGL::InitializeOneOff(
-          EGLDisplayPlatform(GetDC(nullptr)), system_device_id);
-      if (!display) {
-        LOG(ERROR) << "GLSurfaceEGL::InitializeOneOff failed.";
+    case kGLImplementationEGLANGLE:
+      if (!display->Initialize(EGLDisplayPlatform(GetDC(nullptr)))) {
+        LOG(ERROR) << "GLDisplayEGL::Initialize failed.";
         return nullptr;
       }
       DirectCompositionSurfaceWin::InitializeOneOff(display);
-      return display;
-    }
+      break;
     case kGLImplementationMockGL:
     case kGLImplementationStubGL:
       break;
     default:
       NOTREACHED();
   }
-  return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+  return display;
 }
 
 bool InitializeStaticGLBindings(GLImplementationParts implementation) {
@@ -175,7 +173,8 @@
 
 void ShutdownGLPlatform(GLDisplay* display) {
   DirectCompositionSurfaceWin::ShutdownOneOff();
-  GLSurfaceEGL::ShutdownOneOff(static_cast<GLDisplayEGL*>(display));
+  if (display)
+    display->Shutdown();
   ClearBindingsEGL();
   ClearBindingsGL();
 }
diff --git a/ui/ozone/common/gl_ozone_egl.cc b/ui/ozone/common/gl_ozone_egl.cc
index ec67c6c..f175dc87 100644
--- a/ui/ozone/common/gl_ozone_egl.cc
+++ b/ui/ozone/common/gl_ozone_egl.cc
@@ -7,20 +7,20 @@
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_context_egl.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_egl_api_implementation.h"
 #include "ui/gl/gl_gl_api_implementation.h"
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface.h"
-#include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 
 namespace ui {
 
 gl::GLDisplay* GLOzoneEGL::InitializeGLOneOffPlatform(
     uint64_t system_device_id) {
-  gl::GLDisplay* display =
-      gl::GLSurfaceEGL::InitializeOneOff(GetNativeDisplay(), system_device_id);
-  if (!display) {
-    LOG(ERROR) << "GLSurfaceEGL::InitializeOneOff failed.";
+  gl::GLDisplayEGL* display = gl::GetDisplayEGL(system_device_id);
+  if (!display->Initialize(GetNativeDisplay())) {
+    LOG(ERROR) << "GLDisplayEGL::Initialize failed.";
     return nullptr;
   }
   return display;
@@ -50,7 +50,8 @@
 }
 
 void GLOzoneEGL::ShutdownGL(gl::GLDisplay* display) {
-  gl::GLSurfaceEGL::ShutdownOneOff(static_cast<gl::GLDisplayEGL*>(display));
+  if (display)
+    display->Shutdown();
   gl::ClearBindingsGL();
   gl::ClearBindingsEGL();
 }
diff --git a/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm b/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm
index 57c47721..9b57ed860 100644
--- a/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm
+++ b/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm
@@ -8,8 +8,8 @@
 
 #import "base/mac/mac_util.h"
 #include "base/run_loop.h"
+#import "ui/base/cocoa/nswindow_test_util.h"
 #include "ui/base/hit_test.h"
-#import "ui/base/test/nswindow_fullscreen_notification_waiter.h"
 #include "ui/base/test/ui_controls.h"
 #import "ui/base/test/windowed_nsnotification_observer.h"
 #import "ui/events/test/cocoa_test_event_utils.h"
@@ -75,9 +75,7 @@
       setCollectionBehavior:[test_window() collectionBehavior] |
                             NSWindowCollectionBehaviorFullScreenPrimary];
 
-  base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
-      [[NSWindowFullscreenNotificationWaiter alloc]
-          initWithWindow:test_window()]);
+  ui::NSWindowFullscreenNotificationWaiter waiter(widget_->GetNativeWindow());
   const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
 
   // First show the widget. A user shouldn't be able to initiate fullscreen
@@ -115,17 +113,17 @@
   // the NSWindowDelegate is removed, and the pending request to take out of
   // fullscreen is lost. Since a message loop has not yet spun up in this test
   // we can reliably say there will be one enter and one exit, despite all the
-  // toggling above.
-  [waiter waitForEnterCount:1 exitCount:1];
+  // toggling above. Wait only for the exit notification (the enter
+  // notification will be swallowed, because the exit will have been requested
+  // before the enter completes).
+  waiter.WaitForEnterAndExitCount(0, 1);
   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
 }
 
 // Test fullscreen without overlapping calls and without changing collection
 // behavior on the test window.
 TEST_F(BridgedNativeWidgetUITest, FullscreenEnterAndExit) {
-  base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
-      [[NSWindowFullscreenNotificationWaiter alloc]
-          initWithWindow:test_window()]);
+  ui::NSWindowFullscreenNotificationWaiter waiter(widget_->GetNativeWindow());
 
   EXPECT_FALSE(widget_->IsFullscreen());
   const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
@@ -141,12 +139,12 @@
   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
 
   // Should be zero until the runloop spins.
-  EXPECT_EQ(0, [waiter enterCount]);
-  [waiter waitForEnterCount:1 exitCount:0];
+  EXPECT_EQ(0, waiter.enter_count());
+  waiter.WaitForEnterAndExitCount(1, 0);
 
   // Verify it hasn't exceeded.
-  EXPECT_EQ(1, [waiter enterCount]);
-  EXPECT_EQ(0, [waiter exitCount]);
+  EXPECT_EQ(1, waiter.enter_count());
+  EXPECT_EQ(0, waiter.exit_count());
   EXPECT_TRUE(widget_->IsFullscreen());
   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
 
@@ -154,17 +152,15 @@
   EXPECT_FALSE(widget_->IsFullscreen());
   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
 
-  [waiter waitForEnterCount:1 exitCount:1];
-  EXPECT_EQ(1, [waiter enterCount]);
-  EXPECT_EQ(1, [waiter exitCount]);
+  waiter.WaitForEnterAndExitCount(1, 1);
+  EXPECT_EQ(1, waiter.enter_count());
+  EXPECT_EQ(1, waiter.exit_count());
   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
 }
 
 // Test that Widget::Restore exits fullscreen.
 TEST_F(BridgedNativeWidgetUITest, FullscreenRestore) {
-  base::scoped_nsobject<NSWindowFullscreenNotificationWaiter> waiter(
-      [[NSWindowFullscreenNotificationWaiter alloc]
-          initWithWindow:test_window()]);
+  ui::NSWindowFullscreenNotificationWaiter waiter(widget_->GetNativeWindow());
 
   EXPECT_FALSE(widget_->IsFullscreen());
   const gfx::Rect restored_bounds = widget_->GetRestoredBounds();
@@ -172,11 +168,11 @@
 
   widget_->SetFullscreen(true);
   EXPECT_TRUE(widget_->IsFullscreen());
-  [waiter waitForEnterCount:1 exitCount:0];
+  waiter.WaitForEnterAndExitCount(1, 0);
 
   widget_->Restore();
   EXPECT_FALSE(widget_->IsFullscreen());
-  [waiter waitForEnterCount:1 exitCount:1];
+  waiter.WaitForEnterAndExitCount(1, 1);
   EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds());
 }
 
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.mm b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
index ece479d..0ca869c 100644
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.mm
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
@@ -21,6 +21,7 @@
 #include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
 #include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
 #include "ui/base/cocoa/animation_utils.h"
+#include "ui/base/cocoa/nswindow_test_util.h"
 #include "ui/base/cocoa/remote_accessibility_api.h"
 #include "ui/base/cocoa/remote_layer_api.h"
 #include "ui/base/hit_test.h"
@@ -1101,6 +1102,9 @@
 
   // Ensure constraints are re-applied when completing a transition.
   native_widget_mac_->OnSizeConstraintsChanged();
+
+  ui::NSWindowFullscreenNotificationWaiter::NotifyFullscreenTransitionComplete(
+      native_widget_mac_->GetNativeWindow(), actual_fullscreen_state);
 }
 
 void NativeWidgetMacNSWindowHost::OnWindowMiniaturizedChanged(