diff --git a/DEPS b/DEPS
index c46bbd2..63e5c0c 100644
--- a/DEPS
+++ b/DEPS
@@ -148,7 +148,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'ad244143779fc7c11086c100d9b3e3428b990f8d',
+  'v8_revision': '1c967e1fcf136946aa3bd9e0c811d2ba728fa150',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -156,7 +156,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '6a02f06dfd4ec5e0f9efb553059ce65535f4b312',
+  'angle_revision': '3c4cfad19f14a9ac41ce0c8de0cd59e194ba26b5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -207,7 +207,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': '42a883d13292b1a8a667ef7be682b7347268ef1c',
+  'catapult_revision': '5651a8c3364dacbc7a6e4f157840e623b0922dfd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -263,7 +263,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.
-  'spv_tools_revision': 'aa9e8f538041db3055ea443080e0ccc315fa114f',
+  'spv_tools_revision': '76b75c40a1e27939957e6a598292e9f32b4e98d4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -808,7 +808,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '0de389fb85c3ada74a6014c178d975761bc89a22',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9997cc020c16adacc2c9b482530ab22e27492508',
       'condition': 'checkout_linux',
   },
 
@@ -1206,7 +1206,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'edd4fc045ba2949f5a206cb66d84e0ec7d31bc39',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '3616447f3f5fbae7183384049b4662a93e0a4230',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1276,7 +1276,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': '6xVKWv-ssICwyU5FC1osaRpeZio2kM4Tko33I_SIK-EC',
+              'version': '-ZVn1C00lIdjTSJqwdg9GyT_jqzY8RmSrZ7NGfQFeZ4C',
           },
       ],
       'condition': 'checkout_android',
@@ -1374,7 +1374,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'abaae129d9a0c6e1e092067e0b105475df43352e',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '284c30224c2fb49580d0d702e7dec720f6ad08c2',
+    Var('webrtc_git') + '/src.git' + '@' + '1a49c1338830b222bc6b22b4e0690615bcd6fa52',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1415,7 +1415,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d4bb7bbe7fa67bb4e3c4c932e70ac31dc4edd026',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8bf64083d000c5222151542573a4df00c3b76394',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 78acab1..f6cad25 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -585,13 +585,14 @@
     'breve': {
       'filepath': 'chrome/browser/net/spdyproxy|'\
                   'chrome_proxy|'\
-                  'client_hints|'
-                  'chrome/browser/predictors|'
+                  'client_hints|'\
+                  'chrome/browser/predictors|'\
                   'components/blacklist|'\
                   'components/data_use|'\
                   'data_reduction_proxy|'\
                   'data_use_measurement|'\
-                  'lazy_load|'
+                  'lazy_load|'\
+                  'interventions_internals|'\
                   'navigation_predictor|'\
                   'net/nqe|'\
                   'optimization_guide|'\
diff --git a/ash/DEPS b/ash/DEPS
index a219714..147544fe 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -90,7 +90,7 @@
     "+chromeos/cryptohome",
   ],
   "login_types\.*": [
-    "+chromeos/components/proximity_auth/public/interfaces/auth_type.mojom.h",
+    "+chromeos/components/proximity_auth/public/mojom/auth_type.mojom.h",
   ],
   "policy_recommendation_restorer_unittest.cc": [
     "+components/sync_preferences/testing_pref_service_syncable.h"
diff --git a/ash/login/ui/lock_screen_media_controls_view.cc b/ash/login/ui/lock_screen_media_controls_view.cc
index 20e2ff7ef..c4193c1 100644
--- a/ash/login/ui/lock_screen_media_controls_view.cc
+++ b/ash/login/ui/lock_screen_media_controls_view.cc
@@ -214,20 +214,15 @@
   observer_binding_.Bind(mojo::MakeRequest(&media_controller_observer));
   media_controller_ptr_->AddObserver(std::move(media_controller_observer));
 
-  mojo::PendingRemote<media_session::mojom::MediaControllerImageObserver>
-      artwork_observer;
-  artwork_observer_receiver_.Bind(
-      artwork_observer.InitWithNewPipeAndPassReceiver());
   media_controller_ptr_->ObserveImages(
       media_session::mojom::MediaSessionImageType::kArtwork,
-      kMinimumArtworkSize, kDesiredArtworkSize, std::move(artwork_observer));
+      kMinimumArtworkSize, kDesiredArtworkSize,
+      artwork_observer_receiver_.BindNewPipeAndPassRemote());
 
-  mojo::PendingRemote<media_session::mojom::MediaControllerImageObserver>
-      icon_observer;
-  icon_observer_receiver_.Bind(icon_observer.InitWithNewPipeAndPassReceiver());
   media_controller_ptr_->ObserveImages(
       media_session::mojom::MediaSessionImageType::kSourceIcon,
-      kMinimumIconSize, kDesiredIconSize, std::move(icon_observer));
+      kMinimumIconSize, kDesiredIconSize,
+      icon_observer_receiver_.BindNewPipeAndPassRemote());
 }
 
 LockScreenMediaControlsView::~LockScreenMediaControlsView() = default;
diff --git a/ash/public/cpp/login_types.h b/ash/public/cpp/login_types.h
index 2135f39..7f7439f 100644
--- a/ash/public/cpp/login_types.h
+++ b/ash/public/cpp/login_types.h
@@ -9,7 +9,7 @@
 #include "ash/public/cpp/session/user_info.h"
 #include "base/time/time.h"
 #include "base/token.h"
-#include "chromeos/components/proximity_auth/public/interfaces/auth_type.mojom.h"
+#include "chromeos/components/proximity_auth/public/mojom/auth_type.mojom.h"
 
 namespace ash {
 
diff --git a/ash/public/cpp/wallpaper_controller_client.h b/ash/public/cpp/wallpaper_controller_client.h
index 8556241..5f41ae9 100644
--- a/ash/public/cpp/wallpaper_controller_client.h
+++ b/ash/public/cpp/wallpaper_controller_client.h
@@ -15,10 +15,6 @@
   // Opens the wallpaper picker window.
   virtual void OpenWallpaperPicker() = 0;
 
-  // Signals to the client that ash is ready to set wallpapers. The client is
-  // able to decide whatever the first wallpaper it wants to display.
-  virtual void OnReadyToSetWallpaper() = 0;
-
   // Notifies the client that the animation of the first wallpaper since the
   // controller initialization has completed.
   // TODO(crbug.com/875128): Remove this after web-ui login code is completely
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index cadacf6..f684890 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -20,7 +20,7 @@
   ]
 
   public_deps = [
-    "//chromeos/components/proximity_auth/public/interfaces",
+    "//chromeos/components/proximity_auth/public/mojom",
     "//chromeos/services/assistant/public/mojom",
     "//components/account_id/interfaces",
     "//components/sync/mojo:interfaces",
diff --git a/ash/shelf/shelf_context_menu_model_unittest.cc b/ash/shelf/shelf_context_menu_model_unittest.cc
index 640bef14..69504da 100644
--- a/ash/shelf/shelf_context_menu_model_unittest.cc
+++ b/ash/shelf/shelf_context_menu_model_unittest.cc
@@ -50,7 +50,6 @@
 
   // WallpaperControllerClient:
   void OpenWallpaperPicker() override { open_count_++; }
-  void OnReadyToSetWallpaper() override {}
   void OnFirstWallpaperAnimationFinished() override {}
 
  private:
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 2962742..2d9e8ab6 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -865,8 +865,6 @@
   SetGlobalChromeOSWallpapersDir(chromeos_wallpapers_path);
   SetGlobalChromeOSCustomWallpapersDir(chromeos_custom_wallpapers_path);
   SetDevicePolicyWallpaperPath(device_policy_wallpaper_path);
-  if (wallpaper_controller_client_)
-    wallpaper_controller_client_->OnReadyToSetWallpaper();
 }
 
 void WallpaperControllerImpl::SetCustomWallpaper(
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index 3010090..5cc56b80 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -231,17 +231,14 @@
   virtual ~TestWallpaperControllerClient() = default;
 
   size_t open_count() const { return open_count_; }
-  size_t ready_count() const { return ready_count_; }
   size_t animation_count() const { return animation_count_; }
 
   // WallpaperControllerClient:
   void OpenWallpaperPicker() override { open_count_++; }
-  void OnReadyToSetWallpaper() override { ready_count_++; }
   void OnFirstWallpaperAnimationFinished() override { animation_count_++; }
 
  private:
   size_t open_count_ = 0;
-  size_t ready_count_ = 0;
   size_t animation_count_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(TestWallpaperControllerClient);
@@ -548,10 +545,8 @@
   TestWallpaperControllerClient client;
   controller_->SetClient(&client);
 
-  EXPECT_EQ(0u, client.ready_count());
   base::FilePath empty_path;
   controller_->Init(empty_path, empty_path, empty_path, empty_path);
-  EXPECT_EQ(1u, client.ready_count());
 
   EXPECT_EQ(0u, client.open_count());
   EXPECT_TRUE(controller_->CanOpenWallpaperPicker());
diff --git a/chrome/android/java/res/layout/password_entry_editor.xml b/chrome/android/java/res/layout/password_entry_editor.xml
new file mode 100644
index 0000000..d0e26189
--- /dev/null
+++ b/chrome/android/java/res/layout/password_entry_editor.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingStart="@dimen/password_entry_editor_content_spacing"
+    android:paddingEnd="@dimen/password_entry_editor_content_spacing"
+    android:paddingLeft="@dimen/password_entry_editor_content_spacing"
+    android:paddingRight="@dimen/password_entry_editor_content_spacing">
+
+    <!-- Site -->
+    <org.chromium.chrome.browser.widget.CompatibilityTextInputLayout
+        android:id="@+id/site_label"
+        android:labelFor="@+id/site_edit"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/password_entry_editor_field_large_top_margin"
+        android:layout_marginBottom="@dimen/password_entry_editor_field_bottom_margin">
+
+        <EditText
+            tools:ignore="LabelFor"
+            android:id="@+id/site_edit"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:imeOptions="flagNoExtractUi"
+            android:inputType="text"
+            android:hint="@string/password_entry_viewer_site_title"/>
+    </org.chromium.chrome.browser.widget.CompatibilityTextInputLayout>
+
+    <!-- Username -->
+    <org.chromium.chrome.browser.widget.CompatibilityTextInputLayout
+        android:id="@+id/username_label"
+        android:labelFor="@+id/username_edit"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/password_entry_editor_field_top_margin"
+        android:layout_marginBottom="@dimen/password_entry_editor_field_bottom_margin">
+
+        <EditText
+            tools:ignore="LabelFor"
+            android:id="@+id/username_edit"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:imeOptions="flagNoExtractUi"
+            android:inputType="text"
+            android:hint="@string/password_entry_viewer_username_title"/>
+    </org.chromium.chrome.browser.widget.CompatibilityTextInputLayout>
+
+    <!-- Password -->
+    <org.chromium.chrome.browser.widget.CompatibilityTextInputLayout
+        android:id="@+id/password_label"
+        android:labelFor="@+id/password_edit"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/password_entry_editor_field_top_margin"
+        android:layout_marginBottom="@dimen/password_entry_editor_field_bottom_margin">
+
+        <EditText
+            tools:ignore="LabelFor"
+            android:id="@+id/password_edit"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:imeOptions="flagNoExtractUi"
+            android:inputType="textPassword"
+            android:hint="@string/password_entry_viewer_password"/>
+    </org.chromium.chrome.browser.widget.CompatibilityTextInputLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index e4cc550..24ad182 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -133,6 +133,12 @@
     <dimen name="keyboard_accessory_height_with_shadow">56dp</dimen>
     <dimen name="keyboard_accessory_sheet_height">330dp</dimen>
 
+    <!-- Password entry editor dimensions -->
+    <dimen name="password_entry_editor_content_spacing">12dp</dimen>
+    <dimen name="password_entry_editor_field_bottom_margin">8dp</dimen>
+    <dimen name="password_entry_editor_field_large_top_margin">16dp</dimen>
+    <dimen name="password_entry_editor_field_top_margin">8dp</dimen>
+
     <!-- Password generation popup dimensions -->
     <dimen name="password_generation_text_size">14sp</dimen>
     <dimen name="password_generation_horizontal_margin">10dp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/PasswordEntryEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/PasswordEntryEditor.java
index 7720a409..e3551dc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/PasswordEntryEditor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/PasswordEntryEditor.java
@@ -1,14 +1,29 @@
+// 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.
+
 package org.chromium.chrome.browser.preferences.password;
 
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.chromium.chrome.R;
 
 /**
  * Password entry editor that allows editing passwords stored in Chrome.
  */
 public class PasswordEntryEditor extends Fragment {
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.password_entry_editor, container, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
     }
 }
diff --git a/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenterTest.java b/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenterTest.java
index 8e27c73d..cc4c483 100644
--- a/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenterTest.java
+++ b/chrome/android/touchless/javatests/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenterTest.java
@@ -25,6 +25,7 @@
 
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.touchless.NoTouchActivity;
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 8617b8f..118a4e9 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9534,6 +9534,9 @@
     <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_COLLAPSE" desc="Tooltip for and name of a button that hides (collapses) a detailed list of files and directories">
       Collapse...
     </message>
+    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_REMOVE_ACCESS" desc="Text of button in the bubble showing what files and directories an origin can currently access that removes all such access">
+      Remove access
+    </message>
 
 
     <!-- Native File System restricted directory dialog. -->
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_REMOVE_ACCESS.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_REMOVE_ACCESS.png.sha1
new file mode 100644
index 0000000..c14e29b
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_REMOVE_ACCESS.png.sha1
@@ -0,0 +1 @@
+96f471e3c32c05904036e446ad5d98e2646427c6
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 90527b06..7f2d631 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1811,6 +1811,30 @@
     <message name="IDS_SETTINGS_KERBEROS_ERROR_GENERAL" desc="Fallback error message displayed in the 'Add a Kerberos ticket' dialog.">
       Couldn't get Kerberos ticket. Try again, or contact your organization's device admin. (Error code <ph name="ERROR_CODE">$1<ex>123</ex></ph>).
     </message>
+    <message name="IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_NESTED_IN_GROUP" desc="Error message displayed in the Kerberos configuration dialog when the user tries to save a configuration that has a section (some text in angular braces) inside a group (some text in curly braces), e.g. 'someOption = { [someSection] }'. See https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html.">
+      Section nested in group: <ph name="ERROR_LINE">$1<ex>[realms] (inside a block of curly braces '{ ... })</ex></ph>
+    </message>
+    <message name="IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_SYNTAX" desc="Error message displayed in the Kerberos configuration dialog when the user tries to save a configuration that contains a malformed section, e.g. '[someSection'. Sections must have opening and closing angular braces. See https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html.">
+      Section syntax error: <ph name="ERROR_LINE">$1<ex>[realms</ex></ph>
+    </message>
+    <message name="IDS_SETTINGS_KERBEROS_CONFIG_ERROR_EXPECTED_OPENING_CURLY_BRACE" desc="Error message displayed in the Kerberos configuration dialog when the user tries to save a configuration that is missing a curly brace somewhere. See https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html.">
+      Expected opening curly brace: <ph name="ERROR_LINE">$1<ex>ticket_lifetime = 1d (in the line after 'EXAMPLE.COM = )'</ex></ph>
+    </message>
+    <message name="IDS_SETTINGS_KERBEROS_CONFIG_ERROR_EXTRA_CURLY_BRACE" desc="Error message displayed in the Kerberos configuration dialog when the user tries to save a configuration that has a closing curly brace '}' that cannot be matched with an opening curly brace '{'. See https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html.">
+      Extra curly brace: <ph name="ERROR_LINE">$1<ex>} (without having a corresponding opening curly brace '{')</ex></ph>
+    </message>
+    <message name="IDS_SETTINGS_KERBEROS_CONFIG_ERROR_RELATION_SYNTAX_ERROR" desc="Error message displayed in the Kerberos configuration dialog when the user tries to save a configuration that contains a malformed relation. A relation is 'key = value'. A malformed relation would be e.g. 'key : value' instead of 'key = value'. See https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html.">
+      Relation syntax error: <ph name="ERROR_LINE">$1<ex>ticket_lifetime : 1d</ex></ph>
+    </message>
+    <message name="IDS_SETTINGS_KERBEROS_CONFIG_ERROR_KEY_NOT_SUPPORTED" desc="Error message displayed in the Kerberos configuration dialog when the user tries to save a configuration that contains a configuration option that we do not support, e.g. '[libdefaults] ccache_type = 1'. See https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html.">
+      Configuration option not supported: <ph name="ERROR_LINE">$1<ex>ccache_type = 1</ex></ph>
+    </message>
+    <message name="IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_NOT_SUPPORTED" desc="Error message displayed in the Kerberos configuration dialog when the user tries to save a configuration that contains a section that we do not support, e.g. '[appdefaults]'. See https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html.">
+      Section not supported: <ph name="ERROR_LINE">$1<ex>[appdefaults]</ex></ph>
+    </message>
+    <message name="IDS_SETTINGS_KERBEROS_CONFIG_ERROR_KRB5_FAILED_TO_PARSE" desc="Error message displayed in the Kerberos configuration dialog when the user tries to save a configuration that cannot be parsed by the underlying Kerberos library for unknown reasons. See https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html.">
+      Failed to parse configuration
+    </message>
   </if>
 
   <!-- Date/Time Page -->
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 6c10bf1..18e7743 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1568,6 +1568,8 @@
     "signin/account_consistency_mode_manager.h",
     "signin/account_consistency_mode_manager_factory.cc",
     "signin/account_consistency_mode_manager_factory.h",
+    "signin/account_id_from_account_info.cc",
+    "signin/account_id_from_account_info.h",
     "signin/account_investigator_factory.cc",
     "signin/account_investigator_factory.h",
     "signin/account_reconcilor_factory.cc",
@@ -1862,6 +1864,7 @@
   public_deps = [
     "//base",
     "//chrome/common",
+    "//components/account_id",
     "//components/autofill/core/browser",
     "//components/nacl/common:buildflags",
     "//components/payments/core",
@@ -2022,7 +2025,7 @@
     "//components/services/patch/public/mojom",
     "//components/services/quarantine",
     "//components/services/quarantine/public/mojom",
-    "//components/services/unzip/public/interfaces",
+    "//components/services/unzip/public/mojom",
     "//components/sessions",
     "//components/signin/core/browser",
     "//components/signin/internal/identity_manager",  # TODO(974198): remove once closed
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 12a4f04..334c65d 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3518,11 +3518,6 @@
      flag_descriptions::kSendTabToSelfDescription, kOsAll,
      FEATURE_VALUE_TYPE(switches::kSyncSendTabToSelf)},
 
-    {"enable-send-tab-to-self-history",
-     flag_descriptions::kSendTabToSelfHistoryName,
-     flag_descriptions::kSendTabToSelfHistoryDescription, kOsAll,
-     FEATURE_VALUE_TYPE(send_tab_to_self::kSendTabToSelfHistory)},
-
     {"enable-send-tab-to-self-show-sending-ui",
      flag_descriptions::kSendTabToSelfShowSendingUIName,
      flag_descriptions::kSendTabToSelfShowSendingUIDescription, kOsAll,
diff --git a/chrome/browser/android/autofill_assistant/client_android.cc b/chrome/browser/android/autofill_assistant/client_android.cc
index 8a1b0cc..95f7031 100644
--- a/chrome/browser/android/autofill_assistant/client_android.cc
+++ b/chrome/browser/android/autofill_assistant/client_android.cc
@@ -118,7 +118,7 @@
 
   // If an overlay is already shown, then show the rest of the UI.
   if (joverlay_coordinator) {
-    CreateUI(joverlay_coordinator);
+    AttachUI(joverlay_coordinator);
   }
 
   GURL initial_url(base::android::ConvertJavaStringToUTF8(env, jinitial_url));
@@ -159,7 +159,8 @@
   if (!other_client || !other_client->NeedsUI())
     return;
 
-  other_client->SetUI(std::move(ui_ptr));
+  other_client->ui_controller_android_ = std::move(ui_ptr);
+  other_client->AttachUI();
 }
 
 base::android::ScopedJavaLocalRef<jstring> ClientAndroid::GetPrimaryAccountName(
@@ -182,24 +183,28 @@
   }
 }
 
-void ClientAndroid::ShowUI() {
-  if (!controller_) {
-    CreateController();
-    // TODO(crbug.com/806868): allow delaying controller creation, for
-    // onboarding.
-  }
-  if (!ui_controller_android_) {
-    CreateUI(nullptr);
-  }
+void ClientAndroid::AttachUI() {
+  AttachUI(nullptr);
 }
 
-void ClientAndroid::CreateUI(
+void ClientAndroid::AttachUI(
     const JavaParamRef<jobject>& joverlay_coordinator) {
-  std::unique_ptr<UiControllerAndroid> ui_ptr =
-      UiControllerAndroid::CreateFromWebContents(web_contents_,
-                                                 joverlay_coordinator);
-  if (ui_ptr)
-    SetUI(std::move(ui_ptr));
+  if (!ui_controller_android_) {
+    ui_controller_android_ = UiControllerAndroid::CreateFromWebContents(
+        web_contents_, joverlay_coordinator);
+    if (!ui_controller_android_) {
+      // The activity is not or not yet in a mode where attaching the UI is
+      // possible.
+      return;
+    }
+  }
+
+  if (!ui_controller_android_->IsAttached()) {
+    if (!controller_)
+      CreateController();
+
+    ui_controller_android_->Attach(web_contents_, this, controller_.get());
+  }
 }
 
 void ClientAndroid::DestroyUI() {
@@ -241,7 +246,7 @@
 }
 
 UiController* ClientAndroid::GetUiController() {
-  if (ui_controller_android_)
+  if (ui_controller_android_ && ui_controller_android_->IsAttached())
     return ui_controller_android_.get();
 
   static base::NoDestructor<UiController> noop_controller_;
@@ -262,10 +267,8 @@
   if (!controller_)
     return;
 
-  // Lets the controller and the ui controller know shutdown is about to happen.
-  // TODO(b/128300038): Replace Controller::WillShutdown with a Detach call on
-  // ui_controller_android_.
-  controller_->WillShutdown(reason);
+  if (ui_controller_android_ && ui_controller_android_->IsAttached())
+    DestroyUI();
 
   Metrics::RecordDropOut(reason);
 
@@ -308,12 +311,6 @@
   return !ui_controller_android_ && controller_ && controller_->NeedsUI();
 }
 
-void ClientAndroid::SetUI(
-    std::unique_ptr<UiControllerAndroid> ui_controller_android) {
-  ui_controller_android_ = std::move(ui_controller_android);
-  ui_controller_android_->Attach(web_contents_, this, controller_.get());
-}
-
 WEB_CONTENTS_USER_DATA_KEY_IMPL(ClientAndroid)
 
 }  // namespace autofill_assistant.
diff --git a/chrome/browser/android/autofill_assistant/client_android.h b/chrome/browser/android/autofill_assistant/client_android.h
index 478fd24..2f69a4c 100644
--- a/chrome/browser/android/autofill_assistant/client_android.h
+++ b/chrome/browser/android/autofill_assistant/client_android.h
@@ -61,7 +61,7 @@
                      const base::android::JavaParamRef<jstring>& access_token);
 
   // Overrides Client
-  void ShowUI() override;
+  void AttachUI() override;
   void DestroyUI() override;
   std::string GetApiKey() override;
   std::string GetAccountEmailAddress() override;
@@ -84,10 +84,9 @@
   explicit ClientAndroid(content::WebContents* web_contents);
   void CreateController();
   void DestroyController();
-  void CreateUI(
+  void AttachUI(
       const base::android::JavaParamRef<jobject>& joverlay_coordinator);
   bool NeedsUI();
-  void SetUI(std::unique_ptr<UiControllerAndroid> ui_controller_android);
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
@@ -95,7 +94,9 @@
 
   base::android::ScopedJavaGlobalRef<jobject> java_object_;
   std::unique_ptr<Controller> controller_;
+
   std::unique_ptr<UiControllerAndroid> ui_controller_android_;
+
   base::OnceCallback<void(bool, const std::string&)>
       fetch_access_token_callback_;
   std::string server_url_;
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.cc b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
index 1603160..55403d01 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.cc
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.cc
@@ -127,6 +127,7 @@
   client_ = client;
   ui_delegate_ = ui_delegate;
   captured_debug_context_.clear();
+  destroy_timer_.reset();
 
   JNIEnv* env = AttachCurrentThread();
   auto java_web_contents = web_contents->GetJavaWebContents();
@@ -241,11 +242,30 @@
 
       // make sure user sees the error message.
       ExpandBottomSheet();
+
+      // Keep showing the current UI for a while, without getting updates from
+      // the controller, then shut down the UI portion.
+      //
+      // A controller might still get attached while the timer is running,
+      // canceling the destruction.
+      destroy_timer_ = std::make_unique<base::OneShotTimer>();
+      destroy_timer_->Start(FROM_HERE, kGracefulShutdownDelay,
+                            base::BindOnce(&UiControllerAndroid::DestroySelf,
+                                           weak_ptr_factory_.GetWeakPtr()));
+      Detach();
+      return;
+
+    case AutofillAssistantState::TRACKING:
+      SetOverlayState(OverlayState::HIDDEN);
+      AllowShowingSoftKeyboard(true);
+      SetSpinPoodle(false);
+
+      Java_AssistantModel_setVisible(AttachCurrentThread(), GetModel(), false);
+      DestroySelf();
       return;
 
     case AutofillAssistantState::INACTIVE:
-      // TODO(crbug.com/806868): Add support for switching back to the inactive
-      // state, which is the initial state.
+      // Wait for the state to change.
       return;
   }
   NOTREACHED() << "Unknown state: " << static_cast<int>(state);
@@ -603,31 +623,20 @@
 }
 
 // Other methods.
+void UiControllerAndroid::CloseCustomTab() {
+  Java_AutofillAssistantUiController_scheduleCloseCustomTab(
+      AttachCurrentThread(), java_object_);
+}
 
-void UiControllerAndroid::WillShutdown(Metrics::DropOutReason reason) {
-  if (reason == Metrics::DropOutReason::CUSTOM_TAB_CLOSED) {
-    Java_AutofillAssistantUiController_scheduleCloseCustomTab(
-        AttachCurrentThread(), java_object_);
-  }
+void UiControllerAndroid::Detach() {
+  if (!ui_delegate_)
+    return;
 
   // Capture the debug context, for including into a feedback possibly sent
-  // later, after the delegate has been possibly destroyed.
-  DCHECK(ui_delegate_);
+  // later.
   captured_debug_context_ = ui_delegate_->GetDebugContext();
-  AutofillAssistantState final_state = ui_delegate_->GetState();
-  ui_delegate_ = nullptr;
 
-  // Get rid of the UI, either immediately, or with a delay, to give time to
-  // users to read any message.
-  if (final_state != AutofillAssistantState::STOPPED) {
-    DestroySelf();
-  } else {
-    base::PostDelayedTaskWithTraits(
-        FROM_HERE, {content::BrowserThread::UI},
-        base::BindOnce(&UiControllerAndroid::DestroySelf,
-                       weak_ptr_factory_.GetWeakPtr()),
-        kGracefulShutdownDelay);
-  }
+  ui_delegate_ = nullptr;
 }
 
 // Payment request related methods.
diff --git a/chrome/browser/android/autofill_assistant/ui_controller_android.h b/chrome/browser/android/autofill_assistant/ui_controller_android.h
index 5422c61..b07ec999 100644
--- a/chrome/browser/android/autofill_assistant/ui_controller_android.h
+++ b/chrome/browser/android/autofill_assistant/ui_controller_android.h
@@ -11,6 +11,7 @@
 
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
+#include "base/timer/timer.h"
 #include "chrome/browser/android/autofill_assistant/assistant_form_delegate.h"
 #include "chrome/browser/android/autofill_assistant/assistant_header_delegate.h"
 #include "chrome/browser/android/autofill_assistant/assistant_overlay_delegate.h"
@@ -52,16 +53,23 @@
   // instance or until Attach() is called again, with different pointers.
   //
   // |ui_delegate| must remain valid for the lifetime of this instance or until
-  // either Attach() or WillShutdown() are called.
+  // either Attach() or Detach() are called.
   void Attach(content::WebContents* web_contents,
               Client* client,
               UiDelegate* ui_delegate);
 
+  // Detaches the UI from the its delegate. This guarantees the delegate is not
+  // called anymore after the call.
+  void Detach();
+
+  // Returns true if the UI is attached to a delegate.
+  bool IsAttached() { return ui_delegate_; }
+
   // Overrides UiController:
   void OnStateChanged(AutofillAssistantState new_state) override;
   void OnStatusMessageChanged(const std::string& message) override;
   void OnBubbleMessageChanged(const std::string& message) override;
-  void WillShutdown(Metrics::DropOutReason reason) override;
+  void CloseCustomTab() override;
   void OnUserActionsChanged(const std::vector<UserAction>& actions) override;
   void OnPaymentRequestOptionsChanged(
       const PaymentRequestOptions* options) override;
@@ -180,6 +188,10 @@
   // Makes the whole of AA invisible or visible again.
   void SetVisible(bool visible);
 
+  // Timer started when reaching the STOPPED state. It allows keeping the UI up
+  // for a few seconds before it destroys itself.
+  std::unique_ptr<base::OneShotTimer> destroy_timer_;
+
   // Debug context captured previously. If non-empty, GetDebugContext() returns
   // this context.
   std::string captured_debug_context_;
diff --git a/chrome/browser/android/signin/chrome_signin_manager_delegate.cc b/chrome/browser/android/signin/chrome_signin_manager_delegate.cc
index 89e8ff7..71ecd1d 100644
--- a/chrome/browser/android/signin/chrome_signin_manager_delegate.cc
+++ b/chrome/browser/android/signin/chrome_signin_manager_delegate.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service_mobile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/google/core/common/google_util.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
index 4b3e8fe..d2365135 100644
--- a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
+++ b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
@@ -162,21 +162,29 @@
     case apps::mojom::AppType::kExtension:
       NOTREACHED();
   }
-  RecordUma(launch_name, app_type, close_reason, should_persist);
+  RecordUma(launch_name, app_type, close_reason, Source::kHttpOrHttps,
+            should_persist);
 }
 
 // static
 void AppsNavigationThrottle::RecordUma(const std::string& selected_app_package,
                                        apps::mojom::AppType app_type,
                                        IntentPickerCloseReason close_reason,
+                                       Source source,
                                        bool should_persist) {
   PickerAction action = GetPickerAction(app_type, close_reason, should_persist);
   Platform platform = GetDestinationPlatform(selected_app_package, action);
 
-  UMA_HISTOGRAM_ENUMERATION("ChromeOS.Apps.IntentPickerAction", action);
+  // TODO(crbug.com/985233) For now External Protocol Dialog is only querying
+  // ARC apps.
+  if (source == Source::kExternalProtocol) {
+    UMA_HISTOGRAM_ENUMERATION("ChromeOS.Apps.ExternalProtocolDialog", action);
+  } else {
+    UMA_HISTOGRAM_ENUMERATION("ChromeOS.Apps.IntentPickerAction", action);
 
-  UMA_HISTOGRAM_ENUMERATION("ChromeOS.Apps.IntentPickerDestinationPlatform",
-                            platform);
+    UMA_HISTOGRAM_ENUMERATION("ChromeOS.Apps.IntentPickerDestinationPlatform",
+                              platform);
+  }
 }
 
 // static
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_throttle.h b/chrome/browser/apps/intent_helper/apps_navigation_throttle.h
index 4734945..62c1576 100644
--- a/chrome/browser/apps/intent_helper/apps_navigation_throttle.h
+++ b/chrome/browser/apps/intent_helper/apps_navigation_throttle.h
@@ -72,6 +72,7 @@
   static void RecordUma(const std::string& selected_app_package,
                         apps::mojom::AppType app_type,
                         IntentPickerCloseReason close_reason,
+                        Source source,
                         bool should_persist);
 
   static bool IsGoogleRedirectorUrlForTesting(const GURL& url);
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_types.h b/chrome/browser/apps/intent_helper/apps_navigation_types.h
index 3b7cd4f..34f0a96 100644
--- a/chrome/browser/apps/intent_helper/apps_navigation_types.h
+++ b/chrome/browser/apps/intent_helper/apps_navigation_types.h
@@ -60,6 +60,13 @@
   RESUME,
 };
 
+// This enum backs an UMA histogram and must be treated as append-only.
+enum class Source {
+  kHttpOrHttps = 0,
+  kExternalProtocol = 1,
+  kMaxValue = kExternalProtocol
+};
+
 // Represents the data required to display an app in a picker to the user.
 struct IntentPickerAppInfo {
   IntentPickerAppInfo(apps::mojom::AppType type,
diff --git a/chrome/browser/background_sync/background_sync_controller_impl.cc b/chrome/browser/background_sync/background_sync_controller_impl.cc
index 6a8f9758..295e78d8 100644
--- a/chrome/browser/background_sync/background_sync_controller_impl.cc
+++ b/chrome/browser/background_sync/background_sync_controller_impl.cc
@@ -15,6 +15,7 @@
 #include "components/variations/variations_associated_data.h"
 #include "content/public/browser/background_sync_controller.h"
 #include "content/public/browser/background_sync_parameters.h"
+#include "content/public/browser/background_sync_registration.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -194,30 +195,44 @@
   return kEngagementLevelNonePenalty;
 }
 
-base::TimeDelta BackgroundSyncControllerImpl::GetNextEventDelay(
-    const url::Origin& origin,
+base::TimeDelta BackgroundSyncControllerImpl::SnapToMaxOriginFrequency(
     int64_t min_interval,
-    int num_attempts,
-    blink::mojom::BackgroundSyncType sync_type,
+    int64_t min_gap_for_origin) {
+  DCHECK_GE(min_gap_for_origin, 0);
+  DCHECK_GE(min_interval, 0);
+
+  if (min_interval < min_gap_for_origin)
+    return base::TimeDelta::FromMilliseconds(min_gap_for_origin);
+  if (min_interval % min_gap_for_origin == 0)
+    return base::TimeDelta::FromMilliseconds(min_interval);
+  return base::TimeDelta::FromMilliseconds(
+      (min_interval / min_gap_for_origin + 1) * min_gap_for_origin);
+}
+
+base::TimeDelta BackgroundSyncControllerImpl::GetNextEventDelay(
+    const content::BackgroundSyncRegistration& registration,
     content::BackgroundSyncParameters* parameters) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(parameters);
 
+  int num_attempts = registration.num_attempts();
+
   if (!num_attempts) {
     // First attempt.
-    switch (sync_type) {
+    switch (registration.sync_type()) {
       case blink::mojom::BackgroundSyncType::ONE_SHOT:
         return base::TimeDelta();
       case blink::mojom::BackgroundSyncType::PERIODIC:
-        int site_engagement_factor = GetSiteEngagementPenalty(origin.GetURL());
+        int site_engagement_factor =
+            GetSiteEngagementPenalty(registration.origin().GetURL());
         if (!site_engagement_factor)
           return base::TimeDelta::Max();
 
         int64_t effective_gap_ms =
             site_engagement_factor *
             parameters->min_periodic_sync_events_interval.InMilliseconds();
-        return base::TimeDelta::FromMilliseconds(
-            std::max(min_interval, effective_gap_ms));
+        return SnapToMaxOriginFrequency(registration.options()->min_interval,
+                                        effective_gap_ms);
     }
   }
 
diff --git a/chrome/browser/background_sync/background_sync_controller_impl.h b/chrome/browser/background_sync/background_sync_controller_impl.h
index 3816f98f..351a45e 100644
--- a/chrome/browser/background_sync/background_sync_controller_impl.h
+++ b/chrome/browser/background_sync/background_sync_controller_impl.h
@@ -16,6 +16,7 @@
 #include "components/keep_alive_registry/keep_alive_types.h"
 #include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/background_sync_registration.h"
 #include "content/public/browser/browser_thread.h"
 #include "third_party/blink/public/mojom/background_sync/background_sync.mojom.h"
 
@@ -78,10 +79,7 @@
   void ScheduleBrowserWakeUp(
       blink::mojom::BackgroundSyncType sync_type) override;
   base::TimeDelta GetNextEventDelay(
-      const url::Origin& origin,
-      int64_t min_interval,
-      int num_attempts,
-      blink::mojom::BackgroundSyncType sync_type,
+      const content::BackgroundSyncRegistration& registration,
       content::BackgroundSyncParameters* parameters) override;
   std::unique_ptr<BackgroundSyncEventKeepAlive>
   CreateBackgroundSyncEventKeepAlive() override;
@@ -94,6 +92,12 @@
   // blink::mojom::EngagementLevel::NONE.
   int GetSiteEngagementPenalty(const GURL& url) const;
 
+  // Once we've identified the minimum number of hours between each periodicsync
+  // event for an origin, every delay calculated for the origin should be a
+  // multiple of the same.
+  base::TimeDelta SnapToMaxOriginFrequency(int64_t min_interval,
+                                           int64_t min_gap_for_origin);
+
   Profile* profile_;  // This object is owned by profile_.
 
   // Same lifetime as |profile_|.
diff --git a/chrome/browser/background_sync/background_sync_controller_impl_unittest.cc b/chrome/browser/background_sync/background_sync_controller_impl_unittest.cc
index eee3644..01ad1c0 100644
--- a/chrome/browser/background_sync/background_sync_controller_impl_unittest.cc
+++ b/chrome/browser/background_sync/background_sync_controller_impl_unittest.cc
@@ -17,8 +17,10 @@
 #include "components/history/core/test/test_history_database.h"
 #include "components/variations/variations_associated_data.h"
 #include "content/public/browser/background_sync_parameters.h"
+#include "content/public/browser/background_sync_registration.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/background_sync/background_sync.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -32,9 +34,12 @@
 
 const char kFieldTrialGroup[] = "GroupA";
 const char kExampleUrl[] = "https://www.example.com/foo/";
+const char kTag[] = "test_tag";
 
 constexpr base::TimeDelta kSmallerThanMinGap = base::TimeDelta::FromHours(11);
 constexpr base::TimeDelta kLargerThanMinGap = base::TimeDelta::FromHours(13);
+constexpr base::TimeDelta kLargerThanMinGapExpectedDelay =
+    base::TimeDelta::FromHours(24);
 
 std::unique_ptr<KeyedService> BuildTestHistoryService(
     const base::FilePath& file_path,
@@ -73,6 +78,21 @@
         BackgroundSyncControllerImpl::kFieldTrialName, kFieldTrialGroup);
   }
 
+  content::BackgroundSyncRegistration MakeBackgroundSyncRegistration(
+      int min_interval,
+      int num_attempts,
+      blink::mojom::BackgroundSyncType sync_type) {
+    url::Origin origin = url::Origin::Create(GURL(kExampleUrl));
+    content::BackgroundSyncRegistration registration;
+    registration.set_origin(origin);
+
+    blink::mojom::SyncRegistrationOptions options(kTag, min_interval);
+    *registration.options() = std::move(options);
+    registration.set_num_attempts(num_attempts);
+
+    return registration;
+  }
+
   content::TestBrowserThreadBundle thread_bundle_;
   TestingProfile profile_;
   std::unique_ptr<BackgroundSyncControllerImpl> controller_;
@@ -169,40 +189,59 @@
   // Periodic Sync: zero attempts.
   // min_interval < kMinGapBetweenPeriodicSyncEvents.
   base::TimeDelta delay = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ kSmallerThanMinGap.InMilliseconds(),
-      /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC,
+      MakeBackgroundSyncRegistration(
+          /* min_interval= */ kSmallerThanMinGap.InMilliseconds(),
+          /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC),
       &sync_parameters);
   EXPECT_EQ(delay, sync_parameters.min_periodic_sync_events_interval);
 
   // Periodic Sync: zero attempts.
   // |min_interval| > kMinGapBetweenPeriodicSyncEvents.
   delay = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ kLargerThanMinGap.InMilliseconds(),
-      /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC,
+      MakeBackgroundSyncRegistration(
+          /* min_interval= */ kLargerThanMinGap.InMilliseconds(),
+          /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC),
       &sync_parameters);
-  EXPECT_EQ(delay, kLargerThanMinGap);
+  EXPECT_EQ(delay, kLargerThanMinGapExpectedDelay);
+
+  // Periodic Sync: zero attempts.
+  // |min_interval| a multiple of kMinGapBetweenPeriodicSyncEvents.
+  delay = controller_->GetNextEventDelay(
+      MakeBackgroundSyncRegistration(
+          /* min_interval= */ 2 *
+              kLargerThanMinGapExpectedDelay.InMilliseconds(),
+          /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC),
+      &sync_parameters);
+  EXPECT_EQ(delay, 2 * kLargerThanMinGapExpectedDelay);
+
+  // Periodic Sync: zero attempts.
+  // |min_interval| is zero.
+  delay = controller_->GetNextEventDelay(
+      MakeBackgroundSyncRegistration(
+          /* min_interval= */ 0,
+          /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC),
+      &sync_parameters);
+  EXPECT_EQ(delay, sync_parameters.min_periodic_sync_events_interval);
 
   // One-shot Sync.
   delay = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ -1,
-      /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::ONE_SHOT,
+      MakeBackgroundSyncRegistration(
+          /* min_interval= */ -1,
+          /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::ONE_SHOT),
       &sync_parameters);
   EXPECT_EQ(delay, base::TimeDelta());
 
   base::TimeDelta delay_after_attempt1 = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ -1,
-      /* num_attempts= */ 1, blink::mojom::BackgroundSyncType::ONE_SHOT,
+      MakeBackgroundSyncRegistration(
+          /* min_interval= */ -1,
+          /* num_attempts= */ 1, blink::mojom::BackgroundSyncType::ONE_SHOT),
       &sync_parameters);
   EXPECT_EQ(delay_after_attempt1, sync_parameters.initial_retry_delay);
 
   base::TimeDelta delay_after_attempt2 = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ -1,
-      /* num_attempts= */ 2, blink::mojom::BackgroundSyncType::ONE_SHOT,
+      MakeBackgroundSyncRegistration(
+          /* min_interval= */ -1,
+          /* num_attempts= */ 2, blink::mojom::BackgroundSyncType::ONE_SHOT),
       &sync_parameters);
   EXPECT_LT(delay_after_attempt1, delay_after_attempt2);
 }
@@ -214,17 +253,18 @@
   content::BackgroundSyncParameters sync_parameters;
   int64_t min_gap_between_periodic_sync_events_ms =
       sync_parameters.min_periodic_sync_events_interval.InMilliseconds();
-  url::Origin origin = url::Origin::Create(GURL(kExampleUrl));
+  content::BackgroundSyncRegistration registration =
+      MakeBackgroundSyncRegistration(
+          min_gap_between_periodic_sync_events_ms,
+          /*num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC);
+
   SiteEngagementScore::SetParamValuesForTesting();
   SiteEngagementService::Get(&profile_)->ResetBaseScoreForURL(
       GURL(kExampleUrl), SiteEngagementScore::GetMediumEngagementBoundary());
 
   // Medium engagement.
-  base::TimeDelta delay = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ min_gap_between_periodic_sync_events_ms,
-      /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC,
-      &sync_parameters);
+  base::TimeDelta delay =
+      controller_->GetNextEventDelay(registration, &sync_parameters);
   EXPECT_EQ(delay,
             base::TimeDelta::FromMilliseconds(
                 min_gap_between_periodic_sync_events_ms *
@@ -234,11 +274,7 @@
   SiteEngagementService::Get(&profile_)->ResetBaseScoreForURL(
       GURL(kExampleUrl),
       SiteEngagementScore::GetMediumEngagementBoundary() - 1);
-  delay = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ min_gap_between_periodic_sync_events_ms,
-      /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC,
-      &sync_parameters);
+  delay = controller_->GetNextEventDelay(registration, &sync_parameters);
   EXPECT_EQ(delay,
             base::TimeDelta::FromMilliseconds(
                 min_gap_between_periodic_sync_events_ms *
@@ -247,11 +283,7 @@
   // Minimal engagement.
   SiteEngagementService::Get(&profile_)->ResetBaseScoreForURL(GURL(kExampleUrl),
                                                               0.5);
-  delay = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ min_gap_between_periodic_sync_events_ms,
-      /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC,
-      &sync_parameters);
+  delay = controller_->GetNextEventDelay(registration, &sync_parameters);
   EXPECT_EQ(delay,
             base::TimeDelta::FromMilliseconds(
                 min_gap_between_periodic_sync_events_ms *
@@ -260,11 +292,7 @@
   // No engagement.
   SiteEngagementService::Get(&profile_)->ResetBaseScoreForURL(GURL(kExampleUrl),
                                                               0);
-  delay = controller_->GetNextEventDelay(
-      origin,
-      /* min_interval= */ min_gap_between_periodic_sync_events_ms,
-      /* num_attempts= */ 0, blink::mojom::BackgroundSyncType::PERIODIC,
-      &sync_parameters);
+  delay = controller_->GetNextEventDelay(registration, &sync_parameters);
   EXPECT_EQ(delay, base::TimeDelta::Max());
 }
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 49aa9ec..7570542 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -258,7 +258,7 @@
 #include "components/services/patch/public/mojom/constants.mojom.h"
 #include "components/services/quarantine/public/mojom/quarantine.mojom.h"
 #include "components/services/quarantine/quarantine_service.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/spellcheck/spellcheck_buildflags.h"
 #include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
diff --git a/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.cc b/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.cc
index de730a5..b231235 100644
--- a/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.cc
+++ b/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.cc
@@ -105,6 +105,7 @@
     const std::string& selected_app_package,
     apps::mojom::AppType app_type,
     apps::IntentPickerCloseReason close_reason,
+    apps::Source source,
     bool should_persist) {
   if (app_type == apps::mojom::AppType::kArc &&
       (close_reason == apps::IntentPickerCloseReason::PREFERRED_APP_FOUND ||
@@ -113,7 +114,7 @@
                               arc::UserInteractionType::APP_STARTED_FROM_LINK);
   }
   apps::AppsNavigationThrottle::RecordUma(selected_app_package, app_type,
-                                          close_reason, should_persist);
+                                          close_reason, source, should_persist);
 }
 
 ChromeOsAppsNavigationThrottle::ChromeOsAppsNavigationThrottle(
diff --git a/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h b/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h
index 00d51b4d..ee4d00b 100644
--- a/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h
+++ b/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h
@@ -61,6 +61,7 @@
   static void RecordUma(const std::string& selected_app_package,
                         apps::mojom::AppType app_type,
                         apps::IntentPickerCloseReason close_reason,
+                        apps::Source source,
                         bool should_persist);
 
   ChromeOsAppsNavigationThrottle(content::NavigationHandle* navigation_handle,
diff --git a/chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc b/chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc
index 16c9708..eac2110f 100644
--- a/chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc
+++ b/chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc
@@ -442,7 +442,8 @@
   }
 
   chromeos::ChromeOsAppsNavigationThrottle::RecordUma(
-      selected_app_package, app_type, reason, should_persist);
+      selected_app_package, app_type, reason, apps::Source::kExternalProtocol,
+      should_persist);
 }
 
 // Called when ARC returned activity icons for the |handlers|.
@@ -526,6 +527,7 @@
       chromeos::ChromeOsAppsNavigationThrottle::RecordUma(
           std::string(), apps::mojom::AppType::kArc,
           apps::IntentPickerCloseReason::PREFERRED_APP_FOUND,
+          apps::Source::kExternalProtocol,
           /*should_persist=*/false);
     }
     return;  // the |url| has been handled.
diff --git a/chrome/browser/chromeos/arc/intent_helper/arc_intent_picker_app_fetcher.cc b/chrome/browser/chromeos/arc/intent_helper/arc_intent_picker_app_fetcher.cc
index 1083143..55c36ff 100644
--- a/chrome/browser/chromeos/arc/intent_helper/arc_intent_picker_app_fetcher.cc
+++ b/chrome/browser/chromeos/arc/intent_helper/arc_intent_picker_app_fetcher.cc
@@ -237,6 +237,7 @@
     chromeos::ChromeOsAppsNavigationThrottle::RecordUma(
         /*selected_app_package=*/std::string(), apps::mojom::AppType::kUnknown,
         apps::IntentPickerCloseReason::ERROR_BEFORE_PICKER,
+        apps::Source::kHttpOrHttps,
         /*should_persist=*/false);
     std::move(callback).Run(apps::AppsNavigationAction::RESUME, {});
     return;
@@ -320,7 +321,8 @@
       app_type = apps::mojom::AppType::kArc;
     }
     chromeos::ChromeOsAppsNavigationThrottle::RecordUma(
-        package_name, app_type, close_reason, /*should_persist=*/false);
+        package_name, app_type, close_reason, apps::Source::kHttpOrHttps,
+        /*should_persist=*/false);
   }
 
   return preferred_platform;
@@ -340,7 +342,7 @@
     chromeos::ChromeOsAppsNavigationThrottle::RecordUma(
         /*selected_app_package=*/std::string(), apps::mojom::AppType::kUnknown,
         apps::IntentPickerCloseReason::ERROR_BEFORE_PICKER,
-        /*should_persist=*/false);
+        apps::Source::kHttpOrHttps, /*should_persist=*/false);
     std::move(callback).Run({});
     return;
   }
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
index e7740c4..e96a92b 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -48,6 +48,7 @@
 
 using chromeos::disks::Disk;
 using chromeos::disks::DiskMountManager;
+using chromeos::disks::FormatFileSystemType;
 
 namespace {
 
@@ -430,21 +431,21 @@
                   chromeos::CrosDisksClient::GetRemovableDiskMountPoint()
                       .AppendASCII("mount_path1")
                       .AsUTF8Unsafe(),
-                  "vfat", "NEWLABEL1"))
+                  FormatFileSystemType::kVfat, "NEWLABEL1"))
       .Times(1);
   EXPECT_CALL(*disk_mount_manager_mock_,
               FormatMountedDevice(
                   chromeos::CrosDisksClient::GetRemovableDiskMountPoint()
                       .AppendASCII("mount_path2")
                       .AsUTF8Unsafe(),
-                  "exfat", "NEWLABEL2"))
+                  FormatFileSystemType::kExfat, "NEWLABEL2"))
       .Times(1);
   EXPECT_CALL(*disk_mount_manager_mock_,
               FormatMountedDevice(
                   chromeos::CrosDisksClient::GetRemovableDiskMountPoint()
                       .AppendASCII("mount_path3")
                       .AsUTF8Unsafe(),
-                  "ntfs", "NEWLABEL3"))
+                  FormatFileSystemType::kNtfs, "NEWLABEL3"))
       .Times(1);
 
   ASSERT_TRUE(RunComponentExtensionTest("file_browser/format_test"));
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
index 13175bc..23330f5 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
@@ -339,6 +339,22 @@
   return prefix_matches;
 }
 
+chromeos::disks::FormatFileSystemType ApiFormatFileSystemToChromeEnum(
+    api::file_manager_private::FormatFileSystemType filesystem) {
+  switch (filesystem) {
+    case api::file_manager_private::FORMAT_FILE_SYSTEM_TYPE_NONE:
+      return chromeos::disks::FormatFileSystemType::kUnknown;
+    case api::file_manager_private::FORMAT_FILE_SYSTEM_TYPE_VFAT:
+      return chromeos::disks::FormatFileSystemType::kVfat;
+    case api::file_manager_private::FORMAT_FILE_SYSTEM_TYPE_EXFAT:
+      return chromeos::disks::FormatFileSystemType::kExfat;
+    case api::file_manager_private::FORMAT_FILE_SYSTEM_TYPE_NTFS:
+      return chromeos::disks::FormatFileSystemType::kNtfs;
+  }
+  NOTREACHED() << "Unknown format filesystem " << filesystem;
+  return chromeos::disks::FormatFileSystemType::kUnknown;
+}
+
 }  // namespace
 
 ExtensionFunction::ResponseAction
@@ -707,7 +723,7 @@
 
   DiskMountManager::GetInstance()->FormatMountedDevice(
       volume->mount_path().AsUTF8Unsafe(),
-      api::file_manager_private::ToString(params->filesystem),
+      ApiFormatFileSystemToChromeEnum(params->filesystem),
       params->volume_label);
   return RespondNow(NoArguments());
 }
diff --git a/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.cc b/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.cc
index 97f0acd..2f86de50 100644
--- a/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.cc
+++ b/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.cc
@@ -151,9 +151,10 @@
   unmount_errors_[mount_path] = error_code;
 }
 
-void FakeDiskMountManager::FormatMountedDevice(const std::string& mount_path,
-                                               const std::string& filesystem,
-                                               const std::string& label) {}
+void FakeDiskMountManager::FormatMountedDevice(
+    const std::string& mount_path,
+    chromeos::disks::FormatFileSystemType filesystem,
+    const std::string& label) {}
 
 void FakeDiskMountManager::RenameMountedDevice(const std::string& mount_path,
                                                const std::string& volume_name) {
diff --git a/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h b/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h
index 8e1d8e5..504054d 100644
--- a/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h
+++ b/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h
@@ -99,7 +99,7 @@
   void RemountAllRemovableDrives(
       chromeos::MountAccessMode access_mode) override;
   void FormatMountedDevice(const std::string& mount_path,
-                           const std::string& filesystem,
+                           chromeos::disks::FormatFileSystemType filesystem,
                            const std::string& label) override;
   void RenameMountedDevice(const std::string& mount_path,
                            const std::string& volume_name) override;
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index 054ad69d..85e5966 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -909,7 +909,8 @@
             .EnableDriveFs(),
         TestCase("saveFileDialogDriveOfflinePinned").WithBrowser().Offline(),
         TestCase("openFileDialogDefaultFilter").WithBrowser(),
-        TestCase("saveFileDialogDefaultFilter").WithBrowser()));
+        TestCase("saveFileDialogDefaultFilter").WithBrowser(),
+        TestCase("openFileDialogFileListShowContextMenu").WithBrowser()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     CopyBetweenWindows, /* copy_between_windows.js */
diff --git a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
index fc380e1..1d1351b 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_jstest.cc
@@ -207,10 +207,6 @@
   RunGeneratedTest("/background/js/crostini_unittest.html");
 }
 
-IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ListContainer) {
-  RunGeneratedTest("/foreground/js/ui/list_container_unittest.html");
-}
-
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileTableList) {
   RunGeneratedTest("/foreground/js/ui/file_table_list_unittest.html");
 }
diff --git a/chrome/browser/chromeos/kerberos/kerberos_credentials_manager.cc b/chrome/browser/chromeos/kerberos/kerberos_credentials_manager.cc
index b78c4e27..b851d17 100644
--- a/chrome/browser/chromeos/kerberos/kerberos_credentials_manager.cc
+++ b/chrome/browser/chromeos/kerberos/kerberos_credentials_manager.cc
@@ -583,6 +583,23 @@
   std::move(callback).Run(response.error());
 }
 
+void KerberosCredentialsManager::ValidateConfig(
+    const std::string& krb5_conf,
+    ValidateConfigCallback callback) {
+  kerberos::ValidateConfigRequest request;
+  request.set_krb5conf(krb5_conf);
+  KerberosClient::Get()->ValidateConfig(
+      request, base::BindOnce(&KerberosCredentialsManager::OnValidateConfig,
+                              weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void KerberosCredentialsManager::OnValidateConfig(
+    ValidateConfigCallback callback,
+    const kerberos::ValidateConfigResponse& response) {
+  LogError("ValidateConfig", response.error());
+  std::move(callback).Run(std::move(response));
+}
+
 void KerberosCredentialsManager::AcquireKerberosTgt(std::string principal_name,
                                                     const std::string& password,
                                                     ResultCallback callback) {
diff --git a/chrome/browser/chromeos/kerberos/kerberos_credentials_manager.h b/chrome/browser/chromeos/kerberos/kerberos_credentials_manager.h
index 8f8458a..73294e1 100644
--- a/chrome/browser/chromeos/kerberos/kerberos_credentials_manager.h
+++ b/chrome/browser/chromeos/kerberos/kerberos_credentials_manager.h
@@ -37,6 +37,8 @@
   using ResultCallback = base::OnceCallback<void(kerberos::ErrorType)>;
   using ListAccountsCallback =
       base::OnceCallback<void(const kerberos::ListAccountsResponse&)>;
+  using ValidateConfigCallback =
+      base::OnceCallback<void(const kerberos::ValidateConfigResponse&)>;
 
   class Observer : public base::CheckedObserver {
    public:
@@ -121,6 +123,13 @@
                  const std::string& krb5_conf,
                  ResultCallback callback);
 
+  // Verifies that only whitelisted configuration options are used in the
+  // Kerberos configuration |krb5_conf|. The Kerberos daemon does not allow all
+  // options for security reasons. Also performs basic syntax checks. Returns
+  // useful error information.
+  void ValidateConfig(const std::string& krb5_conf,
+                      ValidateConfigCallback callback);
+
   // Gets a Kerberos ticket-granting-ticket for the account with given
   // |principal_name|.
   void AcquireKerberosTgt(std::string principal_name,
@@ -162,6 +171,10 @@
   void OnSetConfig(ResultCallback callback,
                    const kerberos::SetConfigResponse& response);
 
+  // Callback for ValidateConfig().
+  void OnValidateConfig(ValidateConfigCallback callback,
+                        const kerberos::ValidateConfigResponse& response);
+
   // Callback for AcquireKerberosTgt().
   void OnAcquireKerberosTgt(
       ResultCallback callback,
diff --git a/chrome/browser/chromeos/login/signin/oauth2_login_manager.cc b/chrome/browser/chromeos/login/signin/oauth2_login_manager.cc
index 0395ab16..0e07a3e4 100644
--- a/chrome/browser/chromeos/login/signin/oauth2_login_manager.cc
+++ b/chrome/browser/chromeos/login/signin/oauth2_login_manager.cc
@@ -12,12 +12,12 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/browser/signin/chrome_device_id_helper.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chromeos/components/account_manager/account_manager.h"
 #include "chromeos/components/account_manager/account_manager_factory.h"
 #include "chromeos/constants/chromeos_switches.h"
-#include "components/account_id/account_id.h"
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
index bff69a8..d6781fc 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
@@ -55,6 +55,7 @@
 #include "chrome/browser/ui/ash/ash_util.h"
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
 #include "chrome/browser/ui/ash/system_tray_client.h"
+#include "chrome/browser/ui/ash/wallpaper_controller_client.h"
 #include "chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/core_oobe_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
@@ -224,6 +225,12 @@
   if (!timezone.empty()) {
     chromeos::system::SetSystemAndSigninScreenTimezone(timezone);
   }
+
+  // This step requires the session manager to have been initialized and login
+  // display host to be created.
+  DCHECK(session_manager::SessionManager::Get());
+  DCHECK(chromeos::LoginDisplayHost::default_host());
+  WallpaperControllerClient::Get()->SetInitialWallpaper();
 }
 
 struct ShowLoginWizardSwitchLanguageCallbackData {
diff --git a/chrome/browser/chromeos/policy/system_log_uploader.cc b/chrome/browser/chromeos/policy/system_log_uploader.cc
index df74942..7f566d04 100644
--- a/chrome/browser/chromeos/policy/system_log_uploader.cc
+++ b/chrome/browser/chromeos/policy/system_log_uploader.cc
@@ -75,15 +75,8 @@
     return compressed_logs;
 
   for (const auto& syslog_entry : *system_logs) {
-    base::FilePath file_path(temp_dir.GetPath().Append(syslog_entry.first));
-    base::FilePath relative_path;
-    temp_dir.GetPath().AppendRelativePath(file_path, &relative_path);
-
-    if (!base::CreateDirectory(file_path.DirName())) {
-      PLOG(ERROR) << "Can't create directory for log file: "
-                  << file_path.value();
-      continue;
-    }
+    base::FilePath file_name = base::FilePath(syslog_entry.first).BaseName();
+    base::FilePath file_path(temp_dir.GetPath().Append(file_name));
     if (!base::WriteFile(file_path, syslog_entry.second.c_str(),
                          syslog_entry.second.size())) {
       PLOG(ERROR) << "Can't write log file: " << file_path.value();
diff --git a/chrome/browser/extensions/chrome_content_verifier_delegate.cc b/chrome/browser/extensions/chrome_content_verifier_delegate.cc
index 01edaab..0b923c3 100644
--- a/chrome/browser/extensions/chrome_content_verifier_delegate.cc
+++ b/chrome/browser/extensions/chrome_content_verifier_delegate.cc
@@ -45,8 +45,8 @@
 
 namespace {
 
-base::Optional<ContentVerifierDelegate::Mode>& GetModeForTesting() {
-  static base::NoDestructor<base::Optional<ContentVerifierDelegate::Mode>>
+base::Optional<ChromeContentVerifierDelegate::Mode>& GetModeForTesting() {
+  static base::NoDestructor<base::Optional<ChromeContentVerifierDelegate::Mode>>
       testing_mode;
   return *testing_mode;
 }
@@ -57,7 +57,8 @@
 }  // namespace
 
 // static
-ContentVerifierDelegate::Mode ChromeContentVerifierDelegate::GetDefaultMode() {
+ChromeContentVerifierDelegate::Mode
+ChromeContentVerifierDelegate::GetDefaultMode() {
   if (GetModeForTesting())
     return *GetModeForTesting();
 
@@ -65,20 +66,20 @@
 
   Mode experiment_value;
 #if defined(GOOGLE_CHROME_BUILD)
-  experiment_value = ContentVerifierDelegate::ENFORCE_STRICT;
+  experiment_value = ENFORCE_STRICT;
 #else
-  experiment_value = ContentVerifierDelegate::NONE;
+  experiment_value = NONE;
 #endif
   const std::string group =
       base::FieldTrialList::FindFullName(kContentVerificationExperimentName);
   if (group == "EnforceStrict")
-    experiment_value = ContentVerifierDelegate::ENFORCE_STRICT;
+    experiment_value = ENFORCE_STRICT;
   else if (group == "Enforce")
-    experiment_value = ContentVerifierDelegate::ENFORCE;
+    experiment_value = ENFORCE;
   else if (group == "Bootstrap")
-    experiment_value = ContentVerifierDelegate::BOOTSTRAP;
+    experiment_value = BOOTSTRAP;
   else if (group == "None")
-    experiment_value = ContentVerifierDelegate::NONE;
+    experiment_value = NONE;
 
   // The field trial value that normally comes from the server can be
   // overridden on the command line, which we don't want to allow since
@@ -91,7 +92,7 @@
         command_line->GetSwitchValueASCII(::switches::kForceFieldTrials);
     if (forced_trials.find(kContentVerificationExperimentName) !=
         std::string::npos)
-      experiment_value = ContentVerifierDelegate::ENFORCE_STRICT;
+      experiment_value = ChromeContentVerifierDelegate::ENFORCE_STRICT;
   }
 
   Mode cmdline_value = NONE;
@@ -99,15 +100,15 @@
     std::string switch_value = command_line->GetSwitchValueASCII(
         ::switches::kExtensionContentVerification);
     if (switch_value == ::switches::kExtensionContentVerificationBootstrap)
-      cmdline_value = ContentVerifierDelegate::BOOTSTRAP;
+      cmdline_value = BOOTSTRAP;
     else if (switch_value == ::switches::kExtensionContentVerificationEnforce)
-      cmdline_value = ContentVerifierDelegate::ENFORCE;
+      cmdline_value = ENFORCE;
     else if (switch_value ==
              ::switches::kExtensionContentVerificationEnforceStrict)
-      cmdline_value = ContentVerifierDelegate::ENFORCE_STRICT;
+      cmdline_value = ENFORCE_STRICT;
     else
       // If no value was provided (or the wrong one), just default to enforce.
-      cmdline_value = ContentVerifierDelegate::ENFORCE;
+      cmdline_value = ENFORCE;
   }
 
   // We don't want to allow the command-line flags to eg disable enforcement
@@ -134,32 +135,9 @@
 ChromeContentVerifierDelegate::~ChromeContentVerifierDelegate() {
 }
 
-ContentVerifierDelegate::Mode ChromeContentVerifierDelegate::ShouldBeVerified(
+bool ChromeContentVerifierDelegate::ShouldBeVerified(
     const Extension& extension) {
-#if defined(OS_CHROMEOS)
-  if (ExtensionAssetsManagerChromeOS::IsSharedInstall(&extension))
-    return ContentVerifierDelegate::ENFORCE_STRICT;
-#endif
-
-  if (!extension.is_extension() && !extension.is_legacy_packaged_app())
-    return ContentVerifierDelegate::NONE;
-  if (!Manifest::IsAutoUpdateableLocation(extension.location()))
-    return ContentVerifierDelegate::NONE;
-
-  // Use the InstallVerifier's |IsFromStore| method to avoid discrepancies
-  // between which extensions are considered in-store.
-  // See https://crbug.com/766806 for details.
-  if (!InstallVerifier::IsFromStore(extension)) {
-    // It's possible that the webstore update url was overridden for testing
-    // so also consider extensions with the default (production) update url
-    // to be from the store as well.
-    if (ManifestURL::GetUpdateURL(&extension) !=
-        extension_urls::GetDefaultWebstoreUpdateUrl()) {
-      return ContentVerifierDelegate::NONE;
-    }
-  }
-
-  return default_mode_;
+  return GetVerifyMode(extension) != NONE;
 }
 
 ContentVerifierKey ChromeContentVerifierDelegate::GetPublicKey() {
@@ -200,11 +178,11 @@
   if (!extension)
     return;
 
-  Mode mode = ShouldBeVerified(*extension);
+  Mode mode = GetVerifyMode(*extension);
   // If the failure was due to hashes missing, only "enforce_strict" would
   // disable the extension, but not "enforce".
   if (reason == ContentVerifyJob::MISSING_ALL_HASHES &&
-      mode != ContentVerifierDelegate::ENFORCE_STRICT) {
+      mode != ENFORCE_STRICT) {
     return;
   }
 
@@ -215,7 +193,7 @@
     return;
   }
   ExtensionService* service = system->extension_service();
-  if (mode >= ContentVerifierDelegate::ENFORCE) {
+  if (mode >= ENFORCE) {
     if (system->management_policy()->ShouldRepairIfCorrupted(extension)) {
       PendingExtensionManager* pending_manager =
           service->pending_extension_manager();
@@ -254,4 +232,32 @@
   policy_extension_reinstaller_.reset();
 }
 
+ChromeContentVerifierDelegate::Mode
+ChromeContentVerifierDelegate::GetVerifyMode(const Extension& extension) {
+#if defined(OS_CHROMEOS)
+  if (ExtensionAssetsManagerChromeOS::IsSharedInstall(&extension))
+    return ENFORCE_STRICT;
+#endif
+
+  if (!extension.is_extension() && !extension.is_legacy_packaged_app())
+    return NONE;
+  if (!Manifest::IsAutoUpdateableLocation(extension.location()))
+    return NONE;
+
+  // Use the InstallVerifier's |IsFromStore| method to avoid discrepancies
+  // between which extensions are considered in-store.
+  // See https://crbug.com/766806 for details.
+  if (!InstallVerifier::IsFromStore(extension)) {
+    // It's possible that the webstore update url was overridden for testing
+    // so also consider extensions with the default (production) update url
+    // to be from the store as well.
+    if (ManifestURL::GetUpdateURL(&extension) !=
+        extension_urls::GetDefaultWebstoreUpdateUrl()) {
+      return NONE;
+    }
+  }
+
+  return default_mode_;
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/chrome_content_verifier_delegate.h b/chrome/browser/extensions/chrome_content_verifier_delegate.h
index 9a08eee..703530ee 100644
--- a/chrome/browser/extensions/chrome_content_verifier_delegate.h
+++ b/chrome/browser/extensions/chrome_content_verifier_delegate.h
@@ -28,6 +28,25 @@
 
 class ChromeContentVerifierDelegate : public ContentVerifierDelegate {
  public:
+  // Note that it is important for these to appear in increasing "severity"
+  // order, because we use this to let command line flags increase, but not
+  // decrease, the mode you're running in compared to the experiment group.
+  enum Mode {
+    // Do not try to fetch content hashes if they are missing, and do not
+    // enforce them if they are present.
+    NONE = 0,
+
+    // If content hashes are missing, try to fetch them, but do not enforce.
+    BOOTSTRAP,
+
+    // If hashes are present, enforce them. If they are missing, try to fetch
+    // them.
+    ENFORCE,
+
+    // Treat the absence of hashes the same as a verification failure.
+    ENFORCE_STRICT
+  };
+
   static Mode GetDefaultMode();
   static void SetDefaultModeForTesting(base::Optional<Mode> mode);
 
@@ -36,7 +55,7 @@
   ~ChromeContentVerifierDelegate() override;
 
   // ContentVerifierDelegate:
-  Mode ShouldBeVerified(const Extension& extension) override;
+  bool ShouldBeVerified(const Extension& extension) override;
   ContentVerifierKey GetPublicKey() override;
   GURL GetSignatureFetchUrl(const std::string& extension_id,
                             const base::Version& version) override;
@@ -47,8 +66,12 @@
   void Shutdown() override;
 
  private:
+  // Returns what verification mode is appropriate for the given extension, or
+  // NONE if no verification is needed.
+  Mode GetVerifyMode(const Extension& extension);
+
   content::BrowserContext* context_;
-  ContentVerifierDelegate::Mode default_mode_;
+  Mode default_mode_;
 
   // This maps an extension id to a backoff entry for slowing down
   // redownload/reinstall of corrupt policy extensions if it keeps happening
diff --git a/chrome/browser/extensions/content_verifier_browsertest.cc b/chrome/browser/extensions/content_verifier_browsertest.cc
index 1f92505..0100f08 100644
--- a/chrome/browser/extensions/content_verifier_browsertest.cc
+++ b/chrome/browser/extensions/content_verifier_browsertest.cc
@@ -61,7 +61,7 @@
     // Override content verification mode before ExtensionSystemImpl initializes
     // ChromeContentVerifierDelegate.
     ChromeContentVerifierDelegate::SetDefaultModeForTesting(
-        ContentVerifierDelegate::ENFORCE);
+        ChromeContentVerifierDelegate::ENFORCE);
 
     ExtensionBrowserTest::SetUp();
   }
diff --git a/chrome/browser/extensions/content_verifier_hash_fetch_behavior_browsertest.cc b/chrome/browser/extensions/content_verifier_hash_fetch_behavior_browsertest.cc
index f149d1d..a229c22 100644
--- a/chrome/browser/extensions/content_verifier_hash_fetch_behavior_browsertest.cc
+++ b/chrome/browser/extensions/content_verifier_hash_fetch_behavior_browsertest.cc
@@ -62,8 +62,9 @@
     // Override content verification mode before ExtensionSystemImpl initializes
     // ChromeContentVerifierDelegate.
     ChromeContentVerifierDelegate::SetDefaultModeForTesting(
-        uses_enforce_strict_mode() ? ContentVerifierDelegate::ENFORCE_STRICT
-                                   : ContentVerifierDelegate::ENFORCE);
+        uses_enforce_strict_mode()
+            ? ChromeContentVerifierDelegate::ENFORCE_STRICT
+            : ChromeContentVerifierDelegate::ENFORCE);
 
     ExtensionBrowserTest::SetUp();
   }
diff --git a/chrome/browser/extensions/extension_system_impl.cc b/chrome/browser/extensions/extension_system_impl.cc
index 7fcf964..0ee6aea5 100644
--- a/chrome/browser/extensions/extension_system_impl.cc
+++ b/chrome/browser/extensions/extension_system_impl.cc
@@ -224,12 +224,12 @@
   // load any extensions.
   {
     InstallVerifier::Get(profile_)->Init();
-    ContentVerifierDelegate::Mode mode =
+    ChromeContentVerifierDelegate::Mode mode =
         ChromeContentVerifierDelegate::GetDefaultMode();
 #if defined(OS_CHROMEOS)
-    mode = std::max(mode, ContentVerifierDelegate::BOOTSTRAP);
+    mode = std::max(mode, ChromeContentVerifierDelegate::BOOTSTRAP);
 #endif  // defined(OS_CHROMEOS)
-    if (mode >= ContentVerifierDelegate::BOOTSTRAP)
+    if (mode >= ChromeContentVerifierDelegate::BOOTSTRAP)
       content_verifier_->Start();
     info_map()->SetContentVerifier(content_verifier_.get());
 #if defined(OS_CHROMEOS)
diff --git a/chrome/browser/extensions/test_extension_system.cc b/chrome/browser/extensions/test_extension_system.cc
index eeec53c..a74ca234 100644
--- a/chrome/browser/extensions/test_extension_system.cc
+++ b/chrome/browser/extensions/test_extension_system.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/prefs/pref_service.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "components/services/unzip/unzip_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/service_manager_connection.h"
diff --git a/chrome/browser/extensions/zipfile_installer_unittest.cc b/chrome/browser/extensions/zipfile_installer_unittest.cc
index b43d278c..6a4c032 100644
--- a/chrome/browser/extensions/zipfile_installer_unittest.cc
+++ b/chrome/browser/extensions/zipfile_installer_unittest.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "components/services/unzip/unzip_service.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 9edb4d9..d91e6dc0 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1606,11 +1606,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-send-tab-to-self-history",
-    "owners": [ "//components/send_tab_to_self/OWNERS" ],
-    "expiry_milestone": 78
-  },
-  {
     "name": "enable-send-tab-to-self-show-sending-ui",
     "owners": [ "//components/send_tab_to_self/OWNERS" ],
     "expiry_milestone": 77
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index f20eaa23..d4a69a5 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1657,12 +1657,6 @@
     "Allows users to broadcast the tab they send to all of their devices "
     "instead of targetting only one device.";
 
-const char kSendTabToSelfHistoryName[] = "Send tab to self history";
-const char kSendTabToSelfHistoryDescription[] =
-    "Allows users to view tabs that were sent to other synced devices by "
-    "accessing these tabs through a landing page either in history or in "
-    "recent tabs. Requires Send tab to self to also be enabled";
-
 const char kSendTabToSelfShowSendingUIName[] =
     "Send tab to self show sending UI";
 const char kSendTabToSelfShowSendingUIDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 59d5d93..2a232c4 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -987,9 +987,6 @@
 extern const char kSendTabToSelfBroadcastName[];
 extern const char kSendTabToSelfBroadcastDescription[];
 
-extern const char kSendTabToSelfHistoryName[];
-extern const char kSendTabToSelfHistoryDescription[];
-
 extern const char kSendTabToSelfShowSendingUIName[];
 extern const char kSendTabToSelfShowSendingUIDescription[];
 
diff --git a/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc b/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
index 94039d5..c8897585 100644
--- a/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_disable_encryption_flag_browsertest.cc
@@ -49,8 +49,9 @@
 };
 
 // Makes a call and checks that there's encryption or not in the SDP offer.
-// TODO(crbub.com/910216): De-flake this for ChromeOs.
-#if defined(OS_CHROMEOS)
+// TODO(crbug.com/910216): De-flake this for ChromeOs.
+// TODO(crbug.com/984879): De-flake this for MSAN Linux.
+#if defined(OS_CHROMEOS) || (defined(OS_LINUX) && defined(MEMORY_SANITIZER))
 #define MAYBE_VerifyEncryption DISABLED_VerifyEncryption
 #else
 #define MAYBE_VerifyEncryption VerifyEncryption
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
index f0bc77c1..6d047a3 100644
--- a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
@@ -574,6 +574,7 @@
     const url::Origin& origin,
     int process_id,
     int frame_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto origin_it = origins_.find(origin);
   if (origin_it == origins_.end()) {
     // No grants for origin, return directly.
@@ -593,6 +594,57 @@
   origin_state.directory_read_grants.erase(grant_it);
 }
 
+void ChromeNativeFileSystemPermissionContext::RevokeWriteGrants(
+    const url::Origin& origin,
+    int process_id,
+    int frame_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto origin_it = origins_.find(origin);
+  if (origin_it == origins_.end()) {
+    // No grants for origin, return directly.
+    return;
+  }
+  OriginState& origin_state = origin_it->second;
+  // Grants are ordered first by process_id and frame_id, and only within that
+  // by path. As a result lower_bound on a key with an empty path returns the
+  // first grant to remove (if any), and all other grants to remove are
+  // immediately after it.
+  auto grant_it = origin_state.grants.lower_bound(
+      PermissionGrantImpl::Key{base::FilePath(), process_id, frame_id});
+  while (grant_it != origin_state.grants.end() &&
+         grant_it->first.process_id == process_id &&
+         grant_it->first.frame_id == frame_id) {
+    grant_it->second->SetStatus(PermissionStatus::ASK);
+    ++grant_it;
+  }
+}
+
+// static
+void ChromeNativeFileSystemPermissionContext::
+    RevokeGrantsForOriginAndTabFromUIThread(
+        content::BrowserContext* browser_context,
+        const url::Origin& origin,
+        int process_id,
+        int frame_id) {
+  auto permission_context =
+      NativeFileSystemPermissionContextFactory::GetForProfileIfExists(
+          browser_context);
+  if (!permission_context) {
+    // With no context there is nothing to revoke.
+    return;
+  }
+  base::PostTask(
+      FROM_HERE, {content::BrowserThread::IO},
+      base::BindOnce(
+          [](const scoped_refptr<ChromeNativeFileSystemPermissionContext>&
+                 context,
+             const url::Origin& origin, int process_id, int frame_id) {
+            context->RevokeDirectoryReadGrants(origin, process_id, frame_id);
+            context->RevokeWriteGrants(origin, process_id, frame_id);
+          },
+          std::move(permission_context), origin, process_id, frame_id));
+}
+
 void ChromeNativeFileSystemPermissionContext::ShutdownOnUIThread() {}
 
 ChromeNativeFileSystemPermissionContext::
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
index 951dd97..c9e824c 100644
--- a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
@@ -95,6 +95,18 @@
   void RevokeDirectoryReadGrants(const url::Origin& origin,
                                  int process_id,
                                  int frame_id);
+  // Revokes write access for the given origin in the given tab.
+  void RevokeWriteGrants(const url::Origin& origin,
+                         int process_id,
+                         int frame_id);
+
+  // Revokes write access and directory read access for the given origin in the
+  // given tab.
+  static void RevokeGrantsForOriginAndTabFromUIThread(
+      content::BrowserContext* browser_context,
+      const url::Origin& origin,
+      int process_id,
+      int frame_id);
 
   // RefcountedKeyedService:
   void ShutdownOnUIThread() override;
diff --git a/chrome/browser/password_manager/password_store_factory.cc b/chrome/browser/password_manager/password_store_factory.cc
index 00e8d40a..bd13e93f 100644
--- a/chrome/browser/password_manager/password_store_factory.cc
+++ b/chrome/browser/password_manager/password_store_factory.cc
@@ -303,7 +303,7 @@
     (defined(OS_LINUX) && !defined(OS_CHROMEOS))
   std::unique_ptr<password_manager::PasswordStoreSigninNotifier> notifier =
       std::make_unique<password_manager::PasswordStoreSigninNotifierImpl>(
-          profile);
+          IdentityManagerFactory::GetForProfile(profile));
   ps->SetPasswordStoreSigninNotifier(std::move(notifier));
 #endif
 
diff --git a/chrome/browser/password_manager/password_store_signin_notifier_impl.cc b/chrome/browser/password_manager/password_store_signin_notifier_impl.cc
index 0c39326..d6c1a45c 100644
--- a/chrome/browser/password_manager/password_store_signin_notifier_impl.cc
+++ b/chrome/browser/password_manager/password_store_signin_notifier_impl.cc
@@ -4,16 +4,14 @@
 
 #include "chrome/browser/password_manager/password_store_signin_notifier_impl.h"
 
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
 namespace password_manager {
 
 PasswordStoreSigninNotifierImpl::PasswordStoreSigninNotifierImpl(
-    Profile* profile)
-    : profile_(profile) {
-  DCHECK(profile);
+    identity::IdentityManager* identity_manager)
+    : identity_manager_(identity_manager) {
+  DCHECK(identity_manager_);
 }
 
 PasswordStoreSigninNotifierImpl::~PasswordStoreSigninNotifierImpl() {}
@@ -21,11 +19,11 @@
 void PasswordStoreSigninNotifierImpl::SubscribeToSigninEvents(
     PasswordStore* store) {
   set_store(store);
-  IdentityManagerFactory::GetForProfile(profile_)->AddObserver(this);
+  identity_manager_->AddObserver(this);
 }
 
 void PasswordStoreSigninNotifierImpl::UnsubscribeFromSigninEvents() {
-  IdentityManagerFactory::GetForProfile(profile_)->RemoveObserver(this);
+  identity_manager_->RemoveObserver(this);
 }
 
 void PasswordStoreSigninNotifierImpl::OnPrimaryAccountCleared(
@@ -37,8 +35,7 @@
 void PasswordStoreSigninNotifierImpl::OnExtendedAccountInfoRemoved(
     const AccountInfo& info) {
   // Only reacts to content area (non-primary) Gaia account sign-out event.
-  if (info.account_id !=
-      IdentityManagerFactory::GetForProfile(profile_)->GetPrimaryAccountId()) {
+  if (info.account_id != identity_manager_->GetPrimaryAccountId()) {
     NotifySignedOut(info.email, /* primary_account= */ false);
   }
 }
diff --git a/chrome/browser/password_manager/password_store_signin_notifier_impl.h b/chrome/browser/password_manager/password_store_signin_notifier_impl.h
index 1dcb7e7d..a430019 100644
--- a/chrome/browser/password_manager/password_store_signin_notifier_impl.h
+++ b/chrome/browser/password_manager/password_store_signin_notifier_impl.h
@@ -6,12 +6,9 @@
 #define CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_SIGNIN_NOTIFIER_IMPL_H_
 
 #include "base/macros.h"
-
 #include "components/password_manager/core/browser/password_store_signin_notifier.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
-class Profile;
-
 namespace password_manager {
 
 // Responsible for subscribing to Chrome sign-in events and passing them to
@@ -20,7 +17,8 @@
     : public PasswordStoreSigninNotifier,
       public identity::IdentityManager::Observer {
  public:
-  explicit PasswordStoreSigninNotifierImpl(Profile* profile);
+  explicit PasswordStoreSigninNotifierImpl(
+      identity::IdentityManager* identity_manager);
   ~PasswordStoreSigninNotifierImpl() override;
 
   // PasswordStoreSigninNotifier implementations.
@@ -32,7 +30,7 @@
   void OnExtendedAccountInfoRemoved(const AccountInfo& info) override;
 
  private:
-  Profile* const profile_;
+  identity::IdentityManager* identity_manager_ = nullptr;
 };
 
 }  // namespace password_manager
diff --git a/chrome/browser/password_manager/password_store_signin_notifier_impl_unittest.cc b/chrome/browser/password_manager/password_store_signin_notifier_impl_unittest.cc
index 61feeee..c5e6407 100644
--- a/chrome/browser/password_manager/password_store_signin_notifier_impl_unittest.cc
+++ b/chrome/browser/password_manager/password_store_signin_notifier_impl_unittest.cc
@@ -5,12 +5,11 @@
 #include "chrome/browser/password_manager/password_store_signin_notifier_impl.h"
 
 #include "base/bind.h"
-#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
-#include "chrome/test/base/testing_profile.h"
+#include "base/test/scoped_task_environment.h"
 #include "components/password_manager/core/browser/mock_password_store.h"
 #include "components/signin/public/identity_manager/accounts_mutator.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
-#include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::_;
@@ -21,12 +20,6 @@
 class PasswordStoreSigninNotifierImplTest : public testing::Test {
  public:
   PasswordStoreSigninNotifierImplTest() {
-    testing_profile_ = IdentityTestEnvironmentProfileAdaptor::
-        CreateProfileForIdentityTestEnvironment();
-
-    identity_test_env_adaptor_ =
-        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(
-            testing_profile_.get());
     store_ = new MockPasswordStore();
   }
 
@@ -35,21 +28,23 @@
   }
 
   identity::IdentityTestEnvironment* identity_test_env() {
-    return identity_test_env_adaptor_->identity_test_env();
+    return &identity_test_env_;
+  }
+
+  identity::IdentityManager* identity_manager() {
+    return identity_test_env()->identity_manager();
   }
 
  protected:
-  content::TestBrowserThreadBundle thread_bundle;
-  std::unique_ptr<TestingProfile> testing_profile_;
-  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
-      identity_test_env_adaptor_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  identity::IdentityTestEnvironment identity_test_env_;
   scoped_refptr<MockPasswordStore> store_;
 };
 
 // Checks that if a notifier is subscribed on sign-in events, then
 // a password store receives sign-in notifications.
 TEST_F(PasswordStoreSigninNotifierImplTest, Subscribed) {
-  PasswordStoreSigninNotifierImpl notifier(testing_profile_.get());
+  PasswordStoreSigninNotifierImpl notifier(identity_manager());
   notifier.SubscribeToSigninEvents(store_.get());
   identity_test_env()->MakePrimaryAccountAvailable("test@example.com");
   testing::Mock::VerifyAndClearExpectations(store_.get());
@@ -61,7 +56,7 @@
 // Checks that if a notifier is unsubscribed on sign-in events, then
 // a password store receives no sign-in notifications.
 TEST_F(PasswordStoreSigninNotifierImplTest, Unsubscribed) {
-  PasswordStoreSigninNotifierImpl notifier(testing_profile_.get());
+  PasswordStoreSigninNotifierImpl notifier(identity_manager());
   notifier.SubscribeToSigninEvents(store_.get());
   notifier.UnsubscribeFromSigninEvents();
   EXPECT_CALL(*store_, ClearAllGaiaPasswordHash()).Times(0);
@@ -72,7 +67,7 @@
 // Checks that if a notifier is unsubscribed on sign-in events, then
 // a password store receives no sign-in notifications.
 TEST_F(PasswordStoreSigninNotifierImplTest, SignOutContentArea) {
-  PasswordStoreSigninNotifierImpl notifier(testing_profile_.get());
+  PasswordStoreSigninNotifierImpl notifier(identity_manager());
   notifier.SubscribeToSigninEvents(store_.get());
 
   identity_test_env()->MakePrimaryAccountAvailable("username");
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.cc b/chrome/browser/policy/cloud/user_policy_signin_service.cc
index ed1b957..78d29c4 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.cc
@@ -14,8 +14,8 @@
 #include "chrome/browser/policy/cloud/user_policy_signin_service_internal.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/browser/signin/signin_util.h"
-#include "components/account_id/account_id.h"
 #include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
 #include "components/signin/public/identity_manager/account_info.h"
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_base.cc b/chrome/browser/policy/cloud/user_policy_signin_service_base.cc
index e3e46008..fdf5c8f4 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_base.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_base.cc
@@ -13,8 +13,8 @@
 #include "build/build_config.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/common/chrome_content_client.h"
-#include "components/account_id/account_id.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
diff --git a/chrome/browser/resources/chromeos/BUILD.gn b/chrome/browser/resources/chromeos/BUILD.gn
index be07fb1a..72b6c86 100644
--- a/chrome/browser/resources/chromeos/BUILD.gn
+++ b/chrome/browser/resources/chromeos/BUILD.gn
@@ -34,6 +34,31 @@
   output_dir = "$root_gen_dir/chrome"
 }
 
+grit("camera_resources") {
+  source = "camera/camera_resources.grd"
+
+  defines = chrome_grit_defines
+  outputs = [
+    "grit/camera_resources.h",
+    "grit/camera_resources_map.cc",
+    "grit/camera_resources_map.h",
+    "camera_resources.pak",
+  ]
+  output_dir = "$root_gen_dir/chrome"
+
+  deps = [
+    "//media/capture/video/chromeos/mojo:cros_camera_js",
+  ]
+
+  # The .grd contains references to generated files.
+  source_is_generated = true
+
+  grit_flags = [
+    "-E",
+    "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
+  ]
+}
+
 group("closure_compile") {
   deps = [
     "add_supervision:closure_compile",
diff --git a/chrome/browser/resources/chromeos/camera/camera_resources.grd b/chrome/browser/resources/chromeos/camera/camera_resources.grd
new file mode 100644
index 0000000..c9d88fa
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/camera_resources.grd
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+  <outputs>
+    <output filename="grit/camera_resources.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <output filename="grit/camera_resources_map.cc"
+            type="resource_file_map_source" />
+    <output filename="grit/camera_resources_map.h"
+            type="resource_map_header" />
+    <output filename="camera_resources.pak" type="data_package" />
+  </outputs>
+  <release seq="1">
+    <structures>
+      <structure name="IDR_CAMERA_BACKGROUND_JS" file="src/js/background.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_BROWSER_JS" file="src/js/views/browser.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_BUNDLE_JS" file="src/js/google-analytics-bundle.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_CAMERA_JS" file="src/js/views/camera.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_DIALOG_JS" file="src/js/views/dialog.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_FILENAMER_JS" file="src/js/models/filenamer.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_FILESYSTEM_JS" file="src/js/models/filesystem.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_GALLERY_BASE_JS" file="src/js/views/gallery_base.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_GALLERY_JS" file="src/js/models/gallery.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_GALLERYBUTTON_JS" file="src/js/gallerybutton.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_IMAGECAPTURE_JS" file="src/js/mojo/imagecapture.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_LAYOUT_JS" file="src/js/views/camera/layout.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_MAIN_CSS" file="src/css/main.css" type="chrome_html" />
+      <structure name="IDR_CAMERA_MAIN_HTML" file="src/views/main.html" type="chrome_html" />
+      <structure name="IDR_CAMERA_MAIN_JS" file="src/js/main.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_METRICS_JS" file="src/js/metrics.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_MODES_JS" file="src/js/views/camera/modes.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_CONSTRAINTS_PREFERRER_JS" file="src/js/views/camera/constraints_preferrer.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_NAV_JS" file="src/js/nav.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_OPTIONS_JS" file="src/js/views/camera/options.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_PREVIEW_JS" file="src/js/views/camera/preview.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_RECORDTIME_JS" file="src/js/views/camera/recordtime.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_RESOLUTION_EVENT_BROKER_JS" file="src/js/resolution_event_broker.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_SCROLLBAR_JS" file="src/js/scrollbar.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_SETTINGS_JS" file="src/js/views/settings.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_SOUND_JS" file="src/js/sound.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_STATE_JS" file="src/js/state.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_TIMERTICK_JS" file="src/js/views/camera/timertick.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_TOAST_JS" file="src/js/toast.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_TOOLTIP_JS" file="src/js/tooltip.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_UTIL_JS" file="src/js/util.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_VIEW_JS" file="src/js/views/view.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_WARNING_JS" file="src/js/views/warning.js" type="chrome_html" />
+      <structure name="IDR_CAMERA_WEBUI_BROWSER_PROXY" file="src/js/browser_proxy/webui_browser_proxy.js" type="chrome_html" />
+
+    </structures>
+    <includes>
+      <!-- Mojo Lite Bindings -->
+      <include name="IDR_CAMERA_IMAGE_CAPTURE_MOJOM_LITE_JS"
+          file="${root_gen_dir}/media/capture/mojom/image_capture.mojom-lite.js"
+          use_base_dir="false"
+          type="BINDATA"/>
+      <include name="IDR_CAMERA_CAMERA_COMMON_MOJOM_LITE_JS"
+          file="${root_gen_dir}/media/capture/video/chromeos/mojo/camera_common.mojom-lite.js"
+          use_base_dir="false"
+          type="BINDATA"/>
+      <include name="IDR_CAMERA_CAMERA_METADATA_MOJOM_LITE_JS"
+          file="${root_gen_dir}/media/capture/video/chromeos/mojo/camera_metadata.mojom-lite.js"
+          use_base_dir="false"
+          type="BINDATA"/>
+      <include name="IDR_CAMERA_CAMERA_METADATA_TAGS_MOJOM_LITE_JS"
+          file="${root_gen_dir}/media/capture/video/chromeos/mojo/camera_metadata_tags.mojom-lite.js"
+          use_base_dir="false"
+          type="BINDATA"/>
+      <include name="IDR_CAMERA_CROS_IMAGE_CAPTURE_MOJOM_LITE_JS"
+          file="${root_gen_dir}/media/capture/video/chromeos/mojo/cros_image_capture.mojom-lite.js"
+          use_base_dir="false"
+          type="BINDATA"/>
+
+      <include name="IDR_CAMERA_RECORD_END_OGG" file="src/sounds/record_end.ogg" type="BINDATA" />
+      <include name="IDR_CAMERA_RECORD_START_OGG" file="src/sounds/record_start.ogg" type="BINDATA" />
+      <include name="IDR_CAMERA_SHUTTER_OGG" file="src/sounds/shutter.ogg" type="BINDATA" />
+      <include name="IDR_CAMERA_TICK_OGG" file="src/sounds/tick.ogg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_MODE_SQUARE_SVG" file="src/images/camera_mode_square.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_SETTINGS_BUTTON_BACK_SVG" file="src/images/settings_button_back.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_MODE_PORTRAIT_SVG" file="src/images/camera_mode_portrait.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_TIMER_ON_3S_SVG" file="src/images/camera_button_timer_on_3s.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_VIDEO_START_HOVER_SVG" file="src/images/camera_shutter_video_start_hover.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_SPINNER_SVG" file="src/images/spinner.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_INTRO_BANNER_ICON_SVG" file="src/images/camera_intro_banner_icon.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_GRID_OFF_SVG" file="src/images/camera_button_grid_off.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_MIC_ON_SVG" file="src/images/camera_button_mic_on.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_BROWSER_BUTTON_DELETE_SVG" file="src/images/browser_button_delete.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_MODE_VIDEO_SVG" file="src/images/camera_mode_video.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_PHOTO_STOP_SVG" file="src/images/camera_shutter_photo_stop.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_MIC_OFF_SVG" file="src/images/camera_button_mic_off.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_VIDEO_STOP_HOVER_SVG" file="src/images/camera_shutter_video_stop_hover.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_SETTINGS_RESOLUTION_SVG" file="src/images/settings_resolution.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_BROWSER_BUTTON_PRINT_SVG" file="src/images/browser_button_print.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_APP_ICONS_48_PNG" file="src/images/camera_app_icons_48.png" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_TIMER_OFF_SVG" file="src/images/camera_button_timer_off.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_PHOTO_START_ACTIVE_SVG" file="src/images/camera_shutter_photo_start_active.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_APP_ICONS_128_PNG" file="src/images/camera_app_icons_128.png" type="BINDATA" />
+      <include name="IDR_CAMERA_SETTINGS_TIMER_DURATION_SVG" file="src/images/settings_timer_duration.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_SETTINGS_HELP_SVG" file="src/images/settings_help.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_MIRROR_ON_SVG" file="src/images/camera_button_mirror_on.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_SETTINGS_BUTTON_EXPAND_SVG" file="src/images/settings_button_expand.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_GRID_ON_SVG" file="src/images/camera_button_grid_on.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_SWITCH_DEVICE_SVG" file="src/images/camera_button_switch_device.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_PHOTO_STOP_HOVER_SVG" file="src/images/camera_shutter_photo_stop_hover.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_SETTINGS_GRID_TYPE_SVG" file="src/images/settings_grid_type.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_MIRROR_OFF_SVG" file="src/images/camera_button_mirror_off.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_BROWSER_BUTTON_EXPORT_SVG" file="src/images/browser_button_export.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_INTRO_BANNER_CLOSE_SVG" file="src/images/camera_intro_banner_close.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_PHOTO_START_SVG" file="src/images/camera_shutter_photo_start.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_BROWSER_BUTTON_BACK_SVG" file="src/images/browser_button_back.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_VIDEO_STOP_SVG" file="src/images/camera_shutter_video_stop.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_SWITCH_PHOTO_SVG" file="src/images/camera_button_switch_photo.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_SETTINGS_FEEDBACK_SVG" file="src/images/settings_feedback.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_SWITCH_VIDEO_SVG" file="src/images/camera_button_switch_video.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_PHOTO_START_HOVER_SVG" file="src/images/camera_shutter_photo_start_hover.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_TIMER_ON_10S_SVG" file="src/images/camera_button_timer_on_10s.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_SHUTTER_VIDEO_START_SVG" file="src/images/camera_shutter_video_start.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_MODE_PHOTO_SVG" file="src/images/camera_mode_photo.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_FOCUS_AIM_SVG" file="src/images/camera_focus_aim.svg" type="BINDATA" />
+      <include name="IDR_CAMERA_CAMERA_BUTTON_SETTINGS_SVG" file="src/images/camera_button_settings.svg" type="BINDATA" />
+    </includes>
+  </release>
+</grit>
diff --git a/chrome/browser/resources/settings/people_page/kerberos_accounts.html b/chrome/browser/resources/settings/people_page/kerberos_accounts.html
index 09702e2..da29870c 100644
--- a/chrome/browser/resources/settings/people_page/kerberos_accounts.html
+++ b/chrome/browser/resources/settings/people_page/kerberos_accounts.html
@@ -133,8 +133,8 @@
           </cr-button>
 
           <template is="dom-if" if="[[item.isManaged]]">
-            <cr-policy-indicator class="account-toolbar account-policy-indicator"
-                indicator-type="userPolicy">
+            <cr-policy-indicator indicator-type="userPolicy"
+                class="account-toolbar account-policy-indicator">
             </cr-policy-indicator>
           </template>
 
diff --git a/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.js b/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.js
index b33aab6..89d27cd 100644
--- a/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.js
+++ b/chrome/browser/resources/settings/people_page/kerberos_accounts_browser_proxy.js
@@ -24,6 +24,17 @@
  */
 settings.KerberosAccount;
 
+/**
+ * @typedef {{
+ *   error: !settings.KerberosErrorType,
+ *   errorInfo: !{
+ *     code: !settings.KerberosConfigErrorCode,
+ *     lineIndex: (number|undefined)
+ *   }
+ * }}
+ */
+settings.ValidateKerberosConfigResult;
+
 cr.define('settings', function() {
   /**
    *  @enum {number}
@@ -50,6 +61,26 @@
     kDuplicatePrincipalName: 16,
     kInProgress: 17,
     kParsePrincipalFailed: 18,
+    kBadConfig: 19,
+    kJailFailure: 20,
+  };
+
+  /**
+   *  @enum {number}
+   *  Error codes for config validation.
+   *  These values must be kept in sync with the KerberosConfigErrorCode enum in
+   *  third_party/cros_system_api/dbus/kerberos/kerberos_service.proto.
+   */
+  const KerberosConfigErrorCode = {
+    kNone: 0,
+    kSectionNestedInGroup: 1,
+    kSectionSyntax: 2,
+    kExpectedOpeningCurlyBrace: 3,
+    kExtraCurlyBrace: 4,
+    kRelationSyntax: 5,
+    kKeyNotSupported: 6,
+    kSectionNotSupported: 7,
+    kKrb5FailedToParse: 8,
   };
 
   /** @interface */
@@ -81,6 +112,14 @@
     removeAccount(account) {}
 
     /**
+     * Validates |krb5conf| by making sure that it does not contain syntax
+     *     errors or disallowed configuration options.
+     * @param {string} krb5Conf Kerberos configuration data (krb5.conf)
+     * @return {!Promise<!settings.ValidateKerberosConfigResult>}
+     */
+    validateConfig(krb5Conf) {}
+
+    /**
      * Sets |account| as currently active account. Kerberos credentials are
      * consumed from this account.
      * @param {!settings.KerberosAccount} account
@@ -111,6 +150,11 @@
     }
 
     /** @override */
+    validateConfig(krb5conf) {
+      return cr.sendWithPromise('validateKerberosConfig', krb5conf);
+    }
+
+    /** @override */
     setAsActiveAccount(account) {
       chrome.send('setAsActiveKerberosAccount', [account.principalName]);
     }
@@ -120,6 +164,7 @@
 
   return {
     KerberosErrorType: KerberosErrorType,
+    KerberosConfigErrorCode: KerberosConfigErrorCode,
     KerberosAccountsBrowserProxy: KerberosAccountsBrowserProxy,
     KerberosAccountsBrowserProxyImpl: KerberosAccountsBrowserProxyImpl,
   };
diff --git a/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.html b/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.html
index 8bb189b..b6f3fd7 100644
--- a/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.html
+++ b/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.html
@@ -33,7 +33,26 @@
       }
 
       #general-error-container {
-        height: 48px;
+        height: 56px;
+      }
+
+      #config-error-container {
+        height: 40px;
+        margin-top: 16px;
+      }
+
+      #general-error-message,
+      #config-error-message {
+        color: var(--settings-error-color);
+      }
+
+      .inner-error-container {
+        display: flex;
+      }
+
+      iron-icon[icon='cr:error'] {
+        fill: var(--settings-error-color);
+        margin-inline-end: 8px;
       }
 
       #rememberPasswordContainer {
@@ -56,8 +75,9 @@
         </h2>
 
         <div id="general-error-container">
-          <div hidden="[[!showError_(generalErrorText_)]]">
-            <iron-icon id="general-error-icon" icon="cr:warning"></iron-icon>
+          <div class="inner-error-container"
+              hidden="[[!showError_(generalErrorText_)]]">
+            <iron-icon id="error-icon" icon="cr:error"></iron-icon>
             <div id="general-error-message">[[generalErrorText_]]</div>
           </div>
         </div>
@@ -122,6 +142,14 @@
           <settings-textarea id="config" value="{{editableConfig_}}" rows=12
               spellcheck="false" disabled="[[isManaged_]]">
           </settings-textarea>
+
+          <div id="config-error-container">
+            <div class="inner-error-container"
+                hidden="[[!showError_(configErrorText_)]]">
+              <iron-icon id="error-icon" icon="cr:error"></iron-icon>
+              <div id="config-error-message">[[configErrorText_]]</div>
+            </div>
+          </div>
         </div>
 
         <div slot="button-container">
@@ -129,7 +157,8 @@
               on-click="onAdvancedConfigCancel_">
             $i18n{cancel}
           </cr-button>
-          <cr-button class="action-button" on-click="onAdvancedConfigSave_">
+          <cr-button class="action-button" on-click="onAdvancedConfigSave_"
+              disabled="[[inProgress_]]">
             $i18n{save}
           </cr-button>
         </div>
diff --git a/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.js b/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.js
index c088633d..295a14d 100644
--- a/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.js
+++ b/chrome/browser/resources/settings/people_page/kerberos_add_account_dialog.js
@@ -67,6 +67,12 @@
     },
 
     /** @private */
+    configErrorText_: {
+      type: String,
+      value: '',
+    },
+
+    /** @private */
     inProgress_: {
       type: Boolean,
       value: false,
@@ -105,6 +111,15 @@
   /** @private {string} */
   actionButtonLabel_: '',
 
+  /** @private {?settings.KerberosAccountsBrowserProxy} */
+  browserProxy_: null,
+
+  /** @override */
+  created: function() {
+    this.browserProxy_ =
+        settings.KerberosAccountsBrowserProxyImpl.getInstance();
+  },
+
   /** @override */
   attached: function() {
     this.$.addDialog.showModal();
@@ -153,6 +168,7 @@
 
   /** @private */
   onAdd_: function() {
+    assert(!this.inProgress_);
     this.inProgress_ = true;
     this.updateErrorMessages_(settings.KerberosErrorType.kNone);
 
@@ -162,7 +178,7 @@
     // For new accounts (no preset), bail if the account already exists.
     const allowExisting = !!this.presetAccount;
 
-    settings.KerberosAccountsBrowserProxyImpl.getInstance()
+    this.browserProxy_
         .addAccount(
             this.username_, passwordToSubmit, this.rememberPassword_,
             this.config_, allowExisting)
@@ -198,15 +214,31 @@
 
   /** @private */
   onAdvancedConfigCancel_: function() {
+    this.configErrorText_ = '';
     this.showAdvancedConfig_ = false;
     this.$$('#advancedConfigDialog').cancel();
   },
 
   /** @private */
   onAdvancedConfigSave_: function() {
-    this.showAdvancedConfig_ = false;
-    this.config_ = this.editableConfig_;
-    this.$$('#advancedConfigDialog').close();
+    assert(!this.inProgress_);
+    this.inProgress_ = true;
+
+    this.browserProxy_.validateConfig(this.editableConfig_).then(result => {
+      this.inProgress_ = false;
+
+      // Success case. Close dialog.
+      if (result.error == settings.KerberosErrorType.kNone) {
+        this.showAdvancedConfig_ = false;
+        this.config_ = this.editableConfig_;
+        this.configErrorText_ = '';
+        this.$$('#advancedConfigDialog').close();
+        return;
+      }
+
+      // Triggers the UI to update error messages.
+      this.updateConfigErrorMessage_(result);
+    });
   },
 
   onAdvancedConfigClose_: function(event) {
@@ -262,11 +294,107 @@
         break;
       default:
         this.generalErrorText_ =
-            this.i18n('kerberosErrorGeneral', String(error));
+            this.i18n('kerberosErrorGeneral', error.toString());
     }
   },
 
   /**
+   * @param {!settings.ValidateKerberosConfigResult} result Result from a
+   *    validateKerberosConfig() call.
+   * @private
+   */
+  updateConfigErrorMessage_: function(result) {
+    // There should be an error at this point.
+    assert(result.error != settings.KerberosErrorType.kNone);
+
+    // Only handle kBadConfig here. Display generic error otherwise. Should only
+    // occur if something is wrong with D-Bus, but nothing user-induced.
+    if (result.error != settings.KerberosErrorType.kBadConfig) {
+      this.configErrorText_ =
+          this.i18n('kerberosErrorGeneral', result.error.toString());
+      return;
+    }
+
+    let errorLine = '';
+
+    // Don't fall for the classical blunder 0 == false.
+    if (result.errorInfo.lineIndex != undefined) {
+      const textArea = this.$$('#config').shadowRoot.querySelector('#input');
+      errorLine = this.selectAndScrollTo_(textArea, result.errorInfo.lineIndex);
+    }
+
+    // If kBadConfig, the error code should be set.
+    assert(result.errorInfo.code != settings.KerberosConfigErrorCode.kNone);
+    this.configErrorText_ =
+        this.getConfigErrorString_(result.errorInfo.code, errorLine);
+  },
+
+  /**
+   * @param {!settings.KerberosConfigErrorCode} code Error code
+   * @param {!string} errorLine Line where the error occurred
+   * @return {!string} Localized error string that corresponds to code
+   * @private
+   */
+  getConfigErrorString_: function(code, errorLine) {
+    switch (code) {
+      case settings.KerberosConfigErrorCode.kSectionNestedInGroup:
+        return this.i18n('kerberosConfigErrorSectionNestedInGroup', errorLine);
+      case settings.KerberosConfigErrorCode.kSectionSyntax:
+        return this.i18n('kerberosConfigErrorSectionSyntax', errorLine);
+      case settings.KerberosConfigErrorCode.kExpectedOpeningCurlyBrace:
+        return this.i18n(
+            'kerberosConfigErrorExpectedOpeningCurlyBrace', errorLine);
+      case settings.KerberosConfigErrorCode.kExtraCurlyBrace:
+        return this.i18n('kerberosConfigErrorExtraCurlyBrace', errorLine);
+      case settings.KerberosConfigErrorCode.kRelationSyntax:
+        return this.i18n('kerberosConfigErrorRelationSyntax', errorLine);
+      case settings.KerberosConfigErrorCode.kKeyNotSupported:
+        return this.i18n('kerberosConfigErrorKeyNotSupported', errorLine);
+      case settings.KerberosConfigErrorCode.kSectionNotSupported:
+        return this.i18n('kerberosConfigErrorSectionNotSupported', errorLine);
+      case settings.KerberosConfigErrorCode.kKrb5FailedToParse:
+        // Note: This error doesn't have an error line.
+        return this.i18n('kerberosConfigErrorKrb5FailedToParse');
+      default:
+        assertNotReached();
+    }
+  },
+
+  /**
+   * Selects a line in a text area and scrolls to it.
+   * @param {!Element} textArea A textarea element
+   * @param {!number} lineIndex 0-based index of the line to select
+   * @return {!string} The line at lineIndex.
+   * @private
+   */
+  selectAndScrollTo_: function(textArea, lineIndex) {
+    const lines = textArea.value.split('\n');
+    assert(lineIndex >= 0 && lineIndex < lines.length);
+
+    // Compute selection position in characters.
+    let startPos = 0;
+    for (let i = 0; i < lineIndex; i++) {
+      startPos += lines[i].length + 1;
+    }
+
+    // Ignore starting and trailing whitespace for the selection.
+    const trimmedLine = lines[lineIndex].trim();
+    startPos += lines[lineIndex].indexOf(trimmedLine);
+    const endPos = startPos + trimmedLine.length;
+
+    // Set selection.
+    textArea.focus();
+    textArea.setSelectionRange(startPos, endPos);
+
+    // Scroll to center the selected line.
+    const lineHeight = textArea.clientHeight / textArea.rows;
+    const firstLine = Math.max(0, lineIndex - textArea.rows / 2);
+    textArea.scrollTop = lineHeight * firstLine;
+
+    return lines[lineIndex];
+  },
+
+  /**
    * Whether an error element should be shown.
    * Note that !! is not supported in Polymer bindings.
    * @param {?string} errorText Error text to be displayed. Empty if no error.
diff --git a/chrome/browser/resources/settings/people_page/people_page.html b/chrome/browser/resources/settings/people_page/people_page.html
index bde1b521..f2a605b 100644
--- a/chrome/browser/resources/settings/people_page/people_page.html
+++ b/chrome/browser/resources/settings/people_page/people_page.html
@@ -4,6 +4,7 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
 <link rel="import" href="chrome://resources/cr_elements/icons.html">
+<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
@@ -286,7 +287,10 @@
             isKerberosEnabled_, pageVisibility.people.kerberosAccounts)]]">
           <cr-link-row id="kerberos-accounts-subpage-trigger" class="hr"
               on-click="onKerberosAccountsTap_"
-              label="$i18n{kerberosAccountsSubMenuLabel}"></cr-link-row>
+              label="$i18n{kerberosAccountsSubMenuLabel}">
+            <cr-policy-indicator indicator-type="userPolicy">
+            </cr-policy-indicator>
+          </cr-link-row>
         </template>
 </if>
 
diff --git a/chrome/browser/services_unittest.cc b/chrome/browser/services_unittest.cc
index 858d8343..89a565de 100644
--- a/chrome/browser/services_unittest.cc
+++ b/chrome/browser/services_unittest.cc
@@ -9,8 +9,8 @@
 #include "base/test/bind_test_util.h"
 #include "components/services/patch/public/mojom/constants.mojom.h"
 #include "components/services/patch/public/mojom/file_patcher.mojom.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
-#include "components/services/unzip/public/interfaces/unzipper.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
 #include "content/public/browser/system_connector.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
diff --git a/chrome/browser/sharing/click_to_call/click_to_call_context_menu_observer.cc b/chrome/browser/sharing/click_to_call/click_to_call_context_menu_observer.cc
index 91d3ab5..094e625 100644
--- a/chrome/browser/sharing/click_to_call/click_to_call_context_menu_observer.cc
+++ b/chrome/browser/sharing/click_to_call/click_to_call_context_menu_observer.cc
@@ -139,6 +139,9 @@
   if (chosen_device_index >= static_cast<int>(devices_.size()))
     return;
 
+  LogClickToCallSelectedDeviceIndex(kSharingClickToCallUiContextMenu,
+                                    chosen_device_index);
+
   SharingMessage sharing_message;
   sharing_message.mutable_click_to_call_message()->set_phone_number(
       url_.GetContent());
diff --git a/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.cc b/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.cc
index 6ad8c46..c3b1e3bb 100644
--- a/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.cc
+++ b/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/sharing/sharing_service.h"
 #include "chrome/browser/shell_integration.h"
 #include "components/sync_device_info/device_info.h"
+#include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/strings/grit/ui_strings.h"
@@ -30,8 +31,8 @@
 ClickToCallSharingDialogController::~ClickToCallSharingDialogController() =
     default;
 
-std::string ClickToCallSharingDialogController::GetTitle() {
-  return l10n_util::GetStringUTF8(
+base::string16 ClickToCallSharingDialogController::GetTitle() {
+  return l10n_util::GetStringUTF16(
       IDS_BROWSER_SHARING_CLICK_TO_CALL_DIALOG_TITLE_LABEL);
 }
 
@@ -47,8 +48,8 @@
 
   std::vector<App> apps;
   if (!app_name.empty()) {
-    // TODO: Get the icon and ID and test
-    apps.emplace_back(std::string(), std::move(app_name), std::string());
+    apps.emplace_back(vector_icons::kOpenInNewIcon, std::move(app_name),
+                      std::string());
   }
   return apps;
 }
diff --git a/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.h b/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.h
index 5b87398..4d79d48 100644
--- a/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.h
+++ b/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.h
@@ -28,7 +28,7 @@
   ~ClickToCallSharingDialogController() override;
 
   // Overridden from SharingDialogController:
-  std::string GetTitle() override;
+  base::string16 GetTitle() override;
   std::vector<SharingDeviceInfo> GetSyncedDevices() override;
   std::vector<App> GetApps() override;
   void OnDeviceChosen(const SharingDeviceInfo& device,
diff --git a/chrome/browser/sharing/sharing_dialog_controller.cc b/chrome/browser/sharing/sharing_dialog_controller.cc
index 934321d..cf46895 100644
--- a/chrome/browser/sharing/sharing_dialog_controller.cc
+++ b/chrome/browser/sharing/sharing_dialog_controller.cc
@@ -4,12 +4,10 @@
 
 #include "chrome/browser/sharing/sharing_dialog_controller.h"
 
-SharingDialogController::App::App(std::string icon,
+SharingDialogController::App::App(const gfx::VectorIcon& icon,
                                   base::string16 name,
                                   std::string identifier)
-    : icon(std::move(icon)),
-      name(std::move(name)),
-      identifier(std::move(identifier)) {}
+    : icon(icon), name(std::move(name)), identifier(std::move(identifier)) {}
 
 SharingDialogController::App::App(App&& other) = default;
 
diff --git a/chrome/browser/sharing/sharing_dialog_controller.h b/chrome/browser/sharing/sharing_dialog_controller.h
index 0498802f..79c3b644 100644
--- a/chrome/browser/sharing/sharing_dialog_controller.h
+++ b/chrome/browser/sharing/sharing_dialog_controller.h
@@ -9,30 +9,34 @@
 #include <vector>
 
 #include "base/macros.h"
-#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/sharing/sharing_service.h"
 
 class SharingDeviceInfo;
 
+namespace gfx {
+struct VectorIcon;
+}  // namespace gfx
+
 // The controller for desktop dialog with the list of synced devices and apps.
 class SharingDialogController {
  public:
   struct App {
-    App(std::string icon, base::string16 name, std::string identifier);
+    App(const gfx::VectorIcon& icon,
+        base::string16 name,
+        std::string identifier);
     App(App&& other);
     ~App();
-    std::string icon;
+
+    const gfx::VectorIcon& icon;
     base::string16 name;
     std::string identifier;
-
-    DISALLOW_COPY_AND_ASSIGN(App);
   };
 
   SharingDialogController() = default;
   virtual ~SharingDialogController() = default;
 
   // Title of the dialog.
-  virtual std::string GetTitle() = 0;
+  virtual base::string16 GetTitle() = 0;
 
   // Returns filtered list of synced devices for the feature.
   virtual std::vector<SharingDeviceInfo> GetSyncedDevices() = 0;
@@ -46,8 +50,6 @@
 
   // Called when user chooses a local app to complete the task.
   virtual void OnAppChosen(const App& app) = 0;
-
-  DISALLOW_COPY_AND_ASSIGN(SharingDialogController);
 };
 
 #endif  // CHROME_BROWSER_SHARING_SHARING_DIALOG_CONTROLLER_H_
diff --git a/chrome/browser/sharing/sharing_metrics.cc b/chrome/browser/sharing/sharing_metrics.cc
index 8d1ee3a9..c2d79469 100644
--- a/chrome/browser/sharing/sharing_metrics.cc
+++ b/chrome/browser/sharing/sharing_metrics.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/sharing/sharing_metrics.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
 
 namespace {
 
@@ -49,3 +50,19 @@
   base::UmaHistogramExactLinear("Sharing.ClickToCallAppsToShow", count,
                                 /*value_max=*/20);
 }
+
+void LogClickToCallSelectedDeviceIndex(const char* histogram_suffix,
+                                       int index) {
+  base::UmaHistogramExactLinear(
+      base::StrCat(
+          {"Sharing.ClickToCallSelectedDeviceIndex.", histogram_suffix}),
+      index,
+      /*value_max=*/20);
+}
+
+void LogClickToCallSelectedAppIndex(const char* histogram_suffix, int index) {
+  base::UmaHistogramExactLinear(
+      base::StrCat({"Sharing.ClickToCallSelectedAppIndex.", histogram_suffix}),
+      index,
+      /*value_max=*/20);
+}
diff --git a/chrome/browser/sharing/sharing_metrics.h b/chrome/browser/sharing/sharing_metrics.h
index c1f13278..2b224fb 100644
--- a/chrome/browser/sharing/sharing_metrics.h
+++ b/chrome/browser/sharing/sharing_metrics.h
@@ -7,6 +7,11 @@
 
 #include "chrome/browser/sharing/proto/sharing_message.pb.h"
 
+// These histogram suffixes must match the ones in SharingClickToCallUi defined
+// in histograms.xml.
+const char kSharingClickToCallUiContextMenu[] = "ContextMenu";
+const char kSharingClickToCallUiDialog[] = "Dialog";
+
 // Logs the |payload_case| to UMA. This should be called when a SharingMessage
 // is received.
 void LogSharingMessageReceived(
@@ -20,4 +25,14 @@
 // picking an app to start a phone call with.
 void LogClickToCallAppsToShow(int count);
 
+// Logs the |index| of the device selected by the user for Click to Call. The
+// |histogram_suffix| indicates in which UI this event happened and must match
+// one from SharingClickToCallUi defined in histograms.xml.
+void LogClickToCallSelectedDeviceIndex(const char* histogram_suffix, int index);
+
+// Logs the |index| of the app selected by the user for Click to Call. The
+// |histogram_suffix| indicates in which UI this event happened and must match
+// one from SharingClickToCallUi defined in histograms.xml.
+void LogClickToCallSelectedAppIndex(const char* histogram_suffix, int index);
+
 #endif  // CHROME_BROWSER_SHARING_SHARING_METRICS_H_
diff --git a/chrome/browser/signin/account_id_from_account_info.cc b/chrome/browser/signin/account_id_from_account_info.cc
new file mode 100644
index 0000000..f6223e6
--- /dev/null
+++ b/chrome/browser/signin/account_id_from_account_info.cc
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/signin/account_id_from_account_info.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+
+#if defined(OS_CHROMEOS)
+#include "components/user_manager/known_user.h"
+#endif
+
+AccountId AccountIdFromAccountInfo(const CoreAccountInfo& account_info) {
+#if defined(OS_CHROMEOS)
+  return user_manager::known_user::GetAccountId(
+      account_info.email, account_info.gaia, AccountType::GOOGLE);
+#else
+  if (account_info.email.empty() || account_info.gaia.empty())
+    return EmptyAccountId();
+
+  return AccountId::FromUserEmailGaiaId(
+      gaia::CanonicalizeEmail(account_info.email), account_info.gaia);
+#endif
+}
diff --git a/chrome/browser/signin/account_id_from_account_info.h b/chrome/browser/signin/account_id_from_account_info.h
new file mode 100644
index 0000000..cdd6836
--- /dev/null
+++ b/chrome/browser/signin/account_id_from_account_info.h
@@ -0,0 +1,18 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SIGNIN_ACCOUNT_ID_FROM_ACCOUNT_INFO_H_
+#define CHROME_BROWSER_SIGNIN_ACCOUNT_ID_FROM_ACCOUNT_INFO_H_
+
+#include "components/account_id/account_id.h"
+#include "components/signin/public/identity_manager/account_info.h"
+
+// Returns AccountID populated from |account_info|.
+// NOTE: This utility is in //chrome rather than being part of
+// //components/signin/public because it is only //chrome that needs to go back
+// and forth between AccountId and AccountInfo, and it is outside the scope of
+// //components/signin/public to have knowledge about AccountId.
+AccountId AccountIdFromAccountInfo(const CoreAccountInfo& account_info);
+
+#endif  // CHROME_BROWSER_SIGNIN_ACCOUNT_ID_FROM_ACCOUNT_INFO_H_
diff --git a/chrome/browser/signin/account_id_from_account_info_unittest.cc b/chrome/browser/signin/account_id_from_account_info_unittest.cc
new file mode 100644
index 0000000..356e338
--- /dev/null
+++ b/chrome/browser/signin/account_id_from_account_info_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/signin/account_id_from_account_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class AccountIdFromAccountInfoTest : public testing::Test {};
+
+// Tests that AccountIdFromAccountInfo() passes along a canonicalized email to
+// AccountId.
+TEST(AccountIdFromAccountInfoTest,
+     AccountIdFromAccountInfo_CanonicalizesRawEmail) {
+  AccountInfo info;
+  info.email = "test.email@gmail.com";
+  info.gaia = "test_id";
+
+  EXPECT_EQ("testemail@gmail.com",
+            AccountIdFromAccountInfo(info).GetUserEmail());
+}
diff --git a/chrome/browser/signin/chrome_signin_client.cc b/chrome/browser/signin/chrome_signin_client.cc
index 0796173..14368b1 100644
--- a/chrome/browser/signin/chrome_signin_client.cc
+++ b/chrome/browser/signin/chrome_signin_client.cc
@@ -291,7 +291,8 @@
 #if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
   // We only verifiy the token once when Profile is just created.
   if (signin_util::IsForceSigninEnabled() && !force_signin_verifier_)
-    force_signin_verifier_ = std::make_unique<ForceSigninVerifier>(profile_);
+    force_signin_verifier_ = std::make_unique<ForceSigninVerifier>(
+        IdentityManagerFactory::GetForProfile(profile_));
 #endif
 }
 
diff --git a/chrome/browser/signin/force_signin_verifier.cc b/chrome/browser/signin/force_signin_verifier.cc
index 5c65ce6b..b54d674 100644
--- a/chrome/browser/signin/force_signin_verifier.cc
+++ b/chrome/browser/signin/force_signin_verifier.cc
@@ -8,8 +8,6 @@
 
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/identity_manager/access_token_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
@@ -39,11 +37,12 @@
 const char kForceSigninVerificationFailureTimeMetricsName[] =
     "Signin.ForceSigninVerificationTime.Failure";
 
-ForceSigninVerifier::ForceSigninVerifier(Profile* profile)
+ForceSigninVerifier::ForceSigninVerifier(
+    identity::IdentityManager* identity_manager)
     : has_token_verified_(false),
       backoff_entry_(&kForceSigninVerifierBackoffPolicy),
       creation_time_(base::TimeTicks::Now()),
-      identity_manager_(IdentityManagerFactory::GetForProfile(profile)) {
+      identity_manager_(identity_manager) {
   content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
   UMA_HISTOGRAM_BOOLEAN(kForceSigninVerificationMetricsName,
                         ShouldSendRequest());
diff --git a/chrome/browser/signin/force_signin_verifier.h b/chrome/browser/signin/force_signin_verifier.h
index 74633ebc..eb2820cc 100644
--- a/chrome/browser/signin/force_signin_verifier.h
+++ b/chrome/browser/signin/force_signin_verifier.h
@@ -15,8 +15,6 @@
 #include "net/base/backoff_entry.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
 
-class Profile;
-
 namespace identity {
 class IdentityManager;
 class PrimaryAccountAccessTokenFetcher;
@@ -33,7 +31,7 @@
 class ForceSigninVerifier
     : public network::NetworkConnectionTracker::NetworkConnectionObserver {
  public:
-  explicit ForceSigninVerifier(Profile* profile);
+  explicit ForceSigninVerifier(identity::IdentityManager* identity_manager);
   ~ForceSigninVerifier() override;
 
   void OnAccessTokenFetchComplete(GoogleServiceAuthError error,
@@ -76,12 +74,12 @@
 
   // Indicates whether the verification is finished successfully or with a
   // persistent error.
-  bool has_token_verified_;
+  bool has_token_verified_ = false;
   net::BackoffEntry backoff_entry_;
   base::OneShotTimer backoff_request_timer_;
   base::TimeTicks creation_time_;
 
-  identity::IdentityManager* identity_manager_;
+  identity::IdentityManager* identity_manager_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(ForceSigninVerifier);
 };
diff --git a/chrome/browser/signin/force_signin_verifier_unittest.cc b/chrome/browser/signin/force_signin_verifier_unittest.cc
index 36933f19..1238177 100644
--- a/chrome/browser/signin/force_signin_verifier_unittest.cc
+++ b/chrome/browser/signin/force_signin_verifier_unittest.cc
@@ -6,20 +6,22 @@
 
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
-#include "chrome/test/base/testing_profile.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "content/public/browser/network_service_instance.h"
-#include "content/public/test/test_browser_thread_bundle.h"
 #include "services/network/test/test_network_connection_tracker.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-class MockForceSigninVerifier : public ForceSigninVerifier {
+namespace {
+
+class ForceSigninVerifierWithAccessToInternalsForTesting
+    : public ForceSigninVerifier {
  public:
-  explicit MockForceSigninVerifier(Profile* profile)
-      : ForceSigninVerifier(profile) {}
+  explicit ForceSigninVerifierWithAccessToInternalsForTesting(
+      identity::IdentityManager* identity_manager)
+      : ForceSigninVerifier(identity_manager) {}
 
   bool IsDelayTaskPosted() { return GetOneShotTimerForTesting()->IsRunning(); }
 
@@ -32,271 +34,364 @@
   MOCK_METHOD0(CloseAllBrowserWindows, void(void));
 };
 
-class ForceSigninVerifierTest
-    : public ::testing::Test,
-      public network::NetworkConnectionTracker::NetworkConnectionObserver {
+// A NetworkConnectionObserver that invokes a base::RepeatingClosure when
+// NetworkConnectionObserver::OnConnectionChanged() is invoked.
+class NetworkConnectionObserverHelper
+    : public network::NetworkConnectionTracker::NetworkConnectionObserver {
  public:
-  void SetUp() override {
-    profile_ = IdentityTestEnvironmentProfileAdaptor::
-        CreateProfileForIdentityTestEnvironment();
-    identity_test_env_profile_adaptor_ =
-        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_.get());
-    account_info_ =
-        identity_test_env()->MakePrimaryAccountAvailable("email@test.com");
+  explicit NetworkConnectionObserverHelper(base::RepeatingClosure closure)
+      : closure_(std::move(closure)) {
+    DCHECK(!closure_.is_null());
+    content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
   }
 
-  void TearDown() override { verifier_.reset(); }
+  ~NetworkConnectionObserverHelper() override {
+    content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(
+        this);
+  }
 
   void OnConnectionChanged(network::mojom::ConnectionType type) override {
-    wait_for_network_type_change_.QuitWhenIdle();
+    closure_.Run();
   }
 
-  identity::IdentityTestEnvironment* identity_test_env() {
-    return identity_test_env_profile_adaptor_->identity_test_env();
-  }
+ private:
+  base::RepeatingClosure closure_;
 
-  std::unique_ptr<MockForceSigninVerifier> verifier_;
-  content::TestBrowserThreadBundle bundle_;
-  std::unique_ptr<TestingProfile> profile_;
-  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
-      identity_test_env_profile_adaptor_;
-
-  AccountInfo account_info_;
-
-  base::RunLoop wait_for_network_type_change_;
-  base::RunLoop wait_for_network_type_async_return_;
-
-  GoogleServiceAuthError persistent_error_ = GoogleServiceAuthError(
-      GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS);
-  GoogleServiceAuthError transient_error_ =
-      GoogleServiceAuthError(GoogleServiceAuthError::State::CONNECTION_FAILED);
-
-  base::HistogramTester histogram_tester_;
+  DISALLOW_COPY_AND_ASSIGN(NetworkConnectionObserverHelper);
 };
 
-TEST_F(ForceSigninVerifierTest, OnGetTokenSuccess) {
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+// Used to select which type of network type NetworkConnectionTracker should
+// be configured to.
+enum class NetworkConnectionType {
+  Undecided,
+  ConnectionNone,
+  ConnectionWifi,
+  Connection4G,
+};
 
-  ASSERT_NE(nullptr, verifier_->access_token_fetcher());
-  ASSERT_FALSE(verifier_->HasTokenBeenVerified());
-  ASSERT_FALSE(verifier_->IsDelayTaskPosted());
-  EXPECT_CALL(*verifier_.get(), CloseAllBrowserWindows()).Times(0);
+// Used to select which type of response NetworkConnectionTracker should give.
+enum class NetworkResponseType {
+  Undecided,
+  Synchronous,
+  Asynchronous,
+};
 
-  identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
-      account_info_.account_id, /*token=*/"", base::Time());
+// Forces the network connection type to change to |connection_type| and wait
+// till the notification has been propagated to the observers. Also change the
+// response type to be synchronous/asynchronous based on |response_type|.
+void ConfigureNetworkConnectionTracker(NetworkConnectionType connection_type,
+                                       NetworkResponseType response_type) {
+  network::TestNetworkConnectionTracker* tracker =
+      network::TestNetworkConnectionTracker::GetInstance();
 
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
-  ASSERT_TRUE(verifier_->HasTokenBeenVerified());
-  ASSERT_FALSE(verifier_->IsDelayTaskPosted());
-  ASSERT_EQ(0, verifier_->FailureCount());
-  histogram_tester_.ExpectBucketCount(kForceSigninVerificationMetricsName, 1,
-                                      1);
-  histogram_tester_.ExpectTotalCount(
+  switch (response_type) {
+    case NetworkResponseType::Undecided:
+      // nothing to do
+      break;
+
+    case NetworkResponseType::Synchronous:
+      tracker->SetRespondSynchronously(true);
+      break;
+
+    case NetworkResponseType::Asynchronous:
+      tracker->SetRespondSynchronously(false);
+      break;
+  }
+
+  if (connection_type != NetworkConnectionType::Undecided) {
+    network::mojom::ConnectionType mojom_connection_type =
+        network::mojom::ConnectionType::CONNECTION_UNKNOWN;
+
+    switch (connection_type) {
+      case NetworkConnectionType::Undecided:
+        NOTREACHED();
+        break;
+
+      case NetworkConnectionType::ConnectionNone:
+        mojom_connection_type = network::mojom::ConnectionType::CONNECTION_NONE;
+        break;
+
+      case NetworkConnectionType::ConnectionWifi:
+        mojom_connection_type = network::mojom::ConnectionType::CONNECTION_WIFI;
+        break;
+
+      case NetworkConnectionType::Connection4G:
+        mojom_connection_type = network::mojom::ConnectionType::CONNECTION_4G;
+        break;
+    }
+
+    DCHECK_NE(mojom_connection_type,
+              network::mojom::ConnectionType::CONNECTION_UNKNOWN);
+
+    base::RunLoop wait_for_network_type_change;
+    NetworkConnectionObserverHelper scoped_observer(
+        wait_for_network_type_change.QuitWhenIdleClosure());
+
+    tracker->SetConnectionType(mojom_connection_type);
+
+    wait_for_network_type_change.Run();
+  }
+}
+
+// Forces the current sequence's task runner to spin. This is used because the
+// ForceSigninVerifier ends up posting task to the sequence's task runner when
+// MetworkConnectionTracker is returning results asynchronously.
+void SpinCurrentSequenceTaskRunner() {
+  base::RunLoop run_loop;
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                run_loop.QuitClosure());
+  run_loop.Run();
+}
+
+}  // namespace
+
+TEST(ForceSigninVerifierTest, OnGetTokenSuccess) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
+
+  base::HistogramTester histogram_tester;
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
+
+  ASSERT_NE(nullptr, verifier.access_token_fetcher());
+  ASSERT_FALSE(verifier.HasTokenBeenVerified());
+  ASSERT_FALSE(verifier.IsDelayTaskPosted());
+  EXPECT_CALL(verifier, CloseAllBrowserWindows()).Times(0);
+
+  identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
+      account_info.account_id, /*token=*/"", base::Time());
+
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
+  ASSERT_TRUE(verifier.HasTokenBeenVerified());
+  ASSERT_FALSE(verifier.IsDelayTaskPosted());
+  ASSERT_EQ(0, verifier.FailureCount());
+  histogram_tester.ExpectBucketCount(kForceSigninVerificationMetricsName, 1, 1);
+  histogram_tester.ExpectTotalCount(
       kForceSigninVerificationSuccessTimeMetricsName, 1);
-  histogram_tester_.ExpectTotalCount(
+  histogram_tester.ExpectTotalCount(
       kForceSigninVerificationFailureTimeMetricsName, 0);
 }
 
-TEST_F(ForceSigninVerifierTest, OnGetTokenPersistentFailure) {
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+TEST(ForceSigninVerifierTest, OnGetTokenPersistentFailure) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
 
-  ASSERT_NE(nullptr, verifier_->access_token_fetcher());
-  ASSERT_FALSE(verifier_->HasTokenBeenVerified());
-  ASSERT_FALSE(verifier_->IsDelayTaskPosted());
-  EXPECT_CALL(*verifier_.get(), CloseAllBrowserWindows()).Times(1);
+  base::HistogramTester histogram_tester;
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
 
-  identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
-      persistent_error_);
+  ASSERT_NE(nullptr, verifier.access_token_fetcher());
+  ASSERT_FALSE(verifier.HasTokenBeenVerified());
+  ASSERT_FALSE(verifier.IsDelayTaskPosted());
+  EXPECT_CALL(verifier, CloseAllBrowserWindows()).Times(1);
 
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
-  ASSERT_TRUE(verifier_->HasTokenBeenVerified());
-  ASSERT_FALSE(verifier_->IsDelayTaskPosted());
-  ASSERT_EQ(0, verifier_->FailureCount());
-  histogram_tester_.ExpectBucketCount(kForceSigninVerificationMetricsName, 1,
-                                      1);
-  histogram_tester_.ExpectTotalCount(
+  identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
+      GoogleServiceAuthError(
+          GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
+
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
+  ASSERT_TRUE(verifier.HasTokenBeenVerified());
+  ASSERT_FALSE(verifier.IsDelayTaskPosted());
+  ASSERT_EQ(0, verifier.FailureCount());
+  histogram_tester.ExpectBucketCount(kForceSigninVerificationMetricsName, 1, 1);
+  histogram_tester.ExpectTotalCount(
       kForceSigninVerificationSuccessTimeMetricsName, 0);
-  histogram_tester_.ExpectTotalCount(
+  histogram_tester.ExpectTotalCount(
       kForceSigninVerificationFailureTimeMetricsName, 1);
 }
 
-TEST_F(ForceSigninVerifierTest, OnGetTokenTransientFailure) {
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+TEST(ForceSigninVerifierTest, OnGetTokenTransientFailure) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
 
-  ASSERT_NE(nullptr, verifier_->access_token_fetcher());
-  ASSERT_FALSE(verifier_->HasTokenBeenVerified());
-  ASSERT_FALSE(verifier_->IsDelayTaskPosted());
-  EXPECT_CALL(*verifier_.get(), CloseAllBrowserWindows()).Times(0);
+  base::HistogramTester histogram_tester;
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
 
-  identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
-      transient_error_);
+  ASSERT_NE(nullptr, verifier.access_token_fetcher());
+  ASSERT_FALSE(verifier.HasTokenBeenVerified());
+  ASSERT_FALSE(verifier.IsDelayTaskPosted());
+  EXPECT_CALL(verifier, CloseAllBrowserWindows()).Times(0);
 
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
-  ASSERT_FALSE(verifier_->HasTokenBeenVerified());
-  ASSERT_TRUE(verifier_->IsDelayTaskPosted());
-  ASSERT_EQ(1, verifier_->FailureCount());
-  histogram_tester_.ExpectBucketCount(kForceSigninVerificationMetricsName, 1,
-                                      1);
-  histogram_tester_.ExpectTotalCount(
+  identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
+      GoogleServiceAuthError(GoogleServiceAuthError::State::CONNECTION_FAILED));
+
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
+  ASSERT_FALSE(verifier.HasTokenBeenVerified());
+  ASSERT_TRUE(verifier.IsDelayTaskPosted());
+  ASSERT_EQ(1, verifier.FailureCount());
+  histogram_tester.ExpectBucketCount(kForceSigninVerificationMetricsName, 1, 1);
+  histogram_tester.ExpectTotalCount(
       kForceSigninVerificationSuccessTimeMetricsName, 0);
-  histogram_tester_.ExpectTotalCount(
+  histogram_tester.ExpectTotalCount(
       kForceSigninVerificationFailureTimeMetricsName, 0);
 }
 
-TEST_F(ForceSigninVerifierTest, OnLostConnection) {
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+TEST(ForceSigninVerifierTest, OnLostConnection) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
 
-  content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
 
-  identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
-      transient_error_);
+  identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
+      GoogleServiceAuthError(GoogleServiceAuthError::State::CONNECTION_FAILED));
 
-  ASSERT_EQ(1, verifier_->FailureCount());
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
-  ASSERT_TRUE(verifier_->IsDelayTaskPosted());
+  ASSERT_EQ(1, verifier.FailureCount());
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
+  ASSERT_TRUE(verifier.IsDelayTaskPosted());
 
-  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
-      network::mojom::ConnectionType::CONNECTION_NONE);
-  wait_for_network_type_change_.Run();
-  content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionNone,
+                                    NetworkResponseType::Undecided);
 
-  ASSERT_EQ(0, verifier_->FailureCount());
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
-  ASSERT_FALSE(verifier_->IsDelayTaskPosted());
+  ASSERT_EQ(0, verifier.FailureCount());
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
+  ASSERT_FALSE(verifier.IsDelayTaskPosted());
 }
 
-TEST_F(ForceSigninVerifierTest, OnReconnected) {
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+TEST(ForceSigninVerifierTest, OnReconnected) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
 
-  content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
 
-  identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
-      transient_error_);
+  identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
+      GoogleServiceAuthError(GoogleServiceAuthError::State::CONNECTION_FAILED));
 
-  ASSERT_EQ(1, verifier_->FailureCount());
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
-  ASSERT_TRUE(verifier_->IsDelayTaskPosted());
+  ASSERT_EQ(1, verifier.FailureCount());
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
+  ASSERT_TRUE(verifier.IsDelayTaskPosted());
 
-  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
-      network::mojom::ConnectionType::CONNECTION_WIFI);
-  wait_for_network_type_change_.Run();
-  content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionWifi,
+                                    NetworkResponseType::Undecided);
 
-  ASSERT_EQ(0, verifier_->FailureCount());
-  ASSERT_NE(nullptr, verifier_->access_token_fetcher());
-  ASSERT_FALSE(verifier_->IsDelayTaskPosted());
+  ASSERT_EQ(0, verifier.FailureCount());
+  ASSERT_NE(nullptr, verifier.access_token_fetcher());
+  ASSERT_FALSE(verifier.IsDelayTaskPosted());
 }
 
-TEST_F(ForceSigninVerifierTest, GetNetworkStatusAsync) {
-  network::TestNetworkConnectionTracker::GetInstance()->SetRespondSynchronously(
-      false);
+TEST(ForceSigninVerifierTest, GetNetworkStatusAsync) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
 
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::Undecided,
+                                    NetworkResponseType::Asynchronous);
+
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
 
   // There is no network type at first.
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
 
   // Waiting for the network type returns.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, wait_for_network_type_async_return_.QuitClosure());
-  wait_for_network_type_async_return_.Run();
+  SpinCurrentSequenceTaskRunner();
 
   // Get the type and send the request.
-  ASSERT_NE(nullptr, verifier_->access_token_fetcher());
+  ASSERT_NE(nullptr, verifier.access_token_fetcher());
 }
 
-TEST_F(ForceSigninVerifierTest, LaunchVerifierWithoutNetwork) {
-  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
-      network::mojom::ConnectionType::CONNECTION_NONE);
-  network::TestNetworkConnectionTracker::GetInstance()->SetRespondSynchronously(
-      false);
+TEST(ForceSigninVerifierTest, LaunchVerifierWithoutNetwork) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
 
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionNone,
+                                    NetworkResponseType::Asynchronous);
+
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
 
   // There is no network type.
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
 
   // Waiting for the network type returns.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, wait_for_network_type_async_return_.QuitClosure());
-  wait_for_network_type_async_return_.Run();
+  SpinCurrentSequenceTaskRunner();
 
   // Get the type, there is no network connection, don't send the request.
-  ASSERT_EQ(nullptr, verifier_->access_token_fetcher());
+  ASSERT_EQ(nullptr, verifier.access_token_fetcher());
 
   // Network is resumed.
-  content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
-  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
-      network::mojom::ConnectionType::CONNECTION_WIFI);
-  wait_for_network_type_change_.Run();
-  content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionWifi,
+                                    NetworkResponseType::Undecided);
 
   // Send the request.
-  ASSERT_NE(nullptr, verifier_->access_token_fetcher());
+  ASSERT_NE(nullptr, verifier.access_token_fetcher());
 }
 
-TEST_F(ForceSigninVerifierTest, ChangeNetworkFromWIFITo4GWithOnGoingRequest) {
-  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
-      network::mojom::ConnectionType::CONNECTION_WIFI);
-  network::TestNetworkConnectionTracker::GetInstance()->SetRespondSynchronously(
-      false);
+TEST(ForceSigninVerifierTest, ChangeNetworkFromWIFITo4GWithOnGoingRequest) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
 
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionWifi,
+                                    NetworkResponseType::Asynchronous);
 
-  EXPECT_EQ(nullptr, verifier_->access_token_fetcher());
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
+
+  EXPECT_EQ(nullptr, verifier.access_token_fetcher());
 
   // Waiting for the network type returns.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, wait_for_network_type_async_return_.QuitClosure());
-  wait_for_network_type_async_return_.Run();
+  SpinCurrentSequenceTaskRunner();
 
   // The network type if wifi, send the request.
-  auto* first_request = verifier_->access_token_fetcher();
+  auto* first_request = verifier.access_token_fetcher();
   EXPECT_NE(nullptr, first_request);
 
   // Network is changed to 4G.
-  content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
-  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
-      network::mojom::ConnectionType::CONNECTION_4G);
-  wait_for_network_type_change_.Run();
-  content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::Connection4G,
+                                    NetworkResponseType::Undecided);
 
   // There is still one on-going request.
-  EXPECT_EQ(first_request, verifier_->access_token_fetcher());
-  identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
-      account_info_.account_id, /*token=*/"", base::Time());
+  EXPECT_EQ(first_request, verifier.access_token_fetcher());
+  identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
+      account_info.account_id, /*token=*/"", base::Time());
 }
 
-TEST_F(ForceSigninVerifierTest, ChangeNetworkFromWIFITo4GWithFinishedRequest) {
-  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
-      network::mojom::ConnectionType::CONNECTION_WIFI);
-  network::TestNetworkConnectionTracker::GetInstance()->SetRespondSynchronously(
-      false);
+TEST(ForceSigninVerifierTest, ChangeNetworkFromWIFITo4GWithFinishedRequest) {
+  base::test::ScopedTaskEnvironment scoped_task_env;
+  identity::IdentityTestEnvironment identity_test_env;
+  const AccountInfo account_info =
+      identity_test_env.MakePrimaryAccountAvailable("email@test.com");
 
-  verifier_ = std::make_unique<MockForceSigninVerifier>(profile_.get());
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionWifi,
+                                    NetworkResponseType::Asynchronous);
 
-  EXPECT_EQ(nullptr, verifier_->access_token_fetcher());
+  ForceSigninVerifierWithAccessToInternalsForTesting verifier(
+      identity_test_env.identity_manager());
+
+  EXPECT_EQ(nullptr, verifier.access_token_fetcher());
 
   // Waiting for the network type returns.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, wait_for_network_type_async_return_.QuitClosure());
-  wait_for_network_type_async_return_.Run();
+  SpinCurrentSequenceTaskRunner();
 
   // The network type if wifi, send the request.
-  EXPECT_NE(nullptr, verifier_->access_token_fetcher());
+  EXPECT_NE(nullptr, verifier.access_token_fetcher());
 
   // Finishes the request.
-  identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
-      account_info_.account_id, /*token=*/"", base::Time());
-  EXPECT_EQ(nullptr, verifier_->access_token_fetcher());
+  identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
+      account_info.account_id, /*token=*/"", base::Time());
+  EXPECT_EQ(nullptr, verifier.access_token_fetcher());
 
   // Network is changed to 4G.
-  content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
-  network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
-      network::mojom::ConnectionType::CONNECTION_4G);
-  wait_for_network_type_change_.Run();
-  content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
+  ConfigureNetworkConnectionTracker(NetworkConnectionType::Connection4G,
+                                    NetworkResponseType::Undecided);
 
   // No more request because it's verfied already.
-  EXPECT_EQ(nullptr, verifier_->access_token_fetcher());
+  EXPECT_EQ(nullptr, verifier.access_token_fetcher());
 }
diff --git a/chrome/browser/sync/test/integration/two_client_app_list_sync_test.cc b/chrome/browser/sync/test/integration/two_client_app_list_sync_test.cc
index 88a79ad..209749e 100644
--- a/chrome/browser/sync/test/integration/two_client_app_list_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_app_list_sync_test.cc
@@ -381,9 +381,10 @@
   ASSERT_TRUE(AllProfilesHaveSameAppList());
 
   // Flag Default app in Profile 1.
-  extensions::ExtensionPrefs::Get(GetProfile(1))
-      ->UpdateExtensionPref(default_app_id, "was_installed_by_default",
-                            std::make_unique<base::Value>(true));
+  using ALSS = app_list::AppListSyncableService;
+  EXPECT_FALSE(ALSS::AppIsDefaultForTest(GetProfile(1), default_app_id));
+  ALSS::SetAppIsDefaultForTest(GetProfile(1), default_app_id);
+  EXPECT_TRUE(ALSS::AppIsDefaultForTest(GetProfile(1), default_app_id));
 
   // Remove the default app in Profile 0 and verifier, ensure it was removed
   // in Profile 1.
@@ -392,8 +393,7 @@
   ASSERT_TRUE(AllProfilesHaveSameAppList());
 
   // Ensure that a REMOVE_DEFAULT_APP SyncItem entry exists in Profile 1.
-  const app_list::AppListSyncableService::SyncItem* sync_item =
-      GetSyncItem(GetProfile(1), default_app_id);
+  const ALSS::SyncItem* sync_item = GetSyncItem(GetProfile(1), default_app_id);
   ASSERT_TRUE(sync_item);
   ASSERT_EQ(sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP,
             sync_item->item_type);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 68c40dfed..4a58cf3 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1609,6 +1609,8 @@
       "webui/chromeos/bluetooth_dialog_localized_strings_provider.h",
       "webui/chromeos/bluetooth_pairing_dialog.cc",
       "webui/chromeos/bluetooth_pairing_dialog.h",
+      "webui/chromeos/camera/camera_ui.cc",
+      "webui/chromeos/camera/camera_ui.h",
       "webui/chromeos/cellular_setup/cellular_setup_dialog.cc",
       "webui/chromeos/cellular_setup/cellular_setup_dialog.h",
       "webui/chromeos/cellular_setup/cellular_setup_dialog_launcher.cc",
@@ -1863,6 +1865,7 @@
       "//ash/public/cpp/resources:ash_public_unscaled_resources",
       "//ash/public/cpp/vector_icons",
       "//chrome/browser/chromeos",
+      "//chrome/browser/resources/chromeos:camera_resources",
       "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/machine_learning:mojo_bindings",
       "//chrome/services/file_util/public/cpp",
@@ -1913,6 +1916,7 @@
       "//components/session_manager/core",
       "//components/user_manager",
       "//google_apis/drive",
+      "//mojo/public/js:resources_grit",
       "//services/data_decoder/public/cpp",
       "//services/device/public/cpp:device_features",
       "//services/device/public/mojom",
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service.cc b/chrome/browser/ui/app_list/app_list_syncable_service.cc
index f810ee8..bbc0a19f 100644
--- a/chrome/browser/ui/app_list/app_list_syncable_service.cc
+++ b/chrome/browser/ui/app_list/app_list_syncable_service.cc
@@ -16,6 +16,7 @@
 #include "base/stl_util.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/chromeos/arc/arc_util.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/file_manager/app_id.h"
@@ -41,6 +42,7 @@
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
+#include "chrome/services/app_service/public/cpp/app_service_proxy.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/sync/model/sync_change_processor.h"
 #include "components/sync/model/sync_data.h"
@@ -87,10 +89,47 @@
                                            specifics);
 }
 
-bool AppIsDefault(extensions::ExtensionService* service,
-                  const std::string& id) {
-  return service && extensions::ExtensionPrefs::Get(service->profile())
-                        ->WasInstalledByDefault(id);
+bool AppIsDefault(Profile* profile, const std::string& id) {
+  // Querying the extension system is legacy logic from the time that we only
+  // had extension apps. The App Service is the canonical source of truth, when
+  // it is enabled, but we are not there yet (crbug.com/826982).
+  if (extensions::ExtensionPrefs::Get(profile)->WasInstalledByDefault(id))
+    return true;
+  if (!base::FeatureList::IsEnabled(features::kAppServiceAsh))
+    return false;
+
+  bool result = false;
+  apps::AppServiceProxyFactory::GetForProfile(profile)
+      ->AppRegistryCache()
+      .ForOneApp(id, [&result](const apps::AppUpdate& update) {
+        result = update.InstallSource() == apps::mojom::InstallSource::kDefault;
+      });
+  return result;
+}
+
+void SetAppIsDefaultForTest(Profile* profile, const std::string& id) {
+  if (!base::FeatureList::IsEnabled(features::kAppServiceAsh)) {
+    extensions::ExtensionPrefs::Get(profile)->UpdateExtensionPref(
+        id, "was_installed_by_default", std::make_unique<base::Value>(true));
+    return;
+  }
+
+  apps::mojom::AppPtr delta = apps::mojom::App::New();
+  delta->app_type = apps::mojom::AppType::kExtension;
+  delta->app_id = id;
+  delta->install_source = apps::mojom::InstallSource::kDefault;
+
+  std::vector<apps::mojom::AppPtr> deltas;
+  deltas.push_back(std::move(delta));
+
+  apps::AppServiceProxyFactory::GetForProfile(profile)
+      ->AppRegistryCache()
+      .OnApps(std::move(deltas));
+}
+
+bool AppIsDefaultForExtensionService(extensions::ExtensionService* service,
+                                     const std::string& id) {
+  return service && AppIsDefault(service->profile(), id);
 }
 
 bool IsUnRemovableDefaultApp(const std::string& id) {
@@ -265,6 +304,18 @@
   registry->RegisterDictionaryPref(prefs::kAppListLocalState);
 }
 
+// static
+bool AppListSyncableService::AppIsDefaultForTest(Profile* profile,
+                                                 const std::string& id) {
+  return AppIsDefault(profile, id);
+}
+
+// static
+void AppListSyncableService::SetAppIsDefaultForTest(Profile* profile,
+                                                    const std::string& id) {
+  app_list::SetAppIsDefaultForTest(profile, id);
+}
+
 AppListSyncableService::AppListSyncableService(
     Profile* profile,
     extensions::ExtensionSystem* extension_system)
@@ -644,7 +695,8 @@
   // If there is an existing REMOVE_DEFAULT_APP entry, and the app is
   // installed as a Default app, uninstall the app instead of adding it.
   if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP &&
-      AppIsDefault(extension_system_->extension_service(), item->id())) {
+      AppIsDefaultForExtensionService(extension_system_->extension_service(),
+                                      item->id())) {
     VLOG(2) << this
             << ": HandleDefaultApp: Uninstall: " << sync_item->ToString();
     UninstallExtension(extension_system_->extension_service(), item->id());
@@ -732,7 +784,8 @@
   }
 
   if (type == sync_pb::AppListSpecifics::TYPE_APP &&
-      AppIsDefault(extension_system_->extension_service(), id)) {
+      AppIsDefaultForExtensionService(extension_system_->extension_service(),
+                                      id)) {
     // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This
     // will overwrite any existing entry for the item.
     VLOG(2) << this
@@ -866,7 +919,8 @@
       ++updated_items;
     if (specifics.item_type() != sync_pb::AppListSpecifics::TYPE_FOLDER &&
         !IsUnRemovableDefaultApp(item_id) && !AppIsOem(item_id) &&
-        !AppIsDefault(extension_system_->extension_service(), item_id)) {
+        !AppIsDefaultForExtensionService(extension_system_->extension_service(),
+                                         item_id)) {
       VLOG(2) << "Syncing non-default item: " << item_id;
       first_app_list_sync_ = false;
     }
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service.h b/chrome/browser/ui/app_list/app_list_syncable_service.h
index b04cbd5..c363de83 100644
--- a/chrome/browser/ui/app_list/app_list_syncable_service.h
+++ b/chrome/browser/ui/app_list/app_list_syncable_service.h
@@ -103,6 +103,14 @@
   // Registers prefs to support local storage.
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
+  // Some sync behavior depends on whether or not an app was installed by
+  // default (as opposed to e.g. installed via explicit user action). Some
+  // tests want the AppListSyncableService to consider an app to be installed
+  // by default, without going through the heavyweight process of completely
+  // installing an app. These functions facilitate that.
+  static bool AppIsDefaultForTest(Profile* profile, const std::string& id);
+  static void SetAppIsDefaultForTest(Profile* profile, const std::string& id);
+
   // Adds |item| to |sync_items_| and |model_|. If a sync item already exists,
   // updates the existing sync item instead.
   void AddItem(std::unique_ptr<ChromeAppListItem> app_item);
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.cc b/chrome/browser/ui/ash/wallpaper_controller_client.cc
index 95a43f3..f81641e 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.cc
@@ -149,6 +149,50 @@
   InitController();
 }
 
+void WallpaperControllerClient::SetInitialWallpaper() {
+  // Apply device customization.
+  namespace customization_util = chromeos::customization_wallpaper_util;
+  if (customization_util::ShouldUseCustomizedDefaultWallpaper()) {
+    base::FilePath customized_default_small_path;
+    base::FilePath customized_default_large_path;
+    if (customization_util::GetCustomizedDefaultWallpaperPaths(
+            &customized_default_small_path, &customized_default_large_path)) {
+      wallpaper_controller_->SetCustomizedDefaultWallpaperPaths(
+          customized_default_small_path, customized_default_large_path);
+    }
+  }
+
+  // Guest wallpaper should be initialized when guest logs in.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          chromeos::switches::kGuestSession)) {
+    return;
+  }
+
+  // Do not set wallpaper in tests.
+  if (chromeos::WizardController::IsZeroDelayEnabled())
+    return;
+
+  // Show the wallpaper of the active user during an user session.
+  if (user_manager::UserManager::Get()->IsUserLoggedIn()) {
+    ShowUserWallpaper(
+        user_manager::UserManager::Get()->GetActiveUser()->GetAccountId());
+    return;
+  }
+
+  // Show a white wallpaper during OOBE.
+  if (session_manager::SessionManager::Get()->session_state() ==
+      session_manager::SessionState::OOBE) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(1, 1);
+    bitmap.eraseColor(SK_ColorWHITE);
+    wallpaper_controller_->ShowOneShotWallpaper(
+        gfx::ImageSkia::CreateFrom1xBitmap(bitmap));
+    return;
+  }
+
+  ShowWallpaperOnLoginScreen();
+}
+
 // static
 WallpaperControllerClient* WallpaperControllerClient::Get() {
   return g_wallpaper_controller_client_instance;
@@ -457,50 +501,6 @@
                       extensions::AppLaunchSource::kSourceChromeInternal));
 }
 
-void WallpaperControllerClient::OnReadyToSetWallpaper() {
-  // Apply device customization.
-  namespace customization_util = chromeos::customization_wallpaper_util;
-  if (customization_util::ShouldUseCustomizedDefaultWallpaper()) {
-    base::FilePath customized_default_small_path;
-    base::FilePath customized_default_large_path;
-    if (customization_util::GetCustomizedDefaultWallpaperPaths(
-            &customized_default_small_path, &customized_default_large_path)) {
-      wallpaper_controller_->SetCustomizedDefaultWallpaperPaths(
-          customized_default_small_path, customized_default_large_path);
-    }
-  }
-
-  // Guest wallpaper should be initialized when guest logs in.
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          chromeos::switches::kGuestSession)) {
-    return;
-  }
-
-  // Do not set wallpaper in tests.
-  if (chromeos::WizardController::IsZeroDelayEnabled())
-    return;
-
-  // Show the wallpaper of the active user during an user session.
-  if (user_manager::UserManager::Get()->IsUserLoggedIn()) {
-    ShowUserWallpaper(
-        user_manager::UserManager::Get()->GetActiveUser()->GetAccountId());
-    return;
-  }
-
-  // Show a white wallpaper during OOBE.
-  if (session_manager::SessionManager::Get()->session_state() ==
-      session_manager::SessionState::OOBE) {
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(1, 1);
-    bitmap.eraseColor(SK_ColorWHITE);
-    wallpaper_controller_->ShowOneShotWallpaper(
-        gfx::ImageSkia::CreateFrom1xBitmap(bitmap));
-    return;
-  }
-
-  ShowWallpaperOnLoginScreen();
-}
-
 void WallpaperControllerClient::OnFirstWallpaperAnimationFinished() {
   content::NotificationService::current()->Notify(
       chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.h b/chrome/browser/ui/ash/wallpaper_controller_client.h
index 11d72c5..16299018 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client.h
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.h
@@ -28,6 +28,10 @@
   // Tests can provide a mock interface for the ash controller.
   void InitForTesting(ash::WallpaperController* controller);
 
+  // Sets the initial wallpaper. Should be called after the session manager has
+  // been initialized.
+  void SetInitialWallpaper();
+
   static WallpaperControllerClient* Get();
 
   // Returns files identifier for the |account_id|.
@@ -100,7 +104,6 @@
 
   // ash::WallpaperControllerClient:
   void OpenWallpaperPicker() override;
-  void OnReadyToSetWallpaper() override;
   void OnFirstWallpaperAnimationFinished() override;
 
   void DeviceWallpaperImageFilePathChanged();
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 748d193..885c27b 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -35,8 +35,10 @@
 #include "chrome/browser/web_data_service_factory.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/url_constants.h"
+#include "components/autofill/content/browser/autofill_internals_service_factory.h"
 #include "components/autofill/content/browser/content_autofill_driver.h"
 #include "components/autofill/content/browser/content_autofill_driver_factory.h"
+#include "components/autofill/core/browser/autofill_internals_service.h"
 #include "components/autofill/core/browser/form_data_importer.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
 #include "components/autofill/core/browser/ui/payments/card_unmask_prompt_view.h"
@@ -513,6 +515,10 @@
 #endif
 }
 
+LogManager* ChromeAutofillClient::GetLogManager() const {
+  return log_manager_.get();
+}
+
 void ChromeAutofillClient::LoadRiskData(
     base::OnceCallback<void(const std::string&)> callback) {
   ::autofill::LoadRiskData(0, web_contents(), std::move(callback));
@@ -561,6 +567,13 @@
           user_prefs::UserPrefs::Get(web_contents->GetBrowserContext()),
           Profile::FromBrowserContext(web_contents->GetBrowserContext())
               ->IsOffTheRecord()) {
+  // TODO(crbug.com/928595): Replace the closure with a callback to the renderer
+  // that indicates if log messages should be sent from the renderer.
+  log_manager_ =
+      LogManager::Create(AutofillInternalsServiceFactory::GetForBrowserContext(
+                             web_contents->GetBrowserContext()),
+                         base::Closure());
+
 #if !defined(OS_ANDROID)
   // Since ZoomController is also a WebContentsObserver, we need to be careful
   // about disconnecting from it since the relative order of destruction of
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index f58da06..706f894e 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -17,6 +17,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
@@ -126,6 +127,7 @@
   bool ShouldShowSigninPromo() override;
   bool AreServerCardsSupported() override;
   void ExecuteCommand(int id) override;
+  LogManager* GetLogManager() const override;
 
   // RiskDataLoader:
   void LoadRiskData(
@@ -159,6 +161,7 @@
   std::unique_ptr<FormDataImporter> form_data_importer_;
   base::WeakPtr<AutofillPopupControllerImpl> popup_controller_;
   CardUnmaskPromptControllerImpl unmask_controller_;
+  std::unique_ptr<LogManager> log_manager_;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 
diff --git a/chrome/browser/ui/views/location_bar/intent_picker_view.cc b/chrome/browser/ui/views/location_bar/intent_picker_view.cc
index 6d4f9f8..d6712a0 100644
--- a/chrome/browser/ui/views/location_bar/intent_picker_view.cc
+++ b/chrome/browser/ui/views/location_bar/intent_picker_view.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/views/intent_picker_bubble_view.h"
 #include "chrome/grit/generated_resources.h"
-#include "components/omnibox/browser/vector_icons.h"
+#include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 
 #if defined(OS_CHROMEOS)
@@ -82,7 +82,7 @@
 }
 
 const gfx::VectorIcon& IntentPickerView::GetVectorIcon() const {
-  return omnibox::kOpenInNewIcon;
+  return vector_icons::kOpenInNewIcon;
 }
 
 base::string16 IntentPickerView::GetTextForTooltipAndAccessibleName() const {
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
index 31db50d..48fed38a 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
@@ -38,6 +38,12 @@
                       GetWebContents()->HasWritableNativeFileSystemHandles();
   if (has_write_access_ != had_write_access)
     UpdateIconImage();
+
+  // If icon isn't visible, a bubble shouldn't be shown either. Close it if
+  // it was still open.
+  if (!GetVisible())
+    NativeFileSystemUsageBubbleView::CloseCurrentBubble();
+
   return GetVisible() != was_visible || had_write_access != has_write_access_;
 }
 
@@ -79,6 +85,15 @@
             usage.writable_directories =
                 std::move(grants.directory_write_grants);
 
+            if (usage.readable_directories.empty() &&
+                usage.writable_files.empty() &&
+                usage.writable_directories.empty()) {
+              // Async looking up of usage might result in there no longer being
+              // usage by the time we get here, in that case just abort and
+              // don't show the bubble.
+              return;
+            }
+
             NativeFileSystemUsageBubbleView::ShowBubble(web_contents, origin,
                                                         std::move(usage));
           },
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
index b1d285dc..dbd2f6f 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
@@ -7,6 +7,7 @@
 #include "base/i18n/message_formatter.h"
 #include "base/i18n/unicodestring.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -19,6 +20,9 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
 #include "third_party/icu/source/common/unicode/unistr.h"
 #include "third_party/icu/source/common/unicode/utypes.h"
 #include "third_party/icu/source/i18n/unicode/listformatter.h"
@@ -27,6 +31,7 @@
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/button/image_button_factory.h"
 #include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/controls/table/table_view.h"
@@ -376,3 +381,23 @@
   LocationBarBubbleDelegateView::ChildPreferredSizeChanged(child);
   SizeToContents();
 }
+
+std::unique_ptr<views::View>
+NativeFileSystemUsageBubbleView::CreateExtraView() {
+  return views::MdTextButton::CreateSecondaryUiButton(
+      this,
+      l10n_util::GetStringUTF16(IDS_NATIVE_FILE_SYSTEM_USAGE_REMOVE_ACCESS));
+}
+
+void NativeFileSystemUsageBubbleView::ButtonPressed(views::Button* sender,
+                                                    const ui::Event& event) {
+  if (!web_contents())
+    return;
+
+  content::BrowserContext* profile = web_contents()->GetBrowserContext();
+  ChromeNativeFileSystemPermissionContext::
+      RevokeGrantsForOriginAndTabFromUIThread(
+          profile, origin_,
+          web_contents()->GetMainFrame()->GetProcess()->GetID(),
+          web_contents()->GetMainFrame()->GetRoutingID());
+}
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h
index f6b58377..941fc9ae 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h
@@ -10,9 +10,11 @@
 #include "base/files/file_path.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
 #include "ui/base/models/table_model.h"
+#include "ui/views/controls/button/button.h"
 #include "url/origin.h"
 
-class NativeFileSystemUsageBubbleView : public LocationBarBubbleDelegateView {
+class NativeFileSystemUsageBubbleView : public LocationBarBubbleDelegateView,
+                                        public views::ButtonListener {
  public:
   struct Usage {
     Usage();
@@ -70,6 +72,10 @@
   void CloseBubble() override;
   gfx::Size CalculatePreferredSize() const override;
   void ChildPreferredSizeChanged(views::View* child) override;
+  std::unique_ptr<views::View> CreateExtraView() override;
+
+  // views::ButtonListener:
+  void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
   // Singleton instance of the bubble. The bubble can only be shown on the
   // active browser window, so there is no case in which it will be shown
diff --git a/chrome/browser/ui/views/sharing/click_to_call/click_to_call_dialog_view.cc b/chrome/browser/ui/views/sharing/click_to_call/click_to_call_dialog_view.cc
index 2de9c33..82ced7d 100644
--- a/chrome/browser/ui/views/sharing/click_to_call/click_to_call_dialog_view.cc
+++ b/chrome/browser/ui/views/sharing/click_to_call/click_to_call_dialog_view.cc
@@ -24,9 +24,8 @@
 ClickToCallDialogView* g_bubble_ = nullptr;
 
 // Icon sizes in DIP.
-// TODO: Confirm the number with the team designer.
-constexpr int kPrimaryIconSize = 20;
-constexpr int kPrimaryIconBorderWidth = 6;
+constexpr int kPrimaryIconSize = 16;
+constexpr int kPrimaryIconBorderWidth = 8;
 
 SkColor GetColorfromTheme() {
   const ui::NativeTheme* native_theme =
@@ -35,23 +34,25 @@
       ui::NativeTheme::kColorId_DefaultIconColor);
 }
 
-std::unique_ptr<views::ImageView> CreateDeviceIcon(
-    const sync_pb::SyncEnums::DeviceType device_type) {
-  const gfx::VectorIcon* vector_icon;
-  if (device_type == sync_pb::SyncEnums::TYPE_TABLET) {
-    vector_icon = &kTabletIcon;
-  } else {
-    vector_icon = &kHardwareSmartphoneIcon;
-  }
-  gfx::ImageSkia image = gfx::CreateVectorIcon(*vector_icon, kPrimaryIconSize,
-                                               GetColorfromTheme());
+std::unique_ptr<views::ImageView> CreateIconView(const gfx::VectorIcon& icon) {
   auto icon_view = std::make_unique<views::ImageView>();
-  icon_view->SetImage(image);
+  icon_view->SetImage(
+      gfx::CreateVectorIcon(icon, kPrimaryIconSize, GetColorfromTheme()));
   icon_view->SetBorder(
       views::CreateEmptyBorder(gfx::Insets(kPrimaryIconBorderWidth)));
   return icon_view;
 }
 
+std::unique_ptr<views::ImageView> CreateDeviceIcon(
+    const sync_pb::SyncEnums::DeviceType device_type) {
+  const gfx::VectorIcon* vector_icon;
+  if (device_type == sync_pb::SyncEnums::TYPE_TABLET)
+    vector_icon = &kTabletIcon;
+  else
+    vector_icon = &kHardwareSmartphoneIcon;
+  return CreateIconView(*vector_icon);
+}
+
 }  // namespace
 
 // static
@@ -122,6 +123,7 @@
   DCHECK(index < devices_.size() + apps_.size());
 
   if (index < devices_.size()) {
+    LogClickToCallSelectedDeviceIndex(kSharingClickToCallUiDialog, index);
     controller_->OnDeviceChosen(devices_[index], base::DoNothing());
     CloseBubble();
     return;
@@ -130,6 +132,7 @@
   index -= devices_.size();
 
   if (index < apps_.size()) {
+    LogClickToCallSelectedAppIndex(kSharingClickToCallUiDialog, index);
     controller_->OnAppChosen(apps_[index]);
     CloseBubble();
   }
@@ -148,7 +151,7 @@
     auto dialog_button = std::make_unique<HoverButton>(
         this, CreateDeviceIcon(device.device_type()),
         base::UTF8ToUTF16(device.human_readable_name()),
-        base::string16() /* No subtitle */);
+        base::string16() /* Subtitle */);
     dialog_button->SetEnabled(true);
     dialog_button->set_tag(tag++);
     dialog_buttons_.push_back(
@@ -158,15 +161,16 @@
   // Apps:
   LogClickToCallAppsToShow(apps_.size());
   for (const auto& app : apps_) {
-    auto dialog_button = std::make_unique<HoverButton>(this, app.name);
-    // TODO(yasmo): Create Icon View.
+    auto dialog_button =
+        std::make_unique<HoverButton>(this, CreateIconView(app.icon), app.name,
+                                      base::string16() /* Subtitle */);
     dialog_button->SetEnabled(true);
     dialog_button->set_tag(tag++);
     dialog_buttons_.push_back(
         dialog_view->AddChildView(std::move(dialog_button)));
   }
 
-  // TODO: See if GetWidget can be not null:
+  // TODO(yasmo): See if GetWidget can be not null:
   if (GetWidget())
     SizeToContents();
 }
@@ -182,7 +186,7 @@
 }
 
 base::string16 ClickToCallDialogView::GetWindowTitle() const {
-  return base::UTF8ToUTF16(controller_->GetTitle());
+  return controller_->GetTitle();
 }
 
 void ClickToCallDialogView::WindowClosing() {
diff --git a/chrome/browser/ui/views/sharing/click_to_call/click_to_call_dialog_view_unittest.cc b/chrome/browser/ui/views/sharing/click_to_call/click_to_call_dialog_view_unittest.cc
index 3931fc7..61f0a0e 100644
--- a/chrome/browser/ui/views/sharing/click_to_call/click_to_call_dialog_view_unittest.cc
+++ b/chrome/browser/ui/views/sharing/click_to_call/click_to_call_dialog_view_unittest.cc
@@ -13,6 +13,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "components/send_tab_to_self/target_device_info.h"
+#include "components/vector_icons/vector_icons.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/event_utils.h"
@@ -108,8 +109,10 @@
 
   std::vector<ClickToCallSharingDialogController::App> SetUpApps() {
     std::vector<ClickToCallSharingDialogController::App> apps;
-    apps.emplace_back(std::string(), base::UTF8ToUTF16("app_1"), std::string());
-    apps.emplace_back(std::string(), base::UTF8ToUTF16("app_2"), std::string());
+    apps.emplace_back(vector_icons::kOpenInNewIcon, base::UTF8ToUTF16("app_1"),
+                      std::string());
+    apps.emplace_back(vector_icons::kOpenInNewIcon, base::UTF8ToUTF16("app_2"),
+                      std::string());
     return apps;
   }
 
@@ -160,7 +163,7 @@
 
 TEST_F(ClickToCallDialogViewTest, AppPressed) {
   ClickToCallSharingDialogController::App app(
-      std::string(), base::UTF8ToUTF16("app_1"), std::string());
+      vector_icons::kOpenInNewIcon, base::UTF8ToUTF16("app_1"), std::string());
   EXPECT_CALL(*controller_.get(), GetSyncedDevices())
       .WillOnce(Return(ByMove(std::move(devices_))));
   EXPECT_CALL(*controller_.get(), GetApps())
diff --git a/chrome/browser/ui/webui/autofill_internals_ui.cc b/chrome/browser/ui/webui/autofill_internals_ui.cc
index 54cd134..a7710a6 100644
--- a/chrome/browser/ui/webui/autofill_internals_ui.cc
+++ b/chrome/browser/ui/webui/autofill_internals_ui.cc
@@ -7,16 +7,20 @@
 #include <string>
 
 #include "base/bind.h"
-#include "base/scoped_observer.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/browser_resources.h"
-#include "components/autofill/core/browser/autofill_internals_logging.h"
+#include "components/autofill/content/browser/autofill_internals_service_factory.h"
+#include "components/autofill/core/browser/autofill_internals_service.h"
+#include "components/autofill/core/browser/logging/log_receiver.h"
 #include "components/grit/components_resources.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
+using autofill::AutofillInternalsService;
+using autofill::AutofillInternalsServiceFactory;
+
 namespace {
 
 content::WebUIDataSource* CreateAutofillInternalsHTMLSource() {
@@ -32,12 +36,11 @@
 
 // Message handler for AutofillInternalsLoggingImpl. The purpose of this class
 // is to enable safe calls to JavaScript, while the renderer is fully loaded.
-class AutofillInternalsUIHandler
-    : public content::WebUIMessageHandler,
-      public autofill::AutofillInternalsLogging::Observer {
+class AutofillInternalsUIHandler : public content::WebUIMessageHandler,
+                                   public autofill::LogReceiver {
  public:
   AutofillInternalsUIHandler() = default;
-  ~AutofillInternalsUIHandler() override = default;
+  ~AutofillInternalsUIHandler() override;
 
  private:
   // content::WebUIMessageHandler:
@@ -47,23 +50,26 @@
   void OnJavascriptAllowed() override;
   void OnJavascriptDisallowed() override;
 
-  // Implements autofill::AutofillInternalsLogging::Observer.
-  void Log(const base::Value& message) override;
-  void LogRaw(const base::Value& message) override;
-
-  // JavaScript call handler.
-  void OnLoaded(const base::ListValue* args);
+  // LogReceiver implementation.
+  void LogEntry(const base::Value& entry) override;
 
   void StartSubscription();
   void EndSubscription();
 
-  ScopedObserver<autofill::AutofillInternalsLogging,
-                 autofill::AutofillInternalsLogging::Observer>
-      observer_{this};
+  // JavaScript call handler.
+  void OnLoaded(const base::ListValue* args);
+
+  // Whether |this| is registered as a log receiver with the
+  // PasswordManagerInternalsService.
+  bool registered_with_logging_service_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(AutofillInternalsUIHandler);
 };
 
+AutofillInternalsUIHandler::~AutofillInternalsUIHandler() {
+  EndSubscription();
+}
+
 void AutofillInternalsUIHandler::RegisterMessages() {
   web_ui()->RegisterMessageCallback(
       "loaded", base::BindRepeating(&AutofillInternalsUIHandler::OnLoaded,
@@ -86,20 +92,36 @@
       base::Value(Profile::FromWebUI(web_ui())->IsIncognitoProfile()));
 }
 
-void AutofillInternalsUIHandler::Log(const base::Value& message) {
-  CallJavascriptFunction("addLog", message);
-}
-
-void AutofillInternalsUIHandler::LogRaw(const base::Value& message) {
-  CallJavascriptFunction("addRawLog", message);
+void AutofillInternalsUIHandler::LogEntry(const base::Value& entry) {
+  if (!registered_with_logging_service_ || entry.is_none())
+    return;
+  CallJavascriptFunction("addRawLog", entry);
 }
 
 void AutofillInternalsUIHandler::StartSubscription() {
-  observer_.Add(autofill::AutofillInternalsLogging::GetInstance());
+  AutofillInternalsService* service =
+      AutofillInternalsServiceFactory::GetForBrowserContext(
+          Profile::FromWebUI(web_ui()));
+
+  if (!service)
+    return;
+
+  registered_with_logging_service_ = true;
+
+  const auto& past_logs = service->RegisterReceiver(this);
+  for (const auto& entry : past_logs)
+    LogEntry(entry);
 }
 
 void AutofillInternalsUIHandler::EndSubscription() {
-  observer_.RemoveAll();
+  if (!registered_with_logging_service_)
+    return;
+  registered_with_logging_service_ = false;
+  AutofillInternalsService* service =
+      AutofillInternalsServiceFactory::GetForBrowserContext(
+          Profile::FromWebUI(web_ui()));
+  if (service)
+    service->UnregisterReceiver(this);
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index dd0f28bd..5e6f545c 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/ui/webui/bluetooth_internals/bluetooth_internals_ui.h"
 #include "chrome/browser/ui/webui/chromeos/account_manager_welcome_ui.h"
 #include "chrome/browser/ui/webui/chromeos/account_migration_welcome_ui.h"
+#include "chrome/browser/ui/webui/chromeos/camera/camera_ui.h"
 #include "chrome/browser/ui/webui/chromeos/in_session_password_change/password_change_ui.h"
 #include "chrome/browser/ui/webui/components_ui.h"
 #include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
@@ -552,6 +553,10 @@
     return &NewWebUI<SysInternalsUI>;
   if (url.host_piece() == chrome::kChromeUIAssistantOptInHost)
     return &NewWebUI<chromeos::AssistantOptInUI>;
+  if (url.host_piece() == chrome::kChromeUICameraHost &&
+      chromeos::CameraUI::IsEnabled()) {
+    return &NewWebUI<chromeos::CameraUI>;
+  }
 
   if (url.host_piece() == chrome::kChromeUIArcGraphicsTracingHost) {
     if (!base::FeatureList::IsEnabled(arc::kGraphicBuffersVisualizationTool))
diff --git a/chrome/browser/ui/webui/chromeos/camera/camera_ui.cc b/chrome/browser/ui/webui/chromeos/camera/camera_ui.cc
new file mode 100644
index 0000000..d9cb18e
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/camera/camera_ui.cc
@@ -0,0 +1,79 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/chromeos/camera/camera_ui.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/grit/camera_resources.h"
+#include "chrome/grit/camera_resources_map.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "mojo/public/js/grit/mojo_bindings_resources.h"
+
+namespace chromeos {
+
+namespace {
+
+content::WebUIDataSource* CreateCameraUIHTMLSource() {
+  content::WebUIDataSource* source =
+      content::WebUIDataSource::Create(chrome::kChromeUICameraHost);
+
+  // Add all settings resources.
+  for (size_t i = 0; i < kCameraResourcesSize; ++i) {
+    source->AddResourcePath(kCameraResources[i].name,
+                            kCameraResources[i].value);
+  }
+
+  // Add WebUI version of the CCA browser proxy.
+  source->AddResourcePath("src/js/browser_proxy/browser_proxy.js",
+                          IDR_CAMERA_WEBUI_BROWSER_PROXY);
+
+  // Add mojom-lite files under expected paths.
+  source->AddResourcePath("src/js/mojo/image_capture.mojom-lite.js",
+                          IDR_CAMERA_IMAGE_CAPTURE_MOJOM_LITE_JS);
+  source->AddResourcePath("src/js/mojo/camera_common.mojom-lite.js",
+                          IDR_CAMERA_CAMERA_COMMON_MOJOM_LITE_JS);
+  source->AddResourcePath("src/js/mojo/camera_metadata.mojom-lite.js",
+                          IDR_CAMERA_CAMERA_METADATA_MOJOM_LITE_JS);
+  source->AddResourcePath("src/js/mojo/camera_metadata_tags.mojom-lite.js",
+                          IDR_CAMERA_CAMERA_METADATA_TAGS_MOJOM_LITE_JS);
+  source->AddResourcePath("src/js/mojo/cros_image_capture.mojom-lite.js",
+                          IDR_CAMERA_CROS_IMAGE_CAPTURE_MOJOM_LITE_JS);
+  source->AddResourcePath("src/js/mojo/mojo_bindings_lite.js",
+                          IDR_MOJO_MOJO_BINDINGS_LITE_JS);
+
+  source->SetJsonPath("strings.js");
+
+  return source;
+}
+
+}  // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// CameraUI
+//
+///////////////////////////////////////////////////////////////////////////////
+
+CameraUI::CameraUI(content::WebUI* web_ui) : ui::MojoWebUIController(web_ui) {
+  Profile* profile = Profile::FromWebUI(web_ui);
+
+  // Set up the data source.
+  content::WebUIDataSource* source = CreateCameraUIHTMLSource();
+  content::WebUIDataSource::Add(profile, source);
+}
+
+CameraUI::~CameraUI() = default;
+
+// static
+bool CameraUI::IsEnabled() {
+  return base::FeatureList::IsEnabled(chromeos::features::kCameraSystemWebApp);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/camera/camera_ui.h b/chrome/browser/ui/webui/chromeos/camera/camera_ui.h
new file mode 100644
index 0000000..31dabca
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/camera/camera_ui.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_CAMERA_CAMERA_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_CAMERA_CAMERA_UI_H_
+
+#include "base/macros.h"
+#include "ui/webui/mojo_web_ui_controller.h"
+
+namespace chromeos {
+
+class CameraUI : public ui::MojoWebUIController {
+ public:
+  explicit CameraUI(content::WebUI* web_ui);
+  ~CameraUI() override;
+
+  // True when the Camera as a System Web App flag is true.
+  static bool IsEnabled();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CameraUI);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_CAMERA_CAMERA_UI_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.cc b/chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.cc
index 2481ece..c544f08 100644
--- a/chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.cc
@@ -40,6 +40,11 @@
       base::BindRepeating(&KerberosAccountsHandler::HandleRemoveKerberosAccount,
                           weak_factory_.GetWeakPtr()));
   web_ui()->RegisterMessageCallback(
+      "validateKerberosConfig",
+      base::BindRepeating(
+          &KerberosAccountsHandler::HandleValidateKerberosConfig,
+          weak_factory_.GetWeakPtr()));
+  web_ui()->RegisterMessageCallback(
       "setAsActiveKerberosAccount",
       base::BindRepeating(
           &KerberosAccountsHandler::HandleSetAsActiveKerberosAccount,
@@ -51,15 +56,15 @@
   AllowJavascript();
 
   CHECK_EQ(1U, args->GetSize());
-  base::Value callback_id = args->GetList()[0].Clone();
+  const std::string& callback_id = args->GetList()[0].GetString();
 
   KerberosCredentialsManager::Get().ListAccounts(
       base::BindOnce(&KerberosAccountsHandler::OnListAccounts,
-                     weak_factory_.GetWeakPtr(), std::move(callback_id)));
+                     weak_factory_.GetWeakPtr(), callback_id));
 }
 
 void KerberosAccountsHandler::OnListAccounts(
-    base::Value callback_id,
+    const std::string& callback_id,
     const kerberos::ListAccountsResponse& response) {
   base::ListValue accounts;
 
@@ -98,7 +103,7 @@
     accounts.GetList().push_back(std::move(account_dict));
   }
 
-  ResolveJavascriptCallback(callback_id, accounts);
+  ResolveJavascriptCallback(base::Value(callback_id), accounts);
 }
 
 void KerberosAccountsHandler::HandleAddKerberosAccount(
@@ -150,6 +155,35 @@
                             base::Value(static_cast<int>(error)));
 }
 
+void KerberosAccountsHandler::HandleValidateKerberosConfig(
+    const base::ListValue* args) {
+  AllowJavascript();
+
+  CHECK_EQ(2U, args->GetSize());
+  const std::string& callback_id = args->GetList()[0].GetString();
+  const std::string& krb5conf = args->GetList()[1].GetString();
+
+  KerberosCredentialsManager::Get().ValidateConfig(
+      krb5conf, base::BindOnce(&KerberosAccountsHandler::OnValidateConfig,
+                               weak_factory_.GetWeakPtr(), callback_id));
+}
+
+void KerberosAccountsHandler::OnValidateConfig(
+    const std::string& callback_id,
+    const kerberos::ValidateConfigResponse& response) {
+  base::Value error_info(base::Value::Type::DICTIONARY);
+  error_info.SetKey("code", base::Value(response.error_info().code()));
+  if (response.error_info().has_line_index()) {
+    error_info.SetKey("lineIndex",
+                      base::Value(response.error_info().line_index()));
+  }
+
+  base::Value value(base::Value::Type::DICTIONARY);
+  value.SetKey("error", base::Value(static_cast<int>(response.error())));
+  value.SetKey("errorInfo", std::move(error_info));
+  ResolveJavascriptCallback(base::Value(callback_id), std::move(value));
+}
+
 void KerberosAccountsHandler::HandleSetAsActiveKerberosAccount(
     const base::ListValue* args) {
   AllowJavascript();
diff --git a/chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.h b/chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.h
index 53d0a64..78493fb 100644
--- a/chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.h
@@ -55,11 +55,18 @@
   void OnRemoveAccount(const std::string& callback_id,
                        kerberos::ErrorType error);
 
+  // WebUI "validateKerberosConfig" message callback.
+  void HandleValidateKerberosConfig(const base::ListValue* args);
+
+  // Callback for the credential manager's ValidateConfig method.
+  void OnValidateConfig(const std::string& callback_id,
+                        const kerberos::ValidateConfigResponse& response);
+
   // WebUI "setAsActiveKerberosAccount" message callback.
   void HandleSetAsActiveKerberosAccount(const base::ListValue* args);
 
   // Callback for the credential manager's ListAccounts method.
-  void OnListAccounts(base::Value callback_id,
+  void OnListAccounts(const std::string& callback_id,
                       const kerberos::ListAccountsResponse& response);
 
   // Fires the "kerberos-accounts-changed" event, which refreshes the Kerberos
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index ccb9b29..8644f18 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1750,7 +1750,8 @@
               autofill::PersonalDataManagerFactory::GetForProfile(profile),
               profile->GetPrefs(),
               ProfileSyncServiceFactory::GetForProfile(profile),
-              /*is_test_mode=*/false));
+              /*is_test_mode=*/false,
+              /*log_manager=*/nullptr));
 
   AddLocalizedStringsBulk(html_source, kLocalizedStrings,
                           base::size(kLocalizedStrings));
@@ -1858,6 +1859,22 @@
      IDS_SETTINGS_KERBEROS_ERROR_PASSWORD_EXPIRED},
     {"kerberosErrorKdcEncType", IDS_SETTINGS_KERBEROS_ERROR_KDC_ENC_TYPE},
     {"kerberosErrorGeneral", IDS_SETTINGS_KERBEROS_ERROR_GENERAL},
+    {"kerberosConfigErrorSectionNestedInGroup",
+     IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_NESTED_IN_GROUP},
+    {"kerberosConfigErrorSectionSyntax",
+     IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_SYNTAX},
+    {"kerberosConfigErrorExpectedOpeningCurlyBrace",
+     IDS_SETTINGS_KERBEROS_CONFIG_ERROR_EXPECTED_OPENING_CURLY_BRACE},
+    {"kerberosConfigErrorExtraCurlyBrace",
+     IDS_SETTINGS_KERBEROS_CONFIG_ERROR_EXTRA_CURLY_BRACE},
+    {"kerberosConfigErrorRelationSyntax",
+     IDS_SETTINGS_KERBEROS_CONFIG_ERROR_RELATION_SYNTAX_ERROR},
+    {"kerberosConfigErrorKeyNotSupported",
+     IDS_SETTINGS_KERBEROS_CONFIG_ERROR_KEY_NOT_SUPPORTED},
+    {"kerberosConfigErrorSectionNotSupported",
+     IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_NOT_SUPPORTED},
+    {"kerberosConfigErrorKrb5FailedToParse",
+     IDS_SETTINGS_KERBEROS_CONFIG_ERROR_KRB5_FAILED_TO_PARSE},
     {"lockScreenAddFingerprint",
      IDS_SETTINGS_PEOPLE_LOCK_SCREEN_ADD_FINGERPRINT_BUTTON},
     {"lockScreenChangePinButton",
diff --git a/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc b/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc
index 9f94108c..fe0167c 100644
--- a/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc
+++ b/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/signin_util.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
@@ -33,7 +34,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/base/signin_pref_names.h"
-#include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index e905fe92..93af4dc 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -154,6 +154,7 @@
     if (is_chromeos) {
       sources += [
         "$root_gen_dir/ash/public/cpp/resources/ash_public_unscaled_resources.pak",
+        "$root_gen_dir/chrome/camera_resources.pak",
         "$root_gen_dir/chrome/cellular_setup_resources.pak",
         "$root_gen_dir/chrome/multidevice_setup_resources.pak",
         "$root_gen_dir/chrome/os_settings_resources.pak",
@@ -164,6 +165,7 @@
       deps += [
         "//ash/public/cpp/resources:ash_public_unscaled_resources",
         "//chrome/browser/resources:os_settings_resources",
+        "//chrome/browser/resources/chromeos:camera_resources",
         "//chrome/browser/resources/chromeos:cellular_setup_resources",
         "//chrome/browser/resources/chromeos:multidevice_setup_resources",
         "//chromeos/resources",
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index f274f85..11c9d68 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -29,6 +29,7 @@
 const char kChromeUIBluetoothInternalsHost[] = "bluetooth-internals";
 const char kChromeUIBookmarksHost[] = "bookmarks";
 const char kChromeUIBookmarksURL[] = "chrome://bookmarks/";
+const char kChromeUICameraHost[] = "camera";
 const char kChromeUICertificateViewerHost[] = "view-cert";
 const char kChromeUICertificateViewerURL[] = "chrome://view-cert/";
 const char kChromeUIChromeSigninHost[] = "chrome-signin";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 96e5dcb6..70d62499 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -37,6 +37,7 @@
 extern const char kChromeUIBluetoothInternalsHost[];
 extern const char kChromeUIBookmarksHost[];
 extern const char kChromeUIBookmarksURL[];
+extern const char kChromeUICameraHost[];
 extern const char kChromeUICertificateViewerHost[];
 extern const char kChromeUICertificateViewerURL[];
 extern const char kChromeUIChromeSigninHost[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 0bc48af..213876b 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3110,6 +3110,7 @@
     "../browser/sharing/vapid_key_manager_unittest.cc",
     "../browser/shell_integration_win_unittest.cc",
     "../browser/signin/account_consistency_mode_manager_unittest.cc",
+    "../browser/signin/account_id_from_account_info_unittest.cc",
     "../browser/signin/chrome_device_id_helper_unittest.cc",
     "../browser/signin/chrome_signin_client_unittest.cc",
     "../browser/signin/chrome_signin_helper_unittest.cc",
@@ -3351,6 +3352,7 @@
     "//chrome/browser/notifications:unit_tests",
     "//chrome/common:test_support",
     "//chrome/common/media_router:test_support",
+    "//components/account_id",
     "//components/autofill/content/renderer:test_support",
     "//components/browser_sync:test_support",
     "//components/component_updater:test_support",
diff --git a/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.js b/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.js
index f21a91d..bf0b66f 100644
--- a/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.js
+++ b/chrome/test/data/webui/settings/chromeos/people_page_kerberos_accounts_test.js
@@ -46,11 +46,18 @@
         'getAccounts',
         'addAccount',
         'removeAccount',
+        'validateConfig',
         'setAsActiveAccount',
       ]);
 
-      // Simulated error from a addKerberosAccount call.
+      // Simulated error from an addAccount call.
       this.addAccountError = settings.KerberosErrorType.kNone;
+
+      // Simulated error from a validateConfig call.
+      this.validateConfigResult = {
+        error: settings.KerberosErrorType.kNone,
+        errorInfo: {code: settings.KerberosConfigErrorCode.kNone}
+      };
     }
 
     /** @override */
@@ -75,6 +82,12 @@
     }
 
     /** @override */
+    validateConfig(account) {
+      this.methodCalled('validateConfig', account);
+      return Promise.resolve(this.validateConfigResult);
+    }
+
+    /** @override */
     setAsActiveAccount(account) {
       this.methodCalled('setAsActiveAccount', account);
     }
@@ -427,7 +440,7 @@
     }
 
     // Opens the Advanced Config dialog, sets |config| as Kerberos configuration
-    // and clicks 'Save'.
+    // and clicks 'Save'. Returns a promise with the validation result.
     function setConfig(config) {
       advancedConfigButton.click();
       Polymer.dom.flush();
@@ -437,6 +450,7 @@
       configElement.value = config;
       advancedConfigDialog.querySelector('.action-button').click();
       Polymer.dom.flush();
+      return browserProxy.whenCalled('validateConfig');
     }
 
     // Opens the Advanced Config dialog, asserts that |config| is set as
@@ -527,20 +541,22 @@
 
       username.value = EXPECTED_USER;
       password.value = EXPECTED_PASS;
-      setConfig(EXPECTED_CONFIG);
-      rememberPassword.checked = EXPECTED_REMEMBER_PASS;
+      return setConfig(EXPECTED_CONFIG).then(function(result) {
+        rememberPassword.checked = EXPECTED_REMEMBER_PASS;
 
-      assertFalse(actionButton.disabled);
-      actionButton.click();
-      return browserProxy.whenCalled('addAccount').then(function(args) {
-        assertEquals(EXPECTED_USER, args[AddParams.PRINCIPAL_NAME]);
-        assertEquals(EXPECTED_PASS, args[AddParams.PASSWORD]);
-        assertEquals(EXPECTED_REMEMBER_PASS, args[AddParams.REMEMBER_PASSWORD]);
-        assertEquals(EXPECTED_CONFIG, args[AddParams.CONFIG]);
+        assertFalse(actionButton.disabled);
+        actionButton.click();
+        return browserProxy.whenCalled('addAccount').then(function(args) {
+          assertEquals(EXPECTED_USER, args[AddParams.PRINCIPAL_NAME]);
+          assertEquals(EXPECTED_PASS, args[AddParams.PASSWORD]);
+          assertEquals(
+              EXPECTED_REMEMBER_PASS, args[AddParams.REMEMBER_PASSWORD]);
+          assertEquals(EXPECTED_CONFIG, args[AddParams.CONFIG]);
 
-        // Should be false if a new account is added. See also
-        // AllowExistingIsTrueForPresetAccounts test.
-        assertFalse(args[AddParams.ALLOW_EXISTING]);
+          // Should be false if a new account is added. See also
+          // AllowExistingIsTrueForPresetAccounts test.
+          assertFalse(args[AddParams.ALLOW_EXISTING]);
+        });
       });
     });
 
@@ -601,12 +617,19 @@
       assertTrue(!!advancedConfigDialog);
       assertTrue(advancedConfigDialog.open);
       assertTrue(addDialog.hidden);
-      advancedConfigDialog.querySelector('.action-button').click();
-
+      const saveButton = advancedConfigDialog.querySelector('.action-button');
+      assertFalse(saveButton.disabled);
+      saveButton.click();
       Polymer.dom.flush();
-      assertTrue(!dialog.$$('#advancedConfigDialog'));
-      assertFalse(addDialog.hidden);
-      assertTrue(addDialog.open);
+      assertTrue(saveButton.disabled);
+
+      return browserProxy.whenCalled('validateConfig').then(function() {
+        Polymer.dom.flush();
+        assertFalse(saveButton.disabled);
+        assertTrue(!dialog.$$('#advancedConfigDialog'));
+        assertFalse(addDialog.hidden);
+        assertTrue(addDialog.open);
+      });
     });
 
     test('AdvancedConfigurationSaveKeepsConfig', function() {
@@ -621,8 +644,10 @@
       advancedConfigDialog.querySelector('.action-button').click();
 
       // Changed value should stick.
-      Polymer.dom.flush();
-      assertConfig(modifiedConfig);
+      return browserProxy.whenCalled('validateConfig').then(function() {
+        Polymer.dom.flush();
+        assertConfig(modifiedConfig);
+      });
     });
 
     test('AdvancedConfigurationCancelResetsConfig', function() {
@@ -653,6 +678,53 @@
       assertTrue(advancedConfigDialog.querySelector('#config').disabled);
     });
 
+    test('AdvancedConfigurationValidationError', function(done) {
+      advancedConfigButton.click();
+      Polymer.dom.flush();
+      const advancedConfigDialog = dialog.$$('#advancedConfigDialog');
+      assertTrue(!!advancedConfigDialog);
+
+      // Cause a validation error.
+      browserProxy.validateConfigResult = {
+        error: settings.KerberosErrorType.kBadConfig,
+        errorInfo: {
+          code: settings.KerberosConfigErrorCode.kKeyNotSupported,
+          lineIndex: 0
+        }
+      };
+
+      // Clicking the action button (aka 'Save') validates the config.
+      advancedConfigDialog.querySelector('.action-button').click();
+
+      browserProxy.whenCalled('validateConfig').then(() => {
+        // Wait for dialog to process the 'validateConfig' result (sets error
+        // message etc.).
+        setTimeout(() => {
+          // Is some error text set?
+          const configError =
+              advancedConfigDialog.querySelector('#config-error-message');
+          assertTrue(!!configError);
+          assertNotEquals(0, configError.innerText.length);
+
+          // Is something selected?
+          const configElement = advancedConfigDialog.querySelector('#config');
+          const textArea = configElement.$.input;
+          assertEquals(0, textArea.selectionStart);
+          assertNotEquals(0, textArea.selectionEnd);
+
+          // Is the config dialog is still open?
+          assertTrue(advancedConfigDialog.open);
+          assertTrue(addDialog.hidden);
+
+          // Was the config not accepted?
+          advancedConfigDialog.querySelector('.cancel-button').click();
+          Polymer.dom.flush();
+          assertConfig(loadTimeData.getString('defaultKerberosConfig'));
+          done();
+        });
+      });
+    });
+
     // addAccount: KerberosErrorType.kNetworkProblem spawns a general error.
     test('AddAccountError_NetworkProblem', function() {
       checkAddAccountError(
diff --git a/chrome/utility/chrome_content_utility_client.cc b/chrome/utility/chrome_content_utility_client.cc
index 3a9f3134..5a46729 100644
--- a/chrome/utility/chrome_content_utility_client.cc
+++ b/chrome/utility/chrome_content_utility_client.cc
@@ -21,7 +21,7 @@
 #include "components/mirroring/service/mirroring_service.h"
 #include "components/services/patch/patch_service.h"
 #include "components/services/patch/public/mojom/constants.mojom.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "components/services/unzip/unzip_service.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
diff --git a/chromeos/components/proximity_auth/BUILD.gn b/chromeos/components/proximity_auth/BUILD.gn
index 0ffd7d52..c8971a7e 100644
--- a/chromeos/components/proximity_auth/BUILD.gn
+++ b/chromeos/components/proximity_auth/BUILD.gn
@@ -53,7 +53,7 @@
     "//base",
     "//chromeos/components/multidevice",
     "//chromeos/components/multidevice/logging",
-    "//chromeos/components/proximity_auth/public/interfaces",
+    "//chromeos/components/proximity_auth/public/mojom",
     "//chromeos/constants",
     "//chromeos/dbus/power",
     "//chromeos/dbus/session_manager",
diff --git a/chromeos/components/proximity_auth/public/interfaces/BUILD.gn b/chromeos/components/proximity_auth/public/mojom/BUILD.gn
similarity index 91%
rename from chromeos/components/proximity_auth/public/interfaces/BUILD.gn
rename to chromeos/components/proximity_auth/public/mojom/BUILD.gn
index 00e24008..31af832 100644
--- a/chromeos/components/proximity_auth/public/interfaces/BUILD.gn
+++ b/chromeos/components/proximity_auth/public/mojom/BUILD.gn
@@ -4,7 +4,7 @@
 
 import("//mojo/public/tools/bindings/mojom.gni")
 
-mojom("interfaces") {
+mojom("mojom") {
   sources = [
     "auth_type.mojom",
   ]
diff --git a/chromeos/components/proximity_auth/public/interfaces/OWNERS b/chromeos/components/proximity_auth/public/mojom/OWNERS
similarity index 100%
rename from chromeos/components/proximity_auth/public/interfaces/OWNERS
rename to chromeos/components/proximity_auth/public/mojom/OWNERS
diff --git a/chromeos/components/proximity_auth/public/interfaces/auth_type.mojom b/chromeos/components/proximity_auth/public/mojom/auth_type.mojom
similarity index 100%
rename from chromeos/components/proximity_auth/public/interfaces/auth_type.mojom
rename to chromeos/components/proximity_auth/public/mojom/auth_type.mojom
diff --git a/chromeos/components/proximity_auth/screenlock_bridge.h b/chromeos/components/proximity_auth/screenlock_bridge.h
index 1aa2a94e..ff5309c4 100644
--- a/chromeos/components/proximity_auth/screenlock_bridge.h
+++ b/chromeos/components/proximity_auth/screenlock_bridge.h
@@ -13,7 +13,7 @@
 #include "base/observer_list.h"
 #include "base/strings/string16.h"
 #include "base/values.h"
-#include "chromeos/components/proximity_auth/public/interfaces/auth_type.mojom.h"
+#include "chromeos/components/proximity_auth/public/mojom/auth_type.mojom.h"
 #include "components/account_id/account_id.h"
 
 namespace proximity_auth {
diff --git a/chromeos/dbus/kerberos/fake_kerberos_client.cc b/chromeos/dbus/kerberos/fake_kerberos_client.cc
index 2323cab..049197df 100644
--- a/chromeos/dbus/kerberos/fake_kerberos_client.cc
+++ b/chromeos/dbus/kerberos/fake_kerberos_client.cc
@@ -5,10 +5,12 @@
 #include "chromeos/dbus/kerberos/fake_kerberos_client.h"
 
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/files/file_util.h"
 #include "base/location.h"
+#include "base/strings/string_split.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "third_party/cros_system_api/dbus/kerberos/dbus-constants.h"
@@ -25,6 +27,39 @@
 // Fake renewal lifetime for TGTs.
 constexpr base::TimeDelta kTgtRenewal = base::TimeDelta::FromHours(24);
 
+// Blacklist for fake config validation.
+const char* const kBlacklistedConfigOptions[] = {
+    "allow_weak_crypto",
+    "ap_req_checksum_type",
+    "ccache_type",
+    "default_ccache_name ",
+    "default_client_keytab_name",
+    "default_keytab_name",
+    "default_realm",
+    "k5login_authoritative",
+    "k5login_directory",
+    "kdc_req_checksum_type",
+    "plugin_base_dir",
+    "realm_try_domains",
+    "safe_checksum_type",
+    "verify_ap_req_nofail",
+    "default_domain",
+    "v4_instance_convert",
+    "v4_realm",
+    "[appdefaults]",
+    "[plugins]",
+};
+
+// Performs a fake validation of a config line by just checking for some
+// non-whitelisted keywords. Returns true if no blacklisted items are contained.
+bool ValidateConfigLine(const std::string& line) {
+  for (const char* option : kBlacklistedConfigOptions) {
+    if (line.find(option) != std::string::npos)
+      return false;
+  }
+  return true;
+}
+
 // Posts |callback| on the current thread's task runner, passing it the
 // |response| message.
 template <class TProto>
@@ -147,6 +182,30 @@
   PostResponse(std::move(callback), kerberos::ERROR_NONE);
 }
 
+void FakeKerberosClient::ValidateConfig(
+    const kerberos::ValidateConfigRequest& request,
+    ValidateConfigCallback callback) {
+  kerberos::ConfigErrorInfo error_info;
+  error_info.set_code(kerberos::CONFIG_ERROR_NONE);
+
+  std::vector<std::string> lines = base::SplitString(
+      request.krb5conf(), "\r\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  for (size_t line_index = 0; line_index < lines.size(); ++line_index) {
+    if (!ValidateConfigLine(lines[line_index])) {
+      error_info.set_code(kerberos::CONFIG_ERROR_KEY_NOT_SUPPORTED);
+      error_info.set_line_index(static_cast<int>(line_index));
+      break;
+    }
+  }
+
+  kerberos::ValidateConfigResponse response;
+  response.set_error(error_info.code() != kerberos::CONFIG_ERROR_NONE
+                         ? kerberos::ERROR_BAD_CONFIG
+                         : kerberos::ERROR_NONE);
+  *response.mutable_error_info() = std::move(error_info);
+  PostProtoResponse(std::move(callback), response);
+}
+
 void FakeKerberosClient::AcquireKerberosTgt(
     const kerberos::AcquireKerberosTgtRequest& request,
     int password_fd,
diff --git a/chromeos/dbus/kerberos/fake_kerberos_client.h b/chromeos/dbus/kerberos/fake_kerberos_client.h
index 69c391fb..0616ff5 100644
--- a/chromeos/dbus/kerberos/fake_kerberos_client.h
+++ b/chromeos/dbus/kerberos/fake_kerberos_client.h
@@ -32,6 +32,8 @@
                     ListAccountsCallback callback) override;
   void SetConfig(const kerberos::SetConfigRequest& request,
                  SetConfigCallback callback) override;
+  void ValidateConfig(const kerberos::ValidateConfigRequest& request,
+                      ValidateConfigCallback callback) override;
   void AcquireKerberosTgt(const kerberos::AcquireKerberosTgtRequest& request,
                           int password_fd,
                           AcquireKerberosTgtCallback callback) override;
diff --git a/chromeos/dbus/kerberos/kerberos_client.cc b/chromeos/dbus/kerberos/kerberos_client.cc
index ab82cca..6b070206 100644
--- a/chromeos/dbus/kerberos/kerberos_client.cc
+++ b/chromeos/dbus/kerberos/kerberos_client.cc
@@ -82,6 +82,12 @@
     CallProtoMethod(kerberos::kSetConfigMethod, request, std::move(callback));
   }
 
+  void ValidateConfig(const kerberos::ValidateConfigRequest& request,
+                      ValidateConfigCallback callback) override {
+    CallProtoMethod(kerberos::kValidateConfigMethod, request,
+                    std::move(callback));
+  }
+
   void AcquireKerberosTgt(const kerberos::AcquireKerberosTgtRequest& request,
                           int password_fd,
                           AcquireKerberosTgtCallback callback) override {
diff --git a/chromeos/dbus/kerberos/kerberos_client.h b/chromeos/dbus/kerberos/kerberos_client.h
index dd599c7..a79310a 100644
--- a/chromeos/dbus/kerberos/kerberos_client.h
+++ b/chromeos/dbus/kerberos/kerberos_client.h
@@ -32,6 +32,8 @@
       base::OnceCallback<void(const kerberos::ListAccountsResponse& response)>;
   using SetConfigCallback =
       base::OnceCallback<void(const kerberos::SetConfigResponse& response)>;
+  using ValidateConfigCallback = base::OnceCallback<void(
+      const kerberos::ValidateConfigResponse& response)>;
   using AcquireKerberosTgtCallback = base::OnceCallback<void(
       const kerberos::AcquireKerberosTgtResponse& response)>;
   using GetKerberosFilesCallback = base::OnceCallback<void(
@@ -71,6 +73,9 @@
   virtual void SetConfig(const kerberos::SetConfigRequest& request,
                          SetConfigCallback callback) = 0;
 
+  virtual void ValidateConfig(const kerberos::ValidateConfigRequest& request,
+                              ValidateConfigCallback callback) = 0;
+
   virtual void AcquireKerberosTgt(
       const kerberos::AcquireKerberosTgtRequest& request,
       int password_fd,
diff --git a/chromeos/disks/disk_mount_manager.cc b/chromeos/disks/disk_mount_manager.cc
index 76c8380..ec19b3dd 100644
--- a/chromeos/disks/disk_mount_manager.cc
+++ b/chromeos/disks/disk_mount_manager.cc
@@ -49,6 +49,21 @@
   std::move(cb_data->callback).Run(cb_data->error_code);
 }
 
+std::string FormatFileSystemTypeToString(FormatFileSystemType filesystem) {
+  switch (filesystem) {
+    case FormatFileSystemType::kUnknown:
+      return "";
+    case FormatFileSystemType::kVfat:
+      return "vfat";
+    case FormatFileSystemType::kExfat:
+      return "exfat";
+    case FormatFileSystemType::kNtfs:
+      return "ntfs";
+  }
+  NOTREACHED() << "Unknown filesystem type " << static_cast<int>(filesystem);
+  return "";
+}
+
 // The DiskMountManager implementation.
 class DiskMountManagerImpl : public DiskMountManager,
                              public CrosDisksClient::Observer {
@@ -135,8 +150,14 @@
 
   // DiskMountManager override.
   void FormatMountedDevice(const std::string& mount_path,
-                           const std::string& filesystem,
+                           FormatFileSystemType filesystem,
                            const std::string& label) override {
+    if (filesystem == FormatFileSystemType::kUnknown) {
+      LOG(ERROR) << "Unknown filesystem passed to FormatMountedDevice";
+      OnFormatCompleted(FORMAT_ERROR_UNSUPPORTED_FILESYSTEM, mount_path);
+      return;
+    }
+
     MountPointMap::const_iterator mount_point = mount_points_.find(mount_path);
     if (mount_point == mount_points_.end()) {
       LOG(ERROR) << "Mount point with path \"" << mount_path << "\" not found.";
@@ -516,7 +537,7 @@
   }
 
   void OnUnmountPathForFormat(const std::string& device_path,
-                              const std::string& filesystem,
+                              FormatFileSystemType filesystem,
                               const std::string& label,
                               MountError error_code) {
     if (error_code == MOUNT_ERROR_NONE &&
@@ -529,15 +550,16 @@
 
   // Starts device formatting.
   void FormatUnmountedDevice(const std::string& device_path,
-                             const std::string& filesystem,
+                             FormatFileSystemType filesystem,
                              const std::string& label) {
     DiskMap::const_iterator disk = disks_.find(device_path);
     DCHECK(disk != disks_.end() && disk->second->mount_path().empty());
 
-    pending_format_changes_[device_path] = {filesystem, label};
+    const std::string filesystem_str = FormatFileSystemTypeToString(filesystem);
+    pending_format_changes_[device_path] = {filesystem_str, label};
 
     cros_disks_client_->Format(
-        device_path, filesystem, label,
+        device_path, filesystem_str, label,
         base::BindOnce(&DiskMountManagerImpl::OnFormatStarted,
                        weak_ptr_factory_.GetWeakPtr(), device_path));
   }
diff --git a/chromeos/disks/disk_mount_manager.h b/chromeos/disks/disk_mount_manager.h
index 2d6fbc7..8e6d50b 100644
--- a/chromeos/disks/disk_mount_manager.h
+++ b/chromeos/disks/disk_mount_manager.h
@@ -27,6 +27,14 @@
   MOUNT_CONDITION_UNSUPPORTED_FILESYSTEM,
 };
 
+// Possible filesystem types that can be passed to FormatMountedDevice.
+enum class FormatFileSystemType {
+  kUnknown = 0,
+  kVfat = 1,
+  kExfat = 2,
+  kNtfs = 3,
+};
+
 // This class handles the interaction with cros-disks.
 // Other classes can add themselves as observers.
 class COMPONENT_EXPORT(CHROMEOS_DISKS) DiskMountManager {
@@ -178,10 +186,10 @@
   // Formats device mounted at |mount_path| with the given filesystem and label.
   // Also unmounts the device before formatting.
   // Example: mount_path: /media/VOLUME_LABEL
-  //          filesystem: ntfs
+  //          filesystem: FormatFileSystemType::kNtfs
   //          label: MYUSB
   virtual void FormatMountedDevice(const std::string& mount_path,
-                                   const std::string& filesystem,
+                                   FormatFileSystemType filesystem,
                                    const std::string& label) = 0;
 
   // Renames Device given its mount path.
diff --git a/chromeos/disks/disk_mount_manager_unittest.cc b/chromeos/disks/disk_mount_manager_unittest.cc
index a34b8b4..c1372a5 100644
--- a/chromeos/disks/disk_mount_manager_unittest.cc
+++ b/chromeos/disks/disk_mount_manager_unittest.cc
@@ -37,8 +37,11 @@
 const char kReadOnlyDeviceSourcePath[] = "/device/read_only_source_path";
 const char kFileSystemType1[] = "ntfs";
 const char kFileSystemType2[] = "exfat";
-const char kFormatFileSystemType1[] = "vfat";
-const char kFormatFileSystemType2[] = "exfat";
+const FormatFileSystemType kFormatFileSystemType1 = FormatFileSystemType::kVfat;
+const FormatFileSystemType kFormatFileSystemType2 =
+    FormatFileSystemType::kExfat;
+const char kFormatFileSystemType1String[] = "vfat";
+const char kFormatFileSystemType2String[] = "exfat";
 const char kFormatLabel1[] = "UNTITLED";
 const char kFormatLabel2[] = "TESTUSB";
 
@@ -694,7 +697,7 @@
   EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
   EXPECT_EQ(kDevice1SourcePath,
             fake_cros_disks_client_->last_format_device_path());
-  EXPECT_EQ(kFormatFileSystemType1,
+  EXPECT_EQ(kFormatFileSystemType1String,
             fake_cros_disks_client_->last_format_filesystem());
   EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());
 
@@ -752,7 +755,7 @@
   EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
   EXPECT_EQ(kDevice1SourcePath,
             fake_cros_disks_client_->last_format_device_path());
-  EXPECT_EQ(kFormatFileSystemType1,
+  EXPECT_EQ(kFormatFileSystemType1String,
             fake_cros_disks_client_->last_format_filesystem());
   EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());
 
@@ -791,7 +794,7 @@
   EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
   EXPECT_EQ(kDevice1SourcePath,
             fake_cros_disks_client_->last_format_device_path());
-  EXPECT_EQ(kFormatFileSystemType1,
+  EXPECT_EQ(kFormatFileSystemType1String,
             fake_cros_disks_client_->last_format_filesystem());
   EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());
 
@@ -843,7 +846,7 @@
   EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
   EXPECT_EQ(kDevice1SourcePath,
             fake_cros_disks_client_->last_format_device_path());
-  EXPECT_EQ(kFormatFileSystemType1,
+  EXPECT_EQ(kFormatFileSystemType1String,
             fake_cros_disks_client_->last_format_filesystem());
   EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());
 
@@ -867,7 +870,7 @@
             observer_->GetFormatEvent(2));
 
   // Disk should have new values for file system type and device label name
-  EXPECT_EQ(kFormatFileSystemType1,
+  EXPECT_EQ(kFormatFileSystemType1String,
             disks.find(kDevice1SourcePath)->second->file_system_type());
   EXPECT_EQ(kFormatLabel1,
             disks.find(kDevice1SourcePath)->second->device_label());
@@ -894,7 +897,7 @@
   EXPECT_EQ(1, fake_cros_disks_client_->format_call_count());
   EXPECT_EQ(kDevice1SourcePath,
             fake_cros_disks_client_->last_format_device_path());
-  EXPECT_EQ(kFormatFileSystemType1,
+  EXPECT_EQ(kFormatFileSystemType1String,
             fake_cros_disks_client_->last_format_filesystem());
   EXPECT_EQ(kFormatLabel1, fake_cros_disks_client_->last_format_label());
 
@@ -927,7 +930,7 @@
   EXPECT_EQ(2, fake_cros_disks_client_->format_call_count());
   EXPECT_EQ(kDevice1SourcePath,
             fake_cros_disks_client_->last_format_device_path());
-  EXPECT_EQ(kFormatFileSystemType2,
+  EXPECT_EQ(kFormatFileSystemType2String,
             fake_cros_disks_client_->last_format_filesystem());
   EXPECT_EQ(kFormatLabel2, fake_cros_disks_client_->last_format_label());
 
diff --git a/chromeos/disks/mock_disk_mount_manager.h b/chromeos/disks/mock_disk_mount_manager.h
index b9a98160f..a9f33b8 100644
--- a/chromeos/disks/mock_disk_mount_manager.h
+++ b/chromeos/disks/mock_disk_mount_manager.h
@@ -50,7 +50,7 @@
   MOCK_METHOD1(RemountAllRemovableDrives, void(MountAccessMode));
   MOCK_METHOD3(FormatMountedDevice,
                void(const std::string&,
-                    const std::string&,
+                    FormatFileSystemType,
                     const std::string&));
   MOCK_METHOD2(RenameMountedDevice,
                void(const std::string&, const std::string&));
diff --git a/components/autofill/content/browser/BUILD.gn b/components/autofill/content/browser/BUILD.gn
index 1a228a58..a9cea4c 100644
--- a/components/autofill/content/browser/BUILD.gn
+++ b/components/autofill/content/browser/BUILD.gn
@@ -7,6 +7,8 @@
 
 jumbo_static_library("browser") {
   sources = [
+    "autofill_internals_service_factory.cc",
+    "autofill_internals_service_factory.h",
     "content_autofill_driver.cc",
     "content_autofill_driver.h",
     "content_autofill_driver_factory.cc",
@@ -22,6 +24,7 @@
     "//components/autofill/content/common:mojo_interfaces",
     "//components/autofill/core/browser",
     "//components/autofill/core/common",
+    "//components/keyed_service/content",
     "//skia",
   ]
   deps = [
@@ -65,6 +68,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "autofill_internals_service_factory_unittest.cc",
     "content_autofill_driver_unittest.cc",
     "key_press_handler_manager_unittest.cc",
   ]
diff --git a/components/autofill/content/browser/DEPS b/components/autofill/content/browser/DEPS
index 078458d..8891909 100644
--- a/components/autofill/content/browser/DEPS
+++ b/components/autofill/content/browser/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+content/public/browser",
+  "+components/keyed_service/content",
   "+crypto/random.h",
   "+gpu/config/gpu_info.h",
   "+services/device/public",
diff --git a/components/autofill/content/browser/autofill_internals_service_factory.cc b/components/autofill/content/browser/autofill_internals_service_factory.cc
new file mode 100644
index 0000000..eeaae7f
--- /dev/null
+++ b/components/autofill/content/browser/autofill_internals_service_factory.cc
@@ -0,0 +1,37 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/content/browser/autofill_internals_service_factory.h"
+
+#include "components/autofill/core/browser/autofill_internals_service.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+namespace autofill {
+
+// static
+AutofillInternalsService* AutofillInternalsServiceFactory::GetForBrowserContext(
+    content::BrowserContext* context) {
+  return static_cast<AutofillInternalsService*>(
+      GetInstance()->GetServiceForBrowserContext(context, /* create = */ true));
+}
+
+// static
+AutofillInternalsServiceFactory*
+AutofillInternalsServiceFactory::GetInstance() {
+  return base::Singleton<AutofillInternalsServiceFactory>::get();
+}
+
+AutofillInternalsServiceFactory::AutofillInternalsServiceFactory()
+    : BrowserContextKeyedServiceFactory(
+          "AutofillInternalsService",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+AutofillInternalsServiceFactory::~AutofillInternalsServiceFactory() {}
+
+KeyedService* AutofillInternalsServiceFactory::BuildServiceInstanceFor(
+    content::BrowserContext* /* context */) const {
+  return new AutofillInternalsService();
+}
+
+}  // namespace autofill
diff --git a/components/autofill/content/browser/autofill_internals_service_factory.h b/components/autofill/content/browser/autofill_internals_service_factory.h
new file mode 100644
index 0000000..bd4bd5e
--- /dev/null
+++ b/components/autofill/content/browser/autofill_internals_service_factory.h
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOFILL_INTERNALS_SERVICE_FACTORY_H_
+#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOFILL_INTERNALS_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace autofill {
+
+class AutofillInternalsService;
+
+// BrowserContextKeyedServiceFactory for AutofillInternalsService. It
+// does not override BrowserContextKeyedServiceFactory::GetBrowserContextToUse,
+// which means that no service is returned in Incognito.
+class AutofillInternalsServiceFactory
+    : public BrowserContextKeyedServiceFactory {
+ public:
+  static AutofillInternalsService* GetForBrowserContext(
+      content::BrowserContext* context);
+
+  static AutofillInternalsServiceFactory* GetInstance();
+
+ private:
+  friend struct base::DefaultSingletonTraits<AutofillInternalsServiceFactory>;
+
+  AutofillInternalsServiceFactory();
+  ~AutofillInternalsServiceFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(AutofillInternalsServiceFactory);
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOFILL_INTERNALS_SERVICE_FACTORY_H_
diff --git a/components/autofill/content/browser/autofill_internals_service_factory_unittest.cc b/components/autofill/content/browser/autofill_internals_service_factory_unittest.cc
new file mode 100644
index 0000000..bdf7559
--- /dev/null
+++ b/components/autofill/content/browser/autofill_internals_service_factory_unittest.cc
@@ -0,0 +1,76 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/content/browser/autofill_internals_service_factory.h"
+
+#include "components/autofill/core/browser/autofill_internals_service.h"
+#include "components/autofill/core/browser/logging/log_receiver.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using autofill::AutofillInternalsService;
+using autofill::AutofillInternalsServiceFactory;
+
+namespace {
+
+const char kTestText[] = "abcd1234";
+
+class MockLogReceiver : public autofill::LogReceiver {
+ public:
+  MockLogReceiver() {}
+
+  MOCK_METHOD1(LogEntry, void(const base::Value&));
+};
+
+}  // namespace
+
+class AutofillInternalsServiceFactoryTest : public testing::Test {
+ public:
+  content::TestBrowserThreadBundle thread_bundle_;
+  content::TestBrowserContext browser_context_;
+
+  void SetUp() override {
+    BrowserContextDependencyManager::GetInstance()->MarkBrowserContextLive(
+        &browser_context_);
+  }
+
+  void TearDown() override {
+    BrowserContextDependencyManager::GetInstance()
+        ->DestroyBrowserContextServices(&browser_context_);
+  }
+};
+
+// When the profile is not incognito, it should be possible to activate the
+// service.
+TEST_F(AutofillInternalsServiceFactoryTest, ServiceActiveNonIncognito) {
+  browser_context_.set_is_off_the_record(false);
+  AutofillInternalsService* service =
+      AutofillInternalsServiceFactory::GetForBrowserContext(&browser_context_);
+  testing::StrictMock<MockLogReceiver> receiver;
+
+  ASSERT_TRUE(service);
+  EXPECT_EQ(std::vector<base::Value>(), service->RegisterReceiver(&receiver));
+
+  base::Value log_entry = autofill::LogRouter::CreateEntryForText(kTestText);
+  EXPECT_CALL(receiver, LogEntry(testing::Eq(testing::ByRef(log_entry))))
+      .Times(1);
+  service->ProcessLog(kTestText);
+
+  service->UnregisterReceiver(&receiver);
+}
+
+// When the browser profile is incognito, it should not be possible to activate
+// the service.
+TEST_F(AutofillInternalsServiceFactoryTest, ServiceNotActiveIncognito) {
+  browser_context_.set_is_off_the_record(true);
+  AutofillInternalsService* service =
+      AutofillInternalsServiceFactory::GetForBrowserContext(&browser_context_);
+  // BrowserContextKeyedServiceFactory::GetBrowserContextToUse should return
+  // nullptr for |browser_context|, because |browser_context| is incognito.
+  // Therefore the returned |service| should also be nullptr.
+  EXPECT_FALSE(service);
+}
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 82fe5c8d..2ddca8c 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -54,8 +54,8 @@
     "autofill_handler_proxy.h",
     "autofill_ie_toolbar_import_win.cc",
     "autofill_ie_toolbar_import_win.h",
-    "autofill_internals_logging.cc",
-    "autofill_internals_logging.h",
+    "autofill_internals_service.cc",
+    "autofill_internals_service.h",
     "autofill_manager.cc",
     "autofill_manager.h",
     "autofill_manager_test_delegate.h",
@@ -145,6 +145,8 @@
     "geo/subkey_requester.h",
     "logging/log_buffer.cc",
     "logging/log_buffer.h",
+    "logging/log_buffer_submitter.cc",
+    "logging/log_buffer_submitter.h",
     "logging/log_manager.cc",
     "logging/log_manager.h",
     "logging/log_receiver.h",
@@ -532,7 +534,7 @@
     "autofill_experiments_unittest.cc",
     "autofill_external_delegate_unittest.cc",
     "autofill_ie_toolbar_import_win_unittest.cc",
-    "autofill_internals_logging_unittest.cc",
+    "autofill_internals_service_unittest.cc",
     "autofill_manager_unittest.cc",
     "autofill_merge_unittest.cc",
     "autofill_metrics_unittest.cc",
@@ -564,6 +566,7 @@
     "geo/country_names_unittest.cc",
     "geo/phone_number_i18n_unittest.cc",
     "geo/subkey_requester_unittest.cc",
+    "logging/log_buffer_submitter_unittest.cc",
     "logging/log_buffer_unittest.cc",
     "logging/log_manager_unittest.cc",
     "logging/log_router_unittest.cc",
diff --git a/components/autofill/core/browser/autofill_client.cc b/components/autofill/core/browser/autofill_client.cc
index e7c56b1..98d69be2 100644
--- a/components/autofill/core/browser/autofill_client.cc
+++ b/components/autofill/core/browser/autofill_client.cc
@@ -19,4 +19,8 @@
   return std::string();
 }
 
+LogManager* AutofillClient::GetLogManager() const {
+  return nullptr;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 793b0d3..8b4766e9e 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -58,6 +58,7 @@
 class CreditCard;
 class FormDataImporter;
 class FormStructure;
+class LogManager;
 class MigratableCreditCard;
 class PersonalDataManager;
 class StrikeDatabase;
@@ -406,6 +407,10 @@
 
   // Handles simple actions for the autofill popups.
   virtual void ExecuteCommand(int id) = 0;
+
+  // Returns a LogManager instance. May be null for platforms that don't support
+  // this.
+  virtual LogManager* GetLogManager() const;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_experiments.cc b/components/autofill/core/browser/autofill_experiments.cc
index bb6be4a..643d4036 100644
--- a/components/autofill/core/browser/autofill_experiments.cc
+++ b/components/autofill/core/browser/autofill_experiments.cc
@@ -12,12 +12,14 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "components/autofill/core/browser/autofill_internals_logging.h"
+#include "components/autofill/core/browser/autofill_internals_service.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/payments/payments_util.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/autofill_internals/logging_scope.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/autofill/core/common/autofill_prefs.h"
 #include "components/autofill/core/common/autofill_switches.h"
@@ -37,13 +39,15 @@
 bool IsCreditCardUploadEnabled(const PrefService* pref_service,
                                const syncer::SyncService* sync_service,
                                const std::string& user_email,
-                               const AutofillSyncSigninState sync_state) {
+                               const AutofillSyncSigninState sync_state,
+                               LogManager* log_manager) {
   if (!sync_service) {
     // If credit card sync is not active, we're not offering to upload cards.
     AutofillMetrics::LogCardUploadEnabledMetric(
         AutofillMetrics::CardUploadEnabledMetric::SYNC_SERVICE_NULL,
         sync_state);
-    AutofillInternalsLogging::Log("SYNC_SERVICE_NULL");
+    if (log_manager)
+      log_manager->Log() << LoggingScope::kContext << "SYNC_SERVICE_NULL";
     return false;
   }
 
@@ -52,7 +56,10 @@
         AutofillMetrics::CardUploadEnabledMetric::
             SYNC_SERVICE_PERSISTENT_AUTH_ERROR,
         sync_state);
-    AutofillInternalsLogging::Log("SYNC_SERVICE_PERSISTENT_ERROR");
+    if (log_manager) {
+      log_manager->Log() << LoggingScope::kContext
+                         << "SYNC_SERVICE_PERSISTENT_ERROR";
+    }
     return false;
   }
 
@@ -61,8 +68,11 @@
         AutofillMetrics::CardUploadEnabledMetric::
             SYNC_SERVICE_MISSING_AUTOFILL_WALLET_DATA_ACTIVE_TYPE,
         sync_state);
-    AutofillInternalsLogging::Log(
-        "SYNC_SERVICE_MISSING_AUTOFILL_WALLET_ACTIVE_DATA_TYPE");
+    if (log_manager) {
+      log_manager->Log()
+          << LoggingScope::kContext
+          << "SYNC_SERVICE_MISSING_AUTOFILL_WALLET_ACTIVE_DATA_TYPE";
+    }
     return false;
   }
 
@@ -74,8 +84,11 @@
           AutofillMetrics::CardUploadEnabledMetric::
               SYNC_SERVICE_MISSING_AUTOFILL_PROFILE_ACTIVE_TYPE,
           sync_state);
-      AutofillInternalsLogging::Log(
-          "SYNC_SERVICE_MISSING_AUTOFILL_PROFILE_ACTIVE_DATA_TYPE");
+      if (log_manager) {
+        log_manager->Log()
+            << LoggingScope::kContext
+            << "SYNC_SERVICE_MISSING_AUTOFILL_PROFILE_ACTIVE_DATA_TYPE";
+      }
       return false;
     }
   } else {
@@ -91,7 +104,10 @@
           AutofillMetrics::CardUploadEnabledMetric::
               ACCOUNT_WALLET_STORAGE_UPLOAD_DISABLED,
           sync_state);
-      AutofillInternalsLogging::Log("ACCOUNT_WALLET_STORAGE_UPLOAD_DISABLED");
+      if (log_manager) {
+        log_manager->Log() << LoggingScope::kContext
+                           << "ACCOUNT_WALLET_STORAGE_UPLOAD_DISABLED";
+      }
       return false;
     }
   }
@@ -105,7 +121,10 @@
         AutofillMetrics::CardUploadEnabledMetric::
             USING_SECONDARY_SYNC_PASSPHRASE,
         sync_state);
-    AutofillInternalsLogging::Log("USER_HAS_SECONDARY_SYNC_PASSPHRASE");
+    if (log_manager) {
+      log_manager->Log() << LoggingScope::kContext
+                         << "USER_HAS_SECONDARY_SYNC_PASSPHRASE";
+    }
     return false;
   }
 
@@ -115,7 +134,10 @@
     AutofillMetrics::LogCardUploadEnabledMetric(
         AutofillMetrics::CardUploadEnabledMetric::LOCAL_SYNC_ENABLED,
         sync_state);
-    AutofillInternalsLogging::Log("USER_ONLY_SYNCING_LOCALLY");
+    if (log_manager) {
+      log_manager->Log() << LoggingScope::kContext
+                         << "USER_ONLY_SYNCING_LOCALLY";
+    }
     return false;
   }
 
@@ -124,7 +146,10 @@
     AutofillMetrics::LogCardUploadEnabledMetric(
         AutofillMetrics::CardUploadEnabledMetric::PAYMENTS_INTEGRATION_DISABLED,
         sync_state);
-    AutofillInternalsLogging::Log("PAYMENTS_INTEGRATION_DISABLED");
+    if (log_manager) {
+      log_manager->Log() << LoggingScope::kContext
+                         << "PAYMENTS_INTEGRATION_DISABLED";
+    }
     return false;
   }
 
@@ -132,7 +157,8 @@
   if (user_email.empty()) {
     AutofillMetrics::LogCardUploadEnabledMetric(
         AutofillMetrics::CardUploadEnabledMetric::EMAIL_EMPTY, sync_state);
-    AutofillInternalsLogging::Log("USER_EMAIL_EMPTY");
+    if (log_manager)
+      log_manager->Log() << LoggingScope::kContext << "USER_EMAIL_EMPTY";
     return false;
   }
 
@@ -150,7 +176,10 @@
     AutofillMetrics::LogCardUploadEnabledMetric(
         AutofillMetrics::CardUploadEnabledMetric::EMAIL_DOMAIN_NOT_SUPPORTED,
         sync_state);
-    AutofillInternalsLogging::Log("USER_EMAIL_DOMAIN_NOT_SUPPORTED");
+    if (log_manager) {
+      log_manager->Log() << LoggingScope::kContext
+                         << "USER_EMAIL_DOMAIN_NOT_SUPPORTED";
+    }
     return false;
   }
 
@@ -158,7 +187,10 @@
     AutofillMetrics::LogCardUploadEnabledMetric(
         AutofillMetrics::CardUploadEnabledMetric::AUTOFILL_UPSTREAM_DISABLED,
         sync_state);
-    AutofillInternalsLogging::Log("AUTOFILL_UPSTREAM_NOT_ENABLED");
+    if (log_manager) {
+      log_manager->Log() << LoggingScope::kContext
+                         << "AUTOFILL_UPSTREAM_NOT_ENABLED";
+    }
     return false;
   }
 
@@ -171,7 +203,8 @@
 bool IsCreditCardMigrationEnabled(PersonalDataManager* personal_data_manager,
                                   PrefService* pref_service,
                                   syncer::SyncService* sync_service,
-                                  bool is_test_mode) {
+                                  bool is_test_mode,
+                                  LogManager* log_manager) {
   // If |is_test_mode| is set, assume we are in a browsertest and
   // credit card upload should be enabled by default to fix flaky
   // local card migration browsertests.
@@ -179,7 +212,7 @@
       !IsCreditCardUploadEnabled(
           pref_service, sync_service,
           personal_data_manager->GetAccountInfoForPaymentsServer().email,
-          personal_data_manager->GetSyncSigninState())) {
+          personal_data_manager->GetSyncSigninState(), log_manager)) {
     return false;
   }
 
diff --git a/components/autofill/core/browser/autofill_experiments.h b/components/autofill/core/browser/autofill_experiments.h
index c4a3274..b1280e74 100644
--- a/components/autofill/core/browser/autofill_experiments.h
+++ b/components/autofill/core/browser/autofill_experiments.h
@@ -19,6 +19,7 @@
 
 namespace autofill {
 
+class LogManager;
 class PersonalDataManager;
 
 // Returns true if uploading credit cards to Wallet servers is enabled. This
@@ -27,13 +28,15 @@
 bool IsCreditCardUploadEnabled(const PrefService* pref_service,
                                const syncer::SyncService* sync_service,
                                const std::string& user_email,
-                               const AutofillSyncSigninState sync_state);
+                               const AutofillSyncSigninState sync_state,
+                               LogManager* log_manager);
 
 // Returns true if autofill local card migration flow is enabled.
 bool IsCreditCardMigrationEnabled(PersonalDataManager* personal_data_manager,
                                   PrefService* pref_service,
                                   syncer::SyncService* sync_service,
-                                  bool is_test_mode);
+                                  bool is_test_mode,
+                                  LogManager* log_manager);
 
 // Returns true if autofill suggestions are disabled via experiment. The
 // disabled experiment isn't the same as disabling autofill completely since we
diff --git a/components/autofill/core/browser/autofill_experiments_unittest.cc b/components/autofill/core/browser/autofill_experiments_unittest.cc
index b8ff2be7..7fbc4f2 100644
--- a/components/autofill/core/browser/autofill_experiments_unittest.cc
+++ b/components/autofill/core/browser/autofill_experiments_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/autofill/core/common/autofill_prefs.h"
@@ -26,6 +27,7 @@
   void SetUp() override {
     pref_service_.registry()->RegisterBooleanPref(
         prefs::kAutofillWalletImportEnabled, true);
+    log_manager_ = LogManager::Create(nullptr, base::Closure());
   }
 
   bool IsCreditCardUploadEnabled(const AutofillSyncSigninState sync_state) {
@@ -35,13 +37,15 @@
   bool IsCreditCardUploadEnabled(const std::string& user_email,
                                  const AutofillSyncSigninState sync_state) {
     return autofill::IsCreditCardUploadEnabled(&pref_service_, &sync_service_,
-                                               user_email, sync_state);
+                                               user_email, sync_state,
+                                               log_manager_.get());
   }
 
   base::test::ScopedFeatureList scoped_feature_list_;
   TestingPrefServiceSimple pref_service_;
   syncer::TestSyncService sync_service_;
   base::HistogramTester histogram_tester;
+  std::unique_ptr<LogManager> log_manager_;
 };
 
 // Testing each scenario, followed by logging the metrics for various
diff --git a/components/autofill/core/browser/autofill_internals_logging.cc b/components/autofill/core/browser/autofill_internals_logging.cc
deleted file mode 100644
index 06989fd7..0000000
--- a/components/autofill/core/browser/autofill_internals_logging.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/autofill/core/browser/autofill_internals_logging.h"
-
-#include "base/no_destructor.h"
-
-namespace autofill {
-
-LogBuffer& operator<<(LogBuffer& buf, LoggingScope scope) {
-  if (!buf.active())
-    return buf;
-  return buf << Tag{"div"} << Attrib{"scope", LoggingScopeToString(scope)}
-             << Attrib{"class", "log-entry"};
-}
-
-LogBuffer& operator<<(LogBuffer& buf, LogMessage message) {
-  if (!buf.active())
-    return buf;
-  return buf << Tag{"div"} << Attrib{"message", LogMessageToString(message)}
-             << Attrib{"class", "log-message"} << LogMessageValue(message);
-}
-
-// Implementation of AutofillInternalsMessage.
-
-AutofillInternalsMessage::AutofillInternalsMessage(Destination destination)
-    : destination_(destination) {
-  if (destination_ == Destination::kAutofillInternals)
-    buffer_.set_active(IsLogAutofillInternalsActive());
-}
-
-AutofillInternalsMessage::~AutofillInternalsMessage() {
-  base::Value message = buffer_.RetrieveResult();
-  if (message.is_none())
-    return;
-
-  switch (destination_) {
-    case Destination::kAutofillInternals:
-      AutofillInternalsLogging::LogRaw(std::move(message));
-      break;
-    case Destination::kPasswordManagerInternals:
-      LOG(ERROR) << "Not implemented, logging to password manager internals";
-      break;
-  }
-}
-
-// Implementation of AutofillInternalsLogging.
-
-AutofillInternalsLogging::AutofillInternalsLogging() = default;
-
-AutofillInternalsLogging::~AutofillInternalsLogging() = default;
-
-// static
-AutofillInternalsLogging* AutofillInternalsLogging::GetInstance() {
-  static base::NoDestructor<AutofillInternalsLogging> logger;
-  return logger.get();
-}
-
-void AutofillInternalsLogging::AddObserver(
-    AutofillInternalsLogging::Observer* observer) {
-  observers_.AddObserver(observer);
-}
-
-void AutofillInternalsLogging::RemoveObserver(
-    const AutofillInternalsLogging::Observer* observer) {
-  observers_.RemoveObserver(observer);
-}
-
-bool AutofillInternalsLogging::HasObservers() const {
-  return observers_.might_have_observers();
-}
-
-// static
-void AutofillInternalsLogging::Log(const std::string& message) {
-  auto& observers = AutofillInternalsLogging::GetInstance()->observers_;
-  if (!observers.might_have_observers())
-    return;
-  base::Value message_value(message);
-  for (Observer& obs : observers)
-    obs.Log(message_value);
-}
-
-// static
-void AutofillInternalsLogging::LogRaw(const base::Value& message) {
-  auto& observers = AutofillInternalsLogging::GetInstance()->observers_;
-  for (Observer& obs : observers)
-    obs.LogRaw(message);
-}
-
-// Implementation of other methods.
-
-bool IsLogAutofillInternalsActive() {
-  return AutofillInternalsLogging::GetInstance()->HasObservers();
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_internals_logging.h b/components/autofill/core/browser/autofill_internals_logging.h
deleted file mode 100644
index 492a5c4..0000000
--- a/components/autofill/core/browser/autofill_internals_logging.h
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_INTERNALS_LOGGING_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_INTERNALS_LOGGING_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "base/observer_list.h"
-#include "base/observer_list_types.h"
-#include "base/values.h"
-#include "components/autofill/core/browser/logging/log_buffer.h"
-#include "components/autofill/core/common/autofill_internals/log_message.h"
-#include "components/autofill/core/common/autofill_internals/logging_scope.h"
-
-//
-// Autofill Internals Logging
-// --------------------------
-// This framework serves the purpose of exposing log messages of Chrome Autofill
-// (and in the future also Chrome's Password Manager) via
-// chrome://autofill-internals (and in the future also
-// chrome://password-manager-internals).
-//
-// The framework uses a so called LoggingScope as a mechanism to indicate during
-// which phase of processing a website a message was generated. Logging scopes
-// comprise for example the phase of parsing forms, or filling forms. Each
-// logging scope is displayed in a visually distinct color.
-//
-// The framework also uses predefined LogMessages. Each message consists of a
-// fixed label and a string. The labels allow for specialized styling (e.g.
-// highlighting specific messages because they indicate failures).
-//
-// The desired pattern to generate log messages is to pass a scope, a log
-// message and then parameters.
-//
-// LOG_AF_INTERNALS << LoggingScope::kSomeScope << LogMessage::kSomeLogMessage
-//     << Br{} << more << Br{} << parameters;
-//
-// Note that each call of LOG_AF_INTERNALS spawns a new log entry.
-//
-// See components/autofill/core/common/autofill_internals/logging_scope.h for
-// the definition of scopes.
-// See components/autofill/core/common/autofill_internals/log_message.h for
-// the definition of messages.
-
-namespace autofill {
-
-LogBuffer& operator<<(LogBuffer& buf, LoggingScope scope);
-
-LogBuffer& operator<<(LogBuffer& buf, LogMessage message);
-
-// A container for a LogBuffer that submits the buffer to the
-// corresponding internals page on destruction.
-class AutofillInternalsMessage {
- public:
-  // Whether the message will be sent to chrome://autofill-internals or
-  // chrome://password-manager-internals.
-  enum class Destination {
-    kAutofillInternals,
-    kPasswordManagerInternals,
-  };
-
-  explicit AutofillInternalsMessage(Destination destination);
-  ~AutofillInternalsMessage();
-
-  LogBuffer& buffer() { return buffer_; }
-
- private:
-  Destination destination_;
-  LogBuffer buffer_;
-  DISALLOW_COPY_AND_ASSIGN(AutofillInternalsMessage);
-};
-
-// Class that forwards log messages to the WebUI for display.
-class AutofillInternalsLogging {
- public:
-  class Observer : public base::CheckedObserver {
-   public:
-    virtual void Log(const base::Value& message) = 0;
-    virtual void LogRaw(const base::Value& message) = 0;
-  };
-
-  AutofillInternalsLogging();
-  virtual ~AutofillInternalsLogging();
-
-  void AddObserver(AutofillInternalsLogging::Observer* observer);
-  void RemoveObserver(const Observer* observer);
-  bool HasObservers() const;
-
-  // Main API function that is called when something is logged.
-  static void Log(const std::string& message);
-
-  static AutofillInternalsLogging* GetInstance();
-
- private:
-  static void LogRaw(const base::Value& message);
-
-  // Grant access to LogRaw().
-  friend class AutofillInternalsMessage;
-
-  base::ObserverList<AutofillInternalsLogging::Observer> observers_;
-
-  DISALLOW_COPY_AND_ASSIGN(AutofillInternalsLogging);
-};
-
-bool IsLogAutofillInternalsActive();
-
-#define LOG_AF_INTERNALS                                         \
-  AutofillInternalsMessage(                                      \
-      AutofillInternalsMessage::Destination::kAutofillInternals) \
-      .buffer()
-
-#define LOG_PWMGR_INTERNALS                                             \
-  AutofillInternalsMessage(                                             \
-      AutofillInternalsMessage::Destination::kPasswordManagerInternals) \
-      .buffer()
-
-}  // namespace autofill
-
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_INTERNALS_LOGGING_H_
diff --git a/components/autofill/core/browser/autofill_internals_logging_unittest.cc b/components/autofill/core/browser/autofill_internals_logging_unittest.cc
deleted file mode 100644
index 0ba557a..0000000
--- a/components/autofill/core/browser/autofill_internals_logging_unittest.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/autofill/core/browser/autofill_internals_logging.h"
-
-#include "base/json/json_writer.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/utf_string_conversions.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest-death-test.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace autofill {
-
-TEST(AutofillInternalsLogging, Scope) {
-  LogBuffer buffer;
-  buffer << LoggingScope::kContext;
-  std::string json;
-  EXPECT_TRUE(base::JSONWriter::Write(buffer.RetrieveResult(), &json));
-  EXPECT_EQ(R"({"attributes":{"class":"log-entry","scope":"Context"},)"
-            R"("type":"node","value":"div"})",
-            json);
-}
-
-TEST(AutofillInternalsLogging, Message) {
-  LogBuffer buffer;
-  buffer << LogMessage::kParsedForms;
-  std::string json;
-  EXPECT_TRUE(base::JSONWriter::Write(buffer.RetrieveResult(), &json));
-  EXPECT_EQ(R"({"attributes":{"class":"log-message","message":"ParsedForms"},)"
-            R"("children":[{"type":"text","value":"Parsed forms:"}],)"
-            R"("type":"node","value":"div"})",
-            json);
-}
-
-class MockAutofillInternalsLogging : public AutofillInternalsLogging::Observer {
- public:
-  MOCK_METHOD1(Log, void(const base::Value&));
-  MOCK_METHOD1(LogRaw, void(const base::Value&));
-};
-
-// Don't add further tests to this. AutofillInternalsLogging uses a global
-// instance and if more tests are executed in parallel, these can interfere.
-TEST(AutofillInternalsMessage, VerifySubmissionOnDestruction) {
-  LogBuffer buffer;
-  buffer << LoggingScope::kContext;
-  base::Value expected = buffer.RetrieveResult();
-
-  MockAutofillInternalsLogging logging_internals;
-  EXPECT_CALL(logging_internals, LogRaw(testing::Eq(testing::ByRef(expected))));
-  AutofillInternalsLogging::GetInstance()->AddObserver(&logging_internals);
-  LOG_AF_INTERNALS << LoggingScope::kContext;
-  AutofillInternalsLogging::GetInstance()->RemoveObserver(&logging_internals);
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_internals_service.cc b/components/autofill/core/browser/autofill_internals_service.cc
new file mode 100644
index 0000000..7b1a61a
--- /dev/null
+++ b/components/autofill/core/browser/autofill_internals_service.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/autofill_internals_service.h"
+
+namespace autofill {
+
+LogBuffer& operator<<(LogBuffer& buf, LoggingScope scope) {
+  if (!buf.active())
+    return buf;
+  return buf << Tag{"div"} << Attrib{"scope", LoggingScopeToString(scope)}
+             << Attrib{"class", "log-entry"};
+}
+
+LogBuffer& operator<<(LogBuffer& buf, LogMessage message) {
+  if (!buf.active())
+    return buf;
+  return buf << Tag{"div"} << Attrib{"message", LogMessageToString(message)}
+             << Attrib{"class", "log-message"} << LogMessageValue(message);
+}
+
+AutofillInternalsService::AutofillInternalsService() = default;
+AutofillInternalsService::~AutofillInternalsService() = default;
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_internals_service.h b/components/autofill/core/browser/autofill_internals_service.h
new file mode 100644
index 0000000..812e0131
--- /dev/null
+++ b/components/autofill/core/browser/autofill_internals_service.h
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_INTERNALS_SERVICE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_INTERNALS_SERVICE_H_
+
+#include "base/macros.h"
+#include "components/autofill/core/browser/logging/log_buffer.h"
+#include "components/autofill/core/browser/logging/log_router.h"
+#include "components/autofill/core/common/autofill_internals/log_message.h"
+#include "components/autofill/core/common/autofill_internals/logging_scope.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace autofill {
+
+// TODO(crbug.com/928595) This is a temporary home for these operations.
+// They are not really associated with the AutofillInternalsService class.
+LogBuffer& operator<<(LogBuffer& buf, LoggingScope scope);
+
+LogBuffer& operator<<(LogBuffer& buf, LogMessage message);
+
+// TODO(crbug.com/928595) This class is just a KeyedService version of
+// autofill::LogRouter. Either make the LogRouter a KeyedService or at least
+// unify this class with PasswordManagerInternalsService.
+
+// Collects the logs for the autofill internals page and distributes them to all
+// open tabs with the internals page.
+class AutofillInternalsService : public KeyedService,
+                                 public autofill::LogRouter {
+ public:
+  // There are only two ways in which the service depends on the BrowserContext:
+  // 1) There is one service per each non-incognito BrowserContext.
+  // 2) No service will be created for an incognito BrowserContext.
+  // Both properties are guaranteed by the BrowserContextKeyedFactory framework,
+  // so the service itself does not need the context on creation.
+  AutofillInternalsService();
+  ~AutofillInternalsService() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AutofillInternalsService);
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_INTERNALS_SERVICE_H_
diff --git a/components/autofill/core/browser/autofill_internals_service_unittest.cc b/components/autofill/core/browser/autofill_internals_service_unittest.cc
new file mode 100644
index 0000000..5d45ba83
--- /dev/null
+++ b/components/autofill/core/browser/autofill_internals_service_unittest.cc
@@ -0,0 +1,35 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/autofill_internals_service.h"
+
+#include "base/json/json_writer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest-death-test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+
+TEST(AutofillInternalsService, Scope) {
+  LogBuffer buffer;
+  buffer << LoggingScope::kContext;
+  std::string json;
+  EXPECT_TRUE(base::JSONWriter::Write(buffer.RetrieveResult(), &json));
+  EXPECT_EQ(R"({"attributes":{"class":"log-entry","scope":"Context"},)"
+            R"("type":"node","value":"div"})",
+            json);
+}
+
+TEST(AutofillInternalsService, Message) {
+  LogBuffer buffer;
+  buffer << LogMessage::kParsedForms;
+  std::string json;
+  EXPECT_TRUE(base::JSONWriter::Write(buffer.RetrieveResult(), &json));
+  EXPECT_EQ(R"({"attributes":{"class":"log-message","message":"ParsedForms"},)"
+            R"("children":[{"type":"text","value":"Parsed forms:"}],)"
+            R"("type":"node","value":"div"})",
+            json);
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index b8106b2..0cd272f0 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -41,7 +41,7 @@
 #include "components/autofill/core/browser/autofill_data_util.h"
 #include "components/autofill/core/browser/autofill_external_delegate.h"
 #include "components/autofill/core/browser/autofill_field.h"
-#include "components/autofill/core/browser/autofill_internals_logging.h"
+#include "components/autofill/core/browser/autofill_internals_service.h"
 #include "components/autofill/core/browser/autofill_manager_test_delegate.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/autofill_type.h"
@@ -55,6 +55,7 @@
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/geo/country_names.h"
 #include "components/autofill/core/browser/geo/phone_number_i18n.h"
+#include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/metrics/address_form_event_logger.h"
 #include "components/autofill/core/browser/metrics/credit_card_form_event_logger.h"
 #include "components/autofill/core/browser/metrics/form_events.h"
@@ -240,16 +241,17 @@
 }
 
 void LogAutofillTypePredictionsAvailable(
+    LogManager* log_manager,
     const std::vector<FormStructure*>& forms) {
-  if (!IsLogAutofillInternalsActive())
+  if (!log_manager || !log_manager->IsLoggingActive())
     return;
 
   LogBuffer buffer;
   for (FormStructure* form : forms)
     buffer << *form;
 
-  LOG_AF_INTERNALS << LoggingScope::kParsing << LogMessage::kParsedForms
-                   << std::move(buffer);
+  log_manager->Log() << LoggingScope::kParsing << LogMessage::kParsedForms
+                     << std::move(buffer);
 }
 
 }  // namespace
@@ -1181,7 +1183,7 @@
   // annotate forms with the predicted types or add console warnings.
   driver()->SendAutofillTypePredictionsToRenderer(queried_forms);
 
-  LogAutofillTypePredictionsAvailable(queried_forms);
+  LogAutofillTypePredictionsAvailable(log_manager_, queried_forms);
 }
 
 void AutofillManager::OnCreditCardFetched(bool did_succeed,
@@ -1335,6 +1337,7 @@
     AutofillDownloadManagerState enable_download_manager)
     : AutofillHandler(driver),
       client_(client),
+      log_manager_(client_->GetLogManager()),
       app_locale_(app_locale),
       personal_data_(personal_data),
       field_filler_(app_locale, client->GetAddressNormalizer()),
@@ -1759,8 +1762,8 @@
   // queryable forms will be updated once the field type query is complete.
   driver()->SendAutofillTypePredictionsToRenderer(non_queryable_forms);
   driver()->SendAutofillTypePredictionsToRenderer(queryable_forms);
-  LogAutofillTypePredictionsAvailable(non_queryable_forms);
-  LogAutofillTypePredictionsAvailable(queryable_forms);
+  LogAutofillTypePredictionsAvailable(log_manager_, non_queryable_forms);
+  LogAutofillTypePredictionsAvailable(log_manager_, queryable_forms);
 
   // Query the server if at least one of the forms was parsed.
   if (!queryable_forms.empty() && download_manager_) {
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index fce8c537..d855b77 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -59,6 +59,7 @@
 class AutofillType;
 class CreditCard;
 class FormStructureBrowserTest;
+class LogManager;
 
 struct FormData;
 struct FormFieldData;
@@ -519,6 +520,8 @@
 
   AutofillClient* const client_;
 
+  LogManager* log_manager_;
+
   std::string app_locale_;
 
   // The personal data manager, used to save and load personal data to/from the
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index a1fa3f1..9483f6a 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -29,12 +29,12 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "components/autofill/core/browser/autofill_data_util.h"
-#include "components/autofill/core/browser/autofill_internals_logging.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/autofill_type.h"
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/form_parsing/field_candidates.h"
 #include "components/autofill/core/browser/form_parsing/form_field.h"
+#include "components/autofill/core/browser/logging/log_buffer.h"
 #include "components/autofill/core/browser/proto/legacy_proto_bridge.h"
 #include "components/autofill/core/browser/randomized_encoder.h"
 #include "components/autofill/core/browser/rationalization_util.h"
diff --git a/components/autofill/core/browser/logging/log_buffer_submitter.cc b/components/autofill/core/browser/logging/log_buffer_submitter.cc
new file mode 100644
index 0000000..e409d7e6
--- /dev/null
+++ b/components/autofill/core/browser/logging/log_buffer_submitter.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/logging/log_buffer_submitter.h"
+
+#include "components/autofill/core/browser/logging/log_router.h"
+
+namespace autofill {
+
+LogBufferSubmitter::LogBufferSubmitter(LogRouter* destination, bool active)
+    : destination_(destination) {
+  buffer_.set_active(destination != nullptr && active);
+}
+
+LogBufferSubmitter::LogBufferSubmitter(LogBufferSubmitter&& that) noexcept =
+    default;
+
+LogBufferSubmitter::~LogBufferSubmitter() {
+  base::Value message = buffer_.RetrieveResult();
+  if (!destination_ || message.is_none())
+    return;
+  destination_->ProcessLog(std::move(message));
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/logging/log_buffer_submitter.h b/components/autofill/core/browser/logging/log_buffer_submitter.h
new file mode 100644
index 0000000..5925e84
--- /dev/null
+++ b/components/autofill/core/browser/logging/log_buffer_submitter.h
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_LOGGING_LOG_BUFFER_SUBMITTER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_LOGGING_LOG_BUFFER_SUBMITTER_H_
+
+#include "base/macros.h"
+#include "components/autofill/core/browser/logging/log_buffer.h"
+
+namespace autofill {
+
+class LogRouter;
+
+// A container for a LogBuffer that submits the buffer to the passed destination
+// on destruction.
+//
+// Use it in the following way:
+// LogBufferSubmitter(destination) << "Foobar";
+// The submitter is destroyed after this statement and "Foobar" is logged.
+class LogBufferSubmitter {
+ public:
+  LogBufferSubmitter(LogRouter* destination, bool active);
+  LogBufferSubmitter(LogBufferSubmitter&& that) noexcept;
+  ~LogBufferSubmitter();
+
+  LogBuffer& buffer() { return buffer_; }
+  operator LogBuffer&() { return buffer_; }
+
+ private:
+  LogRouter* destination_;
+  LogBuffer buffer_;
+  DISALLOW_COPY_AND_ASSIGN(LogBufferSubmitter);
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_LOGGING_LOG_BUFFER_SUBMITTER_H_
diff --git a/components/autofill/core/browser/logging/log_buffer_submitter_unittest.cc b/components/autofill/core/browser/logging/log_buffer_submitter_unittest.cc
new file mode 100644
index 0000000..7caf49b
--- /dev/null
+++ b/components/autofill/core/browser/logging/log_buffer_submitter_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/logging/log_buffer_submitter.h"
+
+#include "base/callback.h"
+#include "base/values.h"
+#include "components/autofill/core/browser/logging/log_manager.h"
+#include "components/autofill/core/browser/logging/log_receiver.h"
+#include "components/autofill/core/browser/logging/log_router.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest-death-test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+
+class MockLogReceiver : public LogReceiver {
+ public:
+  MOCK_METHOD1(LogEntry, void(const base::Value&));
+};
+
+TEST(LogBufferSubmitter, VerifySubmissionOnDestruction) {
+  LogBuffer buffer;
+  buffer << 42;
+  base::Value expected = buffer.RetrieveResult();
+
+  MockLogReceiver receiver;
+  LogRouter router;
+  ignore_result(router.RegisterReceiver(&receiver));
+  std::unique_ptr<LogManager> log_manager =
+      LogManager::Create(&router, base::Closure());
+
+  EXPECT_CALL(receiver, LogEntry(testing::Eq(testing::ByRef(expected))));
+  log_manager->Log() << 42;
+  log_manager.reset();
+  router.UnregisterReceiver(&receiver);
+}
+
+TEST(LogBufferSubmitter, NoEmptySubmission) {
+  MockLogReceiver receiver;
+  LogRouter router;
+  ignore_result(router.RegisterReceiver(&receiver));
+  std::unique_ptr<LogManager> log_manager =
+      LogManager::Create(&router, base::Closure());
+
+  EXPECT_CALL(receiver, LogEntry(testing::_)).Times(0);
+  log_manager->Log();
+  log_manager.reset();
+  router.UnregisterReceiver(&receiver);
+}
+
+TEST(LogBufferSubmitter, CorrectActivation) {
+  std::unique_ptr<LogManager> log_manager =
+      LogManager::Create(nullptr, base::Closure());
+  EXPECT_FALSE(log_manager->Log().buffer().active());
+
+  LogRouter router;
+  MockLogReceiver receiver;
+  ignore_result(router.RegisterReceiver(&receiver));
+  std::unique_ptr<LogManager> log_manager_2 =
+      LogManager::Create(&router, base::Closure());
+  EXPECT_TRUE(log_manager_2->Log().buffer().active());
+  router.UnregisterReceiver(&receiver);
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/logging/log_manager.cc b/components/autofill/core/browser/logging/log_manager.cc
index a94a4ee1..6ad0e7d 100644
--- a/components/autofill/core/browser/logging/log_manager.cc
+++ b/components/autofill/core/browser/logging/log_manager.cc
@@ -23,6 +23,7 @@
   void LogTextMessage(const std::string& text) const override;
   void LogEntry(base::Value&& entry) const override;
   bool IsLoggingActive() const override;
+  LogBufferSubmitter Log() override;
 
  private:
   // A LogRouter instance obtained on construction. May be null.
@@ -90,6 +91,10 @@
   return can_use_log_router_ && !is_suspended_;
 }
 
+LogBufferSubmitter LogManagerImpl::Log() {
+  return LogBufferSubmitter(log_router_, IsLoggingActive());
+}
+
 }  // namespace
 
 // static
diff --git a/components/autofill/core/browser/logging/log_manager.h b/components/autofill/core/browser/logging/log_manager.h
index 942145a..ba959387 100644
--- a/components/autofill/core/browser/logging/log_manager.h
+++ b/components/autofill/core/browser/logging/log_manager.h
@@ -10,6 +10,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "components/autofill/core/browser/logging/log_buffer_submitter.h"
 
 namespace base {
 class Value;
@@ -53,6 +54,9 @@
   static std::unique_ptr<LogManager> Create(
       LogRouter* log_router,
       base::Closure notification_callback);
+
+  // This is the preferred way to submitting log entries.
+  virtual LogBufferSubmitter Log() = 0;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/logging/stub_log_manager.cc b/components/autofill/core/browser/logging/stub_log_manager.cc
index 52ed346..8fab7f9 100644
--- a/components/autofill/core/browser/logging/stub_log_manager.cc
+++ b/components/autofill/core/browser/logging/stub_log_manager.cc
@@ -18,4 +18,8 @@
   return false;
 }
 
+LogBufferSubmitter StubLogManager::Log() {
+  return LogBufferSubmitter(nullptr, false);
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/logging/stub_log_manager.h b/components/autofill/core/browser/logging/stub_log_manager.h
index c8ec53e..0a84282 100644
--- a/components/autofill/core/browser/logging/stub_log_manager.h
+++ b/components/autofill/core/browser/logging/stub_log_manager.h
@@ -25,6 +25,7 @@
   void LogTextMessage(const std::string& text) const override;
   void LogEntry(base::Value&& entry) const override;
   bool IsLoggingActive() const override;
+  LogBufferSubmitter Log() override;
 
   DISALLOW_COPY_AND_ASSIGN(StubLogManager);
 };
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager.cc b/components/autofill/core/browser/payments/credit_card_save_manager.cc
index f37991e..d843105d 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_save_manager.cc
@@ -273,7 +273,7 @@
   return ::autofill::IsCreditCardUploadEnabled(
       client_->GetPrefs(), client_->GetSyncService(),
       personal_data_manager_->GetAccountInfoForPaymentsServer().email,
-      personal_data_manager_->GetSyncSigninState());
+      personal_data_manager_->GetSyncSigninState(), client_->GetLogManager());
 }
 
 bool CreditCardSaveManager::IsUploadEnabledForNetwork(
diff --git a/components/autofill/core/browser/payments/local_card_migration_manager.cc b/components/autofill/core/browser/payments/local_card_migration_manager.cc
index 0200f835..0394a92 100644
--- a/components/autofill/core/browser/payments/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/payments/local_card_migration_manager.cc
@@ -208,7 +208,7 @@
 bool LocalCardMigrationManager::IsCreditCardMigrationEnabled() {
   return ::autofill::IsCreditCardMigrationEnabled(
       personal_data_manager_, client_->GetPrefs(), client_->GetSyncService(),
-      /*is_test_mode=*/observer_for_testing_);
+      /*is_test_mode=*/observer_for_testing_, client_->GetLogManager());
 }
 
 void LocalCardMigrationManager::OnDidGetUploadDetails(
diff --git a/components/autofill_assistant/browser/client.h b/components/autofill_assistant/browser/client.h
index d8fa36f..1f8703a8 100644
--- a/components/autofill_assistant/browser/client.h
+++ b/components/autofill_assistant/browser/client.h
@@ -23,10 +23,13 @@
  public:
   virtual ~Client() = default;
 
-  // Show the UI, creates one if necessary.
-  virtual void ShowUI() = 0;
+  // Attaches the controller to the UI.
+  //
+  // This method does whatever is necessary to guarantee that, at the end of the
+  // call, there is a Controller associated with a UI.
+  virtual void AttachUI() = 0;
 
-  // Destroys the UI, but keep the controller, if any exists.
+  // Destroys the UI immediately.
   virtual void DestroyUI() = 0;
 
   // Returns the API key to be used for requests to the backend.
diff --git a/components/autofill_assistant/browser/controller.cc b/components/autofill_assistant/browser/controller.cc
index cfc8a4c8..3ccf226 100644
--- a/components/autofill_assistant/browser/controller.cc
+++ b/components/autofill_assistant/browser/controller.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/json/json_writer.h"
 #include "base/no_destructor.h"
 #include "base/strings/string_util.h"
@@ -76,17 +77,6 @@
 }
 
 UiController* Controller::GetUiController() {
-  if (will_shutdown_) {
-    // Never call the UI controller after having announced a shutdown, no matter
-    // what happens; this is part of the contract of UIDelegate.
-    //
-    // TODO(crbug/925947): Once UIController has become observer, clear list of
-    // observers instead.
-    if (!noop_ui_controller_) {
-      noop_ui_controller_ = std::make_unique<UiController>();
-    }
-    return noop_ui_controller_.get();
-  }
   return client_->GetUiController();
 }
 
@@ -401,6 +391,9 @@
 }
 
 void Controller::EnterStoppedState() {
+  if (script_tracker_)
+    script_tracker_->StopScript();
+
   ClearInfoBox();
   SetDetails(nullptr);
   SetUserActions(nullptr);
@@ -409,15 +402,29 @@
 }
 
 void Controller::EnterState(AutofillAssistantState state) {
-  if (state_ == state || state_ == AutofillAssistantState::STOPPED)
+  if (state_ == state)
     return;
-  // TODO(b/128300038): Forbid transition out of stopped again instead of
-  // ignoring it once shutdown sequence is less complex.
 
   DVLOG(2) << __func__ << ": " << state_ << " -> " << state;
 
+  // The only valid way of leaving the STOPPED state is to go back to tracking
+  // mode.
+  DCHECK(state_ != AutofillAssistantState::STOPPED ||
+         (state == AutofillAssistantState::TRACKING && tracking_));
+
+  bool old_needs_ui = NeedsUI();
   state_ = state;
+
   GetUiController()->OnStateChanged(state);
+
+  if (!old_needs_ui && NeedsUI())
+    client_->AttachUI();
+
+  if (ShouldCheckScripts()) {
+    GetOrCheckScripts();
+  } else {
+    StopPeriodicScriptChecks();
+  }
 }
 
 void Controller::SetWebControllerAndServiceForTest(
@@ -427,11 +434,27 @@
   service_ = std::move(service);
 }
 
-void Controller::GetOrCheckScripts() {
-  if (!started_ || script_tracker()->running()) {
+void Controller::OnUrlChange() {
+  if (state_ == AutofillAssistantState::STOPPED) {
+    PerformDelayedShutdownIfNecessary();
     return;
   }
 
+  GetOrCheckScripts();
+}
+
+bool Controller::ShouldCheckScripts() {
+  return state_ == AutofillAssistantState::TRACKING ||
+         state_ == AutofillAssistantState::STARTING ||
+         state_ == AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT ||
+         (state_ == AutofillAssistantState::PROMPT &&
+          (!script_tracker_ || !script_tracker_->running()));
+}
+
+void Controller::GetOrCheckScripts() {
+  if (!ShouldCheckScripts())
+    return;
+
   const GURL& url = GetCurrentURL();
   if (script_domain_ != url.host()) {
     StopPeriodicScriptChecks();
@@ -469,13 +492,13 @@
     periodic_script_check_count_--;
   }
 
-  if (periodic_script_check_count_ <= 0 && !allow_autostart_) {
+  if (periodic_script_check_count_ <= 0 && !allow_autostart()) {
     DCHECK_EQ(0, periodic_script_check_count_);
     periodic_script_check_scheduled_ = false;
     return;
   }
 
-  if (allow_autostart_ && !autostart_timeout_script_path_.empty() &&
+  if (allow_autostart() && !autostart_timeout_script_path_.empty() &&
       tick_clock_->NowTicks() >= absolute_autostart_timeout_) {
     DVLOG(1) << __func__ << " giving up waiting on autostart.";
     std::string script_path = autostart_timeout_script_path_;
@@ -526,18 +549,13 @@
   for (const auto& script_proto : response_proto.scripts()) {
     ProtocolUtils::AddScript(script_proto, &scripts);
   }
-  if (scripts.empty()) {
-    OnNoRunnableScripts();
-    return;
-  }
 
-  if (allow_autostart_) {
-    autostart_timeout_script_path_ =
-        response_proto.script_timeout_error().script_path();
-    autostart_timeout_ = base::TimeDelta::FromMilliseconds(
-        response_proto.script_timeout_error().timeout_ms());
+  autostart_timeout_script_path_ =
+      response_proto.script_timeout_error().script_path();
+  autostart_timeout_ = base::TimeDelta::FromMilliseconds(
+      response_proto.script_timeout_error().timeout_ms());
+  if (allow_autostart())
     absolute_autostart_timeout_ = tick_clock_->NowTicks() + autostart_timeout_;
-  }
 
   DVLOG(2) << __func__ << " from " << script_domain_ << " returned "
            << scripts.size() << " scripts";
@@ -552,9 +570,21 @@
       }
     }
   }
+
+  if (scripts.empty()) {
+    script_tracker()->SetScripts({});
+
+    if (state_ == AutofillAssistantState::TRACKING) {
+      OnFatalError(
+          l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR),
+          Metrics::DropOutReason::NO_SCRIPTS);
+      return;
+    }
+    OnNoRunnableScriptsForPage();
+  }
+
   script_tracker()->SetScripts(std::move(scripts));
-  script_tracker()->CheckScripts();
-  StartPeriodicScriptChecks();
+  GetOrCheckScripts();
 }
 
 void Controller::ExecuteScript(const std::string& script_path,
@@ -565,7 +595,6 @@
 
   touchable_element_area()->Clear();
 
-  StopPeriodicScriptChecks();
   // Runnable scripts will be checked and reported if necessary after executing
   // the script.
   script_tracker_->ClearRunnableScripts();
@@ -584,8 +613,9 @@
                                   const ScriptExecutor::Result& result) {
   if (!result.success) {
     DVLOG(1) << "Failed to execute script " << script_path;
-    OnFatalError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR),
-                 Metrics::DropOutReason::SCRIPT_FAILED);
+    OnScriptError(
+        l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR),
+        Metrics::DropOutReason::SCRIPT_FAILED);
     return;
   }
 
@@ -595,16 +625,29 @@
 
   switch (result.at_end) {
     case ScriptExecutor::SHUTDOWN:
-      client_->Shutdown(Metrics::DropOutReason::SCRIPT_SHUTDOWN);
-      return;
+      if (!tracking_) {
+        client_->Shutdown(Metrics::DropOutReason::SCRIPT_SHUTDOWN);
+        return;
+      }
+      end_state = AutofillAssistantState::TRACKING;
+      break;
 
     case ScriptExecutor::SHUTDOWN_GRACEFULLY:
-      EnterStoppedState();
-      client_->Shutdown(Metrics::DropOutReason::SCRIPT_SHUTDOWN);
-      return;
+      if (!tracking_) {
+        EnterStoppedState();
+        client_->Shutdown(Metrics::DropOutReason::SCRIPT_SHUTDOWN);
+        return;
+      }
+      end_state = AutofillAssistantState::TRACKING;
+      break;
 
     case ScriptExecutor::CLOSE_CUSTOM_TAB:
-      client_->Shutdown(Metrics::DropOutReason::CUSTOM_TAB_CLOSED);
+      GetUiController()->CloseCustomTab();
+      if (!tracking_) {
+        client_->Shutdown(Metrics::DropOutReason::CUSTOM_TAB_CLOSED);
+        return;
+      }
+      end_state = AutofillAssistantState::TRACKING;
       return;
 
     case ScriptExecutor::RESTART:
@@ -621,7 +664,6 @@
       break;
   }
   EnterState(end_state);
-  GetOrCheckScripts();
 }
 
 bool Controller::MaybeAutostartScript(
@@ -630,7 +672,7 @@
   // without first displaying it. This is meant to work only at the very
   // beginning, when no scripts have run, and only if there's exactly one
   // autostartable script.
-  if (!allow_autostart_)
+  if (!allow_autostart())
     return false;
 
   int autostart_count = 0;
@@ -642,7 +684,6 @@
     }
   }
   if (autostart_count == 1) {
-    DisableAutostart();
     ExecuteScript(autostart_path, TriggerContext::CreateEmpty(),
                   AutofillAssistantState::PROMPT);
     return true;
@@ -650,11 +691,6 @@
   return false;
 }
 
-void Controller::DisableAutostart() {
-  allow_autostart_ = false;
-  autostart_timeout_script_path_.clear();
-}
-
 void Controller::InitFromParameters() {
   auto details = std::make_unique<Details>();
   if (details->UpdateFromParameters(*trigger_context_))
@@ -680,50 +716,62 @@
   }
 }
 
+void Controller::Track(std::unique_ptr<TriggerContext> trigger_context,
+                       base::OnceCallback<void()> on_first_check_done) {
+  tracking_ = true;
+
+  if (state_ == AutofillAssistantState::INACTIVE) {
+    trigger_context_ = std::move(trigger_context);
+    InitFromParameters();
+    EnterState(AutofillAssistantState::TRACKING);
+  }
+
+  if (on_first_check_done) {
+    if (has_run_first_check_) {
+      std::move(on_first_check_done).Run();
+    } else {
+      on_has_run_first_check_.emplace_back(std::move(on_first_check_done));
+    }
+  }
+}
+
 bool Controller::NeedsUI() const {
   return state_ != AutofillAssistantState::INACTIVE &&
+         state_ != AutofillAssistantState::TRACKING &&
          state_ != AutofillAssistantState::STOPPED;
 }
 
 void Controller::Start(const GURL& deeplink_url,
                        std::unique_ptr<TriggerContext> trigger_context) {
-  if (state_ != AutofillAssistantState::INACTIVE) {
-    NOTREACHED();
+  if (state_ != AutofillAssistantState::INACTIVE &&
+      state_ != AutofillAssistantState::TRACKING)
     return;
-  }
+
   trigger_context_ = std::move(trigger_context);
   InitFromParameters();
   deeplink_url_ = deeplink_url;
+
+  // Force a re-evaluation of the script, to get a chance to autostart.
+  if (state_ == AutofillAssistantState::TRACKING)
+    script_tracker_->ClearRunnableScripts();
+
+  SetStatusMessage(l10n_util::GetStringFUTF8(
+      IDS_AUTOFILL_ASSISTANT_LOADING, base::UTF8ToUTF16(deeplink_url_.host())));
+  SetProgress(kAutostartInitialProgress);
   EnterState(AutofillAssistantState::STARTING);
-  client_->ShowUI();
-  started_ = true;
-  if (allow_autostart_) {
-    SetStatusMessage(
-        l10n_util::GetStringFUTF8(IDS_AUTOFILL_ASSISTANT_LOADING,
-                                  base::UTF8ToUTF16(deeplink_url_.host())));
-    SetProgress(kAutostartInitialProgress);
-  }
-  GetOrCheckScripts();
 }
 
 AutofillAssistantState Controller::GetState() {
   return state_;
 }
 
-void Controller::WillShutdown(Metrics::DropOutReason reason) {
-  StopPeriodicScriptChecks();
-  if (!will_shutdown_) {
-    UiController* ui_controller = GetUiController();
-    will_shutdown_ = true;
-    ui_controller->WillShutdown(reason);
-  }
-}
-
 void Controller::OnScriptSelected(const std::string& script_path,
                                   std::unique_ptr<TriggerContext> context) {
   DCHECK(!script_path.empty());
   ExecuteScript(script_path, std::move(context),
-                AutofillAssistantState::PROMPT);
+                state_ == AutofillAssistantState::TRACKING
+                    ? AutofillAssistantState::TRACKING
+                    : AutofillAssistantState::PROMPT);
 }
 
 void Controller::UpdateTouchableArea() {
@@ -731,8 +779,7 @@
 }
 
 void Controller::OnUserInteractionInsideTouchableArea() {
-  script_tracker()->CheckScripts();
-  StartPeriodicScriptChecks();
+  GetOrCheckScripts();
 }
 
 std::string Controller::GetDebugContext() {
@@ -899,41 +946,110 @@
     touchable_element_area_->GetVisualViewport(visual_viewport);
 }
 
+void Controller::OnScriptError(const std::string& error_message,
+                               Metrics::DropOutReason reason) {
+  if (state_ == AutofillAssistantState::STOPPED)
+    return;
+
+  SetStatusMessage(error_message);
+  EnterStoppedState();
+
+  if (tracking_) {
+    EnterState(AutofillAssistantState::TRACKING);
+    return;
+  }
+
+  client_->Shutdown(reason);
+}
+
 void Controller::OnFatalError(const std::string& error_message,
                               Metrics::DropOutReason reason) {
-  LOG(ERROR) << "Autofill Assistant has encountered an error and is shutting "
-                "down, reason="
+  LOG(ERROR) << "Autofill Assistant has encountered a fatal error and is "
+                "shutting down, reason="
              << reason;
   if (state_ == AutofillAssistantState::STOPPED)
     return;
 
-  StopPeriodicScriptChecks();
   SetStatusMessage(error_message);
   EnterStoppedState();
-  client_->Shutdown(reason);
-}
 
-void Controller::OnNoRunnableScripts() {
-  if (script_tracker()->running())
-    return;
+  // If we haven't managed to check the set of scripts yet at this point, we
+  // never will.
+  MaybeReportFirstCheckDone();
 
-  if (state_ == AutofillAssistantState::STARTING) {
-    // We're still waiting for the set of initial scripts, but either didn't get
-    // any scripts or didn't get scripts that could possibly become runnable
-    // with a DOM change.
-    OnFatalError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR),
-                 Metrics::DropOutReason::NO_INITIAL_SCRIPTS);
+  if (tracking_ && script_domain_ == GetCurrentURL().host()) {
+    // When tracking the controller should stays until the browser has navigated
+    // away from the last domain that was checked to be able to tell callers
+    // that the set of user actions is empty.
+    delayed_shutdown_reason_ = reason;
     return;
   }
 
-  // We're navigated to a page that has no scripts or the scripts have reached a
-  // state from which they cannot recover through a DOM change.
-  OnFatalError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP),
-               Metrics::DropOutReason::NO_SCRIPTS);
+  client_->Shutdown(reason);
+}
+
+void Controller::PerformDelayedShutdownIfNecessary() {
+  if (delayed_shutdown_reason_ && script_domain_ != GetCurrentURL().host()) {
+    Metrics::DropOutReason reason = delayed_shutdown_reason_.value();
+    delayed_shutdown_reason_ = base::nullopt;
+    client_->Shutdown(reason);
+  }
+}
+
+void Controller::MaybeReportFirstCheckDone() {
+  if (has_run_first_check_)
+    return;
+
+  has_run_first_check_ = true;
+
+  while (!on_has_run_first_check_.empty()) {
+    std::move(on_has_run_first_check_.back()).Run();
+    on_has_run_first_check_.pop_back();
+  }
+}
+
+void Controller::OnNoRunnableScriptsForPage() {
+  if (script_tracker()->running())
+    return;
+
+  switch (state_) {
+    case AutofillAssistantState::STARTING:
+      // We're still waiting for the set of initial scripts, but either didn't
+      // get any scripts or didn't get scripts that could possibly become
+      // runnable with a DOM change.
+      OnScriptError(
+          l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR),
+          Metrics::DropOutReason::NO_INITIAL_SCRIPTS);
+      break;
+
+    case AutofillAssistantState::PROMPT:
+    case AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT:
+      // The user has navigated to a page that has no scripts or the scripts
+      // have reached a state from which they cannot recover through a DOM
+      // change.
+      OnScriptError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP),
+                    Metrics::DropOutReason::NO_SCRIPTS);
+      break;
+
+    default:
+      // Always having a set of scripts to potentially run is not required in
+      // other states.
+      break;
+  }
 }
 
 void Controller::OnRunnableScriptsChanged(
     const std::vector<ScriptHandle>& runnable_scripts) {
+  base::ScopedClosureRunner report_first_check;
+  if (!has_run_first_check_) {
+    // Only report first check done once we're done processing the given set of
+    // scripts - whatever the outcome - so callers can see that outcome in the
+    // state of the controller.
+    report_first_check.ReplaceClosure(
+        base::BindOnce(&Controller::MaybeReportFirstCheckDone,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
   // Script selection is disabled when a script is already running. We will
   // check again and maybe update when the current script has finished.
   if (script_tracker()->running() || state_ == AutofillAssistantState::STOPPED)
@@ -952,7 +1068,7 @@
     }
   }
 
-  // Update the set of scripts in the UI.
+  // Update the set of user actions to report.
   auto user_actions = std::make_unique<std::vector<UserAction>>();
   for (const auto& script : runnable_scripts) {
     UserAction user_action;
@@ -967,18 +1083,22 @@
     user_actions->emplace_back(std::move(user_action));
   }
 
-  if (user_actions->empty() && state_ == AutofillAssistantState::STARTING) {
-    // Continue waiting
-    return;
-  }
+  // Change state, if necessary.
+  switch (state_) {
+    case AutofillAssistantState::TRACKING:
+    case AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT:
+    case AutofillAssistantState::PROMPT:
+      // Don't change state
+      break;
 
-  if (allow_autostart_ ||
-      state_ == AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT) {
-    // Autostart was expected, but only non-autostartable scripts were found.
-    DisableAutostart();
-    EnterState(AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT);
-  } else {
-    EnterState(AutofillAssistantState::PROMPT);
+    case AutofillAssistantState::STARTING:
+      if (!user_actions->empty())
+        EnterState(AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT);
+      break;
+
+    default:
+      if (!user_actions->empty())
+        EnterState(AutofillAssistantState::PROMPT);
   }
   SetUserActions(std::move(user_actions));
 }
@@ -991,7 +1111,7 @@
                                const GURL& validated_url) {
   // validated_url might not be the page URL. Ignore it and always check the
   // last committed url.
-  GetOrCheckScripts();
+  OnUrlChange();
 }
 
 void Controller::DidStartNavigation(
@@ -1024,8 +1144,8 @@
       web_contents()->GetLastCommittedURL().is_valid() &&
       !navigation_handle->WasServerRedirect() &&
       !navigation_handle->IsRendererInitiated()) {
-    OnFatalError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP),
-                 Metrics::DropOutReason::NAVIGATION);
+    OnScriptError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP),
+                  Metrics::DropOutReason::NAVIGATION);
   }
 }
 
@@ -1047,11 +1167,11 @@
   ReportNavigationStateChanged();
 
   if (is_successful)
-    GetOrCheckScripts();
+    OnUrlChange();
 }
 
 void Controller::DocumentAvailableInMainFrame() {
-  GetOrCheckScripts();
+  OnUrlChange();
 }
 
 void Controller::RenderProcessGone(base::TerminationStatus status) {
@@ -1064,7 +1184,7 @@
       base::FeatureList::IsEnabled(features::kAutofillAssistantChromeEntry)) {
     // Show UI again when re-focused in case the web contents moved activity.
     // This is only enabled when tab-switching is enabled.
-    client_->ShowUI();
+    client_->AttachUI();
   }
 }
 
diff --git a/components/autofill_assistant/browser/controller.h b/components/autofill_assistant/browser/controller.h
index eb43b7b..5d98116 100644
--- a/components/autofill_assistant/browser/controller.h
+++ b/components/autofill_assistant/browser/controller.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "components/autofill_assistant/browser/client.h"
 #include "components/autofill_assistant/browser/client_memory.h"
 #include "components/autofill_assistant/browser/client_settings.h"
@@ -56,16 +57,33 @@
              const base::TickClock* tick_clock);
   ~Controller() override;
 
-  // Returns true if the controller is in a state where UI is necessary.
-  bool NeedsUI() const;
+  // Let the controller know it should keep tracking script availability for the
+  // current domain, to be ready to report any scripts as action.
+  //
+  // Activates the controller, if needed and runs it in the background, without
+  // showing any UI until a script is started or Start() is called.
+  //
+  // Only the context of the first call to Track() is taken into account.
+  //
+  // If non-null |on_first_check_done| is called once the result of the first
+  // check of script availability are in - whether they're positive or negative.
+  void Track(std::unique_ptr<TriggerContext> trigger_context,
+             base::OnceCallback<void()> on_first_check_done);
 
-  // Called when autofill assistant can start executing scripts.
+  // Called when autofill assistant should start.
+  //
+  // This shows a UI, containing a progress bar, and executes the first
+  // available autostartable script. Starts checking for scripts, if necessary.
+  //
+  // Start() does nothing if called more than once or if a script is already
+  // running.
+  //
+  // Start() will overwrite any context previously set by Track().
   void Start(const GURL& deeplink_url,
              std::unique_ptr<TriggerContext> trigger_context);
 
-  // Lets the controller know it's about to be deleted. This is normally called
-  // from the client.
-  void WillShutdown(Metrics::DropOutReason reason);
+  // Returns true if the controller is in a state where UI is necessary.
+  bool NeedsUI() const;
 
   // Overrides ScriptExecutorDelegate:
   const ClientSettings& GetSettings() override;
@@ -103,6 +121,8 @@
   void EnterState(AutofillAssistantState state) override;
   void SetPaymentRequestOptions(
       std::unique_ptr<PaymentRequestOptions> options) override;
+  void OnScriptError(const std::string& error_message,
+                     Metrics::DropOutReason reason);
 
   // Overrides autofill_assistant::UiDelegate:
   AutofillAssistantState GetState() override;
@@ -134,6 +154,8 @@
   void GetVisualViewport(RectF* visual_viewport) const override;
   void OnFatalError(const std::string& error_message,
                     Metrics::DropOutReason reason) override;
+  void PerformDelayedShutdownIfNecessary();
+  void MaybeReportFirstCheckDone();
   bool GetResizeViewport() override;
   ConfigureBottomSheetProto::PeekMode GetPeekMode() override;
   void GetOverlayColors(OverlayColors* colors) const override;
@@ -150,7 +172,17 @@
       std::unique_ptr<WebController> web_controller,
       std::unique_ptr<Service> service);
 
+  // Called when the committed URL has or might have changed.
+  void OnUrlChange();
+
+  // Returns true if the controller should keep checking for scripts according
+  // to the current state.
+  bool ShouldCheckScripts();
+
+  // If the controller's state requires scripts to be checked, check them
+  // once right now and schedule regular checks. Otherwise, do nothing.
   void GetOrCheckScripts();
+
   void OnGetScripts(const GURL& url, bool result, const std::string& response);
 
   // Execute |script_path| and, if execution succeeds, enter |end_state| and
@@ -187,7 +219,7 @@
   void OnPaymentRequestContinueButtonClicked();
 
   // Overrides ScriptTracker::Listener:
-  void OnNoRunnableScripts() override;
+  void OnNoRunnableScriptsForPage() override;
   void OnRunnableScriptsChanged(
       const std::vector<ScriptHandle>& runnable_scripts) override;
 
@@ -216,6 +248,7 @@
 
   ElementArea* touchable_element_area();
   ScriptTracker* script_tracker();
+  bool allow_autostart() { return state_ == AutofillAssistantState::STARTING; }
 
   ClientSettings settings_;
   Client* const client_;
@@ -242,7 +275,6 @@
 
   // Domain of the last URL the controller requested scripts from.
   std::string script_domain_;
-  bool allow_autostart_ = true;
 
   // Whether a task for periodic checks is scheduled.
   bool periodic_script_check_scheduled_ = false;
@@ -297,12 +329,6 @@
 
   std::unique_ptr<OverlayColors> overlay_colors_;
 
-  // Flag indicates whether it is ready to fetch and execute scripts.
-  bool started_ = false;
-
-  // True once UiController::WillShutdown has been called.
-  bool will_shutdown_ = false;
-
   std::unique_ptr<PaymentRequestOptions> payment_request_options_;
   std::unique_ptr<PaymentInformation> payment_request_info_;
 
@@ -324,6 +350,31 @@
   // Lazily instantiate in script_tracker().
   std::unique_ptr<ScriptTracker> script_tracker_;
 
+  // If true, the controller is supposed to stay up and running in the
+  // background even without UI, keeping track of scripts.
+  //
+  // This has two main effects:
+  // - the controllers stays alive even after a fatal error, just so it can
+  // immediately report that no actions are available on that website.
+  // - scripts error are not considered fatal errors. The controller reverts
+  // to TRACKING mode.
+  //
+  // This is set by Track().
+  bool tracking_ = false;
+
+  // True once the controller has run the first set of scripts and have either
+  // declared it invalid - and entered stopped state - or have processed its
+  // result - and updated the state and set of available actions.
+  bool has_run_first_check_ = false;
+
+  // Callbacks to call when |has_run_first_check_| becomes true.
+  std::vector<base::OnceCallback<void()>> on_has_run_first_check_;
+
+  // If set, the controller entered the STOPPED state but shutdown was delayed
+  // until the browser has left the |script_domain_| for which the decision was
+  // taken.
+  base::Optional<Metrics::DropOutReason> delayed_shutdown_reason_;
+
   base::WeakPtrFactory<Controller> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(Controller);
diff --git a/components/autofill_assistant/browser/controller_unittest.cc b/components/autofill_assistant/browser/controller_unittest.cc
index 8784d9dd..cf79ef4 100644
--- a/components/autofill_assistant/browser/controller_unittest.cc
+++ b/components/autofill_assistant/browser/controller_unittest.cc
@@ -70,7 +70,7 @@
   std::string GetLocale() override { return ""; }
   std::string GetCountryCode() override { return ""; }
   MOCK_METHOD1(Shutdown, void(Metrics::DropOutReason reason));
-  MOCK_METHOD0(ShowUI, void());
+  MOCK_METHOD0(AttachUI, void());
   MOCK_METHOD0(DestroyUI, void());
 
  private:
@@ -152,14 +152,16 @@
                              ActionsResponseProto actions_response) {
     std::string actions_response_str;
     actions_response.SerializeToString(&actions_response_str);
-    EXPECT_CALL(*mock_service_, OnGetActions(StrEq("script"), _, _, _, _, _))
+    EXPECT_CALL(*mock_service_, OnGetActions(StrEq(path), _, _, _, _, _))
         .WillOnce(RunOnceCallback<5>(true, actions_response_str));
   }
 
   void Start() { Start("http://initialurl.com"); }
 
-  void Start(const std::string& url) {
-    controller_->Start(GURL(url), TriggerContext::CreateEmpty());
+  void Start(const std::string& url_string) {
+    GURL url(url_string);
+    SetLastCommittedUrl(url);
+    controller_->Start(url, TriggerContext::CreateEmpty());
   }
 
   void SetLastCommittedUrl(const GURL& url) {
@@ -170,6 +172,8 @@
     content::NavigationSimulator::NavigateAndCommitFromDocument(
         url, web_contents()->GetMainFrame());
     content::WebContentsTester::For(web_contents())->TestSetIsLoading(false);
+    SetLastCommittedUrl(url);
+    controller_->DidFinishLoad(nullptr, GURL(""));
   }
 
   void SimulateWebContentsFocused() {
@@ -369,6 +373,8 @@
   SupportsScriptResponseProto empty;
   SetNextScriptResponse(empty);
 
+  EXPECT_CALL(fake_client_,
+              Shutdown(Metrics::DropOutReason::NO_INITIAL_SCRIPTS));
   Start("http://a.example.com/path");
   EXPECT_EQ(AutofillAssistantState::STOPPED, controller_->GetState());
 }
@@ -381,6 +387,8 @@
       ->add_domain("http://otherdomain.com");
   SetNextScriptResponse(script_response);
 
+  EXPECT_CALL(fake_client_,
+              Shutdown(Metrics::DropOutReason::NO_INITIAL_SCRIPTS));
   Start("http://a.example.com/path");
   EXPECT_EQ(AutofillAssistantState::STOPPED, controller_->GetState());
 }
@@ -478,11 +486,6 @@
   testing::InSequence seq;
   EXPECT_CALL(fake_client_, Shutdown(Metrics::DropOutReason::SCRIPT_SHUTDOWN));
   EXPECT_TRUE(controller_->PerformUserAction(0));
-
-  // Simulates Client::Shutdown(SCRIPT_SHUTDOWN)
-  EXPECT_CALL(mock_ui_controller_,
-              WillShutdown(Metrics::DropOutReason::SCRIPT_SHUTDOWN));
-  controller_->WillShutdown(Metrics::DropOutReason::SCRIPT_SHUTDOWN);
 }
 
 TEST_F(ControllerTest, Reset) {
@@ -525,12 +528,17 @@
 }
 
 TEST_F(ControllerTest, RefreshScriptWhenDomainChanges) {
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "script");
+  std::string scripts_str;
+  script_response.SerializeToString(&scripts_str);
+
   EXPECT_CALL(*mock_service_,
               OnGetScriptsForUrl(Eq(GURL("http://a.example.com/path1")), _, _))
-      .WillOnce(RunOnceCallback<2>(true, ""));
+      .WillOnce(RunOnceCallback<2>(true, scripts_str));
   EXPECT_CALL(*mock_service_,
               OnGetScriptsForUrl(Eq(GURL("http://b.example.com/path1")), _, _))
-      .WillOnce(RunOnceCallback<2>(true, ""));
+      .WillOnce(RunOnceCallback<2>(true, scripts_str));
 
   Start("http://a.example.com/path1");
   SimulateNavigateToUrl(GURL("http://a.example.com/path2"));
@@ -544,13 +552,31 @@
   AddRunnableScript(&script_response, "autostart")
       ->mutable_presentation()
       ->set_autostart(true);
-  AddRunnableScript(&script_response, "alsorunnable");
   SetNextScriptResponse(script_response);
 
   EXPECT_CALL(*mock_service_, OnGetActions(StrEq("autostart"), _, _, _, _, _))
       .WillOnce(RunOnceCallback<5>(true, ""));
 
+  EXPECT_CALL(fake_client_, AttachUI());
   Start("http://a.example.com/path");
+  EXPECT_EQ(AutofillAssistantState::PROMPT, controller_->GetState());
+
+  ActionsResponseProto runnable_script;
+  runnable_script.add_actions()->mutable_tell()->set_message("runnable");
+  runnable_script.add_actions()->mutable_stop();
+  SetupActionsForScript("runnable", runnable_script);
+
+  // The script "runnable" stops the flow and shutdowns the controller.
+  EXPECT_CALL(fake_client_, Shutdown(Metrics::DropOutReason::SCRIPT_SHUTDOWN));
+  controller_->PerformUserAction(0);
+  EXPECT_EQ(AutofillAssistantState::STOPPED, controller_->GetState());
+
+  // Full history state transitions
+  EXPECT_THAT(states_, ElementsAre(AutofillAssistantState::STARTING,
+                                   AutofillAssistantState::RUNNING,
+                                   AutofillAssistantState::PROMPT,
+                                   AutofillAssistantState::RUNNING,
+                                   AutofillAssistantState::STOPPED));
 }
 
 TEST_F(ControllerTest, AutostartIsNotPassedToTheUi) {
@@ -640,27 +666,27 @@
   EXPECT_TRUE(controller_->GetUserActions().empty());
 }
 
-TEST_F(ControllerTest, ShowUIWhenStarting) {
-  EXPECT_CALL(fake_client_, ShowUI());
+TEST_F(ControllerTest, AttachUIWhenStarting) {
+  EXPECT_CALL(fake_client_, AttachUI());
   Start();
 }
 
-TEST_F(ControllerTest, ShowUIWhenContentsFocused) {
-  SimulateWebContentsFocused();  // must not call ShowUI
+TEST_F(ControllerTest, AttachUIWhenContentsFocused) {
+  SimulateWebContentsFocused();  // must not call AttachUI
 
   testing::InSequence seq;
-  EXPECT_CALL(fake_client_, ShowUI());
+  EXPECT_CALL(fake_client_, AttachUI());
 
   SupportsScriptResponseProto script_response;
   AddRunnableScript(&script_response, "script1");
   SetNextScriptResponse(script_response);
-  Start();  // must call ShowUI
+  Start();  // must call AttachUI
 
-  EXPECT_CALL(fake_client_, ShowUI());
-  SimulateWebContentsFocused();  // must call ShowUI
+  EXPECT_CALL(fake_client_, AttachUI());
+  SimulateWebContentsFocused();  // must call AttachUI
 
   controller_->OnFatalError("test", Metrics::DropOutReason::TAB_CHANGED);
-  SimulateWebContentsFocused();  // must not call ShowUI
+  SimulateWebContentsFocused();  // must not call AttachUI
 }
 
 TEST_F(ControllerTest, KeepCheckingForElement) {
@@ -957,4 +983,313 @@
   EXPECT_THAT(controller_->GetCurrentURL().spec(), navigate_url);
 }
 
+TEST_F(ControllerTest, Track) {
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "runnable");
+  std::string response_str;
+  script_response.SerializeToString(&response_str);
+  EXPECT_CALL(*mock_service_,
+              OnGetScriptsForUrl(GURL("http://example.com/"), _, _))
+      .WillOnce(RunOnceCallback<2>(true, response_str));
+
+  EXPECT_CALL(*mock_service_,
+              OnGetScriptsForUrl(GURL("http://b.example.com/"), _, _))
+      .WillOnce(RunOnceCallback<2>(true, ""));
+
+  // Start tracking at example.com, with one script matching
+  SetLastCommittedUrl(GURL("http://example.com/"));
+
+  controller_->Track(TriggerContext::CreateEmpty(), base::DoNothing());
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+  ASSERT_THAT(controller_->GetUserActions(), SizeIs(1));
+
+  // Execute the script, which requires showing the UI, then go back to tracking
+  // mode
+  EXPECT_CALL(fake_client_, AttachUI());
+  EXPECT_TRUE(controller_->PerformUserAction(0));
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+  EXPECT_THAT(controller_->GetUserActions(), SizeIs(1));
+
+  // Move to a domain for which there are no scripts. This causes the controller
+  // to stop.
+  SimulateNavigateToUrl(GURL("http://b.example.com/"));
+  EXPECT_EQ(AutofillAssistantState::STOPPED, controller_->GetState());
+
+  // Check the full history of state transitions.
+  EXPECT_THAT(states_, ElementsAre(AutofillAssistantState::TRACKING,
+                                   AutofillAssistantState::RUNNING,
+                                   AutofillAssistantState::TRACKING,
+                                   AutofillAssistantState::STOPPED));
+
+  // Shutdown once we've moved from domain b.example.com, for which we know
+  // there are no scripts, to c.example.com, which we don't want to check.
+  EXPECT_CALL(fake_client_, Shutdown(Metrics::DropOutReason::NO_SCRIPTS));
+  SimulateNavigateToUrl(GURL("http://c.example.com/"));
+}
+
+TEST_F(ControllerTest, TrackContinuesAfterScriptError) {
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "runnable");
+  std::string response_str;
+  script_response.SerializeToString(&response_str);
+  EXPECT_CALL(*mock_service_,
+              OnGetScriptsForUrl(GURL("http://example.com/"), _, _))
+      .WillOnce(RunOnceCallback<2>(true, response_str));
+
+  // Start tracking at example.com, with one script matching
+  SetLastCommittedUrl(GURL("http://example.com/"));
+
+  controller_->Track(TriggerContext::CreateEmpty(), base::DoNothing());
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+  ASSERT_THAT(controller_->GetUserActions(), SizeIs(1));
+
+  EXPECT_CALL(*mock_service_, OnGetActions(StrEq("runnable"), _, _, _, _, _))
+      .WillOnce(RunOnceCallback<5>(false, ""));
+
+  // When the script fails, the controller transitions to STOPPED state, then
+  // right away back to TRACKING state.
+  EXPECT_TRUE(controller_->PerformUserAction(0));
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+  EXPECT_THAT(controller_->GetUserActions(), SizeIs(1));
+
+  // Check the full history of state transitions.
+  EXPECT_THAT(states_, ElementsAre(AutofillAssistantState::TRACKING,
+                                   AutofillAssistantState::RUNNING,
+                                   AutofillAssistantState::STOPPED,
+                                   AutofillAssistantState::TRACKING));
+}
+
+TEST_F(ControllerTest, TrackReportsFirstSetOfScripts) {
+  Service::ResponseCallback get_scripts_callback;
+  EXPECT_CALL(*mock_service_, OnGetScriptsForUrl(_, _, _))
+      .WillOnce(
+          Invoke([&get_scripts_callback](const GURL& url,
+                                         const TriggerContext& trigger_context,
+                                         Service::ResponseCallback& callback) {
+            get_scripts_callback = std::move(callback);
+          }));
+
+  SetLastCommittedUrl(GURL("http://example.com/"));
+  bool first_check_done = false;
+  controller_->Track(TriggerContext::CreateEmpty(),
+                     base::BindOnce(
+                         [](Controller* controller, bool* is_done) {
+                           // User actions must have been set when this is
+                           // called
+                           EXPECT_THAT(controller->GetUserActions(), SizeIs(1));
+                           *is_done = true;
+                         },
+                         base::Unretained(controller_.get()),
+                         base::Unretained(&first_check_done)));
+  EXPECT_FALSE(first_check_done);
+
+  ASSERT_TRUE(get_scripts_callback);
+
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "runnable");
+  std::string response_str;
+  script_response.SerializeToString(&response_str);
+  std::move(get_scripts_callback).Run(true, response_str);
+
+  EXPECT_TRUE(first_check_done);
+}
+
+TEST_F(ControllerTest, TrackReportsNoScripts) {
+  SetLastCommittedUrl(GURL("http://example.com/"));
+  base::MockCallback<base::OnceCallback<void()>> callback;
+
+  EXPECT_CALL(callback, Run());
+  controller_->Track(TriggerContext::CreateEmpty(), callback.Get());
+  EXPECT_EQ(AutofillAssistantState::STOPPED, controller_->GetState());
+}
+
+TEST_F(ControllerTest, TrackReportsNoScriptsForNow) {
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "no_match_yet")
+      ->mutable_presentation()
+      ->mutable_precondition()
+      ->add_elements_exist()
+      ->add_selectors("#element");
+  SetNextScriptResponse(script_response);
+
+  SetLastCommittedUrl(GURL("http://example.com/"));
+  base::MockCallback<base::OnceCallback<void()>> callback;
+
+  EXPECT_CALL(callback, Run());
+  controller_->Track(TriggerContext::CreateEmpty(), callback.Get());
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+}
+
+TEST_F(ControllerTest, TrackReportsNoScriptsForThePage) {
+  // Having scripts for the domain but not for the current page is fatal in
+  // STARTING or PROMPT mode, but not in TRACKING mode.
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "no_match_yet")
+      ->mutable_presentation()
+      ->mutable_precondition()
+      ->add_path_pattern("/otherpage.html");
+  SetNextScriptResponse(script_response);
+
+  SetLastCommittedUrl(GURL("http://example.com/"));
+  base::MockCallback<base::OnceCallback<void()>> callback;
+
+  EXPECT_CALL(callback, Run());
+  controller_->Track(TriggerContext::CreateEmpty(), callback.Get());
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+}
+
+TEST_F(ControllerTest, TrackReportsAlreadyDone) {
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "runnable");
+  SetNextScriptResponse(script_response);
+
+  SetLastCommittedUrl(GURL("http://example.com/"));
+  controller_->Track(TriggerContext::CreateEmpty(), base::DoNothing());
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+
+  base::MockCallback<base::OnceCallback<void()>> callback;
+  EXPECT_CALL(callback, Run());
+  controller_->Track(TriggerContext::CreateEmpty(), callback.Get());
+}
+
+TEST_F(ControllerTest, TrackThenAutostart) {
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "runnable");
+  AddRunnableScript(&script_response, "autostart")
+      ->mutable_presentation()
+      ->set_autostart(true);
+  SetNextScriptResponse(script_response);
+
+  SetLastCommittedUrl(GURL("http://example.com/"));
+  controller_->Track(TriggerContext::CreateEmpty(), base::DoNothing());
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+  EXPECT_THAT(controller_->GetUserActions(), SizeIs(1));
+
+  EXPECT_CALL(*mock_service_, OnGetActions(StrEq("autostart"), _, _, _, _, _))
+      .WillOnce(RunOnceCallback<5>(true, ""));
+
+  ActionsResponseProto runnable_script;
+  runnable_script.add_actions()->mutable_tell()->set_message("runnable");
+  runnable_script.add_actions()->mutable_stop();
+  SetupActionsForScript("runnable", runnable_script);
+
+  EXPECT_CALL(fake_client_, AttachUI());
+  Start("http://example.com/");
+  EXPECT_EQ(AutofillAssistantState::PROMPT, controller_->GetState());
+  EXPECT_THAT(controller_->GetUserActions(), SizeIs(1));
+
+  // Run "runnable", which then calls stop and ends. The controller should then
+  // go back to TRACKING mode.
+  controller_->PerformUserAction(0);
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+
+  // Full history of state transitions.
+  EXPECT_THAT(states_, ElementsAre(AutofillAssistantState::TRACKING,
+                                   AutofillAssistantState::STARTING,
+                                   AutofillAssistantState::RUNNING,
+                                   AutofillAssistantState::PROMPT,
+                                   AutofillAssistantState::RUNNING,
+                                   AutofillAssistantState::TRACKING));
+}
+
+TEST_F(ControllerTest, UnexpectedNavigationDuringPromptAction_Tracking) {
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "runnable");
+  SetNextScriptResponse(script_response);
+
+  ActionsResponseProto runnable_script;
+  runnable_script.add_actions()
+      ->mutable_prompt()
+      ->add_choices()
+      ->mutable_chip()
+      ->set_text("continue");
+  std::string never_shown = "never shown";
+  runnable_script.add_actions()->mutable_tell()->set_message(never_shown);
+  SetupActionsForScript("runnable", runnable_script);
+
+  SetLastCommittedUrl(GURL("http://example.com/"));
+  controller_->Track(TriggerContext::CreateEmpty(), base::DoNothing());
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+  ASSERT_THAT(controller_->GetUserActions(), SizeIs(1));
+  EXPECT_EQ(controller_->GetUserActions()[0].chip().text, "runnable");
+
+  // Start the script, which should show a prompt with the continue chip.
+  controller_->PerformUserAction(0);
+  EXPECT_EQ(AutofillAssistantState::PROMPT, controller_->GetState());
+  ASSERT_THAT(controller_->GetUserActions(), SizeIs(1));
+  EXPECT_EQ(controller_->GetUserActions()[0].chip().text, "continue");
+
+  // Browser (not document) initiated navigation while in prompt mode (such as
+  // go back): The controller stops the scripts, shows an error, then goes back
+  // to tracking mode.
+  //
+  // The tell never_shown which follows the prompt action should never be
+  // executed.
+  EXPECT_CALL(mock_ui_controller_, OnStatusMessageChanged(never_shown))
+      .Times(0);
+  EXPECT_CALL(mock_ui_controller_,
+              OnStatusMessageChanged(testing::Not(never_shown)))
+      .Times(testing::AnyNumber());
+
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL("http://example.com/otherpage"));
+
+  EXPECT_EQ(AutofillAssistantState::TRACKING, controller_->GetState());
+  ASSERT_THAT(controller_->GetUserActions(), SizeIs(1));
+  EXPECT_EQ(controller_->GetUserActions()[0].chip().text, "runnable");
+
+  // Full history of state transitions.
+  EXPECT_THAT(states_, ElementsAre(AutofillAssistantState::TRACKING,
+                                   AutofillAssistantState::RUNNING,
+                                   AutofillAssistantState::PROMPT,
+                                   AutofillAssistantState::STOPPED,
+                                   AutofillAssistantState::TRACKING));
+}
+
+TEST_F(ControllerTest, UnexpectedNavigationDuringPromptAction) {
+  SupportsScriptResponseProto script_response;
+  AddRunnableScript(&script_response, "autostart")
+      ->mutable_presentation()
+      ->set_autostart(true);
+  SetNextScriptResponse(script_response);
+
+  ActionsResponseProto autostart_script;
+  autostart_script.add_actions()
+      ->mutable_prompt()
+      ->add_choices()
+      ->mutable_chip()
+      ->set_text("continue");
+  std::string never_shown = "never shown";
+  autostart_script.add_actions()->mutable_tell()->set_message(never_shown);
+  SetupActionsForScript("autostart", autostart_script);
+
+  Start();
+  EXPECT_EQ(AutofillAssistantState::PROMPT, controller_->GetState());
+  ASSERT_THAT(controller_->GetUserActions(), SizeIs(1));
+  EXPECT_EQ(controller_->GetUserActions()[0].chip().text, "continue");
+
+  // Browser (not document) initiated navigation while in prompt mode (such as
+  // go back): The controller stops the scripts, shows an error and shuts down.
+  //
+  // The tell never_shown which follows the prompt action should never be
+  // executed.
+  EXPECT_CALL(mock_ui_controller_, OnStatusMessageChanged(never_shown))
+      .Times(0);
+  EXPECT_CALL(mock_ui_controller_,
+              OnStatusMessageChanged(testing::Not(never_shown)))
+      .Times(testing::AnyNumber());
+
+  EXPECT_CALL(fake_client_, Shutdown(Metrics::DropOutReason::NAVIGATION));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL("http://example.com/otherpage"));
+
+  EXPECT_EQ(AutofillAssistantState::STOPPED, controller_->GetState());
+
+  // Full history of state transitions.
+  EXPECT_THAT(states_, ElementsAre(AutofillAssistantState::STARTING,
+                                   AutofillAssistantState::RUNNING,
+                                   AutofillAssistantState::PROMPT,
+                                   AutofillAssistantState::STOPPED));
+}
+
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/metrics.cc b/components/autofill_assistant/browser/metrics.cc
index 9048e66..57f7ffe 100644
--- a/components/autofill_assistant/browser/metrics.cc
+++ b/components/autofill_assistant/browser/metrics.cc
@@ -19,6 +19,8 @@
 // static
 void Metrics::RecordDropOut(DropOutReason reason) {
   DCHECK_LE(reason, DropOutReason::kMaxValue);
+  DVLOG_IF(3, reason != DropOutReason::AA_START)
+      << "Drop out with reason: " << reason;
   base::UmaHistogramEnumeration(kDropOutEnumName, reason);
 }
 
diff --git a/components/autofill_assistant/browser/mock_ui_controller.h b/components/autofill_assistant/browser/mock_ui_controller.h
index 20b69b5..26d95e2 100644
--- a/components/autofill_assistant/browser/mock_ui_controller.h
+++ b/components/autofill_assistant/browser/mock_ui_controller.h
@@ -25,7 +25,6 @@
   MOCK_METHOD1(OnStatusMessageChanged, void(const std::string& message));
   MOCK_METHOD1(OnBubbleMessageChanged, void(const std::string& message));
   MOCK_METHOD1(OnStateChanged, void(AutofillAssistantState));
-  MOCK_METHOD1(WillShutdown, void(Metrics::DropOutReason));
   MOCK_METHOD1(OnUserActionsChanged,
                void(const std::vector<UserAction>& user_actions));
   MOCK_METHOD1(OnPaymentRequestChanged,
diff --git a/components/autofill_assistant/browser/script_tracker.cc b/components/autofill_assistant/browser/script_tracker.cc
index 74ae284..9caa8af 100644
--- a/components/autofill_assistant/browser/script_tracker.cc
+++ b/components/autofill_assistant/browser/script_tracker.cc
@@ -104,7 +104,7 @@
     // There are no runnable scripts, even though we haven't checked the DOM
     // yet. Report it all immediately.
     UpdateRunnableScriptsIfNecessary();
-    listener_->OnNoRunnableScripts();
+    listener_->OnNoRunnableScriptsForPage();
     TerminatePendingChecks();
     return;
   }
@@ -136,6 +136,10 @@
   executor_->Run(std::move(run_script_callback));
 }
 
+void ScriptTracker::StopScript() {
+  executor_.reset();
+}
+
 void ScriptTracker::ClearRunnableScripts() {
   runnable_scripts_.clear();
 }
@@ -213,8 +217,11 @@
 }
 
 void ScriptTracker::UpdateRunnableScriptsIfNecessary() {
-  if (!RunnablesHaveChanged())
+  if (!has_reported_scripts_) {
+    has_reported_scripts_ = true;
+  } else if (!RunnablesHaveChanged()) {
     return;
+  }
 
   runnable_scripts_.clear();
   SortScripts(&pending_runnable_scripts_);
diff --git a/components/autofill_assistant/browser/script_tracker.h b/components/autofill_assistant/browser/script_tracker.h
index a983e84..373540b 100644
--- a/components/autofill_assistant/browser/script_tracker.h
+++ b/components/autofill_assistant/browser/script_tracker.h
@@ -37,6 +37,9 @@
 
     // Called when the set of runnable scripts have changed. |runnable_scripts|
     // are the new runnable scripts. Runnable scripts are ordered by priority.
+    //
+    // The result of the first check is always reported, even if the set of
+    // scripts that were found is empty.
     virtual void OnRunnableScriptsChanged(
         const std::vector<ScriptHandle>& runnable_scripts) = 0;
 
@@ -47,7 +50,7 @@
     //
     // This is only called when there are scripts. That is, SetScripts was last
     // passed a non-empty vector.
-    virtual void OnNoRunnableScripts() = 0;
+    virtual void OnNoRunnableScriptsForPage() = 0;
   };
 
   // |delegate| and |listener| should outlive this object and should not be
@@ -82,6 +85,9 @@
                      std::unique_ptr<TriggerContext> context,
                      ScriptExecutor::RunScriptCallback callback);
 
+  // Stops a script, if one is running.
+  void StopScript();
+
   // Clears the set of scripts that could be run.
   //
   // Calling this function will clean the bottom bar.
@@ -129,6 +135,10 @@
   ScriptExecutorDelegate* const delegate_;
   ScriptTracker::Listener* const listener_;
 
+  // If true, a set of script has already been reported to
+  // Listener::OnRunnableScriptsChanged.
+  bool has_reported_scripts_ = false;
+
   // Paths and names of scripts known to be runnable (they pass the
   // preconditions).
   //
diff --git a/components/autofill_assistant/browser/script_tracker_unittest.cc b/components/autofill_assistant/browser/script_tracker_unittest.cc
index 09b641c..bdeb912d 100644
--- a/components/autofill_assistant/browser/script_tracker_unittest.cc
+++ b/components/autofill_assistant/browser/script_tracker_unittest.cc
@@ -64,7 +64,7 @@
     runnable_scripts_ = runnable_scripts;
   }
 
-  void OnNoRunnableScripts() override { no_runnable_scripts_anymore_++; }
+  void OnNoRunnableScriptsForPage() override { no_runnable_scripts_anymore_++; }
 
   void SetAndCheckScripts() {
     std::vector<std::unique_ptr<Script>> scripts;
@@ -139,7 +139,7 @@
   tracker_.SetScripts({});
   tracker_.CheckScripts();
   EXPECT_THAT(runnable_scripts(), IsEmpty());
-  EXPECT_EQ(0, runnable_scripts_changed_);
+  EXPECT_EQ(1, runnable_scripts_changed_);
   EXPECT_EQ(0, no_runnable_scripts_anymore_);
 }
 
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto
index c68655f..f3e00700 100644
--- a/components/autofill_assistant/browser/service.proto
+++ b/components/autofill_assistant/browser/service.proto
@@ -980,6 +980,8 @@
     // Server payload originally sent by the server. This should
     // be transmitted as-is by the client without interpreting.
     optional bytes server_payload = 5;
+
+    reserved 13;
   }
   repeated Choice choices = 4;
 }
@@ -1331,6 +1333,8 @@
     // must contain |initial_value| or it will otherwise default to the first
     // value.
     optional int32 initial_value = 4 [default = 0];
+
+    reserved 7;
   }
 
   // A result associated to this counter.
@@ -1445,6 +1449,8 @@
     // then maximum 1 Choice can have this set to true, otherwise the associated
     // FormAction will fail.
     optional bool selected = 2 [default = false];
+
+    reserved 7;
   }
 
   message Result {
diff --git a/components/autofill_assistant/browser/state.h b/components/autofill_assistant/browser/state.h
index d0ff42d0..d7479a7 100644
--- a/components/autofill_assistant/browser/state.h
+++ b/components/autofill_assistant/browser/state.h
@@ -10,20 +10,49 @@
 namespace autofill_assistant {
 
 // High-level states the Autofill Assistant can be in.
+//
+// A typical run, when started from CCT, autostarts a script, then displays a
+// prompt and continues until a script sends the Stop action:
+//
+// INACTIVE -> STARTING -> RUNNING -> PROMPT -> RUNNING -> .. -> STOPPED
+//
+// A typical run, when started from a direct action, goes into tracking mode,
+// execute a script, the goes back to tracking mode:
+//
+// INACTIVE -> TRACKING -> RUNNING -> TRACKING -> ... -> STOPPED
+//
+// See the individual state for possible state transitions.
 enum class AutofillAssistantState {
   // Autofill assistant is not doing or showing anything.
+  //
   // Initial state.
+  // Next states: STARTING, TRACKING, STOPPED
   INACTIVE = 0,
 
+  // Autofill assistant is keeping track of script availability.
+  //
+  // In this mode, no UI is shown and scripts are not autostarted. User
+  // actions might be available.
+  //
+  // Note that it is possible to go from TRACKING to STARTING to trigger
+  // whatever autostartable scripts is defined for a page.
+  //
+  // Next states: STARTING, RUNNING, STOPPED
+  TRACKING,
+
   // Autofill assistant is waiting for an autostart script.
   //
   // Status message, progress and details are initialized to useful values.
+  //
+  // Next states: RUNNING, AUTOSTART_FALLBACK_PROMPT, STOPPED
   STARTING,
 
   // Autofill assistant is manipulating the website.
   //
   // Status message, progress and details kept up-to-date by the running
   // script.
+  //
+  // Next states: PROMPT, MODAL_DIALOG, TRACKING, STARTING, STOPPED
   RUNNING,
 
   // Autofill assistant is waiting for the user to make a choice.
@@ -31,21 +60,34 @@
   // Status message is initialized to a useful value. Chips are set and might be
   // empty. A touchable area must be configured. The user might be filling in
   // the data for a payment request.
+  //
+  // Next states: RUNNING, TRACKING, STOPPED
   PROMPT,
 
   // Autofill assistant is waiting for the user to make the first choice.
   //
   // When autostartable scripts are expected, this is only triggered as a
   // fallback if there are non-autostartable scripts to choose from instead.
+  //
+  // Next states: RUNNING, STOPPED
   AUTOSTART_FALLBACK_PROMPT,
 
   // Autofill assistant is expecting a modal dialog, such as the one asking for
   // CVC.
+  //
+  // Next states: RUNNING
   MODAL_DIALOG,
 
-  // Autofill assistant is stopped, but still visible to the user.
+  // Autofill assistant is stopped, but the controller is still available.
   //
-  // Status message contains the final message.
+  // This is a final state for the UI, which, when entering this state, detaches
+  // itself from the controller, waits for a few seconds to let the user read
+  // the message and then disappears.
+  //
+  // In that scenario, the status message at the time of transition to STOPPED
+  // is supposed to contain the final message.
+  //
+  // Next states: TRACKING.
   STOPPED
 };
 
@@ -61,6 +103,9 @@
     case AutofillAssistantState::INACTIVE:
       out << "INACTIVE";
       break;
+    case AutofillAssistantState::TRACKING:
+      out << "TRACKING";
+      break;
     case AutofillAssistantState::STARTING:
       out << "STARTING";
       break;
diff --git a/components/autofill_assistant/browser/ui_controller.cc b/components/autofill_assistant/browser/ui_controller.cc
index 1dd1af2..6123274 100644
--- a/components/autofill_assistant/browser/ui_controller.cc
+++ b/components/autofill_assistant/browser/ui_controller.cc
@@ -15,7 +15,7 @@
 void UiController::OnStateChanged(AutofillAssistantState new_state) {}
 void UiController::OnStatusMessageChanged(const std::string& message) {}
 void UiController::OnBubbleMessageChanged(const std::string& message) {}
-void UiController::WillShutdown(Metrics::DropOutReason reason) {}
+void UiController::CloseCustomTab() {}
 void UiController::OnUserActionsChanged(
     const std::vector<UserAction>& user_actions) {}
 void UiController::OnPaymentRequestOptionsChanged(
diff --git a/components/autofill_assistant/browser/ui_controller.h b/components/autofill_assistant/browser/ui_controller.h
index 2d897d9..c4bd279d 100644
--- a/components/autofill_assistant/browser/ui_controller.h
+++ b/components/autofill_assistant/browser/ui_controller.h
@@ -40,11 +40,9 @@
   // Report that the bubble / tooltip message has changed.
   virtual void OnBubbleMessageChanged(const std::string& message);
 
-  // Autofill Assistant is about to be shut down for this tab.
-  //
-  // Pointer to UIDelegate will become invalid as soon as this method has
-  // returned.
-  virtual void WillShutdown(Metrics::DropOutReason reason);
+  // If the current chrome activity is a custom tab activity, close it.
+  // Otherwise, do nothing.
+  virtual void CloseCustomTab();
 
   // Report that the set of user actions has changed.
   virtual void OnUserActionsChanged(
diff --git a/components/flags_ui/resources/flags.js b/components/flags_ui/resources/flags.js
index 816f3393..56e69f2 100644
--- a/components/flags_ui/resources/flags.js
+++ b/components/flags_ui/resources/flags.js
@@ -282,7 +282,7 @@
   this.unavailableExperiments_ = Object.assign({}, FlagSearch.SearchContent);
 
   this.searchBox_ = $('search');
-  this.noMatchMsg_ = document.querySelectorAll('.no-match');
+  this.noMatchMsg_ = document.querySelectorAll('.tab-content .no-match');
 
   this.searchIntervalId_ = null;
   this.initialized = false;
diff --git a/components/gcm_driver/BUILD.gn b/components/gcm_driver/BUILD.gn
index 8f86d89..7fe0e26c 100644
--- a/components/gcm_driver/BUILD.gn
+++ b/components/gcm_driver/BUILD.gn
@@ -40,6 +40,8 @@
     "registration_info.h",
     "system_encryptor.cc",
     "system_encryptor.h",
+    "web_push_metrics.cc",
+    "web_push_metrics.h",
     "web_push_sender.cc",
     "web_push_sender.h",
   ]
diff --git a/components/gcm_driver/gcm_driver.cc b/components/gcm_driver/gcm_driver.cc
index 22efc20..e02feaa4 100644
--- a/components/gcm_driver/gcm_driver.cc
+++ b/components/gcm_driver/gcm_driver.cc
@@ -15,6 +15,7 @@
 #include "components/gcm_driver/crypto/gcm_decryption_result.h"
 #include "components/gcm_driver/crypto/gcm_encryption_result.h"
 #include "components/gcm_driver/gcm_app_handler.h"
+#include "components/gcm_driver/web_push_metrics.h"
 
 namespace gcm {
 
@@ -361,7 +362,7 @@
     case GCMEncryptionResult::NO_KEYS:
     case GCMEncryptionResult::INVALID_SHARED_SECRET:
     case GCMEncryptionResult::ENCRYPTION_FAILED: {
-      LOG(ERROR) << "Webpush message encryption failed";
+      LogSendWebPushMessageResult(SendWebPushMessageResult::kEncryptionFailed);
       std::move(callback).Run(base::nullopt);
       return;
     }
diff --git a/components/gcm_driver/web_push_metrics.cc b/components/gcm_driver/web_push_metrics.cc
new file mode 100644
index 0000000..3e175d1
--- /dev/null
+++ b/components/gcm_driver/web_push_metrics.cc
@@ -0,0 +1,15 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gcm_driver/web_push_metrics.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace gcm {
+
+void LogSendWebPushMessageResult(SendWebPushMessageResult result) {
+  base::UmaHistogramEnumeration("GCM.SendWebPushMessageResult", result);
+}
+
+}  // namespace gcm
diff --git a/components/gcm_driver/web_push_metrics.h b/components/gcm_driver/web_push_metrics.h
new file mode 100644
index 0000000..bbdb5ff0e
--- /dev/null
+++ b/components/gcm_driver/web_push_metrics.h
@@ -0,0 +1,28 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_GCM_DRIVER_WEB_PUSH_METRICS_H_
+#define COMPONENTS_GCM_DRIVER_WEB_PUSH_METRICS_H_
+
+namespace gcm {
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class SendWebPushMessageResult {
+  kSuccessful = 0,
+  kEncryptionFailed = 1,
+  kCreateJWTFailed = 2,
+  kNetworkError = 3,
+  kServerError = 4,
+  kParseResponseFailed = 5,
+  kMaxValue = kParseResponseFailed,
+};
+
+// Logs the |result| to UMA. This should be called when after a web push message
+// is sent.
+void LogSendWebPushMessageResult(SendWebPushMessageResult result);
+
+}  // namespace gcm
+
+#endif  // COMPONENTS_GCM_DRIVER_WEB_PUSH_METRICS_H_
diff --git a/components/gcm_driver/web_push_sender.cc b/components/gcm_driver/web_push_sender.cc
index da06900..506ec5df 100644
--- a/components/gcm_driver/web_push_sender.cc
+++ b/components/gcm_driver/web_push_sender.cc
@@ -13,6 +13,7 @@
 #include "components/gcm_driver/common/gcm_message.h"
 #include "components/gcm_driver/crypto/json_web_token_util.h"
 #include "components/gcm_driver/crypto/p256_key_util.h"
+#include "components/gcm_driver/web_push_metrics.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_request_headers.h"
 #include "net/http/http_status_code.h"
@@ -143,6 +144,7 @@
       GetAuthHeader(vapid_key, message.time_to_live);
   if (!auth_header) {
     LOG(ERROR) << "Failed to create JWT";
+    LogSendWebPushMessageResult(SendWebPushMessageResult::kCreateJWTFailed);
     std::move(callback).Run(base::nullopt);
     return;
   }
@@ -164,6 +166,7 @@
   int net_error = url_loader->NetError();
   if (net_error != net::OK) {
     LOG(ERROR) << "Network Error: " << net_error;
+    LogSendWebPushMessageResult(SendWebPushMessageResult::kNetworkError);
     std::move(callback).Run(base::nullopt);
     return;
   }
@@ -172,6 +175,7 @@
       url_loader->ResponseInfo()->headers;
   if (!url_loader->ResponseInfo() || !response_headers) {
     LOG(ERROR) << "Response info not found";
+    LogSendWebPushMessageResult(SendWebPushMessageResult::kServerError);
     std::move(callback).Run(base::nullopt);
     return;
   }
@@ -179,6 +183,7 @@
   int response_code = response_headers->response_code();
   if (!network::cors::IsOkStatus(response_code)) {
     LOG(ERROR) << "HTTP Error: " << response_code;
+    LogSendWebPushMessageResult(SendWebPushMessageResult::kServerError);
     std::move(callback).Run(base::nullopt);
     return;
   }
@@ -186,6 +191,7 @@
   std::string location;
   if (!response_headers->EnumerateHeader(nullptr, "location", &location)) {
     LOG(ERROR) << "Failed to get location header from response";
+    LogSendWebPushMessageResult(SendWebPushMessageResult::kParseResponseFailed);
     std::move(callback).Run(base::nullopt);
     return;
   }
@@ -193,10 +199,12 @@
   size_t slash_pos = location.rfind("/");
   if (slash_pos == std::string::npos) {
     LOG(ERROR) << "Failed to parse message_id from location header";
+    LogSendWebPushMessageResult(SendWebPushMessageResult::kParseResponseFailed);
     std::move(callback).Run(base::nullopt);
     return;
   }
 
+  LogSendWebPushMessageResult(SendWebPushMessageResult::kSuccessful);
   std::move(callback).Run(base::make_optional(location.substr(slash_pos + 1)));
 }
 
diff --git a/components/media_message_center/media_notification_item.cc b/components/media_message_center/media_notification_item.cc
index 22baa31..794cb20 100644
--- a/components/media_message_center/media_notification_item.cc
+++ b/components/media_message_center/media_notification_item.cc
@@ -67,15 +67,11 @@
 
     // TODO(https://crbug.com/931397): Use dip to calculate the size.
     // Bind an observer to be notified when the artwork changes.
-    mojo::PendingRemote<media_session::mojom::MediaControllerImageObserver>
-        artwork_observer;
-    artwork_observer_receiver_.Bind(
-        artwork_observer.InitWithNewPipeAndPassReceiver());
     media_controller_ptr_->ObserveImages(
         media_session::mojom::MediaSessionImageType::kArtwork,
         kMediaSessionNotificationArtworkMinSize,
         kMediaSessionNotificationArtworkDesiredSize,
-        std::move(artwork_observer));
+        artwork_observer_receiver_.BindNewPipeAndPassRemote());
   }
 
   MaybeHideOrShowNotification();
diff --git a/components/omnibox/browser/BUILD.gn b/components/omnibox/browser/BUILD.gn
index e42edbee..ceb481f 100644
--- a/components/omnibox/browser/BUILD.gn
+++ b/components/omnibox/browser/BUILD.gn
@@ -51,7 +51,6 @@
     "https_valid_in_chip.icon",
     "keyword_search.icon",
     "offline_pin.icon",
-    "open_in_new.icon",
     "page.icon",
     "pedal.icon",
     "plus.icon",
diff --git a/components/password_manager/core/browser/password_manager_internals_service.cc b/components/password_manager/core/browser/password_manager_internals_service.cc
index 3d80b32..27ce2043 100644
--- a/components/password_manager/core/browser/password_manager_internals_service.cc
+++ b/components/password_manager/core/browser/password_manager_internals_service.cc
@@ -6,10 +6,7 @@
 
 namespace password_manager {
 
-PasswordManagerInternalsService::PasswordManagerInternalsService() {
-}
-
-PasswordManagerInternalsService::~PasswordManagerInternalsService() {
-}
+PasswordManagerInternalsService::PasswordManagerInternalsService() = default;
+PasswordManagerInternalsService::~PasswordManagerInternalsService() = default;
 
 }  // namespace password_manager
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index bc00fc8a..ab50ea6 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -16339,6 +16339,10 @@
       'desc': '''Caption text of the field 'windows (Chrome OS clients)' in the summary chart of a policy in the generated documentation''',
       'text': '''Windows (<ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph> clients):'''
     },
+    'doc_banner': {
+      'desc': '''A banner shown at the top of the policy documentation''',
+      'text': '''The Chrome Enterprise policy list is moving! Please update your bookmarks to <ph name="POLICY_DOCUMENTATION_URL">https://cloud.google.com/docs/chrome-enterprise/policies/<ex>https://cloud.google.com/docs/chrome-enterprise/policies/</ex></ph>.'''
+    },
     'doc_intro': {
       'desc': '''Introduction text for the generated policy documentation''',
       'text': '''Both Chromium and Google Chrome support the same set of policies. Please note that this document may include unreleased policies (i.e. their 'Supported on' entry refers to a not-yet released version of <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>) which are subject to change or removal without notice and for which no guarantees of any kind are provided, including no guarantees with respect to their security and privacy properties.
diff --git a/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py b/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py
index b7b4b0c..a98f1a6 100755
--- a/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py
+++ b/components/policy/tools/template_writers/writers/doc_atomic_groups_writer.py
@@ -65,7 +65,7 @@
     self._AddPolicyRow(self._summary_tbody, policy)
 
   def BeginTemplate(self):
-    self._BeginTemplate('group_intro')
+    self._BeginTemplate('group_intro', 'banner')
 
   def WriteTemplate(self, template):
     '''Writes the given template definition.
diff --git a/components/policy/tools/template_writers/writers/doc_writer.py b/components/policy/tools/template_writers/writers/doc_writer.py
index 5753b10..dd9ab509c 100755
--- a/components/policy/tools/template_writers/writers/doc_writer.py
+++ b/components/policy/tools/template_writers/writers/doc_writer.py
@@ -705,12 +705,15 @@
       return schema['minimum'] != 0
     return False
 
-  def _BeginTemplate(self, intro_message_id):
+  def _BeginTemplate(self, intro_message_id, banner_message_id):
     # Add a <div> for the summary section.
     if self._GetChromiumVersionString() is not None:
       self.AddComment(self._main_div, self.config['build'] + \
           ' version: ' + self._GetChromiumVersionString())
 
+    banner_div = self._AddStyledElement(self._main_div, 'div', ['div.banner'],
+                                        {}, '')
+    self._AddParagraphs(banner_div, self.GetLocalizedMessage(banner_message_id))
     summary_div = self.AddElement(self._main_div, 'div')
     self.AddElement(summary_div, 'a', {'name': 'top'})
     self.AddElement(summary_div, 'br')
@@ -751,7 +754,7 @@
     self._indent_level -= 1
 
   def BeginTemplate(self):
-    self._BeginTemplate('intro')
+    self._BeginTemplate('intro', 'banner')
 
   def Init(self):
     dom_impl = minidom.getDOMImplementation('')
@@ -816,6 +819,9 @@
     # the style-sheet is a dictionary and the style attributes will be added
     # "by hand" for each element.
     self._STYLE = {
+        'div.banner': 'background-color: rgb(244,204,204); font-size: x-large; '
+                      'border: 1px solid red; padding: 20px; '
+                      'text-align: center;',
         'table': 'border-style: none; border-collapse: collapse;',
         'tr': 'height: 0px;',
         'td': 'border: 1px dotted rgb(170, 170, 170); padding: 7px; '
diff --git a/components/policy/tools/template_writers/writers/doc_writer_unittest.py b/components/policy/tools/template_writers/writers/doc_writer_unittest.py
index cd7d7ab1e..7d2a211 100755
--- a/components/policy/tools/template_writers/writers/doc_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/doc_writer_unittest.py
@@ -93,6 +93,9 @@
         'doc_feature_can_be_mandatory': {
             'text': '_test_feature_mandatory'
         },
+        'doc_banner': {
+            'text': '_test_banner'
+        },
         'doc_intro': {
             'text': '_test_intro'
         },
@@ -162,6 +165,7 @@
     self.writer.BeginTemplate()
     self.assertEquals(
         self.writer._main_div.toxml(), '<div>'
+        '<div style="style_div.banner;"><p>_test_banner</p></div>'
         '<div>'
         '<a name="top"/><br/><p>_test_intro</p><br/><br/><br/>'
         '<table style="style_table;">'
@@ -186,6 +190,7 @@
     self.assertEquals(
         self.writer._main_div.toxml(), '<div>'
         '<!--test_product version: 39.0.0.0-->'
+        '<div style="style_div.banner;"><p>_test_banner</p></div>'
         '<div>'
         '<a name="top"/><br/><p>_test_intro</p><br/><br/><br/>'
         '<table style="style_table;">'
diff --git a/components/send_tab_to_self/features.cc b/components/send_tab_to_self/features.cc
index 9ac7562..5c2ead8 100644
--- a/components/send_tab_to_self/features.cc
+++ b/components/send_tab_to_self/features.cc
@@ -16,9 +16,6 @@
 const base::Feature kSendTabToSelfBroadcast{"SendTabToSelfBroadcast",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kSendTabToSelfHistory{"SendTabToSelfHistory",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
-
 const base::Feature kSendTabToSelfWhenSignedIn{
     "SendTabToSelfWhenSignedIn", base::FEATURE_DISABLED_BY_DEFAULT};
 
@@ -33,9 +30,4 @@
          base::FeatureList::IsEnabled(kSendTabToSelfWhenSignedIn);
 }
 
-bool HistoryViewEnabled() {
-  return base::FeatureList::IsEnabled(switches::kSyncSendTabToSelf) &&
-         base::FeatureList::IsEnabled(kSendTabToSelfHistory);
-}
-
 }  // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/features.h b/components/send_tab_to_self/features.h
index 6e3b42b..922edf0 100644
--- a/components/send_tab_to_self/features.h
+++ b/components/send_tab_to_self/features.h
@@ -19,11 +19,6 @@
 // targeted to a specific device. This only affects the receiving side.
 extern const base::Feature kSendTabToSelfBroadcast;
 
-// If this feature is enabled, the tabs will be accessible after they are shared
-// in the history tab on desktop devices or in the recent tab page on mobile
-// devices.
-extern const base::Feature kSendTabToSelfHistory;
-
 // If this feature is enabled, we will use signed-in, ephemeral data rather than
 // persistent sync data. Users who are signed in can use the feature regardless
 // of whether they have the sync feature enabled.
@@ -39,10 +34,6 @@
 // persistent sync data. Then the feature may be used by signed-in users,
 // regardless of whether they have full sync enabled.
 bool EnabledOnSignIn();
-
-// Returns whether we should show the send tab to self history ui on desktop or
-// the recent tab UI on mobile, in order to access previously sent tabs.
-bool HistoryViewEnabled();
 }  // namespace send_tab_to_self
 
 #endif  // COMPONENTS_SEND_TAB_TO_SELF_FEATURES_H_
diff --git a/components/services/heap_profiling/public/cpp/profiling_client.cc b/components/services/heap_profiling/public/cpp/profiling_client.cc
index c7b4575d..2f0bf6f 100644
--- a/components/services/heap_profiling/public/cpp/profiling_client.cc
+++ b/components/services/heap_profiling/public/cpp/profiling_client.cc
@@ -201,9 +201,11 @@
         reinterpret_cast<const uintptr_t*>(sample.stack.data() +
                                            sample.stack.size()));
     if (g_include_thread_names) {
-      mojo_sample->stack.push_back(
-          reinterpret_cast<uintptr_t>(sample.thread_name));
-      thread_names.insert(sample.thread_name);
+      static const char* kUnknownThreadName = "<unknown>";
+      const char* thread_name =
+          sample.thread_name ? sample.thread_name : kUnknownThreadName;
+      mojo_sample->stack.push_back(reinterpret_cast<uintptr_t>(thread_name));
+      thread_names.insert(thread_name);
     }
     profile->samples.push_back(std::move(mojo_sample));
   }
diff --git a/components/services/unzip/BUILD.gn b/components/services/unzip/BUILD.gn
index 12b78bd..e078e76 100644
--- a/components/services/unzip/BUILD.gn
+++ b/components/services/unzip/BUILD.gn
@@ -18,7 +18,7 @@
 
   public_deps = [
     "//components/services/filesystem/public/mojom",
-    "//components/services/unzip/public/interfaces",
+    "//components/services/unzip/public/mojom",
     "//services/service_manager/public/cpp",
   ]
 }
diff --git a/components/services/unzip/public/cpp/BUILD.gn b/components/services/unzip/public/cpp/BUILD.gn
index 83715f08..87709d4 100644
--- a/components/services/unzip/public/cpp/BUILD.gn
+++ b/components/services/unzip/public/cpp/BUILD.gn
@@ -12,7 +12,7 @@
 
   public_deps = [
     "//components/services/filesystem:lib",
-    "//components/services/unzip/public/interfaces",
+    "//components/services/unzip/public/mojom",
     "//services/service_manager/public/cpp",
   ]
 }
@@ -40,7 +40,7 @@
 
   deps = [
     "//base",
-    "//components/services/unzip/public/interfaces",
+    "//components/services/unzip/public/mojom",
     "//components/strings",
     "//services/service_manager/public/cpp",
   ]
diff --git a/components/services/unzip/public/cpp/manifest.cc b/components/services/unzip/public/cpp/manifest.cc
index 20c8305..6eb82dc 100644
--- a/components/services/unzip/public/cpp/manifest.cc
+++ b/components/services/unzip/public/cpp/manifest.cc
@@ -6,8 +6,8 @@
 
 #include "base/no_destructor.h"
 #include "build/build_config.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
-#include "components/services/unzip/public/interfaces/unzipper.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
 #include "components/strings/grit/components_strings.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
 
diff --git a/components/services/unzip/public/cpp/test_unzip_service.h b/components/services/unzip/public/cpp/test_unzip_service.h
index 0461c1e7..add69f3d3 100644
--- a/components/services/unzip/public/cpp/test_unzip_service.h
+++ b/components/services/unzip/public/cpp/test_unzip_service.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "components/services/unzip/public/interfaces/unzipper.mojom.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/service_manager/public/cpp/service.h"
 #include "services/service_manager/public/cpp/service_binding.h"
diff --git a/components/services/unzip/public/cpp/unzip.cc b/components/services/unzip/public/cpp/unzip.cc
index fa2df5f7..7c66deca 100644
--- a/components/services/unzip/public/cpp/unzip.cc
+++ b/components/services/unzip/public/cpp/unzip.cc
@@ -18,8 +18,8 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "components/services/filesystem/directory_impl.h"
 #include "components/services/filesystem/lock_table.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
-#include "components/services/unzip/public/interfaces/unzipper.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/service_manager/public/cpp/connector.h"
 
diff --git a/components/services/unzip/public/cpp/unzip_unittest.cc b/components/services/unzip/public/cpp/unzip_unittest.cc
index 8a29bdb..c5166bc7e 100644
--- a/components/services/unzip/public/cpp/unzip_unittest.cc
+++ b/components/services/unzip/public/cpp/unzip_unittest.cc
@@ -13,7 +13,7 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "components/services/unzip/unzip_service.h"
 #include "services/service_manager/public/cpp/test/test_connector_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/services/unzip/public/interfaces/BUILD.gn b/components/services/unzip/public/mojom/BUILD.gn
similarity index 95%
rename from components/services/unzip/public/interfaces/BUILD.gn
rename to components/services/unzip/public/mojom/BUILD.gn
index b7874760..9b16b8a 100644
--- a/components/services/unzip/public/interfaces/BUILD.gn
+++ b/components/services/unzip/public/mojom/BUILD.gn
@@ -4,7 +4,7 @@
 
 import("//mojo/public/tools/bindings/mojom.gni")
 
-mojom("interfaces") {
+mojom("mojom") {
   sources = [
     "unzipper.mojom",
   ]
diff --git a/components/services/unzip/public/interfaces/OWNERS b/components/services/unzip/public/mojom/OWNERS
similarity index 100%
rename from components/services/unzip/public/interfaces/OWNERS
rename to components/services/unzip/public/mojom/OWNERS
diff --git a/components/services/unzip/public/interfaces/README.md b/components/services/unzip/public/mojom/README.md
similarity index 100%
rename from components/services/unzip/public/interfaces/README.md
rename to components/services/unzip/public/mojom/README.md
diff --git a/components/services/unzip/public/interfaces/constants.mojom b/components/services/unzip/public/mojom/constants.mojom
similarity index 100%
rename from components/services/unzip/public/interfaces/constants.mojom
rename to components/services/unzip/public/mojom/constants.mojom
diff --git a/components/services/unzip/public/interfaces/unzipper.mojom b/components/services/unzip/public/mojom/unzipper.mojom
similarity index 100%
rename from components/services/unzip/public/interfaces/unzipper.mojom
rename to components/services/unzip/public/mojom/unzipper.mojom
diff --git a/components/services/unzip/unzipper_impl.h b/components/services/unzip/unzipper_impl.h
index 31fb405..a9764867 100644
--- a/components/services/unzip/unzipper_impl.h
+++ b/components/services/unzip/unzipper_impl.h
@@ -9,7 +9,7 @@
 
 #include "base/files/file.h"
 #include "base/macros.h"
-#include "components/services/unzip/public/interfaces/unzipper.mojom.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
 #include "services/service_manager/public/cpp/service_context_ref.h"
 
 namespace unzip {
diff --git a/components/signin/DEPS b/components/signin/DEPS
index 6e49fb7..49bcd04 100644
--- a/components/signin/DEPS
+++ b/components/signin/DEPS
@@ -1,6 +1,5 @@
 include_rules = [
   "+chromeos/components/account_manager",
-  "+components/account_id",
   "+components/content_settings",
   "+components/google/core",
   "+components/image_fetcher/core",
diff --git a/components/signin/core/browser/BUILD.gn b/components/signin/core/browser/BUILD.gn
index 398c52da..902090e5 100644
--- a/components/signin/core/browser/BUILD.gn
+++ b/components/signin/core/browser/BUILD.gn
@@ -56,7 +56,6 @@
 
   public_deps = [
     "//base",
-    "//components/account_id",
     "//components/content_settings/core/browser",
     "//components/content_settings/core/common",
     "//components/keyed_service/core",
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
index 9df3322e..736223e4 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.cc
@@ -27,6 +27,11 @@
 #include "components/user_manager/user_manager.h"
 #endif  // defined(OS_CHROMEOS)
 
+#if defined(OS_IOS)
+#include "components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.h"
+#include "components/signin/public/identity_manager/ios/device_accounts_provider.h"
+#endif
+
 namespace {
 
 #if defined(OS_ANDROID)
@@ -35,7 +40,16 @@
   return std::make_unique<OAuth2TokenServiceDelegateAndroid>(
       account_tracker_service);
 }
-#else  // defined(OS_ANDROID)
+#elif defined(OS_IOS)
+std::unique_ptr<ProfileOAuth2TokenServiceIOSDelegate> CreateIOSOAuthDelegate(
+    SigninClient* signin_client,
+    std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
+    AccountTrackerService* account_tracker_service) {
+  return std::make_unique<ProfileOAuth2TokenServiceIOSDelegate>(
+      signin_client, std::move(device_accounts_provider),
+      account_tracker_service);
+}
+#else  // !defined(OS_ANDROID) && !defined(OS_IOS)
 #if defined(OS_CHROMEOS)
 std::unique_ptr<signin::ProfileOAuth2TokenServiceDelegateChromeOS>
 CreateCrOsOAuthDelegate(
@@ -96,7 +110,7 @@
 #endif  // defined(OS_WIN)
   );
 }
-#endif  // !defined(OS_ANDROID)
+#endif  // defined(OS_ANDROID)
 
 std::unique_ptr<OAuth2TokenServiceDelegate> CreateOAuth2TokenServiceDelegate(
     AccountTrackerService* account_tracker_service,
@@ -110,6 +124,9 @@
     bool delete_signin_cookies_on_exit,
     scoped_refptr<TokenWebData> token_web_data,
 #endif
+#if defined(OS_IOS)
+    std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
+#endif
 #if defined(OS_WIN)
     MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorCallback
         reauth_callback,
@@ -117,7 +134,11 @@
     network::NetworkConnectionTracker* network_connection_tracker) {
 #if defined(OS_ANDROID)
   return CreateAndroidOAuthDelegate(account_tracker_service);
-#else  // defined(OS_ANDROID)
+#elif defined(OS_IOS)
+  return CreateIOSOAuthDelegate(signin_client,
+                                std::move(device_accounts_provider),
+                                account_tracker_service);
+#else  // !defined(OS_ANDROID) && !defined(OS_IOS)
 #if defined(OS_CHROMEOS)
   if (chromeos::switches::IsAccountManagerEnabled()) {
     return CreateCrOsOAuthDelegate(account_tracker_service,
@@ -137,7 +158,7 @@
 #endif
       network_connection_tracker);
 
-#endif  // !defined(OS_ANDROID)
+#endif  // defined(OS_ANDROID)
 }
 
 }  // namespace
@@ -155,6 +176,9 @@
     bool delete_signin_cookies_on_exit,
     scoped_refptr<TokenWebData> token_web_data,
 #endif
+#if defined(OS_IOS)
+    std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
+#endif
 #if defined(OS_WIN)
     MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorCallback
         reauth_callback,
@@ -179,6 +203,9 @@
 #if !defined(OS_ANDROID)
           delete_signin_cookies_on_exit, token_web_data,
 #endif
+#if defined(OS_IOS)
+          std::move(device_accounts_provider),
+#endif
 #if defined(OS_WIN)
           reauth_callback,
 #endif
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
index 76577069..c39bf3524 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service_builder.h
@@ -22,6 +22,10 @@
 class ProfileOAuth2TokenService;
 class SigninClient;
 
+#if defined(OS_IOS)
+class DeviceAccountsProvider;
+#endif
+
 namespace signin {
 enum class AccountConsistencyMethod;
 }
@@ -53,6 +57,9 @@
     bool delete_signin_cookies_on_exit,
     scoped_refptr<TokenWebData> token_web_data,
 #endif
+#if defined(OS_IOS)
+    std::unique_ptr<DeviceAccountsProvider> device_accounts_provider,
+#endif
 #if defined(OS_WIN)
     MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorCallback
         reauth_callback,
diff --git a/components/signin/public/identity_manager/BUILD.gn b/components/signin/public/identity_manager/BUILD.gn
index feadba5..edb4671 100644
--- a/components/signin/public/identity_manager/BUILD.gn
+++ b/components/signin/public/identity_manager/BUILD.gn
@@ -34,7 +34,6 @@
 
   public_deps = [
     "//base",
-    "//components/account_id",
     "//components/keyed_service/core",
     "//components/signin/public/base",
     "//components/signin/public/base:signin_buildflags",
@@ -55,6 +54,10 @@
     deps += [ "//components/user_manager" ]
   }
 
+  if (is_ios) {
+    deps += [ "ios" ]
+  }
+
   allow_circular_includes_from = [
     # This target is a pair with internal/identity_manager. They always go
     # together and include headers from each other.
diff --git a/components/signin/public/identity_manager/account_info.cc b/components/signin/public/identity_manager/account_info.cc
index 8a4bf493..afe459f 100644
--- a/components/signin/public/identity_manager/account_info.cc
+++ b/components/signin/public/identity_manager/account_info.cc
@@ -5,10 +5,6 @@
 #include "components/signin/public/identity_manager/account_info.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 
-#if defined(OS_CHROMEOS)
-#include "components/user_manager/known_user.h"
-#endif
-
 namespace {
 
 // Updates |field| with |new_value| if non-empty and different; if |new_value|
@@ -123,16 +119,3 @@
      << account.is_under_advanced_protection;
   return os;
 }
-
-AccountId AccountIdFromAccountInfo(const CoreAccountInfo& account_info) {
-#if defined(OS_CHROMEOS)
-  return user_manager::known_user::GetAccountId(
-      account_info.email, account_info.gaia, AccountType::GOOGLE);
-#else
-  if (account_info.email.empty() || account_info.gaia.empty())
-    return EmptyAccountId();
-
-  return AccountId::FromUserEmailGaiaId(
-      gaia::CanonicalizeEmail(account_info.email), account_info.gaia);
-#endif
-}
diff --git a/components/signin/public/identity_manager/account_info.h b/components/signin/public/identity_manager/account_info.h
index 597c8a6..e8d5a61a 100644
--- a/components/signin/public/identity_manager/account_info.h
+++ b/components/signin/public/identity_manager/account_info.h
@@ -7,7 +7,6 @@
 
 #include <string>
 
-#include "components/account_id/account_id.h"
 #include "google_apis/gaia/core_account_id.h"
 #include "ui/gfx/image/image.h"
 
@@ -75,7 +74,4 @@
 bool operator!=(const CoreAccountInfo& l, const CoreAccountInfo& r);
 std::ostream& operator<<(std::ostream& os, const CoreAccountInfo& account);
 
-// Returns AccountID populated from |account_info|.
-AccountId AccountIdFromAccountInfo(const CoreAccountInfo& account_info);
-
 #endif  // COMPONENTS_SIGNIN_PUBLIC_IDENTITY_MANAGER_ACCOUNT_INFO_H_
diff --git a/components/signin/public/identity_manager/account_info_unittest.cc b/components/signin/public/identity_manager/account_info_unittest.cc
index 539278e..b265a5d 100644
--- a/components/signin/public/identity_manager/account_info_unittest.cc
+++ b/components/signin/public/identity_manager/account_info_unittest.cc
@@ -130,14 +130,3 @@
   EXPECT_EQ("test_domain", info.hosted_domain);
   EXPECT_EQ("test_url", info.picture_url);
 }
-
-// Tests that AccountIdFromAccountInfo() passes along a canonicalized email to
-// AccountId.
-TEST_F(AccountInfoTest, AccountIdFromAccountInfo_CanonicalizesRawEmail) {
-  AccountInfo info;
-  info.email = "test.email@gmail.com";
-  info.gaia = "test_id";
-
-  EXPECT_EQ("testemail@gmail.com",
-            AccountIdFromAccountInfo(info).GetUserEmail());
-}
diff --git a/components/signin/public/identity_manager/identity_manager_builder.cc b/components/signin/public/identity_manager/identity_manager_builder.cc
index 5c1bd21..5613f46 100644
--- a/components/signin/public/identity_manager/identity_manager_builder.cc
+++ b/components/signin/public/identity_manager/identity_manager_builder.cc
@@ -31,6 +31,7 @@
 
 #if defined(OS_IOS)
 #include "components/signin/internal/identity_manager/device_accounts_synchronizer_impl.h"
+#include "components/signin/public/identity_manager/ios/device_accounts_provider.h"
 #endif
 
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
@@ -117,6 +118,9 @@
 #if !defined(OS_ANDROID)
           params->delete_signin_cookies_on_exit, params->token_web_data,
 #endif
+#if defined(OS_IOS)
+          std::move(params->device_accounts_provider),
+#endif
 #if defined(OS_WIN)
           params->reauth_callback,
 #endif
diff --git a/components/signin/public/identity_manager/identity_manager_builder.h b/components/signin/public/identity_manager/identity_manager_builder.h
index ef85e6d..70ef4be 100644
--- a/components/signin/public/identity_manager/identity_manager_builder.h
+++ b/components/signin/public/identity_manager/identity_manager_builder.h
@@ -27,6 +27,10 @@
 class TokenWebData;
 #endif
 
+#if defined(OS_IOS)
+class DeviceAccountsProvider;
+#endif
+
 namespace image_fetcher {
 class ImageDecoder;
 }
@@ -72,6 +76,10 @@
   bool is_regular_profile;
 #endif
 
+#if defined(OS_IOS)
+  std::unique_ptr<DeviceAccountsProvider> device_accounts_provider;
+#endif
+
 #if defined(OS_WIN)
   base::RepeatingCallback<bool()> reauth_callback;
 #endif
diff --git a/components/update_client/test_configurator.cc b/components/update_client/test_configurator.cc
index 387601d7..8b8ffb9 100644
--- a/components/update_client/test_configurator.cc
+++ b/components/update_client/test_configurator.cc
@@ -10,7 +10,7 @@
 #include "base/version.h"
 #include "components/prefs/pref_service.h"
 #include "components/services/patch/public/mojom/constants.mojom.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "components/update_client/activity_data_service.h"
 #include "components/update_client/net/network_chromium.h"
 #include "components/update_client/patch/patch_impl.h"
diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn
index e13a263..ed80ecaa 100644
--- a/components/vector_icons/BUILD.gn
+++ b/components/vector_icons/BUILD.gn
@@ -43,6 +43,7 @@
     "mic.icon",
     "midi.icon",
     "notifications.icon",
+    "open_in_new.icon",
     "pause.icon",
     "play_arrow.icon",
     "protocol_handler.icon",
diff --git a/components/omnibox/browser/vector_icons/open_in_new.icon b/components/vector_icons/open_in_new.icon
similarity index 100%
rename from components/omnibox/browser/vector_icons/open_in_new.icon
rename to components/vector_icons/open_in_new.icon
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 425e296..452b622 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -513,8 +513,6 @@
     "background_sync/background_sync_network_observer.h",
     "background_sync/background_sync_proxy.cc",
     "background_sync/background_sync_proxy.h",
-    "background_sync/background_sync_registration.cc",
-    "background_sync/background_sync_registration.h",
     "background_sync/background_sync_registration_helper.cc",
     "background_sync/background_sync_registration_helper.h",
     "background_sync/background_sync_status.h",
diff --git a/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc b/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
index 209bfc8..6df440a 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "base/memory/protected_memory_cfi.h"
 #include "base/strings/pattern.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
@@ -438,15 +439,19 @@
   auto cell_interface = ui::AtkTableCellInterface::Get();
   if (cell_interface.has_value()) {
     AtkTableCell* cell = G_TYPE_CHECK_INSTANCE_CAST(
-        (atk_object), cell_interface->GetType(), AtkTableCell);
+        (atk_object), base::UnsanitizedCfiCall(*cell_interface->GetType)(),
+        AtkTableCell);
 
-    cell_interface->GetRowColumnSpan(cell, &row, &col, &row_span, &col_span);
+    base::UnsanitizedCfiCall (*cell_interface->GetRowColumnSpan)(
+        cell, &row, &col, &row_span, &col_span);
 
-    GPtrArray* column_headers = cell_interface->GetColumnHeaderCells(cell);
+    GPtrArray* column_headers =
+        base::UnsanitizedCfiCall(*cell_interface->GetColumnHeaderCells)(cell);
     n_column_headers = column_headers->len;
     g_ptr_array_unref(column_headers);
 
-    GPtrArray* row_headers = cell_interface->GetRowHeaderCells(cell);
+    GPtrArray* row_headers =
+        base::UnsanitizedCfiCall(*cell_interface->GetRowHeaderCells)(cell);
     n_row_headers = row_headers->len;
     g_ptr_array_unref(row_headers);
   } else {
diff --git a/content/browser/background_sync/background_sync_base_browsertest.h b/content/browser/background_sync/background_sync_base_browsertest.h
index 9de12dc..efe7431 100644
--- a/content/browser/background_sync/background_sync_base_browsertest.h
+++ b/content/browser/background_sync/background_sync_base_browsertest.h
@@ -12,9 +12,9 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/single_thread_task_runner.h"
 #include "content/browser/background_sync/background_sync_context_impl.h"
-#include "content/browser/background_sync/background_sync_registration.h"
 #include "content/browser/background_sync/background_sync_status.h"
 #include "content/browser/service_worker/service_worker_registration.h"
+#include "content/public/browser/background_sync_registration.h"
 #include "content/public/test/content_browser_test.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
diff --git a/content/browser/background_sync/background_sync_launcher.h b/content/browser/background_sync/background_sync_launcher.h
index 39c06ba..ec4980e 100644
--- a/content/browser/background_sync/background_sync_launcher.h
+++ b/content/browser/background_sync/background_sync_launcher.h
@@ -71,4 +71,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_BACKGROUND_SYNC_BACKGROUND_SYNC_LAUNCHER_H_
\ No newline at end of file
+#endif  // CONTENT_BROWSER_BACKGROUND_SYNC_BACKGROUND_SYNC_LAUNCHER_H_
diff --git a/content/browser/background_sync/background_sync_manager.cc b/content/browser/background_sync/background_sync_manager.cc
index 1c582d3..546007b4 100644
--- a/content/browser/background_sync/background_sync_manager.cc
+++ b/content/browser/background_sync/background_sync_manager.cc
@@ -199,7 +199,6 @@
 base::TimeDelta GetNextEventDelay(
     scoped_refptr<ServiceWorkerContextWrapper> sw_context_wrapper,
     const BackgroundSyncRegistration& registration,
-    const url::Origin& origin,
     std::unique_ptr<BackgroundSyncParameters> parameters) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -209,9 +208,8 @@
   if (!background_sync_controller)
     return base::TimeDelta::Max();
 
-  return background_sync_controller->GetNextEventDelay(
-      origin, registration.options()->min_interval, registration.num_attempts(),
-      registration.sync_type(), parameters.get());
+  return background_sync_controller->GetNextEventDelay(registration,
+                                                       parameters.get());
 }
 
 void OnSyncEventFinished(scoped_refptr<ServiceWorkerVersion> active_version,
@@ -478,7 +476,7 @@
   if (emulated_offline_sw_[service_worker_id] > 0)
     return;
   emulated_offline_sw_.erase(service_worker_id);
-  FireReadyEvents(BackgroundSyncType::ONE_SHOT, MakeEmptyCompletion());
+  FireReadyEvents(BackgroundSyncType::ONE_SHOT, base::DoNothing::Once());
 }
 
 BackgroundSyncManager::BackgroundSyncManager(
@@ -613,8 +611,8 @@
     }
   }
 
-  FireReadyEvents(BackgroundSyncType::ONE_SHOT, MakeEmptyCompletion());
-  FireReadyEvents(BackgroundSyncType::PERIODIC, MakeEmptyCompletion());
+  FireReadyEvents(BackgroundSyncType::ONE_SHOT, base::DoNothing::Once());
+  FireReadyEvents(BackgroundSyncType::PERIODIC, base::DoNothing::Once());
   base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
 }
 
@@ -763,6 +761,7 @@
 
   BackgroundSyncRegistration registration;
 
+  registration.set_origin(origin);
   *registration.options() = std::move(options);
 
   // TODO(crbug.com/963487): This section below is really confusing. Add a
@@ -776,7 +775,7 @@
     base::PostTaskWithTraitsAndReplyWithResult(
         FROM_HERE, {BrowserThread::UI},
         base::BindOnce(
-            &GetNextEventDelay, service_worker_context_, registration, origin,
+            &GetNextEventDelay, service_worker_context_, registration,
             std::make_unique<BackgroundSyncParameters>(*parameters_)),
         base::BindOnce(&BackgroundSyncManager::RegisterDidGetDelay,
                        weak_ptr_factory_.GetWeakPtr(), sw_registration_id,
@@ -1043,7 +1042,7 @@
     std::unique_ptr<BackgroundSyncEventKeepAlive> keepalive) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
-  FireReadyEvents(BackgroundSyncType::ONE_SHOT, MakeEmptyCompletion(),
+  FireReadyEvents(BackgroundSyncType::ONE_SHOT, base::DoNothing::Once(),
                   std::move(keepalive));
   op_scheduler_.CompleteOperationAndRunNext();
 }
@@ -1207,7 +1206,7 @@
 
   auto fire_events_callback = base::BindOnce(
       &BackgroundSyncManager::FireReadyEvents, weak_ptr_factory_.GetWeakPtr(),
-      sync_type, MakeEmptyCompletion(), /* keepalive= */ nullptr);
+      sync_type, base::DoNothing::Once(), /* keepalive= */ nullptr);
 
   get_delayed_task(sync_type).Reset(std::move(fire_events_callback));
   ScheduleDelayedTask(sync_type, soonest_wakeup_delta);
@@ -1369,7 +1368,8 @@
       CacheStorageSchedulerOp::kBackgroundSync,
       base::BindOnce(&BackgroundSyncManager::FireReadyEventsImpl,
                      weak_ptr_factory_.GetWeakPtr(), sync_type,
-                     std::move(callback), std::move(keepalive)));
+                     op_scheduler_.WrapCallbackToRunNext(std::move(callback)),
+                     std::move(keepalive)));
 }
 
 void BackgroundSyncManager::FireReadyEventsImpl(
@@ -1610,7 +1610,7 @@
     base::PostTaskWithTraitsAndReplyWithResult(
         FROM_HERE, {BrowserThread::UI},
         base::BindOnce(
-            &GetNextEventDelay, service_worker_context_, *registration, origin,
+            &GetNextEventDelay, service_worker_context_, *registration,
             std::make_unique<BackgroundSyncParameters>(*parameters_)),
         base::BindOnce(&BackgroundSyncManager::EventCompleteDidGetDelay,
                        weak_ptr_factory_.GetWeakPtr(),
@@ -1730,8 +1730,8 @@
   }
 
   // Fire any ready events and call RunInBackground if anything is waiting.
-  FireReadyEvents(BackgroundSyncType::ONE_SHOT, MakeEmptyCompletion());
-  FireReadyEvents(BackgroundSyncType::PERIODIC, MakeEmptyCompletion());
+  FireReadyEvents(BackgroundSyncType::ONE_SHOT, base::DoNothing::Once());
+  FireReadyEvents(BackgroundSyncType::PERIODIC, base::DoNothing::Once());
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
 }
@@ -1767,8 +1767,8 @@
 void BackgroundSyncManager::OnNetworkChanged() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
-  FireReadyEvents(BackgroundSyncType::ONE_SHOT, MakeEmptyCompletion());
-  FireReadyEvents(BackgroundSyncType::PERIODIC, MakeEmptyCompletion());
+  FireReadyEvents(BackgroundSyncType::ONE_SHOT, base::DoNothing::Once());
+  FireReadyEvents(BackgroundSyncType::PERIODIC, base::DoNothing::Once());
 }
 
 void BackgroundSyncManager::SetMaxSyncAttemptsImpl(int max_attempts,
diff --git a/content/browser/background_sync/background_sync_manager.h b/content/browser/background_sync/background_sync_manager.h
index 87c2819..f7ef273 100644
--- a/content/browser/background_sync/background_sync_manager.h
+++ b/content/browser/background_sync/background_sync_manager.h
@@ -22,7 +22,6 @@
 #include "base/time/time.h"
 #include "content/browser/background_sync/background_sync.pb.h"
 #include "content/browser/background_sync/background_sync_proxy.h"
-#include "content/browser/background_sync/background_sync_registration.h"
 #include "content/browser/background_sync/background_sync_status.h"
 #include "content/browser/cache_storage/cache_storage_scheduler.h"
 #include "content/browser/devtools/devtools_background_services_context_impl.h"
@@ -31,6 +30,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/background_sync_controller.h"
 #include "content/public/browser/background_sync_parameters.h"
+#include "content/public/browser/background_sync_registration.h"
 #include "content/public/browser/browser_thread.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
 #include "third_party/blink/public/mojom/background_sync/background_sync.mojom.h"
diff --git a/content/browser/background_sync/background_sync_registration_helper.h b/content/browser/background_sync/background_sync_registration_helper.h
index 414e4aee..8c45095 100644
--- a/content/browser/background_sync/background_sync_registration_helper.h
+++ b/content/browser/background_sync/background_sync_registration_helper.h
@@ -10,8 +10,8 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "content/browser/background_sync/background_sync_registration.h"
 #include "content/browser/background_sync/background_sync_status.h"
+#include "content/public/browser/background_sync_registration.h"
 #include "third_party/blink/public/mojom/background_sync/background_sync.mojom.h"
 
 namespace content {
diff --git a/content/browser/media/system_media_controls_notifier.cc b/content/browser/media/system_media_controls_notifier.cc
index 1d963c5..cf725b0 100644
--- a/content/browser/media/system_media_controls_notifier.cc
+++ b/content/browser/media/system_media_controls_notifier.cc
@@ -73,13 +73,10 @@
   media_controller_ptr_->AddObserver(std::move(media_controller_observer_ptr));
 
   // Observe the active media controller for changes to provided artwork.
-  mojo::PendingRemote<media_session::mojom::MediaControllerImageObserver>
-      image_observer_remote;
-  media_controller_image_observer_receiver_.Bind(
-      image_observer_remote.InitWithNewPipeAndPassReceiver());
   media_controller_ptr_->ObserveImages(
       media_session::mojom::MediaSessionImageType::kArtwork, kMinImageSize,
-      kDesiredImageSize, std::move(image_observer_remote));
+      kDesiredImageSize,
+      media_controller_image_observer_receiver_.BindNewPipeAndPassRemote());
 }
 
 void SystemMediaControlsNotifier::CheckLockState() {
diff --git a/content/browser/native_file_system/native_file_system_handle_base.cc b/content/browser/native_file_system/native_file_system_handle_base.cc
index 16d56fd..8e68957d 100644
--- a/content/browser/native_file_system/native_file_system_handle_base.cc
+++ b/content/browser/native_file_system/native_file_system_handle_base.cc
@@ -46,10 +46,12 @@
       return;
 
     is_readable_ = readable;
-    if (is_readable_)
-      web_contents()->AddNativeFileSystemDirectoryHandle(directory_path_);
-    else
-      web_contents()->RemoveNativeFileSystemDirectoryHandle(directory_path_);
+    if (is_directory_) {
+      if (is_readable_)
+        web_contents()->AddNativeFileSystemDirectoryHandle(directory_path_);
+      else
+        web_contents()->RemoveNativeFileSystemDirectoryHandle(directory_path_);
+    }
   }
 
   void SetWritable(bool writable) {
diff --git a/content/browser/service_worker/fake_service_worker.cc b/content/browser/service_worker/fake_service_worker.cc
index 59b10c9..440b4071 100644
--- a/content/browser/service_worker/fake_service_worker.cc
+++ b/content/browser/service_worker/fake_service_worker.cc
@@ -198,6 +198,12 @@
   std::move(callback).Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
 }
 
+void FakeServiceWorker::DispatchContentDeleteEvent(
+    const std::string& id,
+    DispatchContentDeleteEventCallback callback) {
+  std::move(callback).Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
+}
+
 void FakeServiceWorker::Ping(PingCallback callback) {
   std::move(callback).Run();
 }
diff --git a/content/browser/service_worker/fake_service_worker.h b/content/browser/service_worker/fake_service_worker.h
index b82bb2c5..6f7e669f 100644
--- a/content/browser/service_worker/fake_service_worker.h
+++ b/content/browser/service_worker/fake_service_worker.h
@@ -113,6 +113,9 @@
       base::TimeDelta timeout,
       DispatchExtendableMessageEventWithCustomTimeoutCallback callback)
       override;
+  void DispatchContentDeleteEvent(
+      const std::string& id,
+      DispatchContentDeleteEventCallback callback) override;
   void Ping(PingCallback callback) override;
   void SetIdleTimerDelayToZero() override;
 
diff --git a/content/browser/service_worker/service_worker_metrics.cc b/content/browser/service_worker/service_worker_metrics.cc
index 888aa9e..a1e1853 100644
--- a/content/browser/service_worker/service_worker_metrics.cc
+++ b/content/browser/service_worker/service_worker_metrics.cc
@@ -126,6 +126,8 @@
       return "_BACKGROUND_FETCH_SUCCESS";
     case ServiceWorkerMetrics::EventType::PERIODIC_SYNC:
       return "_PERIODIC_SYNC";
+    case ServiceWorkerMetrics::EventType::CONTENT_DELETE:
+      return "_CONTENT_DELETE";
   }
   return "_UNKNOWN";
 }
@@ -193,6 +195,8 @@
       return "Background Fetch Success";
     case EventType::PERIODIC_SYNC:
       return "Periodic Sync";
+    case EventType::CONTENT_DELETE:
+      return "Content Delete";
   }
   NOTREACHED() << "Got unexpected event type: " << static_cast<int>(event_type);
   return "error";
@@ -487,6 +491,9 @@
       UMA_HISTOGRAM_MEDIUM_TIMES(
           "ServiceWorker.PeriodicBackgroundSyncEvent.Time", time);
       break;
+    case EventType::CONTENT_DELETE:
+      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.ContentDeleteEvent.Time", time);
+      break;
 
     case EventType::NAVIGATION_HINT:
     // The navigation hint should not be sent as an event.
diff --git a/content/browser/service_worker/service_worker_metrics.h b/content/browser/service_worker/service_worker_metrics.h
index 0352cd70..6899c889 100644
--- a/content/browser/service_worker/service_worker_metrics.h
+++ b/content/browser/service_worker/service_worker_metrics.h
@@ -103,8 +103,9 @@
     LONG_RUNNING_MESSAGE = 31,
     BACKGROUND_FETCH_SUCCESS = 32,
     PERIODIC_SYNC = 33,
+    CONTENT_DELETE = 34,
     // Add new events to record here.
-    kMaxValue = PERIODIC_SYNC,
+    kMaxValue = CONTENT_DELETE,
   };
 
   // Used for UMA. Append only.
diff --git a/content/browser/service_worker/service_worker_new_script_loader.cc b/content/browser/service_worker/service_worker_new_script_loader.cc
index 669939fa..cdf9f02 100644
--- a/content/browser/service_worker/service_worker_new_script_loader.cc
+++ b/content/browser/service_worker/service_worker_new_script_loader.cc
@@ -199,6 +199,9 @@
 
   DCHECK(client_);
   auto paused_state = version_->TakePausedStateOfChangedScript(request_url_);
+
+  // TODO(https://crbug.com/648295): Handle the case where it returns nullptr,
+  // which means the imported script was not available when checking the update.
   DCHECK(paused_state);
 
   cache_writer_ = std::move(paused_state->cache_writer);
@@ -339,6 +342,8 @@
   //
   // Step 9.5: "Set request's redirect mode to "error"."
   // https://w3c.github.io/ServiceWorker/#update-algorithm
+  //
+  // TODO(https://crbug.com/889798): Follow redirects for imported scripts.
   CommitCompleted(network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT),
                   ServiceWorkerConsts::kServiceWorkerRedirectError);
 }
diff --git a/content/browser/service_worker/service_worker_single_script_update_checker.cc b/content/browser/service_worker/service_worker_single_script_update_checker.cc
index abb583e..e2ca391 100644
--- a/content/browser/service_worker/service_worker_single_script_update_checker.cc
+++ b/content/browser/service_worker/service_worker_single_script_update_checker.cc
@@ -157,6 +157,7 @@
     // default value.
     // TODO(https://crbug.com/972458): Need the test.
     resource_request.credentials_mode = network::mojom::CredentialsMode::kOmit;
+    resource_request.allow_credentials = false;
 
     // |fetch_request_context_type| and |resource_type| roughly correspond to
     // the request's |destination| in the Fetch spec.
@@ -247,9 +248,14 @@
 void ServiceWorkerSingleScriptUpdateChecker::OnReceiveRedirect(
     const net::RedirectInfo& redirect_info,
     const network::ResourceResponseHead& response_head) {
-  // TODO(momohatt): Raise error and terminate the update check here, like
-  // ServiceWorkerNewScriptLoader does.
-  NOTIMPLEMENTED();
+  // Resource requests for the main service worker script should not follow
+  // redirects.
+  // Step 9.5: "Set request's redirect mode to "error"."
+  // https://w3c.github.io/ServiceWorker/#update-algorithm
+  //
+  // TODO(https://crbug.com/889798): Follow redirects for imported scripts.
+  Fail(blink::ServiceWorkerStatusCode::kErrorNetwork,
+       ServiceWorkerConsts::kServiceWorkerRedirectError);
 }
 
 void ServiceWorkerSingleScriptUpdateChecker::OnUploadProgress(
diff --git a/content/browser/web_package/mock_signed_exchange_handler.cc b/content/browser/web_package/mock_signed_exchange_handler.cc
index 19d826b9..86dabf93 100644
--- a/content/browser/web_package/mock_signed_exchange_handler.cc
+++ b/content/browser/web_package/mock_signed_exchange_handler.cc
@@ -55,6 +55,7 @@
     for (const auto& header : params.response_headers)
       head.headers->AddHeader(header);
     head.is_signed_exchange_inner_response = true;
+    head.content_length = head.headers->GetContentLength();
   }
   base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
diff --git a/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc b/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
index 959eee06..eb720f9 100644
--- a/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
+++ b/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
@@ -369,6 +369,7 @@
       const net::SHA256HashValue& header_integrity,
       const std::string& content,
       const std::vector<std::pair<std::string, std::string>>& sxg_outer_headers,
+      const std::vector<std::string>& sxg_inner_headers = {},
       const base::Time& signature_expire_time = base::Time()) {
     auto sxg_request_counter =
         RequestCounter::CreateAndMonitor(embedded_test_server(), sxg_path);
@@ -390,7 +391,8 @@
 
     MockSignedExchangeHandlerFactory factory({MockSignedExchangeHandlerParams(
         sxg_url, SignedExchangeLoadResult::kSuccess, net::OK, inner_url,
-        "text/html", {}, header_integrity, signature_expire_time)});
+        "text/html", sxg_inner_headers, header_integrity,
+        signature_expire_time)});
     ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
     EXPECT_EQ(0, sxg_request_counter->GetRequestCount());
@@ -467,7 +469,33 @@
       "example.com" /* inner_url_hostname */,
       "/target.html" /* inner_url_path */,
       net::SHA256HashValue({{0x01}}) /* header_integrity */, content,
-      {} /* sxg_outer_headers */);
+      {} /* sxg_outer_headers */, {} /* sxg_inner_headers */);
+  const auto cached_exchanges = GetCachedExchanges(shell());
+  // The content of prefetched SXG is larger than the Blob storage limit.
+  // So the SXG should not be stored to the cache.
+  EXPECT_EQ(0u, cached_exchanges.size());
+}
+
+IN_PROC_BROWSER_TEST_P(
+    SignedExchangePrefetchBrowserTest,
+    PrefetchMainResourceSXG_BlobStorageLimitWithContentLength) {
+  // BlobBuilderFromStream's behavior is different when "content-length"
+  // header is set. So we have BlobStorageLimit test with "content-length".
+  SetBlobLimits();
+
+  std::string content = "<head><title>Prefetch Target (SXG)</title></head>";
+  // Make the content larger than the disk space.
+  content.resize(kTestBlobStorageMaxDiskSpace + 1, ' ');
+  LoadPrefetchMainResourceSXGTestPage(
+      "example.com" /* prefetch_page_hostname */,
+      "/prefetch.html" /* prefetch_page_path */,
+      "example.com" /* sxg_hostname */, "/target.sxg" /* sxg_path */,
+      "example.com" /* inner_url_hostname */,
+      "/target.html" /* inner_url_path */,
+      net::SHA256HashValue({{0x01}}) /* header_integrity */, content,
+      {} /* sxg_outer_headers */,
+      {base::StringPrintf("content-length: %" PRIuS,
+                          content.size())} /* sxg_inner_headers */);
   const auto cached_exchanges = GetCachedExchanges(shell());
   // The content of prefetched SXG is larger than the Blob storage limit.
   // So the SXG should not be stored to the cache.
@@ -486,7 +514,8 @@
       "example.com" /* inner_url_hostname */,
       "/target.html" /* inner_url_path */,
       net::SHA256HashValue({{0x01}}) /* header_integrity */, content,
-      {{"cache-control", "no-store"}} /* sxg_outer_headers */);
+      {{"cache-control", "no-store"}} /* sxg_outer_headers */,
+      {} /* sxg_inner_headers */);
   const auto cached_exchanges = GetCachedExchanges(shell());
   // The signed exchange which response header has "cache-control: no-store"
   // header should not be stored to the cache.
@@ -505,7 +534,7 @@
       "example.com" /* inner_url_hostname */,
       "/target.html" /* inner_url_path */,
       net::SHA256HashValue({{0x01}}) /* header_integrity */, content,
-      {{"vary", "*"}} /* sxg_outer_headers */);
+      {{"vary", "*"}} /* sxg_outer_headers */, {} /* sxg_inner_headers */);
   const auto cached_exchanges = GetCachedExchanges(shell());
   // The signed exchange which response header has "vary: *" header should not
   // be stored to the cache.
@@ -524,7 +553,8 @@
       "example.com" /* inner_url_hostname */,
       "/target.html" /* inner_url_path */,
       net::SHA256HashValue({{0x01}}) /* header_integrity */, content,
-      {{"vary", "accept-encoding"}} /* sxg_outer_headers */);
+      {{"vary", "accept-encoding"}} /* sxg_outer_headers */,
+      {} /* sxg_inner_headers */);
   // The signed exchange which response header has "vary: accept-encoding"
   // header should be stored to the cache.
   const auto cached_exchanges = GetCachedExchanges(shell());
@@ -546,7 +576,7 @@
       hostname, "/prefetch.html" /* prefetch_page_path */, hostname, sxg_path,
       hostname, inner_url_path,
       net::SHA256HashValue({{0x01}}) /* header_integrity */, content,
-      {} /* sxg_outer_headers */);
+      {} /* sxg_outer_headers */, {} /* sxg_inner_headers */);
   EXPECT_EQ(1, sxg_request_counter->GetRequestCount());
 
   const GURL sxg_url = embedded_test_server()->GetURL(hostname, sxg_path);
@@ -590,7 +620,8 @@
       {{"cache-control",
         base::StringPrintf("public, max-age=%d",
                            net::HttpCache::kPrefetchReuseMins * 3 *
-                               60)}} /* sxg_outer_headers */);
+                               60)}} /* sxg_outer_headers */,
+      {} /* sxg_inner_headers */);
   EXPECT_EQ(1, sxg_request_counter->GetRequestCount());
 
   const GURL sxg_url = embedded_test_server()->GetURL(hostname, sxg_path);
@@ -637,7 +668,8 @@
       {{"cache-control",
         base::StringPrintf("public, max-age=%d",
                            net::HttpCache::kPrefetchReuseMins * 3 *
-                               60)}} /* sxg_outer_headers */);
+                               60)}} /* sxg_outer_headers */,
+      {} /* sxg_inner_headers */);
   EXPECT_EQ(1, sxg_request_counter->GetRequestCount());
 
   const GURL sxg_url = embedded_test_server()->GetURL(hostname, sxg_path);
@@ -678,7 +710,7 @@
       hostname, "/prefetch.html" /* prefetch_page_path */, hostname, sxg_path,
       hostname, inner_url_path,
       net::SHA256HashValue({{0x01}}) /* header_integrity */, content,
-      {} /* sxg_outer_headers */,
+      {} /* sxg_outer_headers */, {} /* sxg_inner_headers */,
       base::Time::Now() +
           base::TimeDelta::FromMinutes(net::HttpCache::kPrefetchReuseMins * 2));
   EXPECT_EQ(1, sxg_request_counter->GetRequestCount());
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index a0c8248..02478411 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -59,6 +59,8 @@
     "background_sync_controller.h",
     "background_sync_parameters.cc",
     "background_sync_parameters.h",
+    "background_sync_registration.cc",
+    "background_sync_registration.h",
     "background_tracing_config.cc",
     "background_tracing_config.h",
     "background_tracing_manager.h",
diff --git a/content/public/browser/background_sync_controller.h b/content/public/browser/background_sync_controller.h
index 1a67cdd..0f307ab 100644
--- a/content/public/browser/background_sync_controller.h
+++ b/content/public/browser/background_sync_controller.h
@@ -9,6 +9,7 @@
 
 #include "base/time/time.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/background_sync_registration.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
 #include "third_party/blink/public/mojom/background_sync/background_sync.mojom-shared.h"
 
@@ -63,13 +64,11 @@
       blink::mojom::BackgroundSyncType sync_type) {}
 
   // Calculates the delay after which the next sync event should be fired
-  // for a BackgroundSync registration. The delay is based on the |sync_type|.
+  // for a BackgroundSync registration. The delay is based on the sync_type of
+  // the |registration|.
   virtual base::TimeDelta GetNextEventDelay(
-      const url::Origin& origin,
-      int64_t min_interval,
-      int num_attempts,
-      blink::mojom::BackgroundSyncType sync_type,
-      BackgroundSyncParameters* parameters) = 0;
+      const BackgroundSyncRegistration& registration,
+      content::BackgroundSyncParameters* parameters) = 0;
 
   // Keeps the browser alive to allow a one-shot Background Sync registration
   // to finish firing one sync event.
diff --git a/content/browser/background_sync/background_sync_registration.cc b/content/public/browser/background_sync_registration.cc
similarity index 60%
rename from content/browser/background_sync/background_sync_registration.cc
rename to content/public/browser/background_sync_registration.cc
index 8e9a1e8..3ae67cd 100644
--- a/content/browser/background_sync/background_sync_registration.cc
+++ b/content/public/browser/background_sync_registration.cc
@@ -2,16 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/background_sync/background_sync_registration.h"
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/logging.h"
-#include "base/single_thread_task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "content/public/browser/background_sync_registration.h"
 
 namespace content {
 
+BackgroundSyncRegistration::BackgroundSyncRegistration() = default;
+BackgroundSyncRegistration::BackgroundSyncRegistration(
+    const BackgroundSyncRegistration& other) = default;
+BackgroundSyncRegistration& BackgroundSyncRegistration::operator=(
+    const BackgroundSyncRegistration& other) = default;
+BackgroundSyncRegistration::~BackgroundSyncRegistration() = default;
+
 bool BackgroundSyncRegistration::Equals(
     const BackgroundSyncRegistration& other) const {
   return options_.Equals(other.options_);
diff --git a/content/browser/background_sync/background_sync_registration.h b/content/public/browser/background_sync_registration.h
similarity index 80%
rename from content/browser/background_sync/background_sync_registration.h
rename to content/public/browser/background_sync_registration.h
index b458210..db1d5eb 100644
--- a/content/browser/background_sync/background_sync_registration.h
+++ b/content/public/browser/background_sync_registration.h
@@ -2,30 +2,29 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_BACKGROUND_SYNC_BACKGROUND_SYNC_REGISTRATION_H_
-#define CONTENT_BROWSER_BACKGROUND_SYNC_BACKGROUND_SYNC_REGISTRATION_H_
+#ifndef CONTENT_PUBLIC_BROWSER_BACKGROUND_SYNC_REGISTRATION_H_
+#define CONTENT_PUBLIC_BROWSER_BACKGROUND_SYNC_REGISTRATION_H_
 
 #include <stdint.h>
 
-#include <list>
 #include <string>
 
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/time/time.h"
-#include "content/browser/background_sync/background_sync.pb.h"
 #include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/background_sync/background_sync.mojom.h"
+#include "url/origin.h"
 
 namespace content {
 
 class CONTENT_EXPORT BackgroundSyncRegistration {
  public:
-  BackgroundSyncRegistration() = default;
-  BackgroundSyncRegistration(const BackgroundSyncRegistration& other) = default;
+  BackgroundSyncRegistration();
+  BackgroundSyncRegistration(const BackgroundSyncRegistration& other);
   BackgroundSyncRegistration& operator=(
-      const BackgroundSyncRegistration& other) = default;
-  ~BackgroundSyncRegistration() = default;
+      const BackgroundSyncRegistration& other);
+  ~BackgroundSyncRegistration();
 
   bool Equals(const BackgroundSyncRegistration& other) const;
   bool IsFiring() const;
@@ -59,6 +58,10 @@
                : blink::mojom::BackgroundSyncType::ONE_SHOT;
   }
 
+  const url::Origin& origin() const { return origin_; }
+
+  void set_origin(const url::Origin& origin) { origin_ = origin; }
+
  private:
   blink::mojom::SyncRegistrationOptions options_;
   blink::mojom::BackgroundSyncState sync_state_ =
@@ -66,12 +69,13 @@
   int num_attempts_ = 0;
   int max_attempts_ = 0;
   base::Time delay_until_;
+  url::Origin origin_;
 
   // This member is not persisted to disk. It should be false until the client
-  // has acknowledged tha it has resolved its registration promise.
+  // has acknowledged that it has resolved its registration promise.
   bool resolved_ = false;
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_BACKGROUND_SYNC_BACKGROUND_SYNC_REGISTRATION_H_
+#endif  // CONTENT_PUBLIC_BROWSER_BACKGROUND_SYNC_REGISTRATION_H_
diff --git a/content/test/mock_background_sync_controller.cc b/content/test/mock_background_sync_controller.cc
index 6e27341..c6e8ca1f 100644
--- a/content/test/mock_background_sync_controller.cc
+++ b/content/test/mock_background_sync_controller.cc
@@ -28,32 +28,28 @@
   *parameters = background_sync_parameters_;
 }
 
-// |origin| can be used to potentially suspend or penalize registrations based
-// on the level of user engagement. That logic isn't tested here, and |origin|
-// remains unused.
 base::TimeDelta MockBackgroundSyncController::GetNextEventDelay(
-    const url::Origin& origin,
-    int64_t min_interval,
-    int num_attempts,
-    blink::mojom::BackgroundSyncType sync_type,
+    const BackgroundSyncRegistration& registration,
     BackgroundSyncParameters* parameters) {
   DCHECK(parameters);
 
+  int num_attempts = registration.num_attempts();
+
   if (!num_attempts) {
     // First attempt.
-    switch (sync_type) {
+    switch (registration.sync_type()) {
       case blink::mojom::BackgroundSyncType::ONE_SHOT:
         return base::TimeDelta();
       case blink::mojom::BackgroundSyncType::PERIODIC:
         int64_t effective_gap_ms =
             parameters->min_periodic_sync_events_interval.InMilliseconds();
         return base::TimeDelta::FromMilliseconds(
-            std::max(min_interval, effective_gap_ms));
+            std::max(registration.options()->min_interval, effective_gap_ms));
     }
   }
 
   // After a sync event has been fired.
-  DCHECK_LE(num_attempts, parameters->max_sync_attempts);
+  DCHECK_LT(num_attempts, parameters->max_sync_attempts);
   return parameters->initial_retry_delay *
          pow(parameters->retry_delay_factor, num_attempts - 1);
 }
diff --git a/content/test/mock_background_sync_controller.h b/content/test/mock_background_sync_controller.h
index 3300aa3..3bef2d8 100644
--- a/content/test/mock_background_sync_controller.h
+++ b/content/test/mock_background_sync_controller.h
@@ -31,10 +31,7 @@
       blink::mojom::BackgroundSyncType sync_type) override;
   void GetParameterOverrides(BackgroundSyncParameters* parameters) override;
   base::TimeDelta GetNextEventDelay(
-      const url::Origin& origin,
-      int64_t min_interval,
-      int num_attempts,
-      blink::mojom::BackgroundSyncType sync_type,
+      const BackgroundSyncRegistration& registration,
       BackgroundSyncParameters* parameters) override;
   std::unique_ptr<BackgroundSyncController::BackgroundSyncEventKeepAlive>
   CreateBackgroundSyncEventKeepAlive() override;
diff --git a/extensions/browser/api/management/management_api.cc b/extensions/browser/api/management/management_api.cc
index 0ff0806..c7a9648 100644
--- a/extensions/browser/api/management/management_api.cc
+++ b/extensions/browser/api/management/management_api.cc
@@ -40,7 +40,7 @@
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "extensions/common/manifest_handlers/offline_enabled_info.h"
 #include "extensions/common/manifest_handlers/options_page_info.h"
-#include "extensions/common/manifest_handlers/replacement_web_app.h"
+#include "extensions/common/manifest_handlers/replacement_apps.h"
 #include "extensions/common/manifest_url_handlers.h"
 #include "extensions/common/permissions/permission_message.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -842,9 +842,9 @@
         Error(keys::kGestureNeededForInstallReplacementWebAppError));
   }
 
-  DCHECK(ReplacementWebAppInfo::HasReplacementWebApp(extension()));
+  DCHECK(ReplacementAppsInfo::HasReplacementWebApp(extension()));
   const GURL& web_app_url =
-      ReplacementWebAppInfo::GetReplacementWebApp(extension());
+      ReplacementAppsInfo::GetReplacementWebApp(extension());
 
   DCHECK(web_app_url.is_valid());
   DCHECK(web_app_url.SchemeIs(url::kHttpsScheme));
diff --git a/extensions/browser/content_verifier.cc b/extensions/browser/content_verifier.cc
index 1eb3012..83ccff6 100644
--- a/extensions/browser/content_verifier.cc
+++ b/extensions/browser/content_verifier.cc
@@ -523,8 +523,7 @@
   if (shutdown_on_ui_)
     return;
 
-  ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
-  if (mode != ContentVerifierDelegate::NONE) {
+  if (delegate_->ShouldBeVerified(*extension)) {
     base::PostTaskWithTraits(
         FROM_HERE, {content::BrowserThread::IO},
         base::BindOnce(&ContentVerifier::OnExtensionLoadedOnIO, this,
diff --git a/extensions/browser/content_verifier/test_utils.cc b/extensions/browser/content_verifier/test_utils.cc
index 3276267..c99fc00 100644
--- a/extensions/browser/content_verifier/test_utils.cc
+++ b/extensions/browser/content_verifier/test_utils.cc
@@ -133,9 +133,8 @@
 MockContentVerifierDelegate::MockContentVerifierDelegate() = default;
 MockContentVerifierDelegate::~MockContentVerifierDelegate() = default;
 
-ContentVerifierDelegate::Mode MockContentVerifierDelegate::ShouldBeVerified(
-    const Extension& extension) {
-  return ContentVerifierDelegate::ENFORCE_STRICT;
+bool MockContentVerifierDelegate::ShouldBeVerified(const Extension& extension) {
+  return true;
 }
 
 ContentVerifierKey MockContentVerifierDelegate::GetPublicKey() {
diff --git a/extensions/browser/content_verifier/test_utils.h b/extensions/browser/content_verifier/test_utils.h
index 9c83d4e..f6fd587 100644
--- a/extensions/browser/content_verifier/test_utils.h
+++ b/extensions/browser/content_verifier/test_utils.h
@@ -111,8 +111,7 @@
   ~MockContentVerifierDelegate() override;
 
   // ContentVerifierDelegate:
-  ContentVerifierDelegate::Mode ShouldBeVerified(
-      const Extension& extension) override;
+  bool ShouldBeVerified(const Extension& extension) override;
   ContentVerifierKey GetPublicKey() override;
   GURL GetSignatureFetchUrl(const ExtensionId& extension_id,
                             const base::Version& version) override;
diff --git a/extensions/browser/content_verifier_delegate.h b/extensions/browser/content_verifier_delegate.h
index 5bbe1f7a..8447423 100644
--- a/extensions/browser/content_verifier_delegate.h
+++ b/extensions/browser/content_verifier_delegate.h
@@ -23,48 +23,27 @@
 // This is an interface for clients that want to use a ContentVerifier.
 class ContentVerifierDelegate {
  public:
-  // Note that it is important for these to appear in increasing "severity"
-  // order, because we use this to let command line flags increase, but not
-  // decrease, the mode you're running in compared to the experiment group.
-  enum Mode {
-    // Do not try to fetch content hashes if they are missing, and do not
-    // enforce them if they are present.
-    NONE = 0,
-
-    // If content hashes are missing, try to fetch them, but do not enforce.
-    BOOTSTRAP,
-
-    // If hashes are present, enforce them. If they are missing, try to fetch
-    // them.
-    ENFORCE,
-
-    // Treat the absence of hashes the same as a verification failure.
-    ENFORCE_STRICT
-  };
-
   virtual ~ContentVerifierDelegate() {}
 
-  // This should return what verification mode is appropriate for the given
-  // extension, if any.
-  virtual Mode ShouldBeVerified(const Extension& extension) = 0;
+  // Returns whether or not resources from |extension| should be verified.
+  virtual bool ShouldBeVerified(const Extension& extension) = 0;
 
-  // Should return the public key to use for validating signatures via the two
-  // out parameters.
+  // Returns the public key to use for validating signatures via the two out
+  // parameters.
   virtual ContentVerifierKey GetPublicKey() = 0;
 
-  // This should return a URL that can be used to fetch the
-  // verified_contents.json containing signatures for the given extension
-  // id/version pair.
+  // Returns a URL that can be used to fetch the verified_contents.json
+  // containing signatures for the given extension id/version pair.
   virtual GURL GetSignatureFetchUrl(const std::string& extension_id,
                                     const base::Version& version) = 0;
 
-  // This should return the set of file paths for images used within the
-  // browser process. (These may get transcoded during the install process).
+  // Returns the set of file paths for images used within the browser process.
+  // (These may get transcoded during the install process).
   virtual std::set<base::FilePath> GetBrowserImagePaths(
       const extensions::Extension* extension) = 0;
 
-  // Called when the content verifier detects that a read of a file inside
-  // an extension did not match its expected hash.
+  // Called when the content verifier detects that a read of a file inside an
+  // extension did not match its expected hash.
   virtual void VerifyFailed(const std::string& extension_id,
                             ContentVerifyJob::FailureReason reason) = 0;
 
diff --git a/extensions/browser/sandboxed_unpacker_unittest.cc b/extensions/browser/sandboxed_unpacker_unittest.cc
index ea46c59..2583203e 100644
--- a/extensions/browser/sandboxed_unpacker_unittest.cc
+++ b/extensions/browser/sandboxed_unpacker_unittest.cc
@@ -18,7 +18,7 @@
 #include "base/values.h"
 #include "components/crx_file/id_util.h"
 #include "components/services/unzip/public/cpp/test_unzip_service.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "components/services/unzip/unzip_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/extensions/browser/zipfile_installer.cc b/extensions/browser/zipfile_installer.cc
index b748b7a6..a25cbd6b 100644
--- a/extensions/browser/zipfile_installer.cc
+++ b/extensions/browser/zipfile_installer.cc
@@ -10,7 +10,7 @@
 #include "base/task/post_task.h"
 #include "base/task_runner_util.h"
 #include "components/services/unzip/public/cpp/unzip.h"
-#include "components/services/unzip/public/interfaces/unzipper.mojom.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
 #include "extensions/browser/extension_file_task_runner.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/manifest.h"
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 5142bcb..2513ac0 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -228,8 +228,8 @@
       "manifest_handlers/options_page_info.h",
       "manifest_handlers/permissions_parser.cc",
       "manifest_handlers/permissions_parser.h",
-      "manifest_handlers/replacement_web_app.cc",
-      "manifest_handlers/replacement_web_app.h",
+      "manifest_handlers/replacement_apps.cc",
+      "manifest_handlers/replacement_apps.h",
       "manifest_handlers/requirements_info.cc",
       "manifest_handlers/requirements_info.h",
       "manifest_handlers/sandboxed_page_info.cc",
@@ -428,7 +428,7 @@
       "manifest_handlers/incognito_manifest_unittest.cc",
       "manifest_handlers/kiosk_mode_info_unittest.cc",
       "manifest_handlers/oauth2_manifest_unittest.cc",
-      "manifest_handlers/replacement_web_app_unittest.cc",
+      "manifest_handlers/replacement_apps_unittest.cc",
       "manifest_handlers/shared_module_manifest_unittest.cc",
       "message_bundle_unittest.cc",
       "permissions/api_permission_set_unittest.cc",
diff --git a/extensions/common/api/_manifest_features.json b/extensions/common/api/_manifest_features.json
index bd848bcd..b28af751 100644
--- a/extensions/common/api/_manifest_features.json
+++ b/extensions/common/api/_manifest_features.json
@@ -295,6 +295,11 @@
       "login_screen_extension"
     ]
   },
+  "replacement_android_app": {
+    "channel": "trunk",
+    "extension_types": ["platform_app"],
+    "min_manifest_version": 2
+  },
   "replacement_web_app": {
     "channel": "stable",
     "extension_types": ["extension", "platform_app"],
diff --git a/extensions/common/common_manifest_handlers.cc b/extensions/common/common_manifest_handlers.cc
index 5c05aa4..39a3365 100644
--- a/extensions/common/common_manifest_handlers.cc
+++ b/extensions/common/common_manifest_handlers.cc
@@ -27,7 +27,7 @@
 #include "extensions/common/manifest_handlers/nacl_modules_handler.h"
 #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h"
 #include "extensions/common/manifest_handlers/offline_enabled_info.h"
-#include "extensions/common/manifest_handlers/replacement_web_app.h"
+#include "extensions/common/manifest_handlers/replacement_apps.h"
 #include "extensions/common/manifest_handlers/requirements_info.h"
 #include "extensions/common/manifest_handlers/sandboxed_page_info.h"
 #include "extensions/common/manifest_handlers/shared_module_info.h"
@@ -69,7 +69,7 @@
 #endif
   registry->RegisterHandler(std::make_unique<OAuth2ManifestHandler>());
   registry->RegisterHandler(std::make_unique<OfflineEnabledHandler>());
-  registry->RegisterHandler(std::make_unique<ReplacementWebAppHandler>());
+  registry->RegisterHandler(std::make_unique<ReplacementAppsHandler>());
   registry->RegisterHandler(std::make_unique<RequirementsHandler>());
   registry->RegisterHandler(std::make_unique<SandboxedPageHandler>());
   registry->RegisterHandler(std::make_unique<SharedModuleHandler>());
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc
index 897ef554..e0583b1 100644
--- a/extensions/common/manifest_constants.cc
+++ b/extensions/common/manifest_constants.cc
@@ -135,6 +135,7 @@
 const char kPlatformAppContentSecurityPolicy[] = "app.content_security_policy";
 const char kPublicKey[] = "key";
 const char kRemoveButton[] = "remove_button";
+const char kReplacementAndroidApp[] = "replacement_android_app";
 const char kReplacementWebApp[] = "replacement_web_app";
 const char kRequirements[] = "requirements";
 const char kRunAt[] = "run_at";
@@ -598,6 +599,8 @@
     "Invalid value for 'permissions'.";
 const char kInvalidPermissionScheme[] =
     "Invalid scheme for 'permissions[*]'.";
+const char kInvalidReplacementAndroidApp[] =
+    "Invalid value for 'replacement_android_app'";
 const char kInvalidReplacementWebApp[] =
     "Invalid value for 'replacement_web_app'.";
 const char kInvalidRequirement[] =
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h
index 6be8e12..9612b8fd 100644
--- a/extensions/common/manifest_constants.h
+++ b/extensions/common/manifest_constants.h
@@ -136,8 +136,9 @@
 extern const char kPlatformAppBackgroundScripts[];
 extern const char kPlatformAppContentSecurityPolicy[];
 extern const char kPublicKey[];
-extern const char kReplacementWebApp[];
 extern const char kRemoveButton[];
+extern const char kReplacementAndroidApp[];
+extern const char kReplacementWebApp[];
 extern const char kRequirements[];
 extern const char kRunAt[];
 extern const char kSandboxedPages[];
@@ -423,10 +424,11 @@
 extern const char kInvalidOptionsPageExpectUrlInPackage[];
 extern const char kInvalidOptionsPageInHostedApp[];
 extern const char kInvalidPageAction[];
-extern const char kInvalidPermissionWithDetail[];
 extern const char kInvalidPermission[];
 extern const char kInvalidPermissions[];
 extern const char kInvalidPermissionScheme[];
+extern const char kInvalidPermissionWithDetail[];
+extern const char kInvalidReplacementAndroidApp[];
 extern const char kInvalidReplacementWebApp[];
 extern const char kInvalidRequirement[];
 extern const char kInvalidRequirements[];
diff --git a/extensions/common/manifest_handler.h b/extensions/common/manifest_handler.h
index 702a4ba..76ba39a 100644
--- a/extensions/common/manifest_handler.h
+++ b/extensions/common/manifest_handler.h
@@ -177,7 +177,7 @@
   // Any new manifest handlers added may cause the small_map to overflow
   // to the backup std::unordered_map, which we don't want, as that would
   // defeat the optimization of using small_map.
-  static constexpr size_t kHandlerMax = 74;
+  static constexpr size_t kHandlerMax = 75;
   using FallbackMap = std::unordered_map<std::string, ManifestHandler*>;
   using ManifestHandlerMap = base::small_map<FallbackMap, kHandlerMax>;
   using FallbackPriorityMap = std::unordered_map<ManifestHandler*, int>;
diff --git a/extensions/common/manifest_handlers/replacement_apps.cc b/extensions/common/manifest_handlers/replacement_apps.cc
new file mode 100644
index 0000000..5128a008
--- /dev/null
+++ b/extensions/common/manifest_handlers/replacement_apps.cc
@@ -0,0 +1,140 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/manifest_handlers/replacement_apps.h"
+
+#include <memory>
+
+#include "base/strings/utf_string_conversions.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/manifest_constants.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+namespace extensions {
+
+namespace keys = manifest_keys;
+namespace errors = manifest_errors;
+
+namespace {
+
+const char kReplacementApps[] = "replacement_apps";
+
+const ReplacementAppsInfo* GetReplacementAppsInfo(const Extension* extension) {
+  return static_cast<ReplacementAppsInfo*>(
+      extension->GetManifestData(kReplacementApps));
+}
+
+}  // namespace
+
+ReplacementAppsInfo::ReplacementAppsInfo() {}
+
+ReplacementAppsInfo::~ReplacementAppsInfo() {}
+
+// static
+bool ReplacementAppsInfo::HasReplacementWebApp(const Extension* extension) {
+  const ReplacementAppsInfo* info = GetReplacementAppsInfo(extension);
+  return info && !info->replacement_web_app.is_empty();
+}
+
+// static
+GURL ReplacementAppsInfo::GetReplacementWebApp(const Extension* extension) {
+  const ReplacementAppsInfo* info = GetReplacementAppsInfo(extension);
+
+  if (info && !info->replacement_web_app.is_empty()) {
+    return info->replacement_web_app;
+  }
+
+  return GURL();
+}
+
+// static
+bool ReplacementAppsInfo::HasReplacementAndroidApp(const Extension* extension) {
+  const ReplacementAppsInfo* info = GetReplacementAppsInfo(extension);
+  return info && !info->replacement_android_app.empty();
+}
+
+// static
+std::string ReplacementAppsInfo::GetReplacementAndroidApp(
+    const Extension* extension) {
+  const ReplacementAppsInfo* info = GetReplacementAppsInfo(extension);
+
+  if (info && !info->replacement_android_app.empty()) {
+    return info->replacement_android_app;
+  }
+
+  return base::EmptyString();
+}
+
+bool ReplacementAppsInfo::LoadWebApp(const Extension* extension,
+                                     base::string16* error) {
+  const base::Value* app_value = nullptr;
+  if (!extension->manifest()->Get(keys::kReplacementWebApp, &app_value)) {
+    return true;
+  }
+
+  DCHECK(app_value);
+  if (!app_value->is_string()) {
+    *error = base::ASCIIToUTF16(errors::kInvalidReplacementWebApp);
+    return false;
+  }
+
+  const GURL web_app_url(app_value->GetString());
+  if (!web_app_url.is_valid() || !web_app_url.SchemeIs(url::kHttpsScheme)) {
+    *error = base::ASCIIToUTF16(errors::kInvalidReplacementWebApp);
+    return false;
+  }
+
+  replacement_web_app = std::move(web_app_url);
+  return true;
+}
+
+bool ReplacementAppsInfo::LoadAndroidApp(const Extension* extension,
+                                         base::string16* error) {
+  const base::Value* app_value = nullptr;
+  if (!extension->manifest()->Get(keys::kReplacementAndroidApp, &app_value)) {
+    return true;
+  }
+
+  DCHECK(app_value);
+  if (!app_value->is_string()) {
+    *error = base::ASCIIToUTF16(errors::kInvalidReplacementAndroidApp);
+    return false;
+  }
+
+  replacement_android_app = std::move(app_value->GetString());
+  return true;
+}
+
+bool ReplacementAppsInfo::Parse(const Extension* extension,
+                                base::string16* error) {
+  if (!LoadWebApp(extension, error) || !LoadAndroidApp(extension, error)) {
+    return false;
+  }
+  return true;
+}
+
+ReplacementAppsHandler::ReplacementAppsHandler() {}
+
+ReplacementAppsHandler::~ReplacementAppsHandler() {}
+
+bool ReplacementAppsHandler::Parse(Extension* extension,
+                                   base::string16* error) {
+  std::unique_ptr<ReplacementAppsInfo> info(new ReplacementAppsInfo);
+
+  if (!info->Parse(extension, error)) {
+    return false;
+  }
+
+  extension->SetManifestData(kReplacementApps, std::move(info));
+  return true;
+}
+
+base::span<const char* const> ReplacementAppsHandler::Keys() const {
+  static constexpr const char* kKeys[] = {keys::kReplacementWebApp,
+                                          keys::kReplacementAndroidApp};
+  return kKeys;
+}
+
+}  // namespace extensions
\ No newline at end of file
diff --git a/extensions/common/manifest_handlers/replacement_apps.h b/extensions/common/manifest_handlers/replacement_apps.h
new file mode 100644
index 0000000..20c57ceb
--- /dev/null
+++ b/extensions/common/manifest_handlers/replacement_apps.h
@@ -0,0 +1,67 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_REPLACEMENT_APPS_H_
+#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_REPLACEMENT_APPS_H_
+
+#include "base/strings/string16.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_handler.h"
+
+class GURL;
+
+namespace extensions {
+
+// A structure to hold replacement apps that may be specified in the
+// manifest of an extension using the "replacement_web_app"  or
+// "replacement_android_app" keys.
+struct ReplacementAppsInfo : public Extension::ManifestData {
+ public:
+  ReplacementAppsInfo();
+  ~ReplacementAppsInfo() override;
+
+  // Returns true if the |extension| has a replacement web app.
+  static bool HasReplacementWebApp(const Extension* extension);
+
+  // Returns the replacement web app for |extension|.
+  static GURL GetReplacementWebApp(const Extension* extension);
+
+  // Returns true if the |extension| has a replacement android app.
+  static bool HasReplacementAndroidApp(const Extension* extension);
+
+  // Returns the replacement android app package name for |extension|.
+  static std::string GetReplacementAndroidApp(const Extension* extension);
+
+  bool Parse(const Extension* extension, base::string16* error);
+
+ private:
+  bool LoadWebApp(const Extension* extension, base::string16* error);
+  bool LoadAndroidApp(const Extension* extension, base::string16* error);
+
+  // Optional URL of a Web app.
+  GURL replacement_web_app;
+
+  // Optional package name of an Android app.
+  std::string replacement_android_app;
+
+  DISALLOW_COPY_AND_ASSIGN(ReplacementAppsInfo);
+};
+
+// Parses the "replacement_web_app" and "replacement_android_app" manifest keys.
+class ReplacementAppsHandler : public ManifestHandler {
+ public:
+  ReplacementAppsHandler();
+  ~ReplacementAppsHandler() override;
+
+  bool Parse(Extension* extension, base::string16* error) override;
+
+ private:
+  base::span<const char* const> Keys() const override;
+
+  DISALLOW_COPY_AND_ASSIGN(ReplacementAppsHandler);
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_COMMON_MANIFEST_HANDLERS_REPLACEMENT_APPS_H_
diff --git a/extensions/common/manifest_handlers/replacement_apps_unittest.cc b/extensions/common/manifest_handlers/replacement_apps_unittest.cc
new file mode 100644
index 0000000..50cf954
--- /dev/null
+++ b/extensions/common/manifest_handlers/replacement_apps_unittest.cc
@@ -0,0 +1,141 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/stringprintf.h"
+#include "base/test/values_test_util.h"
+#include "components/version_info/version_info.h"
+#include "extensions/common/features/feature_channel.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/manifest_handlers/replacement_apps.h"
+#include "extensions/common/manifest_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+namespace {
+
+class ReplacementAppsManifestTest : public ManifestTest {
+ public:
+  ReplacementAppsManifestTest() : channel_(version_info::Channel::UNKNOWN) {}
+
+ protected:
+  ManifestData CreateManifest(const char* replacement_web_app,
+                              const char* replacement_android_app) {
+    if (replacement_web_app != nullptr && replacement_android_app != nullptr) {
+      // both replacement apps are specified
+      constexpr char kManifest[] =
+          R"({
+             "name": "test",
+             "version": "1",
+             "manifest_version": 2,
+             "app": {
+               "background": {
+                 "scripts": ["background.js"]
+               }
+             },
+             "replacement_web_app": %s,
+             "replacement_android_app": %s
+           })";
+      base::Value manifest = base::test::ParseJson(base::StringPrintf(
+          kManifest, replacement_web_app, replacement_android_app));
+      return ManifestData(base::Value::ToUniquePtrValue(std::move(manifest)),
+                          "test");
+    } else if (replacement_web_app != nullptr) {
+      // only web replacement app specified
+      constexpr char kManifest[] =
+          R"({
+             "name": "test",
+             "version": "1",
+             "manifest_version": 2,
+             "replacement_web_app": %s
+           })";
+      base::Value manifest = base::test::ParseJson(
+          base::StringPrintf(kManifest, replacement_web_app));
+      return ManifestData(base::Value::ToUniquePtrValue(std::move(manifest)),
+                          "test");
+    } else if (replacement_android_app != nullptr) {
+      // only Android replacement app specified
+      constexpr char kManifest[] =
+          R"({
+            "name": "test",
+            "version": "1",
+            "manifest_version": 2,
+            "app": {
+              "background": {
+                "scripts": ["background.js"]
+              }
+            },
+            "replacement_android_app": %s
+            })";
+      base::Value manifest = base::test::ParseJson(
+          base::StringPrintf(kManifest, replacement_android_app));
+      return ManifestData(base::Value::ToUniquePtrValue(std::move(manifest)),
+                          "test");
+    }
+
+    base::Value manifest = base::test::ParseJson(
+        R"({
+             "name": "test",
+             "version": "1",
+             "manifest_version": 2
+           })");
+    return ManifestData(base::Value::ToUniquePtrValue(std::move(manifest)),
+                        "test");
+  }
+
+ private:
+  ScopedCurrentChannel channel_;
+};
+
+}  // namespace
+
+TEST_F(ReplacementAppsManifestTest, InvalidAndroidAppType) {
+  LoadAndExpectError(CreateManifest(nullptr, "123"),
+                     manifest_errors::kInvalidReplacementAndroidApp);
+  LoadAndExpectError(CreateManifest(nullptr, "true"),
+                     manifest_errors::kInvalidReplacementAndroidApp);
+  LoadAndExpectError(CreateManifest(nullptr, "{}"),
+                     manifest_errors::kInvalidReplacementAndroidApp);
+  LoadAndExpectError(CreateManifest(nullptr, R"({"foo": false})"),
+                     manifest_errors::kInvalidReplacementAndroidApp);
+  LoadAndExpectError(CreateManifest(nullptr, R"(["com.company.app"])"),
+                     manifest_errors::kInvalidReplacementAndroidApp);
+}
+
+TEST_F(ReplacementAppsManifestTest, InvalidWebAppType) {
+  LoadAndExpectError(CreateManifest("32", nullptr),
+                     manifest_errors::kInvalidReplacementWebApp);
+  LoadAndExpectError(CreateManifest("true", nullptr),
+                     manifest_errors::kInvalidReplacementWebApp);
+  LoadAndExpectError(CreateManifest(R"("not_a_valid_url")", nullptr),
+                     manifest_errors::kInvalidReplacementWebApp);
+  LoadAndExpectError(CreateManifest("{}", nullptr),
+                     manifest_errors::kInvalidReplacementWebApp);
+  LoadAndExpectError(CreateManifest(R"({"foo": false})", nullptr),
+                     manifest_errors::kInvalidReplacementWebApp);
+  LoadAndExpectError(CreateManifest(R"("http://not_secure.com")", nullptr),
+                     manifest_errors::kInvalidReplacementWebApp);
+  LoadAndExpectError(CreateManifest(R"(["https://secure.com"])", nullptr),
+                     manifest_errors::kInvalidReplacementWebApp);
+  LoadAndExpectError(
+      CreateManifest(R"(["https://www.google.com", "not_a_valid_url"])",
+                     nullptr),
+      manifest_errors::kInvalidReplacementWebApp);
+}
+
+TEST_F(ReplacementAppsManifestTest, VerifyParse) {
+  scoped_refptr<Extension> good = LoadAndExpectSuccess(
+      CreateManifest(R"("https://www.google.com")", R"("com.company.app")"));
+  EXPECT_TRUE(ReplacementAppsInfo::HasReplacementWebApp(good.get()));
+  EXPECT_EQ(ReplacementAppsInfo::GetReplacementWebApp(good.get()),
+            GURL("https://www.google.com"));
+  EXPECT_TRUE(ReplacementAppsInfo::HasReplacementAndroidApp(good.get()));
+  EXPECT_EQ(ReplacementAppsInfo::GetReplacementAndroidApp(good.get()),
+            "com.company.app");
+}
+
+}  // namespace extensions
diff --git a/extensions/common/manifest_handlers/replacement_web_app.cc b/extensions/common/manifest_handlers/replacement_web_app.cc
deleted file mode 100644
index f1251b2..0000000
--- a/extensions/common/manifest_handlers/replacement_web_app.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "extensions/common/manifest_handlers/replacement_web_app.h"
-
-#include <memory>
-
-#include "base/strings/utf_string_conversions.h"
-#include "extensions/common/manifest.h"
-#include "extensions/common/manifest_constants.h"
-#include "url/gurl.h"
-#include "url/url_constants.h"
-
-namespace extensions {
-
-namespace keys = manifest_keys;
-namespace errors = manifest_errors;
-
-namespace {
-
-const ReplacementWebAppInfo* GetReplacementWebAppInfo(
-    const Extension* extension) {
-  return static_cast<ReplacementWebAppInfo*>(
-      extension->GetManifestData(keys::kReplacementWebApp));
-}
-
-}  // namespace
-
-ReplacementWebAppInfo::ReplacementWebAppInfo() {}
-
-ReplacementWebAppInfo::~ReplacementWebAppInfo() {}
-
-// static
-bool ReplacementWebAppInfo::HasReplacementWebApp(const Extension* extension) {
-  const ReplacementWebAppInfo* info = GetReplacementWebAppInfo(extension);
-  return info;
-}
-
-// static
-GURL ReplacementWebAppInfo::GetReplacementWebApp(const Extension* extension) {
-  const ReplacementWebAppInfo* info = GetReplacementWebAppInfo(extension);
-  if (info)
-    return info->replacement_web_app;
-
-  return GURL();
-}
-
-ReplacementWebAppHandler::ReplacementWebAppHandler() {}
-
-ReplacementWebAppHandler::~ReplacementWebAppHandler() {}
-
-bool ReplacementWebAppHandler::Parse(Extension* extension,
-                                     base::string16* error) {
-  std::string string_value;
-  if (!extension->manifest()->GetString(keys::kReplacementWebApp,
-                                        &string_value)) {
-    *error = base::ASCIIToUTF16(errors::kInvalidReplacementWebApp);
-    return false;
-  }
-  const GURL replacement_web_app(string_value);
-  if (!replacement_web_app.is_valid() ||
-      !replacement_web_app.SchemeIs(url::kHttpsScheme)) {
-    *error = base::ASCIIToUTF16(errors::kInvalidReplacementWebApp);
-    return false;
-  }
-  auto info = std::make_unique<ReplacementWebAppInfo>();
-  info->replacement_web_app = std::move(replacement_web_app);
-  extension->SetManifestData(keys::kReplacementWebApp, std::move(info));
-  return true;
-}
-
-base::span<const char* const> ReplacementWebAppHandler::Keys() const {
-  static constexpr const char* kKeys[] = {keys::kReplacementWebApp};
-  return kKeys;
-}
-
-}  // namespace extensions
diff --git a/extensions/common/manifest_handlers/replacement_web_app.h b/extensions/common/manifest_handlers/replacement_web_app.h
deleted file mode 100644
index c721ef56..0000000
--- a/extensions/common/manifest_handlers/replacement_web_app.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef EXTENSIONS_COMMON_MANIFEST_HANDLERS_REPLACEMENT_WEB_APP_H_
-#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_REPLACEMENT_WEB_APP_H_
-
-#include "base/strings/string16.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/manifest_handler.h"
-
-class GURL;
-
-namespace extensions {
-
-// A structure to hold the replacement web app that may be specified in the
-// manifest of an extension using the "replacement_web_app" key.
-struct ReplacementWebAppInfo : public Extension::ManifestData {
-  ReplacementWebAppInfo();
-  ~ReplacementWebAppInfo() override;
-
-  // Returns true if the specified URL has a replacement web app.
-  static bool HasReplacementWebApp(const Extension* extension);
-
-  // Returns the replacement web app for |extension|.
-  static GURL GetReplacementWebApp(const Extension* extension);
-
-  GURL replacement_web_app;
-};
-
-// Parses the "related_web_apps" manifest key.
-class ReplacementWebAppHandler : public ManifestHandler {
- public:
-  ReplacementWebAppHandler();
-  ~ReplacementWebAppHandler() override;
-
-  bool Parse(Extension* extension, base::string16* error) override;
-
- private:
-  base::span<const char* const> Keys() const override;
-
-  DISALLOW_COPY_AND_ASSIGN(ReplacementWebAppHandler);
-};
-
-}  // namespace extensions
-
-#endif  // EXTENSIONS_COMMON_MANIFEST_HANDLERS_REPLACEMENT_WEB_APP_H_
diff --git a/extensions/common/manifest_handlers/replacement_web_app_unittest.cc b/extensions/common/manifest_handlers/replacement_web_app_unittest.cc
deleted file mode 100644
index 33982b0..0000000
--- a/extensions/common/manifest_handlers/replacement_web_app_unittest.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <memory>
-#include <utility>
-
-#include "base/strings/stringprintf.h"
-#include "base/test/values_test_util.h"
-#include "components/version_info/version_info.h"
-#include "extensions/common/features/feature_channel.h"
-#include "extensions/common/manifest_constants.h"
-#include "extensions/common/manifest_handlers/replacement_web_app.h"
-#include "extensions/common/manifest_test.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace extensions {
-
-namespace {
-
-class ReplacementWebAppManifestTest : public ManifestTest {
- public:
-  ReplacementWebAppManifestTest() = default;
-  ~ReplacementWebAppManifestTest() override = default;
-
- protected:
-  ManifestData CreateManifest(const char* replacement_web_app) {
-    constexpr char kManifest[] =
-        R"({
-             "name": "test",
-             "version": "1",
-             "manifest_version": 2,
-             "replacement_web_app": %s
-           })";
-    base::Value manifest = base::test::ParseJson(
-        base::StringPrintf(kManifest, replacement_web_app));
-    return ManifestData(base::Value::ToUniquePtrValue(std::move(manifest)),
-                        "test");
-  }
-};
-
-}  // namespace
-
-TEST_F(ReplacementWebAppManifestTest, InvalidType) {
-  LoadAndExpectError(CreateManifest("32"),
-                     manifest_errors::kInvalidReplacementWebApp);
-  LoadAndExpectError(CreateManifest("true"),
-                     manifest_errors::kInvalidReplacementWebApp);
-  LoadAndExpectError(CreateManifest(R"("not_a_valid_url")"),
-                     manifest_errors::kInvalidReplacementWebApp);
-  LoadAndExpectError(CreateManifest("{}"),
-                     manifest_errors::kInvalidReplacementWebApp);
-  LoadAndExpectError(CreateManifest(R"({"foo": false})"),
-                     manifest_errors::kInvalidReplacementWebApp);
-  LoadAndExpectError(CreateManifest(R"("http://not_secure.com")"),
-                     manifest_errors::kInvalidReplacementWebApp);
-  LoadAndExpectError(CreateManifest(R"(["https://secure.com"])"),
-                     manifest_errors::kInvalidReplacementWebApp);
-  LoadAndExpectError(
-      CreateManifest(R"(["https://www.google.com", "not_a_valid_url"])"),
-      manifest_errors::kInvalidReplacementWebApp);
-}
-
-TEST_F(ReplacementWebAppManifestTest, VerifyParse) {
-  scoped_refptr<Extension> good =
-      LoadAndExpectSuccess(CreateManifest(R"("https://www.google.com")"));
-  EXPECT_TRUE(ReplacementWebAppInfo::HasReplacementWebApp(good.get()));
-  EXPECT_EQ(ReplacementWebAppInfo::GetReplacementWebApp(good.get()),
-            GURL("https://www.google.com"));
-}
-
-}  // namespace extensions
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index f4b57ee..7a91abf8 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -447,7 +447,6 @@
       "os": {
         "type": "linux"
       },
-      "gl_type": "gl",
       "gl_renderer": ".*Mesa.*",
       "features": [
         "disable_post_sub_buffers_for_onscreen_surfaces"
@@ -1850,7 +1849,6 @@
       "os": {
         "type": "linux"
       },
-      "gl_type": "gl",
       "gl_version_string": ".*Mesa.*",
       "features": [
         "disable_post_sub_buffers_for_onscreen_surfaces"
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index f8013a9..a045439a 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -972,7 +972,7 @@
       name: "Android arm Builder (dbg)"
       mixins: "android-ci"
       mixins: "builderless"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
       execution_timeout_secs: 14400  # 4h
     }
 
@@ -981,7 +981,7 @@
       mixins: "android-ci"
       mixins: "builderless"
       mixins: "goma-many-jobs-for-ci"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
       execution_timeout_secs: 14400  # 4h
     }
 
@@ -1079,28 +1079,28 @@
       name: "Android WebView L (dbg)"
       mixins: "android-ci-goma-rbe-prod"
       mixins: "builderless"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
     }
 
     builders {
       name: "Android WebView M (dbg)"
       mixins: "android-ci-goma-rbe-prod"
       mixins: "builderless"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
     }
 
     builders {
       name: "Android WebView N (dbg)"
       mixins: "android-ci-goma-rbe-prod"
       mixins: "builderless"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
     }
 
     builders {
       name: "Android WebView O (dbg)"
       mixins: "android-ci-goma-rbe-prod"
       mixins: "builderless"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
     }
 
     builders {
@@ -1108,14 +1108,14 @@
       mixins: "android-fyi-ci"
       mixins: "builderless"
       mixins: "goma-rbe-prod"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
     }
 
     builders {
       name: "Android x64 Builder (dbg)"
       mixins: "android-ci-goma-rbe-prod"
       mixins: "builderless"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
       execution_timeout_secs: 14400  # 4h
     }
 
@@ -1123,7 +1123,7 @@
       name: "Android x86 Builder (dbg)"
       mixins: "android-ci-goma-rbe-prod"
       mixins: "builderless"
-      dimensions: "os:Ubuntu-14.04"
+      mixins: "linux-xenial"
     }
 
     builders {
@@ -1261,6 +1261,13 @@
     }
 
     builders {
+      name: "android-oreo-arm64-rel"
+      mixins: "android-ci"
+      mixins: "linux-xenial"
+      mixins: "builderless"
+    }
+
+    builders {
       name: "Cast Android (dbg)"
       mixins: "android-ci"
       mixins: "linux-xenial"
@@ -2501,6 +2508,7 @@
       name: "MSAN Release (chained origins)"
       dimensions: "os:Ubuntu-14.04"
       mixins: "fuzz-ci"
+      mixins: "goma-rbe-prod"
     }
     builders {
       name: "TSAN Release"
@@ -2746,6 +2754,7 @@
       name: "MSAN Release (no origins)"
       dimensions: "os:Ubuntu-14.04"
       mixins: "fuzz-ci"
+      mixins: "goma-rbe-prod"
     }
     builders {
       name: "Linux Chromium OS ASan LSan Builder"
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index 0621dc4..92947ba 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -1563,22 +1563,22 @@
   }
   builders {
     name: "buildbucket/luci.chromium.ci/Android WebView L (dbg)"
-    category: "webview"
+    category: "tester|webview"
     short_name: "L"
   }
   builders {
     name: "buildbucket/luci.chromium.ci/Android WebView M (dbg)"
-    category: "webview"
+    category: "tester|webview"
     short_name: "M"
   }
   builders {
     name: "buildbucket/luci.chromium.ci/Android WebView N (dbg)"
-    category: "webview"
+    category: "tester|webview"
     short_name: "N"
   }
   builders {
     name: "buildbucket/luci.chromium.ci/Android WebView O (dbg)"
-    category: "webview"
+    category: "tester|webview"
     short_name: "O"
   }
   builders {
@@ -1602,6 +1602,11 @@
     category: "on_cq"
     short_name: "san"
   }
+  builders {
+    name: "buildbucket/luci.chromium.ci/android-oreo-arm64-rel"
+    category: "on_cq|future"
+    short_name: "O"
+  }
 }
 
 consoles {
@@ -3059,6 +3064,16 @@
     category: "week6|asan"
     short_name: "media"
   }
+  builders {
+    name: "buildbucket/luci.chromium.ci/MSAN Release (chained origins)"
+    category: "week7|msan"
+    short_name: "chain"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.ci/MSAN Release (no origins)"
+    category: "week7|msan"
+    short_name: "none"
+  }
 }
 
 consoles {
diff --git a/infra/config/luci-scheduler.cfg b/infra/config/luci-scheduler.cfg
index 0d789b3..8ee0c4a 100644
--- a/infra/config/luci-scheduler.cfg
+++ b/infra/config/luci-scheduler.cfg
@@ -309,6 +309,7 @@
   triggers: "android-kitkat-arm-rel"
   triggers: "android-marshmallow-arm64-rel"
   triggers: "android-mojo-webview-rel"
+  triggers: "android-oreo-arm64-rel"
   triggers: "android-archive-rel"
   triggers: "chromeos-amd64-generic-asan-rel"
   triggers: "chromeos-amd64-generic-cfi-thin-lto-rel"
@@ -841,6 +842,16 @@
 }
 
 job {
+  id: "android-oreo-arm64-rel"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "android-oreo-arm64-rel"
+  }
+}
+
+job {
   id: "Cast Android (dbg)"
   acl_sets: "default"
   buildbucket: {
diff --git a/ios/chrome/browser/autofill/autofill_controller_unittest.mm b/ios/chrome/browser/autofill/autofill_controller_unittest.mm
index 5553e28..85e5bb9cf 100644
--- a/ios/chrome/browser/autofill/autofill_controller_unittest.mm
+++ b/ios/chrome/browser/autofill/autofill_controller_unittest.mm
@@ -274,6 +274,7 @@
 
   accessory_mediator_ =
       [[FormInputAccessoryMediator alloc] initWithConsumer:nil
+                                                  delegate:nil
                                               webStateList:NULL
                                        personalDataManager:NULL
                                              passwordStore:NULL];
diff --git a/ios/chrome/browser/autofill/form_suggestion_controller_unittest.mm b/ios/chrome/browser/autofill/form_suggestion_controller_unittest.mm
index 08e63a67..9396b3b 100644
--- a/ios/chrome/browser/autofill/form_suggestion_controller_unittest.mm
+++ b/ios/chrome/browser/autofill/form_suggestion_controller_unittest.mm
@@ -196,6 +196,7 @@
 
     accessory_mediator_ =
         [[FormInputAccessoryMediator alloc] initWithConsumer:mock_consumer_
+                                                    delegate:nil
                                                 webStateList:NULL
                                          personalDataManager:NULL
                                                passwordStore:NULL];
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 7ceec7d..7a47fb3 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -559,10 +559,6 @@
     {"identity-disc", flag_descriptions::kIdentityDiscName,
      flag_descriptions::kIdentityDiscDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kIdentityDisc)},
-    {"enable-send-tab-to-self-history",
-     flag_descriptions::kSendTabToSelfHistoryName,
-     flag_descriptions::kSendTabToSelfHistoryDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(send_tab_to_self::kSendTabToSelfHistory)},
     {"toolbar-new-tab-button", flag_descriptions::kToolbarNewTabButtonName,
      flag_descriptions::kToolbarNewTabButtonDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kToolbarNewTabButton)},
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 532e06c..7e3bc48 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -353,12 +353,6 @@
     "Allows users to broadcast the tab they send to all of their devices "
     "instead of targetting only one device.";
 
-const char kSendTabToSelfHistoryName[] = "Send tab to self history";
-const char kSendTabToSelfHistoryDescription[] =
-    "Allows users to view tabs that were sent to other synced devices by "
-    "accessing these tabs through a landing page either in history or in "
-    "recent tabs. Requires Send tab to self to also be enabled";
-
 const char kSendTabToSelfShowSendingUIName[] =
     "Send tab to self show sending UI";
 const char kSendTabToSelfShowSendingUIDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 049f287..bfc664a4 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -300,11 +300,6 @@
 extern const char kSendTabToSelfBroadcastName[];
 extern const char kSendTabToSelfBroadcastDescription[];
 
-// Title and description for the flag to enable the send tab to self history
-// feature.
-extern const char kSendTabToSelfHistoryName[];
-extern const char kSendTabToSelfHistoryDescription[];
-
 // Title and description for the flag to enable the send tab to self sending UI.
 extern const char kSendTabToSelfShowSendingUIName[];
 extern const char kSendTabToSelfShowSendingUIDescription[];
diff --git a/ios/chrome/browser/passwords/password_controller_unittest.mm b/ios/chrome/browser/passwords/password_controller_unittest.mm
index 7a102b3..69866e43 100644
--- a/ios/chrome/browser/passwords/password_controller_unittest.mm
+++ b/ios/chrome/browser/passwords/password_controller_unittest.mm
@@ -18,6 +18,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/values.h"
+#include "components/autofill/core/browser/logging/log_buffer_submitter.h"
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/common/password_form_fill_data.h"
 #include "components/password_manager/core/browser/mock_password_store.h"
@@ -112,6 +113,9 @@
   // Methods not important for testing.
   void OnLogRouterAvailabilityChanged(bool router_can_be_used) override {}
   void SetSuspended(bool suspended) override {}
+  autofill::LogBufferSubmitter Log() override {
+    return autofill::LogBufferSubmitter(nullptr, false);
+  }
 };
 
 // Creates PasswordController with the given |web_state| and a mock client
@@ -231,6 +235,7 @@
                  providers:@[ [passwordController_ suggestionProvider] ]];
       accessoryMediator_ =
           [[FormInputAccessoryMediator alloc] initWithConsumer:nil
+                                                      delegate:nil
                                                   webStateList:NULL
                                            personalDataManager:NULL
                                                  passwordStore:NULL];
diff --git a/ios/chrome/browser/signin/identity_manager_factory.cc b/ios/chrome/browser/signin/identity_manager_factory.cc
index 84c89f6..faa8314 100644
--- a/ios/chrome/browser/signin/identity_manager_factory.cc
+++ b/ios/chrome/browser/signin/identity_manager_factory.cc
@@ -11,75 +11,16 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
 #include "components/pref_registry/pref_registry_syncable.h"
-#include "components/signin/internal/identity_manager/account_fetcher_service.h"
-#include "components/signin/internal/identity_manager/account_tracker_service.h"
-#include "components/signin/internal/identity_manager/accounts_cookie_mutator_impl.h"
-#include "components/signin/internal/identity_manager/device_accounts_synchronizer_impl.h"
-#include "components/signin/internal/identity_manager/diagnostics_provider_impl.h"
-#include "components/signin/internal/identity_manager/gaia_cookie_manager_service.h"
-#include "components/signin/internal/identity_manager/primary_account_manager.h"
-#include "components/signin/internal/identity_manager/primary_account_mutator_impl.h"
-#include "components/signin/internal/identity_manager/primary_account_policy_manager_impl.h"
-#include "components/signin/internal/identity_manager/profile_oauth2_token_service.h"
-#include "components/signin/internal/identity_manager/profile_oauth2_token_service_delegate_ios.h"
 #include "components/signin/public/base/account_consistency_method.h"
 #include "components/signin/public/base/signin_client.h"
-#include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/identity_manager_builder.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/signin/device_accounts_provider_impl.h"
 #include "ios/chrome/browser/signin/identity_manager_factory_observer.h"
 #include "ios/chrome/browser/signin/signin_client_factory.h"
 
-namespace {
-
-std::unique_ptr<ProfileOAuth2TokenService> BuildTokenService(
-    ios::ChromeBrowserState* chrome_browser_state,
-    AccountTrackerService* account_tracker_service) {
-  auto delegate = std::make_unique<ProfileOAuth2TokenServiceIOSDelegate>(
-      SigninClientFactory::GetForBrowserState(chrome_browser_state),
-      std::make_unique<DeviceAccountsProviderImpl>(), account_tracker_service);
-  return std::make_unique<ProfileOAuth2TokenService>(
-      chrome_browser_state->GetPrefs(), std::move(delegate));
-}
-
-std::unique_ptr<AccountTrackerService> BuildAccountTrackerService(
-    ios::ChromeBrowserState* chrome_browser_state) {
-  auto account_tracker_service = std::make_unique<AccountTrackerService>();
-  account_tracker_service->Initialize(chrome_browser_state->GetPrefs(),
-                                      base::FilePath());
-  return account_tracker_service;
-}
-
-std::unique_ptr<AccountFetcherService> BuildAccountFetcherService(
-    SigninClient* signin_client,
-    ProfileOAuth2TokenService* token_service,
-    AccountTrackerService* account_tracker_service) {
-  auto account_fetcher_service = std::make_unique<AccountFetcherService>();
-  account_fetcher_service->Initialize(signin_client, token_service,
-                                      account_tracker_service,
-                                      image_fetcher::CreateIOSImageDecoder());
-  return account_fetcher_service;
-}
-
-std::unique_ptr<PrimaryAccountManager> BuildPrimaryAccountManager(
-    ios::ChromeBrowserState* chrome_browser_state,
-    AccountTrackerService* account_tracker_service,
-    ProfileOAuth2TokenService* token_service) {
-  SigninClient* client =
-      SigninClientFactory::GetForBrowserState(chrome_browser_state);
-  std::unique_ptr<PrimaryAccountManager> service =
-      std::make_unique<PrimaryAccountManager>(
-          client, token_service, account_tracker_service,
-          signin::AccountConsistencyMethod::kMirror,
-          std::make_unique<PrimaryAccountPolicyManagerImpl>(client));
-  service->Initialize(GetApplicationContext()->GetLocalState());
-  return service;
-}
-
-}  // namespace
-
 void IdentityManagerFactory::RegisterBrowserStatePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
   identity::IdentityManager::RegisterProfilePrefs(registry);
@@ -129,50 +70,18 @@
   ios::ChromeBrowserState* browser_state =
       ios::ChromeBrowserState::FromBrowserState(context);
 
-  // Construct the dependencies that IdentityManager will own.
-  std::unique_ptr<AccountTrackerService> account_tracker_service =
-      BuildAccountTrackerService(browser_state);
+  identity::IdentityManagerBuildParams params;
+  params.account_consistency = signin::AccountConsistencyMethod::kMirror;
+  params.device_accounts_provider =
+      std::make_unique<DeviceAccountsProviderImpl>();
+  params.image_decoder = image_fetcher::CreateIOSImageDecoder();
+  params.local_state = GetApplicationContext()->GetLocalState();
+  params.pref_service = browser_state->GetPrefs();
+  params.profile_path = base::FilePath();
+  params.signin_client = SigninClientFactory::GetForBrowserState(browser_state);
 
-  std::unique_ptr<ProfileOAuth2TokenService> token_service =
-      BuildTokenService(browser_state, account_tracker_service.get());
-
-  auto gaia_cookie_manager_service = std::make_unique<GaiaCookieManagerService>(
-      token_service.get(),
-      SigninClientFactory::GetForBrowserState(browser_state));
-
-  std::unique_ptr<PrimaryAccountManager> primary_account_manager =
-      BuildPrimaryAccountManager(browser_state, account_tracker_service.get(),
-                                 token_service.get());
-
-  auto primary_account_mutator =
-      std::make_unique<identity::PrimaryAccountMutatorImpl>(
-          account_tracker_service.get(), primary_account_manager.get(),
-          browser_state->GetPrefs());
-
-  auto accounts_cookie_mutator =
-      std::make_unique<identity::AccountsCookieMutatorImpl>(
-          gaia_cookie_manager_service.get(), account_tracker_service.get());
-
-  auto diagnostics_provider =
-      std::make_unique<identity::DiagnosticsProviderImpl>(
-          token_service.get(), gaia_cookie_manager_service.get());
-
-  std::unique_ptr<AccountFetcherService> account_fetcher_service =
-      BuildAccountFetcherService(
-          SigninClientFactory::GetForBrowserState(browser_state),
-          token_service.get(), account_tracker_service.get());
-
-  auto device_accounts_synchronizer =
-      std::make_unique<identity::DeviceAccountsSynchronizerImpl>(
-          token_service->GetDelegate());
-
-  auto identity_manager = std::make_unique<identity::IdentityManager>(
-      std::move(account_tracker_service), std::move(token_service),
-      std::move(gaia_cookie_manager_service),
-      std::move(primary_account_manager), std::move(account_fetcher_service),
-      std::move(primary_account_mutator),
-      /*accounts_mutator=*/nullptr, std::move(accounts_cookie_mutator),
-      std::move(diagnostics_provider), std::move(device_accounts_synchronizer));
+  std::unique_ptr<identity::IdentityManager> identity_manager =
+      identity::BuildIdentityManager(&params);
 
   for (auto& observer : observer_list_)
     observer.IdentityManagerCreated(identity_manager.get());
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.mm
index fdbf5f41..4c02723 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.mm
@@ -6,6 +6,7 @@
 
 #include <vector>
 
+#include "base/ios/ios_util.h"
 #include "base/mac/foundation_util.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/common/autofill_features.h"
@@ -35,6 +36,7 @@
     AutofillSecurityAlertPresenter,
     AddressCoordinatorDelegate,
     CardCoordinatorDelegate,
+    FormInputAccessoryMediatorDelegate,
     ManualFillAccessoryViewControllerDelegate,
     PasswordCoordinatorDelegate>
 
@@ -89,6 +91,7 @@
 
     _formInputAccessoryMediator = [[FormInputAccessoryMediator alloc]
            initWithConsumer:self.formInputAccessoryViewController
+                   delegate:self
                webStateList:webStateList
         personalDataManager:personalDataManager
               passwordStore:passwordStore];
@@ -167,6 +170,18 @@
   [self.childCoordinators addObject:addressCoordinator];
 }
 
+#pragma mark - FormInputAccessoryMediatorDelegate
+
+- (void)mediatorDidDetectKeyboardHide:(FormInputAccessoryMediator*)mediator {
+  // On iOS 13, beta 3, the popover is not dismissed when the keyboard hides.
+  // This explicitly dismiss any popover.
+  if (base::ios::IsRunningOnIOS13OrLater() && IsIPadIdiom()) {
+    [self stopChildren];
+    [self.formInputAccessoryMediator enableSuggestions];
+    [self.formInputAccessoryViewController resetManualFallbackIcons];
+  }
+}
+
 #pragma mark - ManualFillAccessoryViewControllerDelegate
 
 - (void)keyboardButtonPressed {
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h b/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h
index 237e17e..5ae423e 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h
@@ -14,6 +14,7 @@
 
 @class ChromeCoordinator;
 @protocol FormInputAccessoryConsumer;
+@class FormInputAccessoryMediator;
 @protocol FormInputSuggestionsProvider;
 @class JsSuggestionManager;
 
@@ -27,6 +28,15 @@
 
 class WebStateList;
 
+// Delegate in charge of reacting to accessory mediator events.
+@protocol FormInputAccessoryMediatorDelegate
+
+// The mediator detected that the keyboard was hidden and it is no longer
+// present on the screen.
+- (void)mediatorDidDetectKeyboardHide:(FormInputAccessoryMediator*)mediator;
+
+@end
+
 // This class contains all the logic to get and provide keyboard input accessory
 // views to its consumer. As well as telling the consumer when the default
 // accessory view shoeuld be restored to the system default.
@@ -36,6 +46,7 @@
 // the passed consumer. `webSateList` can be nullptr and `consumer` can be nil.
 - (instancetype)
        initWithConsumer:(id<FormInputAccessoryConsumer>)consumer
+               delegate:(id<FormInputAccessoryMediatorDelegate>)delegate
            webStateList:(WebStateList*)webStateList
     personalDataManager:(autofill::PersonalDataManager*)personalDataManager
           passwordStore:
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.mm
index d0448fc..8b93e34 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.mm
@@ -47,6 +47,9 @@
 // The main consumer for this mediator.
 @property(nonatomic, weak) id<FormInputAccessoryConsumer> consumer;
 
+// The delegate for this object.
+@property(nonatomic, weak) id<FormInputAccessoryMediatorDelegate> delegate;
+
 // The object that manages the currently-shown custom accessory view.
 @property(nonatomic, weak) id<FormInputSuggestionsProvider> currentProvider;
 
@@ -117,6 +120,7 @@
 
 - (instancetype)
        initWithConsumer:(id<FormInputAccessoryConsumer>)consumer
+               delegate:(id<FormInputAccessoryMediatorDelegate>)delegate
            webStateList:(WebStateList*)webStateList
     personalDataManager:(autofill::PersonalDataManager*)personalDataManager
           passwordStore:
@@ -125,7 +129,7 @@
   if (self) {
     _consumer = consumer;
     _consumer.navigationDelegate = self;
-
+    _delegate = delegate;
     if (webStateList) {
       _webStateList = webStateList;
       _webStateListObserver =
@@ -240,6 +244,9 @@
 - (void)keyboardWillChangeToState:(KeyboardState)keyboardState {
   [self updateSuggestionsIfNeeded];
   [self.consumer keyboardWillChangeToState:keyboardState];
+  if (!keyboardState.isVisible) {
+    [self.delegate mediatorDidDetectKeyboardHide:self];
+  }
 }
 
 #pragma mark - FormActivityObserver
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
index 96fe4b2..8a08a3a 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
@@ -377,6 +377,10 @@
     // TODO(crbug.com/908776): OtherPasswordsMatcher is disabled in <11.3.
     return;
   }
+  if (base::ios::IsRunningOnIOS13OrLater() && [ChromeEarlGrey isIPadIdiom]) {
+    // TODO(crbug.com/984977): Support this behavior in iPads again.
+    return;
+  }
 
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_search.imageset/popup_menu_search@2x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_search.imageset/popup_menu_search@2x.png
index faaa842..0ab97e59 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_search.imageset/popup_menu_search@2x.png
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_search.imageset/popup_menu_search@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_search.imageset/popup_menu_search@3x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_search.imageset/popup_menu_search@3x.png
index 32ecd03..7c8897e 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_search.imageset/popup_menu_search@3x.png
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_search.imageset/popup_menu_search@3x.png
Binary files differ
diff --git a/ios/chrome/browser/web/BUILD.gn b/ios/chrome/browser/web/BUILD.gn
index 0d3e82a..0aea609 100644
--- a/ios/chrome/browser/web/BUILD.gn
+++ b/ios/chrome/browser/web/BUILD.gn
@@ -105,7 +105,7 @@
     "//base/test:test_support",
     "//components/search_engines",
     "//components/services/patch/public/mojom",
-    "//components/services/unzip/public/interfaces",
+    "//components/services/unzip/public/mojom",
     "//components/strings:components_strings_grit",
     "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state:test_support",
@@ -228,7 +228,7 @@
     "//components/services/patch/public/mojom",
     "//components/services/unzip:lib",
     "//components/services/unzip/public/cpp:manifest",
-    "//components/services/unzip/public/interfaces",
+    "//components/services/unzip/public/mojom",
     "//components/strings",
     "//components/version_info",
     "//ios/chrome/app/resources:ios_resources",
diff --git a/ios/chrome/browser/web/chrome_overlay_manifests.cc b/ios/chrome/browser/web/chrome_overlay_manifests.cc
index 4786210..20dfe04 100644
--- a/ios/chrome/browser/web/chrome_overlay_manifests.cc
+++ b/ios/chrome/browser/web/chrome_overlay_manifests.cc
@@ -8,7 +8,7 @@
 #include "components/services/patch/public/cpp/manifest.h"
 #include "components/services/patch/public/mojom/constants.mojom.h"
 #include "components/services/unzip/public/cpp/manifest.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "services/identity/public/cpp/manifest.h"
 #include "services/identity/public/mojom/constants.mojom.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
diff --git a/ios/chrome/browser/web/chrome_web_client.mm b/ios/chrome/browser/web/chrome_web_client.mm
index b368b3e..3a7dc37e 100644
--- a/ios/chrome/browser/web/chrome_web_client.mm
+++ b/ios/chrome/browser/web/chrome_web_client.mm
@@ -12,7 +12,7 @@
 #include "components/dom_distiller/core/url_constants.h"
 #include "components/services/patch/patch_service.h"
 #include "components/services/patch/public/mojom/constants.mojom.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
 #include "components/services/unzip/unzip_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/version_info/version_info.h"
diff --git a/ios/chrome/browser/web/services_unittest.mm b/ios/chrome/browser/web/services_unittest.mm
index 34c5f7a4..c6f6277 100644
--- a/ios/chrome/browser/web/services_unittest.mm
+++ b/ios/chrome/browser/web/services_unittest.mm
@@ -6,8 +6,8 @@
 #include "base/test/bind_test_util.h"
 #include "components/services/patch/public/mojom/constants.mojom.h"
 #include "components/services/patch/public/mojom/file_patcher.mojom.h"
-#include "components/services/unzip/public/interfaces/constants.mojom.h"
-#include "components/services/unzip/public/interfaces/unzipper.mojom.h"
+#include "components/services/unzip/public/mojom/constants.mojom.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
 #include "ios/chrome/browser/web/chrome_web_client.h"
 #include "ios/web/public/service_manager_connection.h"
 #include "ios/web/public/test/scoped_testing_web_client.h"
diff --git a/media/capture/video/video_capture_system_impl.cc b/media/capture/video/video_capture_system_impl.cc
index 966c3ee..a0e69a9b 100644
--- a/media/capture/video/video_capture_system_impl.cc
+++ b/media/capture/video/video_capture_system_impl.cc
@@ -12,6 +12,8 @@
 #include "media/base/bind_to_current_loop.h"
 
 #if defined(OS_CHROMEOS)
+#include "base/command_line.h"
+#include "media/base/media_switches.h"
 #include "media/capture/video/chromeos/public/cros_features.h"
 #include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
 #endif  // defined(OS_CHROMEOS)
@@ -168,7 +170,9 @@
     cros::mojom::CrosImageCaptureRequest request) {
   CHECK(factory_);
 
-  if (media::ShouldUseCrosCameraService()) {
+  if (media::ShouldUseCrosCameraService() &&
+      !base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kUseFakeDeviceForMediaStream)) {
     static_cast<VideoCaptureDeviceFactoryChromeOS*>(factory_.get())
         ->BindCrosImageCaptureRequest(std::move(request));
   }
diff --git a/media/gpu/image_processor_test.cc b/media/gpu/image_processor_test.cc
index 0a358c9e..b56823d 100644
--- a/media/gpu/image_processor_test.cc
+++ b/media/gpu/image_processor_test.cc
@@ -6,10 +6,12 @@
 #include <string>
 #include <tuple>
 
+#include "base/at_exit.h"
 #include "base/files/file_path.h"
 #include "base/hash/md5.h"
 #include "base/test/launcher/unit_test_launcher.h"
 #include "base/test/test_suite.h"
+#include "base/test/test_timeouts.h"
 #include "build/build_config.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_frame_layout.h"
@@ -155,6 +157,12 @@
   testing::InitGoogleTest(&argc, argv);
   base::CommandLine::Init(argc, argv);
 
+  // Initialize test timeouts and set up an exit manager which is required to
+  // run callbacks on shutdown.
+  // TODO(dstaessens): Use base::TestSuite which performs initialization.
+  TestTimeouts::Initialize();
+  base::AtExitManager at_exit_manager;
+
   auto* const test_environment = new media::test::VideoTestEnvironment;
   testing::AddGlobalTestEnvironment(test_environment);
   return RUN_ALL_TESTS();
diff --git a/media/gpu/test/video_test_environment.cc b/media/gpu/test/video_test_environment.cc
index db830a1..03bcd002 100644
--- a/media/gpu/test/video_test_environment.cc
+++ b/media/gpu/test/video_test_environment.cc
@@ -7,7 +7,6 @@
 #include "base/command_line.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_task_environment.h"
-#include "base/test/test_timeouts.h"
 #include "build/build_config.h"
 #include "media/gpu/buildflags.h"
 #include "mojo/core/embedder/embedder.h"
@@ -45,7 +44,6 @@
   // Setting up a task environment will create a task runner for the current
   // thread and allow posting tasks to other threads. This is required for video
   // tests to function correctly.
-  TestTimeouts::Initialize();
   task_environment_ = std::make_unique<base::test::ScopedTaskEnvironment>(
       base::test::ScopedTaskEnvironment::MainThreadType::UI);
 
diff --git a/media/gpu/test/video_test_environment.h b/media/gpu/test/video_test_environment.h
index 76838d7..7e5aecb 100644
--- a/media/gpu/test/video_test_environment.h
+++ b/media/gpu/test/video_test_environment.h
@@ -9,7 +9,6 @@
 
 #include <memory>
 
-#include "base/at_exit.h"
 #include "base/files/file_path.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -45,9 +44,6 @@
   // Whether the test environment has been initialized.
   bool initialized_ = false;
 
-  // An exit manager is required to run callbacks on shutdown.
-  base::AtExitManager at_exit_manager;
-
   std::unique_ptr<base::test::ScopedTaskEnvironment> task_environment_;
 
 #if defined(USE_OZONE)
diff --git a/media/gpu/v4l2/v4l2_decode_surface.cc b/media/gpu/v4l2/v4l2_decode_surface.cc
index 5756f98..161d9d63 100644
--- a/media/gpu/v4l2/v4l2_decode_surface.cc
+++ b/media/gpu/v4l2/v4l2_decode_surface.cc
@@ -3,9 +3,13 @@
 // found in the LICENSE file.
 
 #include "media/gpu/v4l2/v4l2_decode_surface.h"
+
+#include <linux/media.h>
 #include <linux/videodev2.h>
+#include <sys/ioctl.h>
 
 #include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
 #include "base/strings/stringprintf.h"
 #include "media/gpu/macros.h"
 
@@ -129,4 +133,63 @@
   return true;
 }
 
+V4L2RequestDecodeSurface::V4L2RequestDecodeSurface(int input_record,
+                                                   int output_record,
+                                                   int request_fd,
+                                                   base::OnceClosure release_cb)
+    : V4L2DecodeSurface(input_record, output_record, std::move(release_cb)),
+      request_fd_(request_fd) {}
+
+// static
+base::Optional<scoped_refptr<V4L2RequestDecodeSurface>>
+V4L2RequestDecodeSurface::Create(int input_record,
+                                 int output_record,
+                                 int request_fd,
+                                 base::OnceClosure release_cb) {
+  // First reinit the request to make sure we can use it for a new submission.
+  int ret = HANDLE_EINTR(ioctl(request_fd, MEDIA_REQUEST_IOC_REINIT));
+  if (ret < 0) {
+    VPLOGF(1) << "Failed to reinit request: ";
+    return base::nullopt;
+  }
+
+  return new V4L2RequestDecodeSurface(input_record, output_record, request_fd,
+                                      std::move(release_cb));
+}
+
+void V4L2RequestDecodeSurface::PrepareSetCtrls(
+    struct v4l2_ext_controls* ctrls) const {
+  DCHECK_NE(ctrls, nullptr);
+  DCHECK_GE(request_fd_, 0);
+
+  ctrls->which = V4L2_CTRL_WHICH_REQUEST_VAL;
+  ctrls->request_fd = request_fd_;
+}
+
+void V4L2RequestDecodeSurface::PrepareQueueBuffer(
+    struct v4l2_buffer* buffer) const {
+  DCHECK_NE(buffer, nullptr);
+  DCHECK_GE(request_fd_, 0);
+
+  buffer->request_fd = request_fd_;
+  buffer->flags |= V4L2_BUF_FLAG_REQUEST_FD;
+  // Copy the buffer index as the timestamp.
+  DCHECK_EQ(static_cast<int>(buffer->index), input_record());
+  buffer->timestamp.tv_sec = 0;
+  buffer->timestamp.tv_usec = buffer->index;
+}
+
+uint64_t V4L2RequestDecodeSurface::GetReferenceID() const {
+  // Convert the input buffer ID to what the internal representation of
+  // the timestamp we submitted will be (tv_usec * 1000).
+  return input_record() * 1000;
+}
+
+bool V4L2RequestDecodeSurface::Submit() const {
+  DCHECK_GE(request_fd_, 0);
+
+  int ret = HANDLE_EINTR(ioctl(request_fd_, MEDIA_REQUEST_IOC_QUEUE));
+  return ret == 0;
+}
+
 }  // namespace media
diff --git a/media/gpu/v4l2/v4l2_decode_surface.h b/media/gpu/v4l2/v4l2_decode_surface.h
index 617ac5f..c9a7e4f 100644
--- a/media/gpu/v4l2/v4l2_decode_surface.h
+++ b/media/gpu/v4l2/v4l2_decode_surface.h
@@ -10,6 +10,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "media/gpu/v4l2/v4l2_device.h"
 #include "ui/gfx/geometry/rect.h"
@@ -149,6 +150,37 @@
   uint32_t config_store_;
 };
 
+// An implementation of V4L2DecodeSurface that uses requests to associate
+// controls/buffers to frames
+class V4L2RequestDecodeSurface : public V4L2DecodeSurface {
+ public:
+  // Constructor method for V4L2RequestDecodeSurface. It will return
+  // base::nullopt if a runtime error occurred when creating the decode surface.
+  //
+  // request_fd is the FD of the request to use for decoding this frame.
+  // Note that it will not be closed after the request is submitted - the caller
+  // is responsible for managing its lifetime.
+  static base::Optional<scoped_refptr<V4L2RequestDecodeSurface>> Create(
+      int input_record,
+      int output_record,
+      int request_fd,
+      base::OnceClosure release_cb);
+
+  void PrepareSetCtrls(struct v4l2_ext_controls* ctrls) const override;
+  void PrepareQueueBuffer(struct v4l2_buffer* buffer) const override;
+  uint64_t GetReferenceID() const override;
+  bool Submit() const override;
+
+ private:
+  // FD of the request to use.
+  const int request_fd_;
+
+  V4L2RequestDecodeSurface(int input_record,
+                           int output_record,
+                           int request_fd,
+                           base::OnceClosure release_cb);
+};
+
 }  // namespace media
 
 #endif  // MEDIA_GPU_V4L2_V4L2_DECODE_SURFACE_H_
diff --git a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
index a1196f1..fc233e5 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
@@ -6,6 +6,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <linux/media.h>
 #include <linux/videodev2.h>
 #include <poll.h>
 #include <string.h>
@@ -463,6 +464,27 @@
     VLOGF(1) << "Could not allocate enough output buffers";
     return false;
   }
+
+  // Open media device if we are discovering that we support requests.
+  supports_requests_ =
+      static_cast<bool>(reqbufs.capabilities & V4L2_BUF_CAP_SUPPORTS_REQUESTS);
+  if (supports_requests_) {
+    VLOGF(2) << "Using request API";
+    DCHECK(!media_fd_.is_valid());
+    // Let's try to open the media device
+    // TODO(crbug.com/985230): remove this hardcoding, replace with V4L2Device
+    // integration.
+    int media_fd = open("/dev/media-dec0", O_RDWR, 0);
+    if (media_fd < 0) {
+      VPLOGF(1) << "Failed to open media device: ";
+      NOTIFY_ERROR(PLATFORM_FAILURE);
+    }
+    media_fd_ = base::ScopedFD(media_fd);
+
+  } else {
+    VLOGF(2) << "Using config store";
+  }
+
   input_buffer_map_.resize(reqbufs.count);
   for (size_t i = 0; i < input_buffer_map_.size(); ++i) {
     free_input_buffers_.push_back(i);
@@ -489,6 +511,19 @@
     }
     input_buffer_map_[i].address = address;
     input_buffer_map_[i].length = buffer.m.planes[0].length;
+
+    if (supports_requests_) {
+      int request_fd;
+
+      DCHECK(media_fd_.is_valid());
+      int ret = HANDLE_EINTR(
+          ioctl(media_fd_.get(), MEDIA_IOC_REQUEST_ALLOC, &request_fd));
+      if (ret < 0) {
+        VPLOGF(1) << "Failed to create request: ";
+        return false;
+      }
+      input_buffer_map_[i].request_fd = base::ScopedFD(request_fd);
+    }
   }
 
   return true;
@@ -582,6 +617,8 @@
 
   input_buffer_map_.clear();
   free_input_buffers_.clear();
+
+  media_fd_.reset();
 }
 
 void V4L2SliceVideoDecodeAccelerator::DismissPictures(
@@ -2017,11 +2054,24 @@
   DCHECK(decoder_current_bitstream_buffer_ != nullptr);
   input_record.input_id = decoder_current_bitstream_buffer_->input_id;
 
-  scoped_refptr<V4L2DecodeSurface> dec_surface =
-      new V4L2ConfigStoreDecodeSurface(
-          input, output,
-          base::BindOnce(&V4L2SliceVideoDecodeAccelerator::ReuseOutputBuffer,
-                         base::Unretained(this), output));
+  scoped_refptr<V4L2DecodeSurface> dec_surface;
+
+  if (supports_requests_) {
+    auto ret = V4L2RequestDecodeSurface::Create(
+        input, output, input_record.request_fd.get(),
+        base::BindOnce(&V4L2SliceVideoDecodeAccelerator::ReuseOutputBuffer,
+                       base::Unretained(this), output));
+
+    if (!ret)
+      return nullptr;
+
+    dec_surface = std::move(ret).value();
+  } else {
+    dec_surface = new V4L2ConfigStoreDecodeSurface(
+        input, output,
+        base::BindOnce(&V4L2SliceVideoDecodeAccelerator::ReuseOutputBuffer,
+                       base::Unretained(this), output));
+  }
 
   DVLOGF(4) << "Created surface " << input << " -> " << output;
   return dec_surface;
diff --git a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h
index 143a8e6..f26c553 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h
@@ -14,6 +14,7 @@
 #include <vector>
 
 #include "base/containers/queue.h"
+#include "base/files/scoped_file.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
@@ -84,6 +85,8 @@
     size_t length;
     size_t bytes_used;
     bool at_device;
+    // Request fd used for this input buffer if request API is used.
+    base::ScopedFD request_fd;
   };
 
   // Record for output buffers.
@@ -388,6 +391,10 @@
   std::list<int> free_input_buffers_;
   // Mapping of int index to an input buffer record.
   std::vector<InputRecord> input_buffer_map_;
+  // Set to true by CreateInputBuffers() if the codec driver supports requests
+  bool supports_requests_ = false;
+  // Stores the media file descriptor if request API is used
+  base::ScopedFD media_fd_;
 
   // Output queue state.
   bool output_streamon_;
diff --git a/media/gpu/video_decode_accelerator_perf_tests.cc b/media/gpu/video_decode_accelerator_perf_tests.cc
index 779675e..c0675f2 100644
--- a/media/gpu/video_decode_accelerator_perf_tests.cc
+++ b/media/gpu/video_decode_accelerator_perf_tests.cc
@@ -427,8 +427,9 @@
   media::test::g_env = static_cast<media::test::VideoPlayerTestEnvironment*>(
       testing::AddGlobalTestEnvironment(test_environment));
 
+  // Launch all tests sequentially and disable batching.
   base::TestSuite test_suite(argc, argv);
-  return base::LaunchUnitTestsSerially(
-      argc, argv,
+  return base::LaunchUnitTestsWithOptions(
+      argc, argv, 1, 0, true,
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
 }
diff --git a/media/gpu/video_decode_accelerator_tests.cc b/media/gpu/video_decode_accelerator_tests.cc
index f0b8be5a..6e608f2 100644
--- a/media/gpu/video_decode_accelerator_tests.cc
+++ b/media/gpu/video_decode_accelerator_tests.cc
@@ -380,8 +380,9 @@
   media::test::g_env = static_cast<media::test::VideoPlayerTestEnvironment*>(
       testing::AddGlobalTestEnvironment(test_environment));
 
+  // Launch all tests sequentially and disable batching.
   base::TestSuite test_suite(argc, argv);
-  return base::LaunchUnitTestsSerially(
-      argc, argv,
+  return base::LaunchUnitTestsWithOptions(
+      argc, argv, 1, 0, true,
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
 }
diff --git a/services/device/usb/fake_usb_device_handle.cc b/services/device/usb/fake_usb_device_handle.cc
index cf03e1b..4acf535 100644
--- a/services/device/usb/fake_usb_device_handle.cc
+++ b/services/device/usb/fake_usb_device_handle.cc
@@ -14,6 +14,11 @@
 
 namespace device {
 
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+
 FakeUsbDeviceHandle::FakeUsbDeviceHandle(const uint8_t* data, size_t size)
     : data_(data), size_(size), position_(0) {}
 
diff --git a/services/device/usb/fake_usb_device_handle.h b/services/device/usb/fake_usb_device_handle.h
index b9390ccb..3c246dcf 100644
--- a/services/device/usb/fake_usb_device_handle.h
+++ b/services/device/usb/fake_usb_device_handle.h
@@ -40,9 +40,9 @@
   void ResetDevice(ResultCallback callback) override;
   void ClearHalt(uint8_t endpoint, ResultCallback callback) override;
 
-  void ControlTransfer(UsbTransferDirection direction,
-                       UsbControlTransferType request_type,
-                       UsbControlTransferRecipient recipient,
+  void ControlTransfer(mojom::UsbTransferDirection direction,
+                       mojom::UsbControlTransferType request_type,
+                       mojom::UsbControlTransferRecipient recipient,
                        uint8_t request,
                        uint16_t value,
                        uint16_t index,
@@ -61,7 +61,7 @@
                               unsigned int timeout,
                               IsochronousTransferCallback callback) override;
 
-  void GenericTransfer(UsbTransferDirection direction,
+  void GenericTransfer(mojom::UsbTransferDirection direction,
                        uint8_t endpoint_number,
                        scoped_refptr<base::RefCountedBytes> buffer,
                        unsigned int timeout,
diff --git a/services/device/usb/mock_usb_device_handle.h b/services/device/usb/mock_usb_device_handle.h
index ba19ffa..f8f0c91 100644
--- a/services/device/usb/mock_usb_device_handle.h
+++ b/services/device/usb/mock_usb_device_handle.h
@@ -67,9 +67,9 @@
   MOCK_METHOD2(ClearHaltInternal,
                void(uint8_t endpoint, ResultCallback& callback));
 
-  void ControlTransfer(UsbTransferDirection direction,
-                       UsbControlTransferType request_type,
-                       UsbControlTransferRecipient recipient,
+  void ControlTransfer(mojom::UsbTransferDirection direction,
+                       mojom::UsbControlTransferType request_type,
+                       mojom::UsbControlTransferRecipient recipient,
                        uint8_t request,
                        uint16_t value,
                        uint16_t index,
@@ -80,9 +80,9 @@
                             index, buffer, timeout, callback);
   }
   MOCK_METHOD9(ControlTransferInternal,
-               void(UsbTransferDirection direction,
-                    UsbControlTransferType request_type,
-                    UsbControlTransferRecipient recipient,
+               void(mojom::UsbTransferDirection direction,
+                    mojom::UsbControlTransferType request_type,
+                    mojom::UsbControlTransferRecipient recipient,
                     uint8_t request,
                     uint16_t value,
                     uint16_t index,
@@ -117,7 +117,7 @@
                     unsigned int timeout,
                     IsochronousTransferCallback& callback));
 
-  void GenericTransfer(UsbTransferDirection direction,
+  void GenericTransfer(mojom::UsbTransferDirection direction,
                        uint8_t endpoint,
                        scoped_refptr<base::RefCountedBytes> buffer,
                        unsigned int timeout,
@@ -125,7 +125,7 @@
     GenericTransferInternal(direction, endpoint, buffer, timeout, callback);
   }
   MOCK_METHOD5(GenericTransferInternal,
-               void(UsbTransferDirection direction,
+               void(mojom::UsbTransferDirection direction,
                     uint8_t endpoint,
                     scoped_refptr<base::RefCountedBytes> buffer,
                     unsigned int timeout,
diff --git a/services/device/usb/mojo/BUILD.gn b/services/device/usb/mojo/BUILD.gn
index 53fab9b..e75fbb24 100644
--- a/services/device/usb/mojo/BUILD.gn
+++ b/services/device/usb/mojo/BUILD.gn
@@ -10,8 +10,6 @@
     "device_manager_impl.h",
     "device_manager_test.cc",
     "device_manager_test.h",
-    "type_converters.cc",
-    "type_converters.h",
   ]
 
   deps = [
diff --git a/services/device/usb/mojo/device_impl.cc b/services/device/usb/mojo/device_impl.cc
index 18ad2ca..87323de 100644
--- a/services/device/usb/mojo/device_impl.cc
+++ b/services/device/usb/mojo/device_impl.cc
@@ -18,7 +18,6 @@
 #include "base/memory/ref_counted_memory.h"
 #include "base/stl_util.h"
 #include "services/device/public/cpp/usb/usb_utils.h"
-#include "services/device/usb/mojo/type_converters.h"
 #include "services/device/usb/usb_descriptors.h"
 #include "services/device/usb/usb_device.h"
 
@@ -26,6 +25,9 @@
 
 using mojom::UsbControlTransferParamsPtr;
 using mojom::UsbControlTransferRecipient;
+using mojom::UsbIsochronousPacketPtr;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
 
 namespace usb {
 
@@ -58,32 +60,28 @@
 void OnIsochronousTransferIn(
     mojom::UsbDevice::IsochronousTransferInCallback callback,
     scoped_refptr<base::RefCountedBytes> buffer,
-    const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) {
+    std::vector<UsbIsochronousPacketPtr> packets) {
   std::vector<uint8_t> data;
   if (buffer) {
     // TODO(rockot/reillyg): Take advantage of the ability to access the
     // std::vector<uint8_t> within a base::RefCountedBytes to move instead of
     // copy.
-    uint32_t buffer_size =
-        std::accumulate(packets.begin(), packets.end(), 0u,
-                        [](const uint32_t& a,
-                           const UsbDeviceHandle::IsochronousPacket& packet) {
-                          return a + packet.length;
-                        });
+    uint32_t buffer_size = std::accumulate(
+        packets.begin(), packets.end(), 0u,
+        [](const uint32_t& a, const UsbIsochronousPacketPtr& packet) {
+          return a + packet->length;
+        });
     data.resize(buffer_size);
     std::copy(buffer->front(), buffer->front() + buffer_size, data.begin());
   }
-  std::move(callback).Run(
-      data,
-      mojo::ConvertTo<std::vector<mojom::UsbIsochronousPacketPtr>>(packets));
+  std::move(callback).Run(data, std::move(packets));
 }
 
 void OnIsochronousTransferOut(
     mojom::UsbDevice::IsochronousTransferOutCallback callback,
     scoped_refptr<base::RefCountedBytes> buffer,
-    const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) {
-  std::move(callback).Run(
-      mojo::ConvertTo<std::vector<mojom::UsbIsochronousPacketPtr>>(packets));
+    std::vector<UsbIsochronousPacketPtr> packets) {
+  std::move(callback).Run(std::move(packets));
 }
 
 }  // namespace
diff --git a/services/device/usb/mojo/device_impl_unittest.cc b/services/device/usb/mojo/device_impl_unittest.cc
index 1b86c5b..4fa24a31 100644
--- a/services/device/usb/mojo/device_impl_unittest.cc
+++ b/services/device/usb/mojo/device_impl_unittest.cc
@@ -27,7 +27,6 @@
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/device/usb/mock_usb_device.h"
 #include "services/device/usb/mock_usb_device_handle.h"
-#include "services/device/usb/mojo/type_converters.h"
 #include "services/device/usb/usb_descriptors.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -40,6 +39,8 @@
 using mojom::UsbControlTransferType;
 using mojom::UsbDevicePtr;
 using mojom::UsbIsochronousPacketPtr;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
 
 namespace usb {
 
@@ -243,22 +244,20 @@
     mock_inbound_data_.push(data);
   }
 
-  void AddMockInboundPackets(
-      const std::vector<uint8_t>& data,
-      const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) {
+  void AddMockInboundPackets(const std::vector<uint8_t>& data,
+                             std::vector<UsbIsochronousPacketPtr> packets) {
     mock_inbound_data_.push(data);
-    mock_inbound_packets_.push(packets);
+    mock_inbound_packets_.push(std::move(packets));
   }
 
   void AddMockOutboundData(const std::vector<uint8_t>& data) {
     mock_outbound_data_.push(data);
   }
 
-  void AddMockOutboundPackets(
-      const std::vector<uint8_t>& data,
-      const std::vector<UsbDeviceHandle::IsochronousPacket>& packets) {
+  void AddMockOutboundPackets(const std::vector<uint8_t>& data,
+                              std::vector<UsbIsochronousPacketPtr> packets) {
     mock_outbound_data_.push(data);
-    mock_outbound_packets_.push(packets);
+    mock_outbound_packets_.push(std::move(packets));
   }
 
  private:
@@ -385,16 +384,17 @@
     mock_inbound_data_.pop();
 
     ASSERT_FALSE(mock_inbound_packets_.empty());
-    std::vector<UsbDeviceHandle::IsochronousPacket> packets =
-        mock_inbound_packets_.front();
-    ASSERT_EQ(packets.size(), packet_lengths.size());
-    for (size_t i = 0; i < packets.size(); ++i) {
-      EXPECT_EQ(packets[i].length, packet_lengths[i])
-          << "Packet lengths differ at index: " << i;
-    }
+    std::vector<UsbIsochronousPacketPtr> packets =
+        std::move(mock_inbound_packets_.front());
     mock_inbound_packets_.pop();
 
-    std::move(callback).Run(buffer, packets);
+    ASSERT_EQ(packets.size(), packet_lengths.size());
+    for (size_t i = 0; i < packets.size(); ++i) {
+      EXPECT_EQ(packets[i]->length, packet_lengths[i])
+          << "Packet lengths differ at index: " << i;
+    }
+
+    std::move(callback).Run(buffer, std::move(packets));
   }
 
   void IsochronousTransferOut(
@@ -415,16 +415,17 @@
     mock_outbound_data_.pop();
 
     ASSERT_FALSE(mock_outbound_packets_.empty());
-    std::vector<UsbDeviceHandle::IsochronousPacket> packets =
-        mock_outbound_packets_.front();
-    ASSERT_EQ(packets.size(), packet_lengths.size());
-    for (size_t i = 0; i < packets.size(); ++i) {
-      EXPECT_EQ(packets[i].length, packet_lengths[i])
-          << "Packet lengths differ at index: " << i;
-    }
+    std::vector<UsbIsochronousPacketPtr> packets =
+        std::move(mock_outbound_packets_.front());
     mock_outbound_packets_.pop();
 
-    std::move(callback).Run(buffer, packets);
+    ASSERT_EQ(packets.size(), packet_lengths.size());
+    for (size_t i = 0; i < packets.size(); ++i) {
+      EXPECT_EQ(packets[i]->length, packet_lengths[i])
+          << "Packet lengths differ at index: " << i;
+    }
+
+    std::move(callback).Run(buffer, std::move(packets));
   }
 
   std::unique_ptr<base::MessageLoop> message_loop_;
@@ -437,10 +438,8 @@
 
   base::queue<std::vector<uint8_t>> mock_inbound_data_;
   base::queue<std::vector<uint8_t>> mock_outbound_data_;
-  base::queue<std::vector<UsbDeviceHandle::IsochronousPacket>>
-      mock_inbound_packets_;
-  base::queue<std::vector<UsbDeviceHandle::IsochronousPacket>>
-      mock_outbound_packets_;
+  base::queue<std::vector<UsbIsochronousPacketPtr>> mock_inbound_packets_;
+  base::queue<std::vector<UsbIsochronousPacketPtr>> mock_outbound_packets_;
 
   std::set<uint8_t> claimed_interfaces_;
 
@@ -898,11 +897,16 @@
     loop.Run();
   }
 
-  std::vector<UsbDeviceHandle::IsochronousPacket> fake_packets(4);
-  for (size_t i = 0; i < fake_packets.size(); ++i) {
-    fake_packets[i].length = 8;
-    fake_packets[i].transferred_length = 8;
-    fake_packets[i].status = UsbTransferStatus::COMPLETED;
+  std::vector<UsbIsochronousPacketPtr> fake_packets_in(4);
+  for (size_t i = 0; i < fake_packets_in.size(); ++i) {
+    fake_packets_in[i] = mojom::UsbIsochronousPacket::New();
+    fake_packets_in[i]->length = 8;
+    fake_packets_in[i]->transferred_length = 8;
+    fake_packets_in[i]->status = UsbTransferStatus::COMPLETED;
+  }
+  std::vector<UsbIsochronousPacketPtr> fake_packets_out;
+  for (const auto& packet : fake_packets_in) {
+    fake_packets_out.push_back(packet->Clone());
   }
   std::vector<uint32_t> fake_packet_lengths(4, 8);
 
@@ -919,8 +923,8 @@
             fake_inbound_data.begin());
 
   AddMockConfig(ConfigBuilder(1).AddInterface(7, 0, 1, 2, 3).Build());
-  AddMockOutboundPackets(fake_outbound_data, fake_packets);
-  AddMockInboundPackets(fake_inbound_data, fake_packets);
+  AddMockOutboundPackets(fake_outbound_data, std::move(fake_packets_in));
+  AddMockInboundPackets(fake_inbound_data, std::move(fake_packets_out));
 
   EXPECT_CALL(mock_handle(), IsochronousTransferOutInternal(
                                  0x01, _, fake_packet_lengths, 0, _));
diff --git a/services/device/usb/mojo/device_manager_impl.cc b/services/device/usb/mojo/device_manager_impl.cc
index 093a9f7..035429c 100644
--- a/services/device/usb/mojo/device_manager_impl.cc
+++ b/services/device/usb/mojo/device_manager_impl.cc
@@ -18,7 +18,6 @@
 #include "services/device/public/mojom/usb_enumeration_options.mojom.h"
 #include "services/device/public/mojom/usb_manager_client.mojom.h"
 #include "services/device/usb/mojo/device_impl.h"
-#include "services/device/usb/mojo/type_converters.h"
 #include "services/device/usb/usb_device.h"
 #include "services/device/usb/usb_service.h"
 
diff --git a/services/device/usb/mojo/type_converters.cc b/services/device/usb/mojo/type_converters.cc
deleted file mode 100644
index e244311b..0000000
--- a/services/device/usb/mojo/type_converters.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/device/usb/mojo/type_converters.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <map>
-#include <utility>
-
-namespace mojo {
-
-// static
-device::mojom::UsbIsochronousPacketPtr
-TypeConverter<device::mojom::UsbIsochronousPacketPtr,
-              device::UsbDeviceHandle::IsochronousPacket>::
-    Convert(const device::UsbDeviceHandle::IsochronousPacket& packet) {
-  auto info = device::mojom::UsbIsochronousPacket::New();
-  info->length = packet.length;
-  info->transferred_length = packet.transferred_length;
-  info->status =
-      mojo::ConvertTo<device::mojom::UsbTransferStatus>(packet.status);
-  return info;
-}
-
-}  // namespace mojo
diff --git a/services/device/usb/mojo/type_converters.h b/services/device/usb/mojo/type_converters.h
deleted file mode 100644
index bcc5d9e..0000000
--- a/services/device/usb/mojo/type_converters.h
+++ /dev/null
@@ -1,30 +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 SERVICES_DEVICE_USB_MOJO_TYPE_CONVERTERS_H_
-#define SERVICES_DEVICE_USB_MOJO_TYPE_CONVERTERS_H_
-
-#include <vector>
-
-#include "mojo/public/cpp/bindings/type_converter.h"
-#include "services/device/public/mojom/usb_device.mojom.h"
-#include "services/device/public/mojom/usb_manager.mojom.h"
-#include "services/device/usb/usb_device_handle.h"
-
-// Type converters to translate between internal device/usb data types and
-// public Mojo interface data types. This must be included by any source file
-// that uses these conversions explicitly or implicitly.
-
-namespace mojo {
-
-template <>
-struct TypeConverter<device::mojom::UsbIsochronousPacketPtr,
-                     device::UsbDeviceHandle::IsochronousPacket> {
-  static device::mojom::UsbIsochronousPacketPtr Convert(
-      const device::UsbDeviceHandle::IsochronousPacket& packet);
-};
-
-}  // namespace mojo
-
-#endif  // SERVICES_DEVICE_USB_MOJO_TYPE_CONVERTERS_H_
diff --git a/services/device/usb/usb_descriptors.cc b/services/device/usb/usb_descriptors.cc
index 8b46aa4..b1bda072 100644
--- a/services/device/usb/usb_descriptors.cc
+++ b/services/device/usb/usb_descriptors.cc
@@ -20,6 +20,19 @@
 
 namespace device {
 
+using mojom::UsbAlternateInterfaceInfoPtr;
+using mojom::UsbConfigurationInfoPtr;
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbDeviceInfoPtr;
+using mojom::UsbEndpointInfoPtr;
+using mojom::UsbInterfaceInfoPtr;
+using mojom::UsbSynchronizationType;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+using mojom::UsbTransferType;
+using mojom::UsbUsageType;
+
 namespace {
 
 using IndexMap = std::map<uint8_t, base::string16>;
diff --git a/services/device/usb/usb_descriptors.h b/services/device/usb/usb_descriptors.h
index 676fabc..ba173395 100644
--- a/services/device/usb/usb_descriptors.h
+++ b/services/device/usb/usb_descriptors.h
@@ -20,16 +20,6 @@
 
 class UsbDeviceHandle;
 
-using UsbTransferType = mojom::UsbTransferType;
-using UsbTransferDirection = mojom::UsbTransferDirection;
-using UsbSynchronizationType = mojom::UsbSynchronizationType;
-using UsbUsageType = mojom::UsbUsageType;
-using UsbEndpointInfoPtr = mojom::UsbEndpointInfoPtr;
-using UsbAlternateInterfaceInfoPtr = mojom::UsbAlternateInterfaceInfoPtr;
-using UsbInterfaceInfoPtr = mojom::UsbInterfaceInfoPtr;
-using UsbConfigurationInfoPtr = mojom::UsbConfigurationInfoPtr;
-using UsbDeviceInfoPtr = mojom::UsbDeviceInfoPtr;
-
 struct CombinedInterfaceInfo {
   CombinedInterfaceInfo() = default;
   CombinedInterfaceInfo(const mojom::UsbInterfaceInfo* interface,
@@ -57,7 +47,7 @@
   uint8_t i_product = 0;
   uint8_t i_serial_number = 0;
   uint8_t num_configurations = 0;
-  UsbDeviceInfoPtr device_info;
+  mojom::UsbDeviceInfoPtr device_info;
 };
 
 void ReadUsbDescriptors(
@@ -73,20 +63,20 @@
     base::OnceCallback<void(std::unique_ptr<std::map<uint8_t, base::string16>>)>
         callback);
 
-UsbEndpointInfoPtr BuildUsbEndpointInfoPtr(const uint8_t* data);
+mojom::UsbEndpointInfoPtr BuildUsbEndpointInfoPtr(const uint8_t* data);
 
-UsbEndpointInfoPtr BuildUsbEndpointInfoPtr(uint8_t address,
-                                           uint8_t attributes,
-                                           uint16_t maximum_packet_size,
-                                           uint8_t polling_interval);
+mojom::UsbEndpointInfoPtr BuildUsbEndpointInfoPtr(uint8_t address,
+                                                  uint8_t attributes,
+                                                  uint16_t maximum_packet_size,
+                                                  uint8_t polling_interval);
 
-UsbInterfaceInfoPtr BuildUsbInterfaceInfoPtr(const uint8_t* data);
+mojom::UsbInterfaceInfoPtr BuildUsbInterfaceInfoPtr(const uint8_t* data);
 
-UsbInterfaceInfoPtr BuildUsbInterfaceInfoPtr(uint8_t interface_number,
-                                             uint8_t alternate_setting,
-                                             uint8_t interface_class,
-                                             uint8_t interface_subclass,
-                                             uint8_t interface_protocol);
+mojom::UsbInterfaceInfoPtr BuildUsbInterfaceInfoPtr(uint8_t interface_number,
+                                                    uint8_t alternate_setting,
+                                                    uint8_t interface_class,
+                                                    uint8_t interface_subclass,
+                                                    uint8_t interface_protocol);
 
 void AggregateInterfacesForConfig(mojom::UsbConfigurationInfo* config);
 
@@ -95,9 +85,10 @@
     uint8_t interface_number,
     uint8_t alternate_setting);
 
-UsbConfigurationInfoPtr BuildUsbConfigurationInfoPtr(const uint8_t* data);
+mojom::UsbConfigurationInfoPtr BuildUsbConfigurationInfoPtr(
+    const uint8_t* data);
 
-UsbConfigurationInfoPtr BuildUsbConfigurationInfoPtr(
+mojom::UsbConfigurationInfoPtr BuildUsbConfigurationInfoPtr(
     uint8_t configuration_value,
     bool self_powered,
     bool remote_wakeup,
diff --git a/services/device/usb/usb_descriptors_unittest.cc b/services/device/usb/usb_descriptors_unittest.cc
index 6493e53..4f858e4 100644
--- a/services/device/usb/usb_descriptors_unittest.cc
+++ b/services/device/usb/usb_descriptors_unittest.cc
@@ -20,6 +20,14 @@
 
 namespace device {
 
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbSynchronizationType;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+using mojom::UsbTransferType;
+using mojom::UsbUsageType;
+
 namespace {
 
 ACTION_P2(InvokeCallback, data, length) {
diff --git a/services/device/usb/usb_device_handle.h b/services/device/usb/usb_device_handle.h
index df5ad67c..35e354c6 100644
--- a/services/device/usb/usb_device_handle.h
+++ b/services/device/usb/usb_device_handle.h
@@ -26,25 +26,17 @@
 
 class UsbDevice;
 
-using UsbTransferStatus = mojom::UsbTransferStatus;
-using UsbControlTransferType = mojom::UsbControlTransferType;
-using UsbControlTransferRecipient = mojom::UsbControlTransferRecipient;
-
 // UsbDeviceHandle class provides basic I/O related functionalities.
 class UsbDeviceHandle : public base::RefCountedThreadSafe<UsbDeviceHandle> {
  public:
-  struct IsochronousPacket {
-    uint32_t length;
-    uint32_t transferred_length;
-    UsbTransferStatus status;
-  };
-
   using ResultCallback = base::OnceCallback<void(bool)>;
-  using TransferCallback = base::OnceCallback<
-      void(UsbTransferStatus, scoped_refptr<base::RefCountedBytes>, size_t)>;
-  using IsochronousTransferCallback =
-      base::OnceCallback<void(scoped_refptr<base::RefCountedBytes>,
-                              const std::vector<IsochronousPacket>& packets)>;
+  using TransferCallback =
+      base::OnceCallback<void(mojom::UsbTransferStatus,
+                              scoped_refptr<base::RefCountedBytes>,
+                              size_t)>;
+  using IsochronousTransferCallback = base::OnceCallback<void(
+      scoped_refptr<base::RefCountedBytes>,
+      std::vector<mojom::UsbIsochronousPacketPtr> packets)>;
 
   virtual scoped_refptr<UsbDevice> GetDevice() const = 0;
 
@@ -68,9 +60,9 @@
   virtual void ResetDevice(ResultCallback callback) = 0;
   virtual void ClearHalt(uint8_t endpoint, ResultCallback callback) = 0;
 
-  virtual void ControlTransfer(UsbTransferDirection direction,
-                               UsbControlTransferType request_type,
-                               UsbControlTransferRecipient recipient,
+  virtual void ControlTransfer(mojom::UsbTransferDirection direction,
+                               mojom::UsbControlTransferType request_type,
+                               mojom::UsbControlTransferRecipient recipient,
                                uint8_t request,
                                uint16_t value,
                                uint16_t index,
@@ -91,7 +83,7 @@
       unsigned int timeout,
       IsochronousTransferCallback callback) = 0;
 
-  virtual void GenericTransfer(UsbTransferDirection direction,
+  virtual void GenericTransfer(mojom::UsbTransferDirection direction,
                                uint8_t endpoint_number,
                                scoped_refptr<base::RefCountedBytes> buffer,
                                unsigned int timeout,
diff --git a/services/device/usb/usb_device_handle_impl.cc b/services/device/usb/usb_device_handle_impl.cc
index 7f46cc6..0ff7dacc 100644
--- a/services/device/usb/usb_device_handle_impl.cc
+++ b/services/device/usb/usb_device_handle_impl.cc
@@ -32,6 +32,13 @@
 
 namespace device {
 
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbIsochronousPacketPtr;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+using mojom::UsbTransferType;
+
 void HandleTransferCompletion(PlatformUsbTransferHandle transfer);
 
 namespace {
@@ -475,13 +482,16 @@
   base::OnceClosure closure;
   if (transfer_type_ == UsbTransferType::ISOCHRONOUS) {
     DCHECK_NE(LIBUSB_TRANSFER_COMPLETED, platform_transfer_->status);
-    std::vector<IsochronousPacket> packets(platform_transfer_->num_iso_packets);
+    std::vector<UsbIsochronousPacketPtr> packets(
+        platform_transfer_->num_iso_packets);
     for (size_t i = 0; i < packets.size(); ++i) {
-      packets[i].length = platform_transfer_->iso_packet_desc[i].length;
-      packets[i].transferred_length = 0;
-      packets[i].status = status;
+      packets[i] = mojom::UsbIsochronousPacket::New();
+      packets[i]->length = platform_transfer_->iso_packet_desc[i].length;
+      packets[i]->transferred_length = 0;
+      packets[i]->status = status;
     }
-    closure = base::BindOnce(std::move(iso_callback_), buffer_, packets);
+    closure =
+        base::BindOnce(std::move(iso_callback_), buffer_, std::move(packets));
   } else {
     closure = base::BindOnce(std::move(callback_), status, buffer_,
                              bytes_transferred);
@@ -493,19 +503,21 @@
 }
 
 void UsbDeviceHandleImpl::Transfer::IsochronousTransferComplete() {
-  std::vector<IsochronousPacket> packets(platform_transfer_->num_iso_packets);
+  std::vector<UsbIsochronousPacketPtr> packets(
+      platform_transfer_->num_iso_packets);
   for (size_t i = 0; i < packets.size(); ++i) {
-    packets[i].length = platform_transfer_->iso_packet_desc[i].length;
-    packets[i].transferred_length =
+    packets[i] = mojom::UsbIsochronousPacket::New();
+    packets[i]->length = platform_transfer_->iso_packet_desc[i].length;
+    packets[i]->transferred_length =
         platform_transfer_->iso_packet_desc[i].actual_length;
-    packets[i].status =
+    packets[i]->status =
         ConvertTransferStatus(platform_transfer_->iso_packet_desc[i].status);
   }
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(&UsbDeviceHandleImpl::TransferComplete,
-                                        device_handle_, base::Unretained(this),
-                                        base::BindOnce(std::move(iso_callback_),
-                                                       buffer_, packets)));
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&UsbDeviceHandleImpl::TransferComplete,
+                                device_handle_, base::Unretained(this),
+                                base::BindOnce(std::move(iso_callback_),
+                                               buffer_, std::move(packets))));
 }
 
 scoped_refptr<UsbDevice> UsbDeviceHandleImpl::GetDevice() const {
@@ -1020,15 +1032,15 @@
     UsbTransferStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  std::vector<UsbDeviceHandle::IsochronousPacket> packets(
-      packet_lengths.size());
+  std::vector<UsbIsochronousPacketPtr> packets(packet_lengths.size());
   for (size_t i = 0; i < packet_lengths.size(); ++i) {
-    packets[i].length = packet_lengths[i];
-    packets[i].transferred_length = 0;
-    packets[i].status = status;
+    packets[i] = mojom::UsbIsochronousPacket::New();
+    packets[i]->length = packet_lengths[i];
+    packets[i]->transferred_length = 0;
+    packets[i]->status = status;
   }
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(std::move(callback), nullptr, packets));
+  task_runner_->PostTask(FROM_HERE, base::BindOnce(std::move(callback), nullptr,
+                                                   std::move(packets)));
 }
 
 void UsbDeviceHandleImpl::SubmitTransfer(std::unique_ptr<Transfer> transfer) {
diff --git a/services/device/usb/usb_device_handle_impl.h b/services/device/usb/usb_device_handle_impl.h
index 7cf379a..71cc60e 100644
--- a/services/device/usb/usb_device_handle_impl.h
+++ b/services/device/usb/usb_device_handle_impl.h
@@ -54,9 +54,9 @@
   void ResetDevice(ResultCallback callback) override;
   void ClearHalt(uint8_t endpoint, ResultCallback callback) override;
 
-  void ControlTransfer(UsbTransferDirection direction,
-                       UsbControlTransferType request_type,
-                       UsbControlTransferRecipient recipient,
+  void ControlTransfer(mojom::UsbTransferDirection direction,
+                       mojom::UsbControlTransferType request_type,
+                       mojom::UsbControlTransferRecipient recipient,
                        uint8_t request,
                        uint16_t value,
                        uint16_t index,
@@ -75,7 +75,7 @@
                               unsigned int timeout,
                               IsochronousTransferCallback callback) override;
 
-  void GenericTransfer(UsbTransferDirection direction,
+  void GenericTransfer(mojom::UsbTransferDirection direction,
                        uint8_t endpoint_number,
                        scoped_refptr<base::RefCountedBytes> buffer,
                        unsigned int timeout,
@@ -128,7 +128,7 @@
   void ReportIsochronousTransferError(
       UsbDeviceHandle::IsochronousTransferCallback callback,
       const std::vector<uint32_t> packet_lengths,
-      UsbTransferStatus status);
+      mojom::UsbTransferStatus status);
 
   // Submits a transfer and starts tracking it. Retains the buffer and copies
   // the completion callback until the transfer finishes, whereupon it invokes
diff --git a/services/device/usb/usb_device_handle_unittest.cc b/services/device/usb/usb_device_handle_unittest.cc
index f571258..0fca89e 100644
--- a/services/device/usb/usb_device_handle_unittest.cc
+++ b/services/device/usb/usb_device_handle_unittest.cc
@@ -21,6 +21,11 @@
 
 namespace device {
 
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+
 namespace {
 
 class UsbDeviceHandleTest : public ::testing::Test {
diff --git a/services/device/usb/usb_device_handle_usbfs.cc b/services/device/usb/usb_device_handle_usbfs.cc
index ec93787..cb74ead 100644
--- a/services/device/usb/usb_device_handle_usbfs.cc
+++ b/services/device/usb/usb_device_handle_usbfs.cc
@@ -28,6 +28,13 @@
 
 namespace device {
 
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbIsochronousPacketPtr;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+using mojom::UsbTransferType;
+
 namespace {
 
 uint8_t ConvertEndpointDirection(UsbTransferDirection direction) {
@@ -172,7 +179,7 @@
 
   void* operator new(std::size_t size, size_t number_of_iso_packets);
   void RunCallback(UsbTransferStatus status, size_t bytes_transferred);
-  void RunIsochronousCallback(const std::vector<IsochronousPacket>& packets);
+  void RunIsochronousCallback(std::vector<UsbIsochronousPacketPtr> packets);
 
   scoped_refptr<base::RefCountedBytes> control_transfer_buffer;
   scoped_refptr<base::RefCountedBytes> buffer;
@@ -406,10 +413,10 @@
 }
 
 void UsbDeviceHandleUsbfs::Transfer::RunIsochronousCallback(
-    const std::vector<IsochronousPacket>& packets) {
+    std::vector<UsbIsochronousPacketPtr> packets) {
   DCHECK_EQ(urb.type, USBDEVFS_URB_TYPE_ISO);
   DCHECK(isoc_callback);
-  std::move(isoc_callback).Run(buffer, packets);
+  std::move(isoc_callback).Run(buffer, std::move(packets));
 }
 
 UsbDeviceHandleUsbfs::UsbDeviceHandleUsbfs(
@@ -833,17 +840,19 @@
   transfer->timeout_closure.Cancel();
 
   if (transfer->urb.type == USBDEVFS_URB_TYPE_ISO) {
-    std::vector<IsochronousPacket> packets(transfer->urb.number_of_packets);
+    std::vector<UsbIsochronousPacketPtr> packets(
+        transfer->urb.number_of_packets);
     for (size_t i = 0; i < packets.size(); ++i) {
-      packets[i].length = transfer->urb.iso_frame_desc[i].length;
-      packets[i].transferred_length =
+      packets[i] = mojom::UsbIsochronousPacket::New();
+      packets[i]->length = transfer->urb.iso_frame_desc[i].length;
+      packets[i]->transferred_length =
           transfer->urb.iso_frame_desc[i].actual_length;
-      packets[i].status = ConvertTransferResult(
+      packets[i]->status = ConvertTransferResult(
           transfer->urb.status == 0 ? transfer->urb.iso_frame_desc[i].status
                                     : transfer->urb.status);
     }
 
-    transfer->RunIsochronousCallback(packets);
+    transfer->RunIsochronousCallback(std::move(packets));
   } else {
     if (transfer->urb.status == 0 &&
         transfer->urb.type == USBDEVFS_URB_TYPE_CONTROL) {
@@ -887,15 +896,15 @@
     UsbDeviceHandle::IsochronousTransferCallback callback,
     UsbTransferStatus status) {
   DCHECK(sequence_checker_.CalledOnValidSequence());
-  std::vector<UsbDeviceHandle::IsochronousPacket> packets(
-      packet_lengths.size());
+  std::vector<UsbIsochronousPacketPtr> packets(packet_lengths.size());
   for (size_t i = 0; i < packet_lengths.size(); ++i) {
-    packets[i].length = packet_lengths[i];
-    packets[i].transferred_length = 0;
-    packets[i].status = status;
+    packets[i] = mojom::UsbIsochronousPacket::New();
+    packets[i]->length = packet_lengths[i];
+    packets[i]->transferred_length = 0;
+    packets[i]->status = status;
   }
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(std::move(callback), nullptr, packets));
+  task_runner_->PostTask(FROM_HERE, base::BindOnce(std::move(callback), nullptr,
+                                                   std::move(packets)));
 }
 
 void UsbDeviceHandleUsbfs::SetUpTimeoutCallback(Transfer* transfer,
@@ -951,13 +960,15 @@
   transfer->timeout_closure.Cancel();
 
   if (transfer->urb.type == USBDEVFS_URB_TYPE_ISO) {
-    std::vector<IsochronousPacket> packets(transfer->urb.number_of_packets);
+    std::vector<UsbIsochronousPacketPtr> packets(
+        transfer->urb.number_of_packets);
     for (size_t i = 0; i < packets.size(); ++i) {
-      packets[i].length = transfer->urb.iso_frame_desc[i].length;
-      packets[i].transferred_length = 0;
-      packets[i].status = status;
+      packets[i] = mojom::UsbIsochronousPacket::New();
+      packets[i]->length = transfer->urb.iso_frame_desc[i].length;
+      packets[i]->transferred_length = 0;
+      packets[i]->status = status;
     }
-    transfer->RunIsochronousCallback(packets);
+    transfer->RunIsochronousCallback(std::move(packets));
   } else {
     transfer->RunCallback(status, 0);
   }
diff --git a/services/device/usb/usb_device_handle_usbfs.h b/services/device/usb/usb_device_handle_usbfs.h
index 2cbb3af4..c598baf 100644
--- a/services/device/usb/usb_device_handle_usbfs.h
+++ b/services/device/usb/usb_device_handle_usbfs.h
@@ -47,9 +47,9 @@
                                     ResultCallback callback) override;
   void ResetDevice(ResultCallback callback) override;
   void ClearHalt(uint8_t endpoint, ResultCallback callback) override;
-  void ControlTransfer(UsbTransferDirection direction,
-                       UsbControlTransferType request_type,
-                       UsbControlTransferRecipient recipient,
+  void ControlTransfer(mojom::UsbTransferDirection direction,
+                       mojom::UsbControlTransferType request_type,
+                       mojom::UsbControlTransferRecipient recipient,
                        uint8_t request,
                        uint16_t value,
                        uint16_t index,
@@ -65,7 +65,7 @@
                               const std::vector<uint32_t>& packet_lengths,
                               unsigned int timeout,
                               IsochronousTransferCallback callback) override;
-  void GenericTransfer(UsbTransferDirection direction,
+  void GenericTransfer(mojom::UsbTransferDirection direction,
                        uint8_t endpoint_number,
                        scoped_refptr<base::RefCountedBytes> buffer,
                        unsigned int timeout,
@@ -94,7 +94,7 @@
     uint8_t alternate_setting;
   };
   struct EndpointInfo {
-    UsbTransferType type;
+    mojom::UsbTransferType type;
     const mojom::UsbInterfaceInfo* interface;
   };
 
@@ -114,11 +114,11 @@
   void ReportIsochronousError(
       const std::vector<uint32_t>& packet_lengths,
       UsbDeviceHandle::IsochronousTransferCallback callback,
-      UsbTransferStatus status);
+      mojom::UsbTransferStatus status);
   void SetUpTimeoutCallback(Transfer* transfer, unsigned int timeout);
   void OnTimeout(Transfer* transfer);
   std::unique_ptr<Transfer> RemoveFromTransferList(Transfer* transfer);
-  void CancelTransfer(Transfer* transfer, UsbTransferStatus status);
+  void CancelTransfer(Transfer* transfer, mojom::UsbTransferStatus status);
   void DiscardUrbBlocking(Transfer* transfer);
   void UrbDiscarded(Transfer* transfer);
 
diff --git a/services/device/usb/usb_device_handle_win.cc b/services/device/usb/usb_device_handle_win.cc
index 9e83ca0..8ffd894 100644
--- a/services/device/usb/usb_device_handle_win.cc
+++ b/services/device/usb/usb_device_handle_win.cc
@@ -36,6 +36,11 @@
 
 namespace device {
 
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+
 namespace {
 
 uint8_t BuildRequestFlags(UsbTransferDirection direction,
@@ -702,14 +707,15 @@
     const std::vector<uint32_t>& packet_lengths,
     IsochronousTransferCallback callback,
     UsbTransferStatus status) {
-  std::vector<IsochronousPacket> packets(packet_lengths.size());
+  std::vector<mojom::UsbIsochronousPacketPtr> packets(packet_lengths.size());
   for (size_t i = 0; i < packet_lengths.size(); ++i) {
-    packets[i].length = packet_lengths[i];
-    packets[i].transferred_length = 0;
-    packets[i].status = status;
+    packets[i] = mojom::UsbIsochronousPacket::New();
+    packets[i]->length = packet_lengths[i];
+    packets[i]->transferred_length = 0;
+    packets[i]->status = status;
   }
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(std::move(callback), nullptr, packets));
+  task_runner_->PostTask(FROM_HERE, base::BindOnce(std::move(callback), nullptr,
+                                                   std::move(packets)));
 }
 
 }  // namespace device
diff --git a/services/device/usb/usb_device_handle_win.h b/services/device/usb/usb_device_handle_win.h
index f2695e1cf..37da15fa 100644
--- a/services/device/usb/usb_device_handle_win.h
+++ b/services/device/usb/usb_device_handle_win.h
@@ -42,9 +42,9 @@
   void ResetDevice(ResultCallback callback) override;
   void ClearHalt(uint8_t endpoint, ResultCallback callback) override;
 
-  void ControlTransfer(UsbTransferDirection direction,
-                       UsbControlTransferType request_type,
-                       UsbControlTransferRecipient recipient,
+  void ControlTransfer(mojom::UsbTransferDirection direction,
+                       mojom::UsbControlTransferType request_type,
+                       mojom::UsbControlTransferRecipient recipient,
                        uint8_t request,
                        uint16_t value,
                        uint16_t index,
@@ -63,7 +63,7 @@
                               unsigned int timeout,
                               IsochronousTransferCallback callback) override;
 
-  void GenericTransfer(UsbTransferDirection direction,
+  void GenericTransfer(mojom::UsbTransferDirection direction,
                        uint8_t endpoint_number,
                        scoped_refptr<base::RefCountedBytes> buffer,
                        unsigned int timeout,
@@ -101,14 +101,14 @@
 
   struct Endpoint {
     const mojom::UsbInterfaceInfo* interface;
-    UsbTransferType type;
+    mojom::UsbTransferType type;
   };
 
   bool OpenInterfaceHandle(Interface* interface);
   void RegisterEndpoints(const CombinedInterfaceInfo& interface);
   void UnregisterEndpoints(const CombinedInterfaceInfo& interface);
   WINUSB_INTERFACE_HANDLE GetInterfaceForControlTransfer(
-      UsbControlTransferRecipient recipient,
+      mojom::UsbControlTransferRecipient recipient,
       uint16_t index);
   void SetInterfaceAlternateSettingBlocking(uint8_t interface_number,
                                             uint8_t alternate_setting,
@@ -138,7 +138,7 @@
                         size_t bytes_transferred);
   void ReportIsochronousError(const std::vector<uint32_t>& packet_lengths,
                               IsochronousTransferCallback callback,
-                              UsbTransferStatus status);
+                              mojom::UsbTransferStatus status);
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/services/device/usb/webusb_descriptors.cc b/services/device/usb/webusb_descriptors.cc
index 5f2661a..9279974 100644
--- a/services/device/usb/webusb_descriptors.cc
+++ b/services/device/usb/webusb_descriptors.cc
@@ -14,6 +14,11 @@
 
 namespace device {
 
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+
 namespace {
 
 // These constants are defined by the Universal Serial Device 3.0 Specification
diff --git a/services/device/usb/webusb_descriptors_unittest.cc b/services/device/usb/webusb_descriptors_unittest.cc
index e4fbf60..2bd9a54 100644
--- a/services/device/usb/webusb_descriptors_unittest.cc
+++ b/services/device/usb/webusb_descriptors_unittest.cc
@@ -18,6 +18,11 @@
 
 namespace device {
 
+using mojom::UsbControlTransferRecipient;
+using mojom::UsbControlTransferType;
+using mojom::UsbTransferDirection;
+using mojom::UsbTransferStatus;
+
 namespace {
 
 const uint8_t kExampleBosDescriptor[] = {
diff --git a/services/media_session/public/cpp/test/test_media_controller.cc b/services/media_session/public/cpp/test/test_media_controller.cc
index 8c53228c..5246b685 100644
--- a/services/media_session/public/cpp/test/test_media_controller.cc
+++ b/services/media_session/public/cpp/test/test_media_controller.cc
@@ -11,12 +11,9 @@
     mojom::MediaControllerPtr& controller,
     int minimum_size_px,
     int desired_size_px) {
-  mojo::PendingRemote<mojom::MediaControllerImageObserver> remote;
-  receiver_.Bind(remote.InitWithNewPipeAndPassReceiver());
-
   controller->ObserveImages(mojom::MediaSessionImageType::kArtwork,
                             minimum_size_px, desired_size_px,
-                            std::move(remote));
+                            receiver_.BindNewPipeAndPassRemote());
   controller.FlushForTesting();
 }
 
diff --git a/services/tracing/perfetto/consumer_host_unittest.cc b/services/tracing/perfetto/consumer_host_unittest.cc
index a2f7725..4aa5afa 100644
--- a/services/tracing/perfetto/consumer_host_unittest.cc
+++ b/services/tracing/perfetto/consumer_host_unittest.cc
@@ -296,7 +296,6 @@
                             public mojo::DataPipeDrainer::Client {
  public:
   void SetUp() override {
-    PerfettoTracedProcess::Get()->ClearDataSourcesForTesting();
     PerfettoTracedProcess::ResetTaskRunnerForTesting();
     PerfettoTracedProcess::Get()->ClearDataSourcesForTesting();
     threaded_service_ = std::make_unique<ThreadedPerfettoService>();
diff --git a/services/tracing/perfetto/perfetto_integration_unittest.cc b/services/tracing/perfetto/perfetto_integration_unittest.cc
index 1f14550b..1441d517 100644
--- a/services/tracing/perfetto/perfetto_integration_unittest.cc
+++ b/services/tracing/perfetto/perfetto_integration_unittest.cc
@@ -39,8 +39,8 @@
   void SetUp() override {
     PerfettoTracedProcess::ResetTaskRunnerForTesting();
     PerfettoTracedProcess::Get()->ClearDataSourcesForTesting();
-    data_source_ =
-        std::make_unique<TestDataSource>(kPerfettoTestDataSourceName, 0);
+    data_source_ = TestDataSource::CreateAndRegisterDataSource(
+        kPerfettoTestDataSourceName, 0);
     perfetto_service_ = std::make_unique<PerfettoService>();
     RunUntilIdle();
   }
diff --git a/services/tracing/perfetto/system_perfetto_unittest.cc b/services/tracing/perfetto/system_perfetto_unittest.cc
index c63cd91c..34e6051 100644
--- a/services/tracing/perfetto/system_perfetto_unittest.cc
+++ b/services/tracing/perfetto/system_perfetto_unittest.cc
@@ -29,6 +29,7 @@
 #include "services/tracing/public/cpp/tracing_features.h"
 #include "testing/gtest/include/gtest/gtest-death-test.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/perfetto/include/perfetto/ext/tracing/ipc/default_socket.h"
 #include "third_party/perfetto/protos/perfetto/config/trace_config.pb.h"
 #include "third_party/perfetto/protos/perfetto/trace/trace.pb.h"
 
@@ -56,8 +57,10 @@
     // Ensure system tracing is enabled for all tests.
     base::test::ScopedFeatureList feature_list;
     feature_list.InitAndEnableFeature(features::kEnablePerfettoSystemTracing);
-    // Construct the process global object and clear data sources between tests.
-    PerfettoTracedProcess::Get()->ClearDataSourcesForTesting();
+    PerfettoTracedProcess::ResetTaskRunnerForTesting();
+    // To ensure we have a fully clean PerfettoTracedProcess reconstruct it at
+    // the beginning of each test.
+    PerfettoTracedProcess::ReconstructForTesting(perfetto::GetProducerSocket());
 
     EXPECT_TRUE(tmp_dir_.CreateUniqueTempDir());
     // We need to set TMPDIR environment variable because when a new producer
@@ -70,7 +73,6 @@
     // hermetic.
     old_tmp_dir_ = getenv("TMPDIR");
     setenv("TMPDIR", tmp_dir_.GetPath().value().c_str(), true);
-
     // Set up the system socket locations in a valid tmp directory.
     producer_socket_ =
         tmp_dir_.GetPath().Append(FILE_PATH_LITERAL("producer")).value();
@@ -80,11 +82,11 @@
     // Construct three default TestDataSources which write out different amount
     // of packets to make it easy to verify which data source has written
     // packets.
-    data_sources_.push_back(
-        std::make_unique<TestDataSource>(kPerfettoTestDataSourceName, 1));
-    data_sources_.push_back(std::make_unique<TestDataSource>(
+    data_sources_.push_back(TestDataSource::CreateAndRegisterDataSource(
+        kPerfettoTestDataSourceName, 1));
+    data_sources_.push_back(TestDataSource::CreateAndRegisterDataSource(
         base::StrCat({kPerfettoTestDataSourceName, "1"}), 3));
-    data_sources_.push_back(std::make_unique<TestDataSource>(
+    data_sources_.push_back(TestDataSource::CreateAndRegisterDataSource(
         base::StrCat({kPerfettoTestDataSourceName, "2"}), 7));
 
     // Construct the service and wait for it to completely set up.
@@ -133,6 +135,17 @@
   }
 
   ~SystemPerfettoTest() override {
+    // The real "AndroidSystemProducer" must be destroyed on the correct
+    // sequence however that sequence is tied to the |scoped_task_environment_|
+    // which is being deleted now. Therefore to prevent it crashing a future
+    // test we destroy it now.
+    PerfettoTracedProcess::GetTaskRunner()->GetOrCreateTaskRunner()->PostTask(
+        FROM_HERE, base::BindOnce([]() {
+          PerfettoTracedProcess::Get()
+              ->SetSystemProducerForTesting(std::make_unique<DummyProducer>(
+                  PerfettoTracedProcess::GetTaskRunner()))
+              .reset();
+        }));
     RunUntilIdle();
     if (old_tmp_dir_) {
       // Restore the old value back to its initial value.
@@ -202,8 +215,7 @@
   const char* old_tmp_dir_ = nullptr;
 };
 
-// TODO(crbug.com/983509): test is flaky.
-TEST_F(SystemPerfettoTest, DISABLED_SystemTraceEndToEnd) {
+TEST_F(SystemPerfettoTest, SystemTraceEndToEnd) {
   auto system_service = CreateMockSystemService();
 
   // Set up the producer to talk to the system.
@@ -279,8 +291,7 @@
   EXPECT_EQ(0, remove(path.c_str()));
 }
 
-// TODO(crbug.com/983509): test is flaky.
-TEST_F(SystemPerfettoTest, DISABLED_OneSystemSourceWithMultipleLocalSources) {
+TEST_F(SystemPerfettoTest, OneSystemSourceWithMultipleLocalSources) {
   auto system_service = CreateMockSystemService();
 
   // Start a trace using the system Perfetto service.
@@ -368,9 +379,7 @@
   PerfettoProducer::DeleteSoonForTesting(std::move(system_producer));
 }
 
-// TODO(crbug.com/983509): test is flaky.
-TEST_F(SystemPerfettoTest,
-       DISABLED_MultipleSystemSourceWithOneLocalSourcesLocalFirst) {
+TEST_F(SystemPerfettoTest, MultipleSystemSourceWithOneLocalSourcesLocalFirst) {
   auto system_service = CreateMockSystemService();
 
   base::RunLoop local_no_more_packets_runloop;
@@ -465,8 +474,7 @@
   PerfettoProducer::DeleteSoonForTesting(std::move(system_producer));
 }
 
-// TODO(crbug.com/983509): test is flaky.
-TEST_F(SystemPerfettoTest, DISABLED_MultipleSystemAndLocalSources) {
+TEST_F(SystemPerfettoTest, MultipleSystemAndLocalSources) {
   auto system_service = CreateMockSystemService();
 
   // Start a trace using the system Perfetto service.
@@ -557,8 +565,7 @@
   PerfettoProducer::DeleteSoonForTesting(std::move(system_producer));
 }
 
-// TODO(crbug.com/983509): test is flaky.
-TEST_F(SystemPerfettoTest, DISABLED_MultipleSystemAndLocalSourcesLocalFirst) {
+TEST_F(SystemPerfettoTest, MultipleSystemAndLocalSourcesLocalFirst) {
   auto system_service = CreateMockSystemService();
 
   // We construct it up front so it connects to the service before the local
@@ -645,8 +652,7 @@
   PerfettoProducer::DeleteSoonForTesting(std::move(system_producer));
 }
 
-// TODO(crbug.com/983509): test is flaky.
-TEST_F(SystemPerfettoTest, DISABLED_SystemToLowAPILevel) {
+TEST_F(SystemPerfettoTest, SystemToLowAPILevel) {
   if (base::android::BuildInfo::GetInstance()->sdk_int() >=
       base::android::SDK_VERSION_P) {
     LOG(INFO) << "Skipping SystemToLowAPILevel test, this phone supports the "
@@ -696,8 +702,7 @@
   EXPECT_EQ(0u, run_test(/* check_sdk_level = */ true));
 }
 
-// TODO(crbug.com/983509): test is flaky.
-TEST_F(SystemPerfettoTest, DISABLED_RespectsFeatureList) {
+TEST_F(SystemPerfettoTest, RespectsFeatureList) {
   {
     base::test::ScopedFeatureList feature_list;
     feature_list.InitAndEnableFeature(features::kEnablePerfettoSystemTracing);
diff --git a/services/tracing/perfetto/test_utils.cc b/services/tracing/perfetto/test_utils.cc
index 23e691d..8f54932 100644
--- a/services/tracing/perfetto/test_utils.cc
+++ b/services/tracing/perfetto/test_utils.cc
@@ -19,10 +19,19 @@
 
 namespace tracing {
 
+// static
+std::unique_ptr<TestDataSource> TestDataSource::CreateAndRegisterDataSource(
+    const std::string& data_source_name,
+    size_t send_packet_count) {
+  auto data_source = std::unique_ptr<TestDataSource>(
+      new TestDataSource(data_source_name, send_packet_count));
+  PerfettoTracedProcess::Get()->AddDataSource(data_source.get());
+  return data_source;
+}
+
 TestDataSource::TestDataSource(const std::string& data_source_name,
                                size_t send_packet_count)
     : DataSourceBase(data_source_name), send_packet_count_(send_packet_count) {
-  PerfettoTracedProcess::Get()->AddDataSource(this);
 }
 
 TestDataSource::~TestDataSource() = default;
@@ -277,8 +286,8 @@
   // construct the data source the TestDataSource can not race.
   producer_client_ = std::make_unique<MockProducerClient>(
       /* num_data_sources = */ 1, std::move(on_tracing_started));
-  data_source_ =
-      std::make_unique<TestDataSource>(data_source_name, num_packets);
+  data_source_ = TestDataSource::CreateAndRegisterDataSource(data_source_name,
+                                                             num_packets);
 
   producer_host_ = std::make_unique<MockProducerHost>(
       producer_name, data_source_name, service, producer_client_.get(),
diff --git a/services/tracing/perfetto/test_utils.h b/services/tracing/perfetto/test_utils.h
index 04740718..bb70b5c0 100644
--- a/services/tracing/perfetto/test_utils.h
+++ b/services/tracing/perfetto/test_utils.h
@@ -22,7 +22,9 @@
 
 class TestDataSource : public PerfettoTracedProcess::DataSourceBase {
  public:
-  TestDataSource(const std::string& data_source_name, size_t send_packet_count);
+  static std::unique_ptr<TestDataSource> CreateAndRegisterDataSource(
+      const std::string& data_source_name,
+      size_t send_packet_count);
   ~TestDataSource() override;
 
   void WritePacketBigly();
@@ -47,6 +49,8 @@
   void set_start_tracing_callback(base::OnceClosure start_tracing_callback);
 
  private:
+  TestDataSource(const std::string& data_source_name, size_t send_packet_count);
+
   size_t send_packet_count_;
   perfetto::DataSourceConfig config_;
   base::OnceClosure start_tracing_callback_ = base::OnceClosure();
diff --git a/services/tracing/perfetto/track_event_json_exporter.cc b/services/tracing/perfetto/track_event_json_exporter.cc
index a27f484e..14eefc11 100644
--- a/services/tracing/perfetto/track_event_json_exporter.cc
+++ b/services/tracing/perfetto/track_event_json_exporter.cc
@@ -706,7 +706,7 @@
   // For flags and ID we need to determine all possible flag bits and set them
   // correctly.
   uint32_t flags = 0;
-  base::Optional<uint32_t> id;
+  base::Optional<uint64_t> id;
   switch (event.id_case()) {
     case TrackEvent::LegacyEvent::kUnscopedId:
       flags |= TRACE_EVENT_FLAG_HAS_ID;
diff --git a/services/tracing/public/cpp/perfetto/perfetto_traced_process.cc b/services/tracing/public/cpp/perfetto/perfetto_traced_process.cc
index bb38e33..c2a8602 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_traced_process.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_traced_process.cc
@@ -93,12 +93,19 @@
                      weak_ptr_factory_.GetWeakPtr(), system_socket));
 }
 
-PerfettoTracedProcess::~PerfettoTracedProcess() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-}
+PerfettoTracedProcess::~PerfettoTracedProcess() {}
 
 void PerfettoTracedProcess::ClearDataSourcesForTesting() {
-  data_sources_.clear();
+  base::RunLoop source_cleared;
+  GetTaskRunner()->GetOrCreateTaskRunner()->PostTaskAndReply(
+      FROM_HERE,
+      base::BindOnce(
+          [](PerfettoTracedProcess* traced_process) {
+            traced_process->data_sources_.clear();
+          },
+          base::Unretained(this)),
+      source_cleared.QuitClosure());
+  source_cleared.Run();
 }
 
 std::unique_ptr<ProducerClient>
diff --git a/storage/browser/blob/blob_flattener_unittest.cc b/storage/browser/blob/blob_flattener_unittest.cc
index ee0f3d6..553eb2bc 100644
--- a/storage/browser/blob/blob_flattener_unittest.cc
+++ b/storage/browser/blob/blob_flattener_unittest.cc
@@ -10,8 +10,8 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
-#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/time/time.h"
 #include "storage/browser/blob/blob_data_builder.h"
@@ -117,7 +117,7 @@
   base::ScopedTempDir temp_dir_;
   scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner();
 
-  base::MessageLoop fake_io_message_loop;
+  base::test::ScopedTaskEnvironment scoped_task_environment;
   std::unique_ptr<BlobStorageContext> context_;
 };
 
diff --git a/storage/browser/blob/blob_memory_controller_unittest.cc b/storage/browser/blob/blob_memory_controller_unittest.cc
index a80344c1..d6aa1af 100644
--- a/storage/browser/blob/blob_memory_controller_unittest.cc
+++ b/storage/browser/blob/blob_memory_controller_unittest.cc
@@ -8,9 +8,9 @@
 #include "base/bind_helpers.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
-#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/system/sys_info.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -165,7 +165,7 @@
 
   scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner();
 
-  base::MessageLoop fake_io_message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
 };
 
 TEST_F(BlobMemoryControllerTest, Strategy) {
diff --git a/storage/browser/blob/blob_storage_context_unittest.cc b/storage/browser/blob/blob_storage_context_unittest.cc
index 275588e..4e244926 100644
--- a/storage/browser/blob/blob_storage_context_unittest.cc
+++ b/storage/browser/blob/blob_storage_context_unittest.cc
@@ -16,9 +16,9 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/memory/ref_counted.h"
-#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
@@ -115,7 +115,7 @@
   base::ScopedTempDir temp_dir_;
   scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner();
 
-  base::MessageLoop fake_io_message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   std::unique_ptr<BlobStorageContext> context_;
 };
 
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 26a001e..9feb0eb 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -26088,6 +26088,151 @@
       }
     ]
   },
+  "android-oreo-arm64-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "system_webview_shell_layout_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "OPM1.171019.021",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "system_webview_shell_layout_test_apk"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_cts_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/android_webview/tools/cts_archive",
+              "location": "android_webview/tools/cts_archive",
+              "revision": "version:1.6"
+            },
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "OPM1.171019.021",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 3
+        },
+        "test": "webview_cts_tests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_ui_test_app_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "OPM1.171019.021",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "webview_ui_test_app_test_apk"
+      }
+    ]
+  },
   "android-pie-arm64-dbg": {
     "gtest_tests": [
       {
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index d4442f9..a8ee6363 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -472,6 +472,14 @@
       },
     },
   },
+  'oreo_fleet': {
+    'swarming': {
+      'dimensions': {
+        'device_os': 'OPM1.171019.021',
+        'device_os_flavor': 'google',
+      },
+    },
+  },
   'pie': {
     'swarming': {
       'dimensions': {
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 3846e7b1..8e15392c 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -224,20 +224,13 @@
         'os_type': 'android',
       },
       'Android WebView O (dbg)': {
+        'mixins': [
+          'oreo_fleet',
+          'walleye',
+        ],
         'test_suites': {
           'gtest_tests': 'webview_bot_gtests',
         },
-        'swarming': {
-          'dimension_sets': [
-            {
-              'device_os': 'OPM1.171019.021',
-              'device_os_flavor': 'google',
-              'device_os_type': 'userdebug',
-              'device_type': 'walleye',
-              'os': 'Android',
-            },
-          ],
-        },
         'use_swarming': True,
         'os_type': 'android',
       },
@@ -562,6 +555,17 @@
         },
         'os_type': 'android',
       },
+      'android-oreo-arm64-rel': {
+        'mixins': [
+          'oreo_fleet',
+          'walleye',
+        ],
+        'test_suites': {
+          'gtest_tests': 'webview_bot_gtests',
+        },
+        'use_swarming': True,
+        'os_type': 'android',
+      },
       'android-pie-arm64-dbg': {
         'swarming': {
           'dimension_sets': [
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 0d38d68..a97f3a5 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2468,6 +2468,28 @@
             ]
         }
     ],
+    "HeapProfilerReporting": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "sampling-rate": "1000000"
+                    },
+                    "enable_features": [
+                        "HeapProfilerReporting"
+                    ]
+                }
+            ]
+        }
+    ],
     "HomePageButtonForceEnabled": [
         {
             "platforms": [
diff --git a/third_party/blink/public/mojom/service_worker/service_worker.mojom b/third_party/blink/public/mojom/service_worker/service_worker.mojom
index 3ea8f70..a6b9d8af 100644
--- a/third_party/blink/public/mojom/service_worker/service_worker.mojom
+++ b/third_party/blink/public/mojom/service_worker/service_worker.mojom
@@ -258,6 +258,8 @@
           => (ServiceWorkerEventStatus status);
   DispatchExtendableMessageEvent(ExtendableMessageEvent event)
       => (ServiceWorkerEventStatus status);
+  // Ref: https://github.com/rknoll/content-index
+  DispatchContentDeleteEvent(string id) => (ServiceWorkerEventStatus status);
 
   // TODO(crbug.com/869714): Remove this code for long living service workers
   // when Android Messages no longer requires it.
diff --git a/third_party/blink/renderer/bindings/modules/BUILD.gn b/third_party/blink/renderer/bindings/modules/BUILD.gn
index e400dc8..3084d87f 100644
--- a/third_party/blink/renderer/bindings/modules/BUILD.gn
+++ b/third_party/blink/renderer/bindings/modules/BUILD.gn
@@ -23,6 +23,7 @@
     "//third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_event.idl",
     "//third_party/blink/renderer/modules/background_sync/periodic_sync_event.idl",
     "//third_party/blink/renderer/modules/background_sync/sync_event.idl",
+    "//third_party/blink/renderer/modules/content_index/content_index_event.idl",
     "//third_party/blink/renderer/modules/cookie_store/cookie_change_event.idl",
     "//third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.idl",
     "//third_party/blink/renderer/modules/device_orientation/device_motion_event.idl",
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 500e277..7177784 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1235,7 +1235,8 @@
 
 inline bool Element::FastHasAttribute(const QualifiedName& name) const {
 #if DCHECK_IS_ON()
-  DCHECK(FastAttributeLookupAllowed(name));
+  DCHECK(FastAttributeLookupAllowed(name))
+      << TagQName().ToString().Utf8() << "/@" << name.ToString().Utf8();
 #endif
   return GetElementData() &&
          GetElementData()->Attributes().FindIndex(name) != kNotFound;
@@ -1244,7 +1245,8 @@
 inline const AtomicString& Element::FastGetAttribute(
     const QualifiedName& name) const {
 #if DCHECK_IS_ON()
-  DCHECK(FastAttributeLookupAllowed(name));
+  DCHECK(FastAttributeLookupAllowed(name))
+      << TagQName().ToString().Utf8() << "/@" << name.ToString().Utf8();
 #endif
   if (GetElementData()) {
     if (const Attribute* attribute = GetElementData()->Attributes().Find(name))
diff --git a/third_party/blink/renderer/core/dom/slot_assignment_engine.cc b/third_party/blink/renderer/core/dom/slot_assignment_engine.cc
index 30c4bb9..4c247b4 100644
--- a/third_party/blink/renderer/core/dom/slot_assignment_engine.cc
+++ b/third_party/blink/renderer/core/dom/slot_assignment_engine.cc
@@ -42,6 +42,7 @@
 void SlotAssignmentEngine::RecalcSlotAssignments() {
   if (shadow_roots_needing_recalc_.IsEmpty())
     return;
+  TRACE_EVENT0("blink", "SlotAssignmentEngine::RecalcSlotAssignments");
   for (auto& shadow_root :
        HeapHashSet<WeakMember<ShadowRoot>>(shadow_roots_needing_recalc_)) {
     DCHECK(shadow_root->isConnected());
diff --git a/third_party/blink/renderer/core/events/event_type_names.json5 b/third_party/blink/renderer/core/events/event_type_names.json5
index 4fdda0dc..3a1712d 100644
--- a/third_party/blink/renderer/core/events/event_type_names.json5
+++ b/third_party/blink/renderer/core/events/event_type_names.json5
@@ -83,6 +83,7 @@
     "controllerchange",
     "cookiechange",
     "copy",
+    "contentdelete",
     "crossoriginmessage",
     "cuechange",
     "cut",
diff --git a/third_party/blink/renderer/core/html/html_image_element.cc b/third_party/blink/renderer/core/html/html_image_element.cc
index a06d37c..7cb5e84 100644
--- a/third_party/blink/renderer/core/html/html_image_element.cc
+++ b/third_party/blink/renderer/core/html/html_image_element.cc
@@ -435,12 +435,14 @@
     ResetFormOwner();
   if (listener_)
     GetDocument().GetMediaQueryMatcher().AddViewportListener(listener_);
-  Node* parent = parentNode();
-  if (parent && IsHTMLPictureElement(*parent))
-    ToHTMLPictureElement(parent)->AddListenerToSourceChildren();
+  bool was_added_to_picture_parent = false;
+  if (auto* picture_parent = ToHTMLPictureElementOrNull(parentNode())) {
+    picture_parent->AddListenerToSourceChildren();
+    was_added_to_picture_parent = picture_parent == insertion_point;
+  }
 
   bool image_was_modified = false;
-  if (GetDocument().IsActive()) {
+  if (GetDocument().IsActive() && was_added_to_picture_parent) {
     ImageCandidate candidate = FindBestFitImageFromPictureParent();
     if (!candidate.IsEmpty()) {
       SetBestFitURLAndDPRFromImageCandidate(candidate);
@@ -462,9 +464,8 @@
     ResetFormOwner();
   if (listener_) {
     GetDocument().GetMediaQueryMatcher().RemoveViewportListener(listener_);
-    Node* parent = parentNode();
-    if (parent && IsHTMLPictureElement(*parent))
-      ToHTMLPictureElement(parent)->RemoveListenerFromSourceChildren();
+    if (auto* picture_parent = ToHTMLPictureElementOrNull(parentNode()))
+      picture_parent->RemoveListenerFromSourceChildren();
   }
   HTMLElement::RemovedFrom(insertion_point);
 }
diff --git a/third_party/blink/renderer/core/html/html_source_element.cc b/third_party/blink/renderer/core/html/html_source_element.cc
index 0348f09..ef77c39 100644
--- a/third_party/blink/renderer/core/html/html_source_element.cc
+++ b/third_party/blink/renderer/core/html/html_source_element.cc
@@ -100,21 +100,22 @@
   Element* parent = parentElement();
   if (auto* media = ToHTMLMediaElementOrNull(parent))
     media->SourceWasAdded(this);
-  if (auto* picture = ToHTMLPictureElementOrNull(parent))
-    picture->SourceOrMediaChanged();
+  if (parent == insertion_point && IsHTMLPictureElement(parent))
+    ToHTMLPictureElement(parent)->SourceOrMediaChanged();
   return kInsertionDone;
 }
 
 void HTMLSourceElement::RemovedFrom(ContainerNode& removal_root) {
   Element* parent = parentElement();
-  auto* element = DynamicTo<Element>(&removal_root);
-  if (element && !parent)
-    parent = element;
+  bool was_removed_from_parent = !parent;
+  if (was_removed_from_parent)
+    parent = DynamicTo<Element>(&removal_root);
   if (auto* media = ToHTMLMediaElementOrNull(parent))
     media->SourceWasRemoved(this);
   if (auto* picture = ToHTMLPictureElementOrNull(parent)) {
     RemoveMediaQueryListListener();
-    picture->SourceOrMediaChanged();
+    if (was_removed_from_parent)
+      picture->SourceOrMediaChanged();
   }
   HTMLElement::RemovedFrom(removal_root);
 }
diff --git a/third_party/blink/renderer/core/paint/theme_painter.cc b/third_party/blink/renderer/core/paint/theme_painter.cc
index 1c27072..e63a1c9 100644
--- a/third_party/blink/renderer/core/paint/theme_painter.cc
+++ b/third_party/blink/renderer/core/paint/theme_painter.cc
@@ -158,7 +158,7 @@
         DEPRECATE_APPEARANCE(doc, ButtonForSelect);
       } else {
         const AtomicString& type =
-            To<Element>(node)->FastGetAttribute(html_names::kTypeAttr);
+            To<Element>(node)->getAttribute(html_names::kTypeAttr);
         // https://github.com/twbs/bootstrap/pull/29053
         if (type == "button" || type == "reset" || type == "submit") {
           COUNT_APPEARANCE(doc, ButtonForBootstrapLooseSelector);
diff --git a/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.cc b/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.cc
index c0d7a68..622e959 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.cc
+++ b/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.cc
@@ -114,7 +114,7 @@
   }
 }
 
-SVGSMILElement* SMILAnimationSandwich::UpdateAnimationValues() {
+SVGSMILElement* SMILAnimationSandwich::ApplyAnimationValues() {
   if (active_.IsEmpty())
     return nullptr;
   // Results are accumulated to the first animation that animates and
@@ -143,6 +143,8 @@
   }
   active_.Shrink(0);
 
+  result_element->ApplyResultsToTarget();
+
   return result_element;
 }
 
diff --git a/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.h b/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.h
index 1883e9f..1511fe9 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.h
+++ b/third_party/blink/renderer/core/svg/animation/smil_animation_sandwich.h
@@ -64,7 +64,7 @@
 
   void UpdateTiming(double elapsed, bool seek_to_time);
   void SendEvents(double elapsed, bool seek_to_time);
-  SVGSMILElement* UpdateAnimationValues();
+  SVGSMILElement* ApplyAnimationValues();
 
   SMILTime GetNextFireTime();
 
diff --git a/third_party/blink/renderer/core/svg/animation/smil_time_container.cc b/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
index 80e9849..8b39203 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
+++ b/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
@@ -79,7 +79,13 @@
   DCHECK(!prevent_scheduled_animations_changes_);
 #endif
 
-  auto key = std::make_pair(target, attribute_name);
+  // Separate out Discard and AnimateMotion
+  QualifiedName name = (animation->HasTagName(svg_names::kAnimateMotionTag) ||
+                        animation->HasTagName(svg_names::kDiscardTag))
+                           ? animation->TagQName()
+                           : attribute_name;
+
+  auto key = std::make_pair(target, name);
   auto& sandwich =
       scheduled_animations_.insert(key, nullptr).stored_value->value;
   if (!sandwich)
@@ -101,7 +107,13 @@
   DCHECK(!prevent_scheduled_animations_changes_);
 #endif
 
-  auto key = std::make_pair(target, attribute_name);
+  // Separate out Discard and AnimateMotion
+  QualifiedName name = (animation->HasTagName(svg_names::kAnimateMotionTag) ||
+                        animation->HasTagName(svg_names::kDiscardTag))
+                           ? animation->TagQName()
+                           : attribute_name;
+
+  auto key = std::make_pair(target, name);
   AnimationsMap::iterator it = scheduled_animations_.find(key);
   CHECK(it != scheduled_animations_.end());
 
@@ -379,8 +391,8 @@
   if (!GetDocument().IsActive())
     return;
 
-  UpdateAnimations(elapsed, seek_to_time);
-  ApplyAnimations(elapsed);
+  UpdateAnimationTimings(elapsed, seek_to_time);
+  ApplyAnimationValues(elapsed);
 
   SMILTime earliest_fire_time = SMILTime::Unresolved();
   for (auto& sandwich : scheduled_animations_) {
@@ -395,7 +407,8 @@
   ScheduleAnimationFrame(delay_time);
 }
 
-void SMILTimeContainer::UpdateAnimations(double elapsed, bool seek_to_time) {
+void SMILTimeContainer::UpdateAnimationTimings(double elapsed,
+                                               bool seek_to_time) {
   DCHECK(GetDocument().IsActive());
 
 #if DCHECK_IS_ON()
@@ -435,13 +448,13 @@
   }
 }
 
-void SMILTimeContainer::ApplyAnimations(double elapsed) {
+void SMILTimeContainer::ApplyAnimationValues(double elapsed) {
 #if DCHECK_IS_ON()
   prevent_scheduled_animations_changes_ = true;
 #endif
   HeapVector<Member<SVGSMILElement>> animations_to_apply;
   for (auto& sandwich : active_sandwiches_) {
-    if (SVGSMILElement* animation = sandwich->UpdateAnimationValues())
+    if (SVGSMILElement* animation = sandwich->ApplyAnimationValues())
       animations_to_apply.push_back(animation);
   }
   active_sandwiches_.Shrink(0);
@@ -453,15 +466,12 @@
     return;
   }
 
+  // Everything bellow handles "discard" elements.
   UseCounter::Count(&GetDocument(), WebFeature::kSVGSMILAnimationAppliedEffect);
 
   std::sort(animations_to_apply.begin(), animations_to_apply.end(),
             PriorityCompare(elapsed));
 
-  // Apply results to target elements.
-  for (const auto& timed_element : animations_to_apply)
-    timed_element->ApplyResultsToTarget();
-
 #if DCHECK_IS_ON()
   prevent_scheduled_animations_changes_ = false;
 #endif
diff --git a/third_party/blink/renderer/core/svg/animation/smil_time_container.h b/third_party/blink/renderer/core/svg/animation/smil_time_container.h
index 43f9a0e..ce762bf 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_time_container.h
+++ b/third_party/blink/renderer/core/svg/animation/smil_time_container.h
@@ -110,8 +110,8 @@
   bool CanScheduleFrame(SMILTime earliest_fire_time) const;
   void UpdateAnimationsAndScheduleFrameIfNeeded(double elapsed,
                                                 bool seek_to_time = false);
-  void UpdateAnimations(double elapsed, bool seek_to_time);
-  void ApplyAnimations(double elapsed);
+  void UpdateAnimationTimings(double elapsed, bool seek_to_time);
+  void ApplyAnimationValues(double elapsed);
   void ServiceOnNextFrame();
   void ScheduleWakeUp(double delay_time, FrameSchedulingState);
   bool HasPendingSynchronization() const;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 521ad40b..bcbf7096 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -494,6 +494,7 @@
   if (!canvas()->GetDocument().GetFrame())
     return;
 
+  base::TimeTicks start_time = base::TimeTicks::Now();
   canvas()->GetDocument().UpdateStyleAndLayoutTreeForNode(canvas());
 
   // The following early exit is dependent on the cache not being empty
@@ -573,6 +574,10 @@
       new_font);  // Create a string copy since newFont can be
                   // deleted inside realizeSaves.
   ModifiableState().SetUnparsedFont(new_font_safe_copy);
+
+  base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
+  base::UmaHistogramMicrosecondsTimesUnderTenMilliseconds(
+      "Canvas.TextMetrics.SetFont", elapsed);
 }
 
 void CanvasRenderingContext2D::DidProcessTask(
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
index dd14ec4..2c18e2d 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
@@ -330,6 +330,7 @@
   if (GetState().HasRealizedFont() && new_font == GetState().UnparsedFont())
     return;
 
+  base::TimeTicks start_time = base::TimeTicks::Now();
   OffscreenFontCache& font_cache = GetOffscreenFontCache();
 
   Font* cached_font = font_cache.GetFont(new_font);
@@ -362,6 +363,9 @@
     ModifiableState().SetFont(font, Host()->GetFontSelector());
   }
   ModifiableState().SetUnparsedFont(new_font);
+  base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
+  base::UmaHistogramMicrosecondsTimesUnderTenMilliseconds(
+      "OffscreenCanvas.TextMetrics.SetFont", elapsed);
 }
 
 static inline TextDirection ToTextDirection(
diff --git a/third_party/blink/renderer/modules/content_index/BUILD.gn b/third_party/blink/renderer/modules/content_index/BUILD.gn
index 42ca7af..b89caf3cb 100644
--- a/third_party/blink/renderer/modules/content_index/BUILD.gn
+++ b/third_party/blink/renderer/modules/content_index/BUILD.gn
@@ -10,6 +10,9 @@
     "content_description_type_converter.h",
     "content_index.cc",
     "content_index.h",
+    "content_index_event.cc",
+    "content_index_event.h",
+    "service_worker_global_scope_content_index.h",
     "service_worker_registration_content_index.cc",
     "service_worker_registration_content_index.h",
   ]
diff --git a/third_party/blink/renderer/modules/content_index/content_index_event.cc b/third_party/blink/renderer/modules/content_index/content_index_event.cc
new file mode 100644
index 0000000..6e5595e
--- /dev/null
+++ b/third_party/blink/renderer/modules/content_index/content_index_event.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/content_index/content_index_event.h"
+
+#include "third_party/blink/renderer/modules/service_worker/extendable_event_init.h"
+
+namespace blink {
+
+ContentIndexEvent::ContentIndexEvent(const AtomicString& type,
+                                     ContentIndexEventInit* init,
+                                     WaitUntilObserver* observer)
+    : ExtendableEvent(type, init, observer), id_(init->id()) {}
+
+ContentIndexEvent::~ContentIndexEvent() = default;
+
+const String& ContentIndexEvent::id() const {
+  return id_;
+}
+
+const AtomicString& ContentIndexEvent::InterfaceName() const {
+  return event_interface_names::kContentIndexEvent;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/content_index/content_index_event.h b/third_party/blink/renderer/modules/content_index/content_index_event.h
new file mode 100644
index 0000000..53d277d
--- /dev/null
+++ b/third_party/blink/renderer/modules/content_index/content_index_event.h
@@ -0,0 +1,47 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_INDEX_CONTENT_INDEX_EVENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_INDEX_CONTENT_INDEX_EVENT_H_
+
+#include "third_party/blink/renderer/modules/content_index/content_index_event_init.h"
+#include "third_party/blink/renderer/modules/event_modules.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/modules/service_worker/extendable_event.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+
+class WaitUntilObserver;
+
+class MODULES_EXPORT ContentIndexEvent final : public ExtendableEvent {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  static ContentIndexEvent* Create(const AtomicString& type,
+                                   ContentIndexEventInit* init) {
+    return MakeGarbageCollected<ContentIndexEvent>(type, init,
+                                                   /* observer= */ nullptr);
+  }
+
+  ContentIndexEvent(const AtomicString& type,
+                    ContentIndexEventInit* init,
+                    WaitUntilObserver* observer);
+  ~ContentIndexEvent() override;
+
+  // Web exposed attribute defined in the IDL file.
+  const String& id() const;
+
+  // ExtendableEvent interface.
+  const AtomicString& InterfaceName() const override;
+
+ private:
+  String id_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_INDEX_CONTENT_INDEX_EVENT_H_
diff --git a/third_party/blink/renderer/modules/content_index/content_index_event.idl b/third_party/blink/renderer/modules/content_index/content_index_event.idl
new file mode 100644
index 0000000..e69e766
--- /dev/null
+++ b/third_party/blink/renderer/modules/content_index/content_index_event.idl
@@ -0,0 +1,13 @@
+// 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.
+
+// https://github.com/rknoll/content-index
+
+[
+    Constructor(DOMString type, ContentIndexEventInit id),
+    Exposed=ServiceWorker,
+    RuntimeEnabled=ContentIndex
+] interface ContentIndexEvent : ExtendableEvent {
+    readonly attribute DOMString id;
+};
diff --git a/third_party/blink/renderer/modules/content_index/content_index_event_init.idl b/third_party/blink/renderer/modules/content_index/content_index_event_init.idl
new file mode 100644
index 0000000..2ddc075
--- /dev/null
+++ b/third_party/blink/renderer/modules/content_index/content_index_event_init.idl
@@ -0,0 +1,9 @@
+// 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.
+
+// https://github.com/rknoll/content-index
+
+dictionary ContentIndexEventInit : ExtendableEventInit {
+    required DOMString id;
+};
diff --git a/third_party/blink/renderer/modules/content_index/service_worker_global_scope_content_index.h b/third_party/blink/renderer/modules/content_index/service_worker_global_scope_content_index.h
new file mode 100644
index 0000000..c9d11ee
--- /dev/null
+++ b/third_party/blink/renderer/modules/content_index/service_worker_global_scope_content_index.h
@@ -0,0 +1,22 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_INDEX_SERVICE_WORKER_GLOBAL_SCOPE_CONTENT_INDEX_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_INDEX_SERVICE_WORKER_GLOBAL_SCOPE_CONTENT_INDEX_H_
+
+#include "third_party/blink/renderer/core/dom/events/event_target.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+
+namespace blink {
+
+class ServiceWorkerGlobalScopeContentIndex {
+  STATIC_ONLY(ServiceWorkerGlobalScopeContentIndex);
+
+ public:
+  DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(contentdelete, kContentdelete)
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CONTENT_INDEX_SERVICE_WORKER_GLOBAL_SCOPE_CONTENT_INDEX_H_
diff --git a/third_party/blink/renderer/modules/content_index/service_worker_global_scope_content_index.idl b/third_party/blink/renderer/modules/content_index/service_worker_global_scope_content_index.idl
new file mode 100644
index 0000000..e4fe3e51
--- /dev/null
+++ b/third_party/blink/renderer/modules/content_index/service_worker_global_scope_content_index.idl
@@ -0,0 +1,12 @@
+// 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.
+
+// https://github.com/rknoll/content-index
+
+[
+    ImplementedAs=ServiceWorkerGlobalScopeContentIndex,
+    RuntimeEnabled=ContentIndex
+] partial interface ServiceWorkerGlobalScope {
+    attribute EventHandler oncontentdelete;
+};
diff --git a/third_party/blink/renderer/modules/image_downloader/image_downloader_base.cc b/third_party/blink/renderer/modules/image_downloader/image_downloader_base.cc
index 441d2ee..20735e6 100644
--- a/third_party/blink/renderer/modules/image_downloader/image_downloader_base.cc
+++ b/third_party/blink/renderer/modules/image_downloader/image_downloader_base.cc
@@ -91,11 +91,11 @@
 
   // Remove the image fetcher from our pending list. We're in the callback from
   // MultiResolutionImageResourceFetcher, best to delay deletion.
-  for (auto* iter = image_fetchers_.begin(); iter != image_fetchers_.end();
-       ++iter) {
-    if (iter->Get() == fetcher) {
-      iter->Release();
-      image_fetchers_.erase(iter);
+  for (auto* it = image_fetchers_.begin(); it != image_fetchers_.end(); ++it) {
+    MultiResolutionImageResourceFetcher* image_fetcher = it->Get();
+    DCHECK(image_fetcher);
+    if (image_fetcher == fetcher) {
+      it = image_fetchers_.erase(it);
       break;
     }
   }
@@ -115,7 +115,6 @@
     // Will run callbacks with an empty image vector.
     fetchers->ContextDestroyed(context);
   }
-
   image_fetchers_.clear();
 }
 
diff --git a/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.cc b/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.cc
index cce10509..638e22f 100644
--- a/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.cc
+++ b/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.cc
@@ -142,8 +142,7 @@
 }
 
 void ImageDownloaderImpl::Dispose() {
-  if (binding_.is_bound())
-    binding_.Unbind();
+  binding_.Close();
 }
 
 void ImageDownloaderImpl::ContextDestroyed(ExecutionContext*) {
diff --git a/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.h b/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.h
index 59e6dec..1b31733 100644
--- a/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.h
+++ b/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.h
@@ -18,6 +18,7 @@
     : public GarbageCollectedFinalized<ImageDownloaderImpl>,
       public ImageDownloaderBase,
       public mojom::blink::ImageDownloader {
+  USING_PRE_FINALIZER(ImageDownloaderImpl, Dispose);
   USING_GARBAGE_COLLECTED_MIXIN(ImageDownloaderImpl);
 
  public:
@@ -50,6 +51,8 @@
                         int32_t http_status_code,
                         const WTF::Vector<SkBitmap>& images);
 
+  // USING_PRE_FINALIZER interface.
+  // Called before the object gets garbage collected.
   void Dispose();
 
   mojo::Binding<mojom::blink::ImageDownloader> binding_;
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni
index ae6b629..8cde57a6 100644
--- a/third_party/blink/renderer/modules/modules_idl_files.gni
+++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -101,6 +101,7 @@
           "clipboard/clipboard_item.idl",
           "contacts_picker/contacts_manager.idl",
           "content_index/content_index.idl",
+          "content_index/content_index_event.idl",
           "cookie_store/cookie_change_event.idl",
           "cookie_store/cookie_store.idl",
           "cookie_store/extendable_cookie_change_event.idl",
@@ -571,6 +572,7 @@
           "contacts_picker/contact_info.idl",
           "contacts_picker/contacts_select_options.idl",
           "content_index/content_description.idl",
+          "content_index/content_index_event_init.idl",
           "cookie_store/cookie_change_event_init.idl",
           "cookie_store/cookie_list_item.idl",
           "cookie_store/cookie_store_delete_options.idl",
@@ -892,6 +894,7 @@
           "canvas/canvas2d/canvas_path.idl",
           "clipboard/navigator_clipboard.idl",
           "contacts_picker/navigator_contacts.idl",
+          "content_index/service_worker_global_scope_content_index.idl",
           "content_index/service_worker_registration_content_index.idl",
           "cookie_store/service_worker_global_scope_cookie_store.idl",
           "cookie_store/window_cookie_store.idl",
diff --git a/third_party/blink/renderer/modules/service_worker/DEPS b/third_party/blink/renderer/modules/service_worker/DEPS
index a5c5adf5..380aee71 100644
--- a/third_party/blink/renderer/modules/service_worker/DEPS
+++ b/third_party/blink/renderer/modules/service_worker/DEPS
@@ -15,6 +15,7 @@
     "service_worker_global_scope\.cc": [
         "+third_party/blink/renderer/modules/background_fetch",
         "+third_party/blink/renderer/modules/background_sync",
+        "+third_party/blink/renderer/modules/content_index",
         "+third_party/blink/renderer/modules/cookie_store",
         "+third_party/blink/renderer/modules/notifications",
         "+third_party/blink/renderer/modules/payments",
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
index 5c527560..53fe6a29c4 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
@@ -69,6 +69,8 @@
 #include "third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_event.h"
 #include "third_party/blink/renderer/modules/background_sync/periodic_sync_event.h"
 #include "third_party/blink/renderer/modules/background_sync/sync_event.h"
+#include "third_party/blink/renderer/modules/content_index/content_index_event.h"
+#include "third_party/blink/renderer/modules/content_index/content_index_event_init.h"
 #include "third_party/blink/renderer/modules/cookie_store/cookie_change_event.h"
 #include "third_party/blink/renderer/modules/cookie_store/extendable_cookie_change_event.h"
 #include "third_party/blink/renderer/modules/event_target_modules.h"
@@ -1206,6 +1208,19 @@
                    event_id, status);
 }
 
+void ServiceWorkerGlobalScope::DidHandleContentDeleteEvent(
+    int event_id,
+    mojom::ServiceWorkerEventStatus status) {
+  DCHECK(IsContextThread());
+  TRACE_EVENT_WITH_FLOW1(
+      "ServiceWorker", "ServiceWorkerGlobalScope::DidHandleContentDeleteEvent",
+      TRACE_ID_WITH_SCOPE(kServiceWorkerGlobalScopeTraceScope,
+                          TRACE_ID_LOCAL(event_id)),
+      TRACE_EVENT_FLAG_FLOW_IN, "status", MojoEnumToString(status));
+  RunEventCallback(&content_delete_callbacks_, timeout_timer_.get(), event_id,
+                   status);
+}
+
 void ServiceWorkerGlobalScope::SetIsInstalling(bool is_installing) {
   is_installing_ = is_installing;
   if (is_installing)
@@ -1934,6 +1949,31 @@
   DispatchExtendableEvent(event, observer);
 }
 
+void ServiceWorkerGlobalScope::DispatchContentDeleteEvent(
+    const String& id,
+    DispatchContentDeleteEventCallback callback) {
+  DCHECK(IsContextThread());
+  int event_id = timeout_timer_->StartEvent(
+      CreateAbortCallback(&content_delete_callbacks_));
+  content_delete_callbacks_.Set(event_id, std::move(callback));
+  TRACE_EVENT_WITH_FLOW0(
+      "ServiceWorker", "ServiceWorkerGlobalScope::DispatchContentDeleteEvent",
+      TRACE_ID_WITH_SCOPE(kServiceWorkerGlobalScopeTraceScope,
+                          TRACE_ID_LOCAL(event_id)),
+      TRACE_EVENT_FLAG_FLOW_OUT);
+
+  WaitUntilObserver* observer = WaitUntilObserver::Create(
+      this, WaitUntilObserver::kContentDelete, event_id);
+
+  auto* init = ContentIndexEventInit::Create();
+  init->setId(id);
+
+  auto* event = MakeGarbageCollected<ContentIndexEvent>(
+      event_type_names::kContentdelete, init, observer);
+
+  DispatchExtendableEvent(event, observer);
+}
+
 void ServiceWorkerGlobalScope::Ping(PingCallback callback) {
   DCHECK(IsContextThread());
   std::move(callback).Run();
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h
index 4ec1ff3..d6fbc9e 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h
@@ -264,6 +264,8 @@
                                     mojom::ServiceWorkerEventStatus);
   void DidHandleCookieChangeEvent(int event_id,
                                   mojom::ServiceWorkerEventStatus);
+  void DidHandleContentDeleteEvent(int event_id,
+                                   mojom::ServiceWorkerEventStatus);
 
   mojom::blink::CacheStoragePtrInfo TakeCacheStorage();
 
@@ -427,6 +429,9 @@
       const WebCanonicalCookie& cookie,
       ::network::mojom::blink::CookieChangeCause cause,
       DispatchCookieChangeEventCallback callback) override;
+  void DispatchContentDeleteEvent(
+      const String& id,
+      DispatchContentDeleteEventCallback callback) override;
   void Ping(PingCallback callback) override;
   void SetIdleTimerDelayToZero() override;
 
@@ -494,6 +499,7 @@
   HashMap<int, DispatchCookieChangeEventCallback>
       cookie_change_event_callbacks_;
   HashMap<int, DispatchExtendableMessageEventCallback> message_event_callbacks_;
+  HashMap<int, DispatchContentDeleteEventCallback> content_delete_callbacks_;
 
   // Maps for response callbacks.
   // These are mapped from an event id to the Mojo interface pointer which is
diff --git a/third_party/blink/renderer/modules/service_worker/wait_until_observer.cc b/third_party/blink/renderer/modules/service_worker/wait_until_observer.cc
index c1aa32f..c7bb4f2c 100644
--- a/third_party/blink/renderer/modules/service_worker/wait_until_observer.cc
+++ b/third_party/blink/renderer/modules/service_worker/wait_until_observer.cc
@@ -325,6 +325,10 @@
       service_worker_global_scope->DidHandleBackgroundFetchSuccessEvent(
           event_id_, status);
       break;
+    case kContentDelete:
+      service_worker_global_scope->DidHandleContentDeleteEvent(event_id_,
+                                                               status);
+      break;
   }
 }
 
diff --git a/third_party/blink/renderer/modules/service_worker/wait_until_observer.h b/third_party/blink/renderer/modules/service_worker/wait_until_observer.h
index 05f1e11..162abc3 100644
--- a/third_party/blink/renderer/modules/service_worker/wait_until_observer.h
+++ b/third_party/blink/renderer/modules/service_worker/wait_until_observer.h
@@ -46,7 +46,8 @@
     kBackgroundFetchAbort,
     kBackgroundFetchClick,
     kBackgroundFetchFail,
-    kBackgroundFetchSuccess
+    kBackgroundFetchSuccess,
+    kContentDelete,
   };
 
   static WaitUntilObserver* Create(ExecutionContext*, EventType, int event_id);
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_state_record.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_state_record.cc
index a431258c..7676c0a 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_state_record.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_state_record.cc
@@ -60,15 +60,15 @@
   resolver->Reject(MakeGarbageCollected<DOMException>(
       DOMExceptionCode::kAbortError, "Wake Lock released"));
 
-  // 2. If record.[[ActiveLocks]] does not contain lockPromise, abort these
+  // 2. Let document be the responsible document of the current settings object.
+  // 3. Let record be the platform wake lock's state record associated with
+  // document and type.
+  // 4. If record.[[ActiveLocks]] does not contain lockPromise, abort these
   // steps.
   auto iterator = active_locks_.find(resolver);
   if (iterator == active_locks_.end())
     return;
 
-  // 3. Let document be the responsible document of the current settings object.
-  // 4. Let record be the platform wake lock's state record associated with
-  // document and type.
   // 5. Remove lockPromise from record.[[ActiveLocks]].
   active_locks_.erase(iterator);
 
diff --git a/third_party/blink/renderer/platform/fonts/web_font_decoder.cc b/third_party/blink/renderer/platform/fonts/web_font_decoder.cc
index dad31d2..8d64c2565 100644
--- a/third_party/blink/renderer/platform/fonts/web_font_decoder.cc
+++ b/third_party/blink/renderer/platform/fonts/web_font_decoder.cc
@@ -195,7 +195,7 @@
   // Most web fonts are compressed, so the result can be much larger than
   // the original.
   ots::ExpandingMemoryStream output(buffer->size(), kMaxWebFontSize);
-  double start = CurrentTime();
+  base::ElapsedTimer timer;
   BlinkOTSContext ots_context;
   SharedBuffer::DeprecatedFlatData flattened_buffer(buffer);
   const char* data = flattened_buffer.Data();
@@ -211,7 +211,7 @@
   }
 
   const size_t decoded_length = SafeCast<size_t>(output.Tell());
-  RecordDecodeSpeedHistogram(data, buffer->size(), CurrentTime() - start,
+  RecordDecodeSpeedHistogram(data, buffer->size(), timer.Elapsed().InSecondsF(),
                              decoded_length);
 
   sk_sp<SkData> sk_data = SkData::MakeWithCopy(output.get(), decoded_length);
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations
index 9eeec3c..c7eebf2 100644
--- a/third_party/blink/web_tests/MSANExpectations
+++ b/third_party/blink/web_tests/MSANExpectations
@@ -306,3 +306,9 @@
 
 # Sheriff 2019-07-11
 crbug.com/856601 [ Linux ] virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Pass Timeout ]
+
+# Sheriff 2019-07-19
+crbug.com/856601 [ Linux ] virtual/not-omt-sw-fetch/external/wpt/fetch/api/idl.any.serviceworker.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] virtual/omt-worker-fetch/external/wpt/fetch/cors-rfc1918/idlharness.tentative.any.serviceworker.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] external/wpt/WebCryptoAPI/idlharness.https.any.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] external/wpt/web-animations/interfaces/Animation/idlharness.window.html [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 6ef40c98..a7f4a51 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -4320,7 +4320,6 @@
 crbug.com/626703 crbug.com/930297 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html [ Timeout Crash ]
 crbug.com/626703 crbug.com/930297 external/wpt/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.html [ Timeout Crash ]
 crbug.com/626703 external/wpt/html/semantics/embedded-content/the-embed-element/embed-represent-nothing-04.html [ Failure ]
-crbug.com/626703 external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations.html [ Timeout Pass ]
 crbug.com/626703 external/wpt/html/semantics/embedded-content/the-video-element/video_initially_paused.html [ Failure ]
 crbug.com/626703 external/wpt/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-menu.html [ Failure ]
 crbug.com/626703 external/wpt/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-skip-no-boxes.html [ Failure ]
@@ -5011,6 +5010,13 @@
 # Geolocation tests
 crbug.com/745079 external/wpt/geolocation-API/PositionOptions.https.html [ Failure ]
 
+# Service worker updates need to handle redirect appropriately.
+crbug.com/889798 external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
+crbug.com/889798 virtual/blink-cors/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
+crbug.com/889798 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
+crbug.com/889798 virtual/not-omt-sw-fetch/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
+crbug.com/889798 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
+
 # Sheriff failures 2017-07-03
 crbug.com/708994 http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
 crbug.com/708994 virtual/blink-cors/http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
@@ -6042,7 +6048,6 @@
 ### external/wpt/fetch/sec-metadata/
 crbug.com/947023 external/wpt/fetch/sec-metadata/font.tentative.https.sub.html [ Pass Failure ]
 crbug.com/947023 external/wpt/fetch/sec-metadata/report.tentative.https.sub.html [ Pass Timeout ]
-crbug.com/947023 external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html [ Pass Failure ]
 
 # Sheriff 2019-04-02
 crbug.com/947690 [ Debug ] http/tests/history/back-during-beforeunload.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
index b3b5bed..47e3615a 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
@@ -10869,6 +10869,18 @@
      {}
     ]
    ],
+   "css/CSS2/floats/float-no-content-beside-001.html": [
+    [
+     "css/CSS2/floats/float-no-content-beside-001.html",
+     [
+      [
+       "/css/CSS2/floats/float-no-content-beside-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/CSS2/floats/float-nowrap-2.html": [
     [
      "css/CSS2/floats/float-nowrap-2.html",
@@ -99153,18 +99165,6 @@
      {}
     ]
    ],
-   "css/filter-effects/backdrop-filter-plus-mask.html": [
-    [
-     "css/filter-effects/backdrop-filter-plus-mask.html",
-     [
-      [
-       "/css/filter-effects/backdrop-filter-plus-mask-ref.html",
-       "=="
-      ]
-     ],
-     {}
-    ]
-   ],
    "css/filter-effects/backdrop-filter-reference-filter.html": [
     [
      "css/filter-effects/backdrop-filter-reference-filter.html",
@@ -115861,18 +115861,6 @@
      {}
     ]
    ],
-   "mathml/presentation-markup/fractions/frac-linethickness-002.html": [
-    [
-     "mathml/presentation-markup/fractions/frac-linethickness-002.html",
-     [
-      [
-       "/mathml/presentation-markup/fractions/frac-linethickness-002-ref.html",
-       "=="
-      ]
-     ],
-     {}
-    ]
-   ],
    "mathml/presentation-markup/fractions/frac-linethickness-003.html": [
     [
      "mathml/presentation-markup/fractions/frac-linethickness-003.html",
@@ -127842,6 +127830,9 @@
    "css/CSS2/floats-clear/support/test-tr.png": [
     []
    ],
+   "css/CSS2/floats/float-no-content-beside-001-ref.html": [
+    []
+   ],
    "css/CSS2/floats/float-nowrap-1-notref.html": [
     []
    ],
@@ -147240,9 +147231,6 @@
    "css/filter-effects/backdrop-filter-plus-filter-ref.html": [
     []
    ],
-   "css/filter-effects/backdrop-filter-plus-mask-ref.html": [
-    []
-   ],
    "css/filter-effects/backdrop-filter-update-ref.html": [
     []
    ],
@@ -161178,9 +161166,6 @@
    "mathml/presentation-markup/fractions/frac-linethickness-001-ref.html": [
     []
    ],
-   "mathml/presentation-markup/fractions/frac-linethickness-002-ref.html": [
-    []
-   ],
    "mathml/presentation-markup/fractions/frac-linethickness-003-notref.html": [
     []
    ],
@@ -175656,6 +175641,9 @@
    "webxr/resources/webxr_util.js": [
     []
    ],
+   "webxr/xrRigidTransform_constructor.https-expected.txt": [
+    []
+   ],
    "workers/META.yml": [
     []
    ],
@@ -248481,6 +248469,12 @@
      {}
     ]
    ],
+   "mathml/presentation-markup/fractions/frac-linethickness-002.html": [
+    [
+     "mathml/presentation-markup/fractions/frac-linethickness-002.html",
+     {}
+    ]
+   ],
    "mathml/presentation-markup/fractions/frac-parameters-1.html": [
     [
      "mathml/presentation-markup/fractions/frac-parameters-1.html",
@@ -277824,7 +277818,9 @@
    "service-workers/service-worker/update.https.html": [
     [
      "service-workers/service-worker/update.https.html",
-     {}
+     {
+      "timeout": "long"
+     }
     ]
    ],
    "service-workers/service-worker/waiting.https.html": [
@@ -328131,6 +328127,14 @@
    "ad9220b3a06085c458f7100c896100fb32f562e8",
    "testharness"
   ],
+  "css/CSS2/floats/float-no-content-beside-001-ref.html": [
+   "758f5875de59239162353df353054bb95eaa0d43",
+   "support"
+  ],
+  "css/CSS2/floats/float-no-content-beside-001.html": [
+   "f073453ecaf5a884ed11606d05b1b5066f2e50e3",
+   "reftest"
+  ],
   "css/CSS2/floats/float-nowrap-1-notref.html": [
    "540c8048af61a2c7804d99ff14c3a2bf1f87e6ad",
    "support"
@@ -399867,14 +399871,6 @@
    "3a2d8feaeefc82c20afd3de2c2cf9ce9bf6aed11",
    "reftest"
   ],
-  "css/filter-effects/backdrop-filter-plus-mask-ref.html": [
-   "15786e7ac873c6cbb4b5e6919f8f0d02bd522a79",
-   "support"
-  ],
-  "css/filter-effects/backdrop-filter-plus-mask.html": [
-   "e1390af38d47b1eb6eec2df85299e279030aafd8",
-   "reftest"
-  ],
   "css/filter-effects/backdrop-filter-reference-filter.html": [
    "6c61a9620aa82d23375fd31900d87bcfd873a5e1",
    "reftest"
@@ -416952,7 +416948,7 @@
    "support"
   ],
   "fetch/sec-metadata/resources/xslt-test.sub.xml": [
-   "4beb9af8d282f2672ab08c4c369d1fe0b061e80f",
+   "2d4dff5fc3efff235827cc8024a87710c1377972",
    "support"
   ],
   "fetch/sec-metadata/script.tentative.https.sub.html": [
@@ -416996,7 +416992,7 @@
    "support"
   ],
   "fetch/sec-metadata/xslt.tentative.https.sub.html": [
-   "0d429266288e8a77bcdc5076d3f248bf0ef509b5",
+   "c4e716a6664e217a62e011e5fe3c454ba8acda0e",
    "testharness"
   ],
   "fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html": [
@@ -440567,13 +440563,9 @@
    "4d6bda2c1670d9e615c7c3ffbc3891b90a22ecd4",
    "reftest"
   ],
-  "mathml/presentation-markup/fractions/frac-linethickness-002-ref.html": [
-   "bf1938bd282bf09ded7fe9e62b4e9ddd6a24f139",
-   "support"
-  ],
   "mathml/presentation-markup/fractions/frac-linethickness-002.html": [
-   "a128ab30c3abb150794c35b55183b374bf3598b5",
-   "reftest"
+   "c3b3d69e59ad20fac86889f6869cc487eda8ec8d",
+   "testharness"
   ],
   "mathml/presentation-markup/fractions/frac-linethickness-003-notref.html": [
    "934d66633397313175e98c4bda871bb0a95e5db0",
@@ -463852,7 +463844,7 @@
    "support"
   ],
   "resource-timing/resources/embed-navigate-back.html": [
-   "3c3def1d1a1d04279ad2bbceb220879398943681",
+   "ed14328190cddc9f449ba00f4cddf04cc7652b0f",
    "support"
   ],
   "resource-timing/resources/embed-navigate.html": [
@@ -463908,7 +463900,7 @@
    "support"
   ],
   "resource-timing/resources/iframe-navigate-back.html": [
-   "c5c1fc1ad4dfaaedc441ae8854079402e56d1106",
+   "ed2424cba3ab1069006997382bf062dd6e518e38",
    "support"
   ],
   "resource-timing/resources/iframe-navigate.html": [
@@ -463992,7 +463984,7 @@
    "support"
   ],
   "resource-timing/resources/object-navigate-back.html": [
-   "df32ab0c19956164582ac41265ecb33ad5bae87c",
+   "1023f984d5b9dbf4e50e8d0971565ebb0eef7fde",
    "support"
   ],
   "resource-timing/resources/object-navigate.html": [
@@ -467928,7 +467920,7 @@
    "support"
   ],
   "service-workers/service-worker/resources/update-worker.py": [
-   "bc9b32ad3e68870d9f540524e70cd7947346e5c8",
+   "2bc99a3d044b3d75a40ce60379dc145220d49eda",
    "support"
   ],
   "service-workers/service-worker/resources/update/update-after-oneday.https.html": [
@@ -468164,7 +468156,7 @@
    "testharness"
   ],
   "service-workers/service-worker/update.https.html": [
-   "6717d4d7ac289c8a18b1500e21795fd16c5321e7",
+   "6f7db0542b7898b1765a3d43ed1618cf1d1e45b9",
    "testharness"
   ],
   "service-workers/service-worker/waiting.https.html": [
@@ -488363,8 +488355,12 @@
    "f76c3c430e084709a73f6b419fba659667cd53a5",
    "testharness"
   ],
+  "webxr/xrRigidTransform_constructor.https-expected.txt": [
+   "b2e9421bda52b00e1db7563fe12617289086c58f",
+   "support"
+  ],
   "webxr/xrRigidTransform_constructor.https.html": [
-   "6a54fff808d93ac4423364b9c8b9d528a7e520c0",
+   "15a7f2b5771a78dba2036ec46933e826c9a54355",
    "testharness"
   ],
   "webxr/xrRigidTransform_inverse.https.html": [
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/floats/float-no-content-beside-001-ref.html b/third_party/blink/web_tests/external/wpt/css/CSS2/floats/float-no-content-beside-001-ref.html
new file mode 100644
index 0000000..758f587
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/floats/float-no-content-beside-001-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS Floats — reference</title>
+<link rel=author title="Jonathan Kew" href="jkew@mozilla.com">
+<style>
+p { width: 10em; border: solid aqua; }
+span { float: left; width: 5em; height: 5em; border: solid blue; }
+</style>
+
+<div>Test passes if all three examples render the same:</div>
+
+<p><span></span><br>Supercalifragilisticexpialidocious</p>
+
+<br style="clear:both">
+
+<p><span></span><br>Supercalifragilisticexpialidocious</p>
+
+<br style="clear:both">
+
+<p><span></span><br>Supercalifragilisticexpialidocious</p>
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/floats/float-no-content-beside-001.html b/third_party/blink/web_tests/external/wpt/css/CSS2/floats/float-no-content-beside-001.html
new file mode 100644
index 0000000..f073453e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/floats/float-no-content-beside-001.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>CSS Floats — narrow containing block</title>
+<meta name=assert content="If a shortened line box is too small to contain any content, then the line box is shifted downward">
+<link rel=help href="https://www.w3.org/TR/CSS2/visuren.html#floats">
+<link rel=match href="float-no-content-beside-001-ref.html">
+<link rel=author title="Jonathan Kew" href="jkew@mozilla.com">
+<style>
+p { width: 10em; border: solid aqua; }
+span { float: left; width: 5em; height: 5em; border: solid blue; }
+</style>
+
+<div>Test passes if all three examples render the same:</div>
+
+<p><span></span>Supercalifragilisticexpialidocious</p>
+
+<br style="clear:both">
+
+<p><span></span> Supercalifragilisticexpialidocious</p>
+
+<br style="clear:both">
+
+<p><span></span><br>Supercalifragilisticexpialidocious</p>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/xslt-test.sub.xml b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/xslt-test.sub.xml
index 4beb9af..2d4dff5 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/xslt-test.sub.xml
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/resources/xslt-test.sub.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<?xml-stylesheet href="https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=xslt-same-origin" type="text/xsl" ?>
-<?xml-stylesheet href="https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=xslt-same-site" type="text/xsl" ?>
-<?xml-stylesheet href="https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=xslt-cross-site" type="text/xsl" ?>
+<?xml-stylesheet href="https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=xslt-same-origin{{GET[token]}}" type="text/xsl" ?>
+<?xml-stylesheet href="https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=xslt-same-site{{GET[token]}}" type="text/xsl" ?>
+<?xml-stylesheet href="https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=xslt-cross-site{{GET[token]}}" type="text/xsl" ?>
 
 <!-- postMessage parent back when the resources are loaded -->
 <script xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html
index 0d42926..c4e716a 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html
@@ -4,30 +4,32 @@
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src=/fetch/sec-metadata/resources/helper.js></script>
+<script src=/common/utils.js></script>
 <script>
   // Open a window with XML document which loads resources via <?xml-stylesheet/> tag
-  let w = window.open("resources/xslt-test.sub.xml");
+  let nonce = token();
+  let w = window.open("resources/xslt-test.sub.xml?token=" + nonce);
   window.addEventListener('message', function(e) {
     if (e.source != w)
       return;
 
     promise_test(t => {
       let expected = {"dest":"xslt", "site":"same-origin", "user":"", "mode": "same-origin"};
-      return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-same-origin")
+      return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-same-origin" + nonce)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
     }, "Same-Origin xslt");
 
     promise_test(t => {
       let expected = {"dest":"xslt", "site":"same-site", "user":"", "mode": "no-cors"};
-      return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-same-site")
+      return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-same-site" + nonce)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
     }, "Same-site xslt");
 
     promise_test(t => {
       let expected = {"dest":"xslt", "site":"cross-site", "user":"", "mode": "no-cors"};
-      return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-cross-site")
+      return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-cross-site" + nonce)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
     }, "Cross-site xslt");
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations-expected.txt
index dd030ada..d60045fc 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/relevant-mutations-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 81 tests; 60 PASS, 21 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 81 tests; 64 PASS, 17 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS src set
 PASS src changed
 PASS src removed
@@ -70,12 +70,12 @@
 PASS inserted/removed children of img
 PASS picture is inserted; img has src
 PASS picture is inserted; img has srcset
-FAIL picture is inserted; img has previous sibling source assert_unreached: update the image data was run Reached unreachable code
-FAIL picture is inserted; img has following sibling source assert_unreached: update the image data was run Reached unreachable code
+PASS picture is inserted; img has previous sibling source
+PASS picture is inserted; img has following sibling source
 PASS picture is removed; img has src
 PASS picture is removed; img has srcset
-FAIL picture is removed; img has previous sibling source assert_unreached: update the image data was run Reached unreachable code
-FAIL picture is removed; img has following sibling source assert_unreached: update the image data was run Reached unreachable code
+PASS picture is removed; img has previous sibling source
+PASS picture is removed; img has following sibling source
 PASS parent is picture, following img inserted
 PASS parent is picture, following img removed
 PASS parent is picture, following img has src set
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002-ref.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002-ref.html
deleted file mode 100644
index bf1938bd..0000000
--- a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002-ref.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>fractions linethickness</title>
-    <style type="text/css">
-      @font-face {
-        font-family: TestFont;
-        src: url("/fonts/math/fraction-rulethickness10000.woff");
-      }
-      math {
-        /* FractionRuleThickness = 10000 * 1 / 1000 = 10px; */
-        font-family: "TestFont";
-        font-size: 1px;
-      }
-    </style>
-  </head>
-  <body>
-    <p>This test passes if you see fraction with a cyan denominator and
-      a blue numerator as tall as its black bar.</p>
-    <math>
-      <mfrac linethickness="0px">
-        <mspace width="20px" height="0px" style="background: blue"></mspace>
-        <mspace width="20px" height="10px" style="background: cyan"></mspace>
-      </mfrac>
-    </math>
-    <math>
-      <mfrac linethickness="50px">
-        <mspace width="20px" height="50px" style="background: blue"></mspace>
-        <mspace width="20px" height="10px" style="background: cyan"></mspace>
-      </mfrac>
-    </math>
-    <math>
-      <mfrac>
-        <mspace width="20px" height="70px" style="background: blue"></mspace>
-        <mspace width="20px" height="10px" style="background: cyan"></mspace>
-      </mfrac>
-    </math>
-  </body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002.html
index a128ab30..c3b3d69 100644
--- a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002.html
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/fractions/frac-linethickness-002.html
@@ -4,39 +4,82 @@
     <meta charset="utf-8">
     <title>fractions linethickness</title>
     <link rel="help" href="https://mathml-refresh.github.io/mathml-core/#fractions-mfrac">
-    <meta name="assert" content="Verifies fraction with negative, percent and named space linethickness values.">
-    <link rel="match" href="frac-linethickness-002-ref.html">
+    <meta name="assert" content="Verifies fraction with positive, negative, percent and named space linethickness values.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
     <style type="text/css">
       @font-face {
         font-family: TestFont;
         src: url("/fonts/math/fraction-rulethickness10000.woff");
       }
       math {
-        /* FractionRuleThickness = 10000 * 1 / 1000 = 10px; */
+        /* FractionRuleThickness = 10000 * 10 / 1000 = 100px; */
         font-family: "TestFont";
-        font-size: 1px;
+        font-size: 10px;
       }
     </style>
+    <script>
+      function LineThickness(aId) {
+        var mfrac = document.getElementById(aId);
+        var numBox = mfrac.firstElementChild.getBoundingClientRect();
+        var denumBox = mfrac.lastElementChild.getBoundingClientRect();
+        return denumBox.top - numBox.bottom;
+      }
+
+      setup({ explicit_done: true });
+      window.addEventListener("load", function() {
+        // Delay the check to workaround WebKit's bug https://webkit.org/b/174030.
+        requestAnimationFrame(() => { document.fonts.ready.then(runTests); });
+      });
+
+      function runTests() {
+        var defaultRuleThickness = 100;
+        var epsilon = 2;
+
+        test(function() {
+          assert_approx_equals(LineThickness("positive"),  5.67 * 10, epsilon);
+        }, "Positive");
+
+        test(function() {
+          /* Negative values are treated as 0 */
+          assert_approx_equals(LineThickness("negative"),  0, epsilon);
+        }, "Negative");
+
+        test(function() {
+          assert_approx_equals(LineThickness("percent"), defaultRuleThickness * 234 / 100, epsilon);
+        }, "Percentage");
+
+        test(function() {
+          /* Namedspace values are invalid in MathML Core. */
+          assert_approx_equals(LineThickness("namedspace"), defaultRuleThickness, epsilon);
+        }, "Named space");
+
+        done();
+      }
+    </script>
   </head>
   <body>
-    <p>This test passes if you see fraction with a cyan denominator and
-      a blue numerator as tall as its black bar.</p>
     <math>
-      <mfrac linethickness="-1.23em">
-        <mspace width="20px" height="0px" style="background: blue"></mspace>
+      <mfrac id="positive" linethickness="5.67em">
+        <mspace width="20px" height="10px" style="background: blue"></mspace>
         <mspace width="20px" height="10px" style="background: cyan"></mspace>
       </mfrac>
     </math>
     <math>
-      <mfrac linethickness="500%">
-        <mspace width="20px" height="50px" style="background: blue"></mspace>
+      <mfrac id="negative" linethickness="-1.23em">
+        <mspace width="20px" height="10px" style="background: blue"></mspace>
         <mspace width="20px" height="10px" style="background: cyan"></mspace>
       </mfrac>
     </math>
     <math>
-      <!-- Namedspace values are invalid in MathML Core. -->
-      <mfrac linethickness="veryverythickmathspace">
-        <mspace width="20px" height="70px" style="background: blue"></mspace>
+      <mfrac id="percent" linethickness="234%">
+        <mspace width="20px" height="10px" style="background: blue"></mspace>
+        <mspace width="20px" height="10px" style="background: cyan"></mspace>
+      </mfrac>
+    </math>
+    <math>
+      <mfrac id="namedspace" linethickness="veryverythickmathspace">
+        <mspace width="20px" height="10px" style="background: blue"></mspace>
         <mspace width="20px" height="10px" style="background: cyan"></mspace>
       </mfrac>
     </math>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/embed-navigate-back.html b/third_party/blink/web_tests/external/wpt/resource-timing/resources/embed-navigate-back.html
index 3c3def1d..ed14328 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resources/embed-navigate-back.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/embed-navigate-back.html
@@ -3,6 +3,7 @@
 <head>
 <meta charset="utf-8" />
 <title>Resource Timing embed navigate - back button navigation</title>
+<body onunload="/*disable bfcache*/"></body>
 <script src="/common/get-host-info.sub.js"></script>
 <script src="nested-contexts.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/iframe-navigate-back.html b/third_party/blink/web_tests/external/wpt/resource-timing/resources/iframe-navigate-back.html
index c5c1fc1..ed2424cb 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resources/iframe-navigate-back.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/iframe-navigate-back.html
@@ -3,6 +3,7 @@
 <head>
 <meta charset="utf-8" />
 <title>Resource Timing iframe navigate - back button navigation</title>
+<body onunload="/*disable bfcache*/"></body>
 <script src="/common/get-host-info.sub.js"></script>
 <script src="nested-contexts.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/object-navigate-back.html b/third_party/blink/web_tests/external/wpt/resource-timing/resources/object-navigate-back.html
index df32ab0..1023f98 100644
--- a/third_party/blink/web_tests/external/wpt/resource-timing/resources/object-navigate-back.html
+++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/object-navigate-back.html
@@ -3,6 +3,7 @@
 <head>
 <meta charset="utf-8" />
 <title>Resource Timing object navigate - back button navigation</title>
+<body onunload="/*disable bfcache*/"></body>
 <script src="/common/get-host-info.sub.js"></script>
 <script src="nested-contexts.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html
index e52adfa..07ea4943 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>Tests for importScripts: redirect</title>
+<script src="/common/utils.js"></script>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
@@ -20,14 +21,35 @@
     await service_worker_unregister(t, scope);
     let reg = await navigator.serviceWorker.register(
       'resources/import-scripts-redirect-worker.js', { scope: scope });
-    assert_not_equals(reg.installing, null, 'worker is installing');
+    assert_not_equals(reg.installing, null, 'before update');
     await wait_for_state(t, reg.installing, 'activated');
     await Promise.all([
       wait_for_update(t, reg),
       reg.update()
     ]);
-    assert_not_equals(reg.installing, null, 'worker is installing');
+    assert_not_equals(reg.installing, null, 'after update');
     await reg.unregister();
-  }, 'importScripts() supports redirects and can be updated');
+  },
+  "an imported script redirects, and the body changes during the update check");
+
+promise_test(async t => {
+    const key = token();
+    const scope = 'resources/import-scripts-redirect';
+    await service_worker_unregister(t, scope);
+    let reg = await navigator.serviceWorker.register(
+      `resources/import-scripts-redirect-on-second-time-worker.js?Key=${key}`,
+      { scope });
+    t.add_cleanup(() => reg.unregister());
+
+    assert_not_equals(reg.installing, null, 'before update');
+    await wait_for_state(t, reg.installing, 'activated');
+    await Promise.all([
+      wait_for_update(t, reg),
+      reg.update()
+    ]);
+    assert_not_equals(reg.installing, null, 'after update');
+  },
+  "an imported script doesn't redirect initially, then redirects during " +
+  "the update check and the body changes");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-redirect-on-second-time-worker.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-redirect-on-second-time-worker.js
new file mode 100644
index 0000000..f612ab8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-redirect-on-second-time-worker.js
@@ -0,0 +1,7 @@
+// This worker imports a script that returns 200 on the first request and a
+// redirect on the second request. The resulting body also changes each time it
+// is requested.
+const params = new URLSearchParams(location.search);
+const key = params.get('Key');
+importScripts(`update-worker.py?Key=${key}&Mode=redirect&` +
+              `Redirect=update-worker.py?Key=${key}%26Mode=normal`);
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/update-worker.py b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/update-worker.py
index bc9b32a..446d547c 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/update-worker.py
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/update-worker.py
@@ -1,46 +1,54 @@
-import time
+import urllib
+
+def redirect_response(request, response, visited_count):
+  # |visited_count| is used as a unique id to differentiate responses
+  # every time.
+  location = 'empty.js'
+  if 'Redirect' in request.GET:
+      location = urllib.unquote(request.GET['Redirect'])
+  return (301,
+  [
+    ('Cache-Control', 'no-cache, must-revalidate'),
+    ('Pragma', 'no-cache'),
+    ('Content-Type', 'application/javascript'),
+    ('Location', location),
+  ],
+  '/* %s */' % str(visited_count))
+
+def ok_response(request, response, visited_count,
+                extra_body='', mime_type='application/javascript'):
+  # |visited_count| is used as a unique id to differentiate responses
+  # every time.
+  return (
+    [
+      ('Cache-Control', 'no-cache, must-revalidate'),
+      ('Pragma', 'no-cache'),
+      ('Content-Type', mime_type)
+    ],
+    '/* %s */ %s' % (str(visited_count), extra_body))
 
 def main(request, response):
-    # Set mode to 'init' for initial fetch.
-    mode = 'init'
-    if 'mode' in request.cookies:
-        mode = request.cookies['mode'].value
+  key = request.GET["Key"]
+  mode = request.GET["Mode"]
 
-    # no-cache itself to ensure the user agent finds a new version for each update.
-    headers = [('Cache-Control', 'no-cache, must-revalidate'),
-               ('Pragma', 'no-cache')]
+  visited_count = request.server.stash.take(key)
+  if visited_count is None:
+    visited_count = 0
 
-    content_type = ''
-    extra_body = ''
+  # Keep how many times the test requested this resource.
+  visited_count += 1
+  request.server.stash.put(key, visited_count)
 
-    if mode == 'init':
-        # Set a normal mimetype.
-        # Set cookie value to 'normal' so the next fetch will work in 'normal' mode.
-        content_type = 'application/javascript'
-        response.set_cookie('mode', 'normal')
-    elif mode == 'normal':
-        # Set a normal mimetype.
-        # Set cookie value to 'error' so the next fetch will work in 'error' mode.
-        content_type = 'application/javascript'
-        response.set_cookie('mode', 'error');
-    elif mode == 'error':
-        # Set a disallowed mimetype.
-        # Set cookie value to 'syntax-error' so the next fetch will work in 'syntax-error' mode.
-        content_type = 'text/html'
-        response.set_cookie('mode', 'syntax-error');
-    elif mode == 'syntax-error':
-        # Set cookie value to 'throw-install' so the next fetch will work in 'throw-install' mode.
-        content_type = 'application/javascript'
-        response.set_cookie('mode', 'throw-install');
-        extra_body = 'badsyntax(isbad;'
-    elif mode == 'throw-install':
-        # Unset and delete cookie to clean up the test setting.
-        content_type = 'application/javascript'
-        response.delete_cookie('mode')
-        extra_body = "addEventListener('install', function(e) { throw new Error('boom'); });"
-
-    headers.append(('Content-Type', content_type))
-    # Return a different script for each access.  Use .time() and .clock() for
-    # best time resolution across different platforms.
-    return headers, '/* %s %s */ %s' % (time.time(), time.clock(), extra_body)
-
+  # Return a response based on |mode| only when it's the second time (== update).
+  if visited_count == 2:
+    if mode == 'normal':
+      return ok_response(request, response, visited_count)
+    if mode == 'bad_mime_type':
+      return ok_response(request, response, visited_count, mime_type='text/html')
+    if mode == 'redirect':
+      return redirect_response(request, response, visited_count)
+    if mode == 'syntax_error':
+      return ok_response(request, response, visited_count, extra_body='badsyntax(isbad;')
+    if mode == 'throw_install':
+      return ok_response(request, response, visited_count, extra_body="addEventListener('install', function(e) { throw new Error('boom'); });")
+  return ok_response(request, response, visited_count)
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/update.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/update.https-expected.txt
new file mode 100644
index 0000000..2a9900e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/update.https-expected.txt
@@ -0,0 +1,10 @@
+This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = assert_throws: function "function() { throw e }" threw object "SecurityError: Failed to update a ServiceWorker: The script resource is behind a redirect, which is disallowed." ("SecurityError") expected object "TypeError" ("TypeError")
+PASS update() should succeed when new script is available.
+PASS update() should fail when mime type is invalid.
+PASS update() should fail when a response for the main script is redirect.
+PASS update() should fail when a new script contains a syntax error.
+PASS update() should resolve when the install event throws.
+PASS update() should fail when the pending uninstall flag is set.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/update.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/update.https.html
index 6717d4d..7232419 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/update.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/update.https.html
@@ -1,5 +1,7 @@
 <!DOCTYPE html>
 <title>Service Worker: Registration update()</title>
+<meta name="timeout" content="long">
+<script src="/common/utils.js"></script>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/testharness-helpers.js"></script>
@@ -7,115 +9,116 @@
 <script>
 'use strict';
 
-promise_test(function(t) {
-    var scope = 'resources/simple.txt';
-    var worker_url = 'resources/update-worker.py';
-    var expected_url = normalizeURL(worker_url);
-    var registration;
+const SCOPE = 'resources/simple.txt';
+const WORKER_URL_BASE = 'resources/update-worker.py';
 
-    return service_worker_unregister_and_register(t, worker_url, scope)
-      .then(function(r) {
-          registration = r;
-          return wait_for_state(t, registration.installing, 'activated');
-        })
-      .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null in the initial state.');
-          assert_equals(registration.waiting, null,
-                        'waiting should be null in the initial state.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should exist in the initial state.');
-          // A new worker (generated by update-worker.py) should be found.
-          // The returned promise should resolve when a new worker script is
-          // fetched and starts installing.
-          return Promise.all([registration.update(),
-                              wait_for_update(t, registration)]);
-        })
-      .then(function() {
-          assert_equals(registration.installing.scriptURL, expected_url,
-                        'new installing should be set after update resolves.');
-          assert_equals(registration.waiting, null,
-                        'waiting should still be null after update resolves.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update found.');
-          return wait_for_state(t, registration.installing, 'installed');
-        })
-      .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null after installing.');
-          assert_equals(registration.waiting.scriptURL, expected_url,
-                        'waiting should be set after installing.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after installing.');
-          return wait_for_state(t, registration.waiting, 'activated');
-        })
-      .then(function() {
-          assert_equals(registration.installing, null,
-                        'installing should be null after activated.');
-          assert_equals(registration.waiting, null,
-                        'waiting should be null after activated.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'new worker should be promoted to active.');
-        })
-      .then(function() {
-          // A new worker(generated by update-worker.py) should be found.
-          // The returned promise should reject as update-worker.py sets the
-          // mimetype to a disallowed value for this attempt.
-          return registration.update();
-        })
-      .then(
-        function() { assert_unreached("update() should reject."); },
-        function(e) {
-          assert_throws('SecurityError', function() { throw e; },
-                        'Using a disallowed mimetype should make update() ' +
-                        'promise reject with a SecurityError.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update failure.');
+async function prepare_ready_registration(t, mode) {
+  const key = token();
+  const worker_url = `${WORKER_URL_BASE}?Key=${key}&Mode=${mode}`;
+  const expected_url = normalizeURL(worker_url);
+  const registration = await service_worker_unregister_and_register(
+      t, worker_url, SCOPE);
+  await wait_for_state(t, registration.installing, 'activated');
+  assert_equals(registration.installing, null,
+                'prepare_ready: installing');
+  assert_equals(registration.waiting, null,
+                'prepare_ready: waiting');
+  assert_equals(registration.active.scriptURL, expected_url,
+                'prepare_ready: active');
+  return [registration, expected_url];
+}
 
-          // A new worker(generated by update-worker.py) should be found.
-          // The returned promise should reject as update-worker.py returns
-          // a worker script with a syntax error.
-          return registration.update();
-        })
-      .then(
-        function() { assert_unreached("update() should reject."); },
-        function(e) {
-          assert_throws({name: 'TypeError'}, function () { throw e; },
-                        'A script syntax error should make update() ' +
-                        'promise reject with a TypeError.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update failure.');
+function assert_installing_and_active(registration, expected_url) {
+  assert_equals(registration.installing.scriptURL, expected_url,
+                'assert_installing_and_active: installing');
+  assert_equals(registration.waiting, null,
+                'assert_installing_and_active: waiting');
+  assert_equals(registration.active.scriptURL, expected_url,
+                'assert_installing_and_active: active');
+}
 
-          // A new worker(generated by update-worker.py) should be found.
-          // The returned promise should not reject, even though
-          // update-worker.py returns a worker script that throws in the
-          // install event handler.
-          return Promise.all([registration.update(),
-                              wait_for_update(t, registration)]);
-        })
-      .then(function() {
-          assert_equals(registration.installing.scriptURL, expected_url,
-                        'new installing should be set after update resolves.');
-          assert_equals(registration.waiting, null,
-                        'waiting should be null after activated.');
-          assert_equals(registration.active.scriptURL, expected_url,
-                        'active should still exist after update found.');
+function assert_waiting_and_active(registration, expected_url) {
+  assert_equals(registration.installing, null,
+                'assert_waiting_and_active: installing');
+  assert_equals(registration.waiting.scriptURL, expected_url,
+                'assert_waiting_and_active: waiting');
+  assert_equals(registration.active.scriptURL, expected_url,
+                'assert_waiting_and_active: active');
+}
 
-          // We need to hold a client alive so that unregister() below doesn't
-          // remove the registration before update() has had a chance to look
-          // at the pending uninstall flag.
-          return with_iframe(scope);
-        })
-      .then(function(frame) {
-          t.add_cleanup(function() {
-              frame.remove();
-            });
+function assert_active_only(registration, expected_url) {
+  assert_equals(registration.installing, null,
+                'assert_active_only: installing');
+  assert_equals(registration.waiting, null,
+                'assert_active_only: waiting');
+  assert_equals(registration.active.scriptURL, expected_url,
+                'assert_active_only: active');
+}
 
-          return assert_promise_rejects(
-              Promise.all([registration.unregister(), registration.update()]),
-              new TypeError(),
-              'Calling update() while the uninstalling flag is set ' +
-              'should return a promise that rejects with a TypeError.');
-        });
-  }, 'Update a registration.');
+promise_test(async t => {
+  const [registration, expected_url] =
+      await prepare_ready_registration(t, 'normal');
+  t.add_cleanup(() => registration.unregister());
+
+  await Promise.all([registration.update(), wait_for_update(t, registration)]);
+  assert_installing_and_active(registration, expected_url);
+
+  await wait_for_state(t, registration.installing, 'installed');
+  assert_waiting_and_active(registration, expected_url);
+
+  await wait_for_state(t, registration.waiting, 'activated');
+  assert_active_only(registration, expected_url);
+}, 'update() should succeed when new script is available.');
+
+promise_test(async t => {
+  const [registration, expected_url] =
+      await prepare_ready_registration(t, 'bad_mime_type');
+  t.add_cleanup(() => registration.unregister());
+
+  promise_rejects(t, 'SecurityError', registration.update());
+  assert_active_only(registration, expected_url);
+}, 'update() should fail when mime type is invalid.');
+
+promise_test(async t => {
+  const [registration, expected_url] =
+      await prepare_ready_registration(t, 'redirect');
+  t.add_cleanup(() => registration.unregister());
+
+  promise_rejects(t, new TypeError(), registration.update())
+  assert_active_only(registration, expected_url);
+}, 'update() should fail when a response for the main script is redirect.');
+
+promise_test(async t => {
+  const [registration, expected_url] =
+      await prepare_ready_registration(t, 'syntax_error');
+  t.add_cleanup(() => registration.unregister());
+
+  promise_rejects(t, new TypeError(), registration.update());
+  assert_active_only(registration, expected_url);
+}, 'update() should fail when a new script contains a syntax error.');
+
+promise_test(async t => {
+  const [registration, expected_url] =
+      await prepare_ready_registration(t, 'throw_install');
+  t.add_cleanup(() => registration.unregister());
+
+  await Promise.all([registration.update(), wait_for_update(t, registration)]);
+  assert_installing_and_active(registration, expected_url);
+}, 'update() should resolve when the install event throws.');
+
+promise_test(async t => {
+  const [registration, expected_url] =
+      await prepare_ready_registration(t, 'normal');
+  t.add_cleanup(() => registration.unregister());
+
+  // We need to hold a client alive so that unregister() below doesn't remove
+  // the registration before update() has had a chance to look at the pending
+  // uninstall flag.
+  const frame = await with_iframe(SCOPE);
+  t.add_cleanup(() => frame.remove());
+
+  promise_rejects(
+      t, new TypeError(),
+      Promise.all([registration.unregister(), registration.update()]));
+}, 'update() should fail when the pending uninstall flag is set.')
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrRigidTransform_constructor.https-expected.txt b/third_party/blink/web_tests/external/wpt/webxr/xrRigidTransform_constructor.https-expected.txt
new file mode 100644
index 0000000..b2e9421b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrRigidTransform_constructor.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL XRRigidTransform constructor works assert_throws: Constructor should throw TypeError for non-1 position w values function "() => new XRRigidTransform(
+      coordDict([1.0, 2.0, 3.0, 0.5]),
+      coordDict([1.1, 2.1, 3.1, 1.0])
+  )" did not throw
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrRigidTransform_constructor.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrRigidTransform_constructor.https.html
index 6a54fff8..15a7f2b 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrRigidTransform_constructor.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrRigidTransform_constructor.https.html
@@ -41,22 +41,22 @@
         (point.w * point.w));
   };
 
-  let checkDOMPoint = function(point, x, y, z, w) {
+  let checkDOMPoint = function(point, x, y, z, w, desc) {
     t.step(() => {
-      assert_approx_equals(point.x, x, FLOAT_EPSILON);
-      assert_approx_equals(point.y, y, FLOAT_EPSILON);
-      assert_approx_equals(point.z, z, FLOAT_EPSILON);
-      assert_approx_equals(point.w, w, FLOAT_EPSILON);
+      assert_approx_equals(point.x, x, FLOAT_EPSILON, `${desc}: x value`);
+      assert_approx_equals(point.y, y, FLOAT_EPSILON, `${desc}: y value`);
+      assert_approx_equals(point.z, z, FLOAT_EPSILON, `${desc}: z value`);
+      assert_approx_equals(point.w, w, FLOAT_EPSILON, `${desc}: w value`);
     });
   };
 
-  let checkTransform = function(transformObj) {
+  let checkTransform = function(transformObj, desc) {
     t.step(() => {
-      assert_not_equals(transformObj, null);
-      assert_not_equals(transformObj.position, null);
-      assert_not_equals(transformObj.orientation, null);
-      assert_not_equals(transformObj.matrix, null);
-      assert_equals(transformObj.matrix.length, 16);
+      assert_not_equals(transformObj, null, `${desc}: exists`);
+      assert_not_equals(transformObj.position, null, `${desc}: position exists`);
+      assert_not_equals(transformObj.orientation, null, `${desc}: orientation exists`);
+      assert_not_equals(transformObj.matrix, null, `${desc}: matrix exists`);
+      assert_equals(transformObj.matrix.length, 16, `${desc}: matrix of correct length`);
     });
   };
 
@@ -65,43 +65,46 @@
   let transform = new XRRigidTransform(
       createDOMPoint([1.0, 2.0, 3.0]),
       createDOMPoint([1.1, 2.1, 3.1, 1.0]));
-  checkTransform(transform);
-  checkDOMPoint(transform.position, 1.0, 2.0, 3.0, 1.0);
-  assert_approx_equals(quaternionLength(transform.orientation), 1.0, FLOAT_EPSILON);
+  checkTransform(transform, "Arbitrary transform");
+  checkDOMPoint(transform.position, 1.0, 2.0, 3.0, 1.0, "Arbitrary transform position");
+  assert_approx_equals(quaternionLength(transform.orientation), 1.0, FLOAT_EPSILON,
+                       "Arbitrary transform is normalized");
 
   // test creating identity transform
   let identity = new XRRigidTransform();
-  checkTransform(identity);
-  checkDOMPoint(identity.position, 0.0, 0.0, 0.0, 1.0);
-  checkDOMPoint(identity.orientation, 0.0, 0.0, 0.0, 1.0);
-
-  // test creating transform with quaternion of length 0
-  // constructor should not crash
-  let zeroLength = new XRRigidTransform(
-      createDOMPoint([1.0, 2.0, 3.0]),
-      createDOMPoint([0.0, 0.0, 0.0, 0.0]));
-  checkTransform(zeroLength);
+  checkTransform(identity, "Identity transform");
+  checkDOMPoint(identity.position, 0.0, 0.0, 0.0, 1.0, "Identity transform position");
+  checkDOMPoint(identity.orientation, 0.0, 0.0, 0.0, 1.0, "Identity transform orientation");
 
   // create transform with only position specified
   transform = new XRRigidTransform(createDOMPoint([1.0, 2.0, 3.0]));
-  checkTransform(transform);
+  checkTransform(transform, "Position-only");
 
   // create transform with only orientation specified
   transform = new XRRigidTransform(undefined, createDOMPoint([1.1, 2.1, 3.1, 1.0]));
-  checkTransform(transform);
+  checkTransform(transform, "orientation-only");
 
   // create transform with DOMPointReadOnly
   transform = new XRRigidTransform(
       createDOMPointReadOnly([1.0, 2.0, 3.0]),
       createDOMPointReadOnly([1.1, 2.1, 3.1, 1.0]));
-  checkTransform(transform);
+  checkTransform(transform, "Created with DOMPointReadOnly");
 
   // create transform with dictionary
   transform = new XRRigidTransform(
       coordDict([1.0, 2.0, 3.0]),
       coordDict([1.1, 2.1, 3.1, 1.0]));
-  checkTransform(transform);
+  checkTransform(transform, "Created with dict");
 
+  assert_throws(new TypeError(), () => new XRRigidTransform(
+      coordDict([1.0, 2.0, 3.0, 0.5]),
+      coordDict([1.1, 2.1, 3.1, 1.0])
+  ), "Constructor should throw TypeError for non-1 position w values");
+
+  assert_throws("InvalidStateError", () => new XRRigidTransform(
+      coordDict([1.0, 2.0, 3.0, 1.0]),
+      coordDict([0, 0, 0, 0])
+  ), "Constructor should throw InvalidStateError for non-normalizeable orientation values");
   resolve();
 });
 
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 6e0be3f..2595679 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -147,6 +147,10 @@
     method constructor
     method delete
     method getDescriptions
+interface ContentIndexEvent : ExtendableEvent
+    attribute @@toStringTag
+    getter id
+    method constructor
 interface CookieStore : EventTarget
     attribute @@toStringTag
     method constructor
@@ -3677,6 +3681,7 @@
     getter onbackgroundfetchfail
     getter onbackgroundfetchsuccess
     getter oncanmakepayment
+    getter oncontentdelete
     getter oncookiechange
     getter onfetch
     getter oninstall
@@ -3700,6 +3705,7 @@
     setter onbackgroundfetchfail
     setter onbackgroundfetchsuccess
     setter oncanmakepayment
+    setter oncontentdelete
     setter oncookiechange
     setter onfetch
     setter oninstall
diff --git a/third_party/blink/web_tests/http/tests/websocket/send-receive-arraybuffer-1MB.html b/third_party/blink/web_tests/http/tests/websocket/send-receive-arraybuffer-1MB.html
new file mode 100644
index 0000000..e8c2dd3
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/websocket/send-receive-arraybuffer-1MB.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+function randomValuesArrayBuffer(length)
+{
+  const array = new Uint8Array(length);
+  // crypto.getRandomValues has a quota. See
+  // https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues.
+  const cryptoQuota = 65535;
+  let index = 0;
+  const buffer = array.buffer;
+  while(index < buffer.byteLength) {
+    const bufferView = array.subarray(index, index + cryptoQuota);
+    window.crypto.getRandomValues(bufferView);
+    index += cryptoQuota;
+  }
+  return buffer;
+}
+
+function runTest(buffer, description) {
+  async_test((test) => {
+    const ws = new WebSocket("ws://127.0.0.1:8880/echo");
+    ws.binaryType = "arraybuffer";
+    let message = undefined;
+    ws.onopen = test.step_func(() => {
+      ws.send(buffer);
+    });
+    ws.onmessage = test.step_func((event) => {
+      if (event.data === "Goodbye")
+        return;
+      assert_equals(message, undefined);
+      message = event.data;
+      // Sending "Goodbye" lets the server close the connection.
+      ws.send("Goodbye");
+    });
+    ws.onclose = test.step_func_done((e) => {
+      assert_true(e.wasClean);
+      assert_equals(message.byteLength, buffer.byteLength);
+      // Checking bytes in JS is slow with MSAN, so we stringify arrays and rely
+      // on native String comparison instead.
+      const actualString = new Uint8Array(message).toString();
+      const expectedString = new Uint8Array(buffer).toString();
+      assert_equals(actualString, expectedString);
+    });
+  }, description);
+}
+
+runTest(new ArrayBuffer(0), "empty array buffer");
+runTest(new TextEncoder().encode('Hello, world!'), "text array buffer");
+runTest(randomValuesArrayBuffer(256), "random 256 values");
+runTest(randomValuesArrayBuffer(2560), "random 2560 values");
+runTest(randomValuesArrayBuffer(25600), "random 25600 values");
+runTest(randomValuesArrayBuffer(256000), "random 256000 values");
+runTest(randomValuesArrayBuffer(1000000), "random 1000000 values");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/svg/animations/resources/SVGAnimationTestCase-testharness.js b/third_party/blink/web_tests/svg/animations/resources/SVGAnimationTestCase-testharness.js
new file mode 100644
index 0000000..9cf1856a
--- /dev/null
+++ b/third_party/blink/web_tests/svg/animations/resources/SVGAnimationTestCase-testharness.js
@@ -0,0 +1,69 @@
+// Inspired by Layoutests/animations/animation-test-helpers.js
+
+function moveAnimationTimelineAndSample(index) {
+    var animationId = expectedResults[index][0];
+    var time = expectedResults[index][1];
+    var sampleCallback = expectedResults[index][2];
+    var animation = rootSVGElement.ownerDocument.getElementById(animationId);
+
+    // If we want to sample the animation end, add a small delta, to reliable point past the end of the animation.
+    newTime = time;
+    newTime += animation.getStartTime();
+
+    // The sample time is relative to the start time of the animation, take that into account.
+    rootSVGElement.setCurrentTime(newTime);
+    sampleCallback();
+}
+
+var currentTest = 0;
+var expectedResults;
+
+function sampleAnimation(t) {
+    if (currentTest == expectedResults.length) {
+        t.done();
+        return;
+    }
+
+    moveAnimationTimelineAndSample(currentTest);
+    ++currentTest;
+
+    setTimeout(t.step_func(function () { sampleAnimation(t); }), 0);
+}
+
+function runAnimationTest(t, expected) {
+    if (!expected)
+        throw("Expected results are missing!");
+    if (currentTest > 0)
+        throw("Not allowed to call runAnimationTest() twice");
+
+    expectedResults = expected;
+    testCount = expectedResults.length;
+    currentTest = 0;
+
+    // Immediately sample, if the first time is zero.
+    if (expectedResults[0][1] == 0) {
+        expectedResults[0][2]();
+        ++currentTest;
+    }
+
+  setTimeout(t.step_func(function () { sampleAnimation(this); }), 50);
+}
+
+function smil_async_test(func) {
+  async_test(t => {
+    window.onload = t.step_func(function () {
+      // Pause animations, we'll drive them manually.
+      // This also ensures that the timeline is paused before
+      // it starts. This should make the instance time of the below
+      // 'click' (for instance) 0, and hence minimize rounding
+      // errors for the addition in moveAnimationTimelineAndSample.
+      rootSVGElement.pauseAnimations();
+
+      // If eg. an animation is running with begin="0s", and
+      // we want to sample the first time, before the animation
+      // starts, then we can't delay the testing by using an
+      // onclick event, as the animation would be past start time.
+      func(t);
+    });
+  });
+}
diff --git a/third_party/blink/web_tests/svg/animations/script-tests/svgPreserveAspectRatio-animation-1.js b/third_party/blink/web_tests/svg/animations/script-tests/svgPreserveAspectRatio-animation-1.js
deleted file mode 100644
index 02adaefd..0000000
--- a/third_party/blink/web_tests/svg/animations/script-tests/svgPreserveAspectRatio-animation-1.js
+++ /dev/null
@@ -1,62 +0,0 @@
-description("Test 'to' animation of SVGPreserveAspectRatio.");
-createSVGTestCase();
-
-// Setup test document
-rootSVGElement.setAttribute("id", "root");
-rootSVGElement.setAttribute("preserveAspectRatio", "xMaxYMin meet");
-
-var rect = createSVGElement("rect");
-rect.setAttribute("id", "rect");
-rect.setAttribute("width", "100");
-rect.setAttribute("height", "100");
-rect.setAttribute("fill", "green");
-rect.setAttribute("onclick", "executeTest()");
-rootSVGElement.appendChild(rect);
-
-var animate = createSVGElement("animate");
-animate.setAttribute("id", "animation");
-animate.setAttribute("attributeName", "preserveAspectRatio");
-animate.setAttribute("begin", "rect.click");
-animate.setAttribute("dur", "4s");
-animate.setAttribute("from", "xMinYMin meet");
-animate.setAttribute("to", "xMaxYMid slice");
-rootSVGElement.appendChild(animate);
-
-// Setup animation test
-function sample1() {
-    shouldBe("rootSVGElement.preserveAspectRatio.animVal.align", "SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN");
-    shouldBe("rootSVGElement.preserveAspectRatio.animVal.meetOrSlice", "SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET");
-
-    shouldBe("rootSVGElement.preserveAspectRatio.baseVal.align", "SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN");
-    shouldBe("rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice", "SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET");
-}
-
-function sample2() {
-    shouldBe("rootSVGElement.preserveAspectRatio.animVal.align", "SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN");
-    shouldBe("rootSVGElement.preserveAspectRatio.animVal.meetOrSlice", "SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET");
-
-    shouldBe("rootSVGElement.preserveAspectRatio.baseVal.align", "SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN");
-    shouldBe("rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice", "SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET");
-}
-
-function sample3() {
-    shouldBe("rootSVGElement.preserveAspectRatio.animVal.align", "SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID");
-    shouldBe("rootSVGElement.preserveAspectRatio.animVal.meetOrSlice", "SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE");
-
-    shouldBe("rootSVGElement.preserveAspectRatio.baseVal.align", "SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN");
-    shouldBe("rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice", "SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET");
-}
-
-function executeTest() {
-    const expectedValues = [
-        // [animationId, time, sampleCallback]
-        ["animation", 0.0,   sample1],
-        ["animation", 2.0,   sample2],
-        ["animation", 3.999, sample3],
-        ["animation", 4.001, sample1]
-    ];
-
-    runAnimationTest(expectedValues);
-}
-
-var successfullyParsed = true;
diff --git a/third_party/blink/web_tests/svg/animations/svgPreserveAspectRatio-animation-1-expected.txt b/third_party/blink/web_tests/svg/animations/svgPreserveAspectRatio-animation-1-expected.txt
deleted file mode 100644
index 9a6161d..0000000
--- a/third_party/blink/web_tests/svg/animations/svgPreserveAspectRatio-animation-1-expected.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-SVG 1.1 dynamic animation tests
-
-Test 'to' animation of SVGPreserveAspectRatio.
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-PASS rootSVGElement.preserveAspectRatio.animVal.align is SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN
-PASS rootSVGElement.preserveAspectRatio.animVal.meetOrSlice is SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET
-PASS rootSVGElement.preserveAspectRatio.baseVal.align is SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN
-PASS rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice is SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET
-PASS rootSVGElement.preserveAspectRatio.animVal.align is SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN
-PASS rootSVGElement.preserveAspectRatio.animVal.meetOrSlice is SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET
-PASS rootSVGElement.preserveAspectRatio.baseVal.align is SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN
-PASS rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice is SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET
-PASS rootSVGElement.preserveAspectRatio.animVal.align is SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID
-PASS rootSVGElement.preserveAspectRatio.animVal.meetOrSlice is SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE
-PASS rootSVGElement.preserveAspectRatio.baseVal.align is SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN
-PASS rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice is SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET
-PASS rootSVGElement.preserveAspectRatio.animVal.align is SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN
-PASS rootSVGElement.preserveAspectRatio.animVal.meetOrSlice is SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET
-PASS rootSVGElement.preserveAspectRatio.baseVal.align is SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN
-PASS rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice is SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
diff --git a/third_party/blink/web_tests/svg/animations/svgPreserveAspectRatio-animation-1.html b/third_party/blink/web_tests/svg/animations/svgPreserveAspectRatio-animation-1.html
index 8250b91b..a13a86ed 100644
--- a/third_party/blink/web_tests/svg/animations/svgPreserveAspectRatio-animation-1.html
+++ b/third_party/blink/web_tests/svg/animations/svgPreserveAspectRatio-animation-1.html
@@ -1,14 +1,62 @@
 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
 <html>
+<title>Test 'to' animation of SVGPreserveAspectRatio.</title>
 <head>
-<script src="../../resources/js-test.js"></script>
-<script src="resources/SVGTestCase.js"></script>
-<script src="resources/SVGAnimationTestCase.js"></script>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/SVGAnimationTestCase-testharness.js"></script>
 </head>
-<body onload="runSMILTest()">
-<h1>SVG 1.1 dynamic animation tests</h1>
-<p id="description"></p>
-<div id="console"></div>
-<script src="script-tests/svgPreserveAspectRatio-animation-1.js"></script>
+<body onload="">
+<svg id="root" preserveAspectRatio="xMaxYMin meet">
+  <animate id="animation" attributeName="preserveAspectRatio" dur="4s" from="xMinYMin meet" to="xMaxYMid slice">
+</svg>
+<script>
+var rootSVGElement;
+// Setup animation test
+rootSVGElement = document.querySelector("#root");
+function sample1() {
+    assert_equals(rootSVGElement.preserveAspectRatio.animVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN, "1.1");
+    assert_equals(rootSVGElement.preserveAspectRatio.animVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "1.2");
+
+    assert_equals(rootSVGElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN, "1.3");
+    assert_equals(rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "1.4");
+}
+
+function sample2() {
+    assert_equals(rootSVGElement.preserveAspectRatio.animVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN, "2.1");
+    assert_equals(rootSVGElement.preserveAspectRatio.animVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "2.2");
+
+    assert_equals(rootSVGElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN, "2.3");
+    assert_equals(rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "2.4");
+}
+
+function sample3() {
+    assert_equals(rootSVGElement.preserveAspectRatio.animVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID, "3.1");
+    assert_equals(rootSVGElement.preserveAspectRatio.animVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE, "3.2");
+
+    assert_equals(rootSVGElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN, "3.3");
+    assert_equals(rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "3.4");
+}
+
+function sample4() {
+    assert_equals(rootSVGElement.preserveAspectRatio.animVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN, "4.1");
+    assert_equals(rootSVGElement.preserveAspectRatio.animVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "4.2");
+
+    assert_equals(rootSVGElement.preserveAspectRatio.baseVal.align, SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN, "4.3");
+    assert_equals(rootSVGElement.preserveAspectRatio.baseVal.meetOrSlice, SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, "4.3");
+}
+
+smil_async_test(t => {
+  const expectedValues = [
+    // [animationId, time, sampleCallback]
+    ["animation", 0.1,   sample1],
+    ["animation", 1.5,   sample2],
+    ["animation", 3.999, sample3],
+    ["animation", 4.001, sample4],
+  ];
+
+  runAnimationTest(t, expectedValues);
+});
+</script>
 </body>
 </html>
diff --git a/third_party/r8/README.chromium b/third_party/r8/README.chromium
index 093a5af1..7c7fb1e 100644
--- a/third_party/r8/README.chromium
+++ b/third_party/r8/README.chromium
@@ -14,14 +14,8 @@
 Turning off vertical and horizontal class merging, as described in
 https://issuetracker.google.com/122902374#comment11. We should no longer make
 this modification once the linked bug is fixed.
--  public boolean enableHorizontalClassMerging = true;
--  public boolean enableVerticalClassMerging = true;
-+  public boolean enableHorizontalClassMerging = false;
-+  public boolean enableVerticalClassMerging = false;
 
 Turning off nullable inlining since it caused a performance regression here:
 crbug.com/965189. If R8 ever mitigates this, we can re-enable this.
--  public boolean enableInliningOfInvokesWithNullableReceivers =
--      System.getProperty("com.android.tools.r8.disableInliningOfInvokesWithNullableReceivers")
--          == null;
-+  public boolean enableInliningOfInvokesWithNullableReceivers = false;
+
+Patch is located at //third_party/r8/local_modifications.diff.
diff --git a/third_party/r8/local_modifications.diff b/third_party/r8/local_modifications.diff
new file mode 100644
index 0000000..21c5806
--- /dev/null
+++ b/third_party/r8/local_modifications.diff
@@ -0,0 +1,26 @@
+diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+index 2e1a254fd..8a2693e1f 100644
+--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
++++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+@@ -161,8 +161,8 @@ public class InternalOptions {
+
+   // Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
+   public boolean enableDynamicTypeOptimization = true;
+-  public boolean enableHorizontalClassMerging = true;
+-  public boolean enableVerticalClassMerging = true;
++  public boolean enableHorizontalClassMerging = false;
++  public boolean enableVerticalClassMerging = false;
+   public boolean enableArgumentRemoval = true;
+   public boolean enableUnusedArgumentRemoval = true;
+   public boolean enableUnusedInterfaceRemoval = true;
+@@ -173,9 +173,7 @@ public class InternalOptions {
+   public boolean enableInliningOfInvokesWithDefinitelyNullReceivers =
+       System.getProperty("com.android.tools.r8.disableInliningOfInvokesWithDefinitelyNullReceivers")
+           == null;
+-  public boolean enableInliningOfInvokesWithNullableReceivers =
+-      System.getProperty("com.android.tools.r8.disableInliningOfInvokesWithNullableReceivers")
+-          == null;
++  public boolean enableInliningOfInvokesWithNullableReceivers = false;
+   public boolean enableClassInlining = true;
+   public boolean enableClassStaticizer = true;
+   public boolean enableInitializedClassesAnalysis = true;
diff --git a/third_party/tcmalloc/README.chromium b/third_party/tcmalloc/README.chromium
index 82003c79..46e65178 100644
--- a/third_party/tcmalloc/README.chromium
+++ b/third_party/tcmalloc/README.chromium
@@ -104,7 +104,7 @@
 - Added tc_malloc_skip_new_handler.
 - Added TCMALLOC_DONT_REPLACE_SYSTEM_ALLOC which bypasses the libc_override logic.
 - Backported 7df7f14 "issue-693: enable futex usage on arm" from upstream.
-- Don't use the tls model 'initial-exec' in chromeos on arm with gcc.
+- Don't use the tls model 'initial-exec' on arm with gcc.
 - Update addr2line-pdb.c to fix format string errors and use relative addresses
   matching linux's behavior more closely.
 - Changed kint64min to not depend on undefined behavior.
diff --git a/third_party/tcmalloc/gperftools-2.0/chromium/src/thread_cache.cc b/third_party/tcmalloc/gperftools-2.0/chromium/src/thread_cache.cc
index 7ca92719..19c68c3c 100644
--- a/third_party/tcmalloc/gperftools-2.0/chromium/src/thread_cache.cc
+++ b/third_party/tcmalloc/gperftools-2.0/chromium/src/thread_cache.cc
@@ -69,8 +69,11 @@
 //
 // gcc has a problem with this tls model on arm.
 // See https://bugs.chromium.org/p/chromium/issues/detail?id=650137
+// This problem affected other variables using thread_local as g_thread_id in
+// PlatformThread.
+// See https://bugs.chromium.org/p/chromium/issues/detail?id=973767
 #if defined(HAVE___ATTRIBUTE__) && !defined(PGO_GENERATE) && \
-    !(!defined(__clang__) && defined(OS_CHROMEOS) && defined(__arm__))
+    !(defined(__GNUC__) && !defined(__clang__) && defined(__arm__))
    __attribute__ ((tls_model ("initial-exec")))
 # endif
    ;
diff --git a/tools/determinism/compare_build_artifacts.py b/tools/determinism/compare_build_artifacts.py
index decacc9..aa1848f 100755
--- a/tools/determinism/compare_build_artifacts.py
+++ b/tools/determinism/compare_build_artifacts.py
@@ -105,7 +105,7 @@
         for i in xrange(min(len(lhs_data), len(rhs_data))):
           if lhs_data[i] != rhs_data[i]:
             num_diffs += 1
-        if streams is not None:
+        if len(streams) < MAX_STREAMS:
           for idx in xrange(NUM_CHUNKS_IN_BLOCK):
             lhs_chunk = lhs_data[idx * CHUNK_SIZE:(idx + 1) * CHUNK_SIZE]
             rhs_chunk = rhs_data[idx * CHUNK_SIZE:(idx + 1) * CHUNK_SIZE]
@@ -114,7 +114,6 @@
                 streams.append((offset + CHUNK_SIZE * idx,
                                 lhs_chunk, rhs_chunk))
               else:
-                streams = None
                 break
       offset += len(lhs_data)
       del lhs_data
diff --git a/tools/gritsettings/resource_ids b/tools/gritsettings/resource_ids
index c403608c..010c826 100644
--- a/tools/gritsettings/resource_ids
+++ b/tools/gritsettings/resource_ids
@@ -83,15 +83,22 @@
     "includes": [11000],
     "structures": [11850],
   },
+  "chrome/browser/resources/chromeos/camera/camera_resources.grd": {
+    "includes": [11930],
+    "structures": [11990],
+  },
+  "chrome/browser/resources/chromeos/camera/strings/camera_strings.grd": {
+    "messages": [12030],
+  },
   "chrome/browser/resources/chromeos/cellular_setup/cellular_setup_resources.grd": {
-    "structures": [11940],
+    "structures": [12100],
   },
   "chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_resources.grd": {
-    "structures": [11945],
+    "structures": [12110],
   },
   "chrome/browser/resources/component_extension_resources.grd": {
-    "includes": [12000],
-    "structures": [12250],
+    "includes": [12120],
+    "structures": [12260],
   },
   "chrome/browser/resources/downloads/downloads_resources_vulcanized.grd": {
     "includes": [12300],
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index eb71047..354e4199 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -70,6 +70,7 @@
       # This bot must use the gpu_tests mixin to match 'Android FYI Release (Nexus 5X)'
       # on the chromium.gpu waterfall, which it mirrors via trybots.pyl.
       'android-marshmallow-arm64-rel': 'gpu_tests_android_release_bot_minimal_symbols_arm64',
+      'android-oreo-arm64-rel': 'android_release_bot_minimal_symbols_arm64_webview_google',
     },
 
     'chromium.android.fyi': {
@@ -1068,6 +1069,11 @@
       'strip_debug_info',
     ],
 
+    'android_release_bot_minimal_symbols_arm64_webview_google': [
+      'android', 'release_bot', 'minimal_symbols', 'arm64',
+      'strip_debug_info', 'webview_google',
+    ],
+
     'android_release_thumb_bot': [
       'android', 'release_bot', 'arm_thumb',
     ],
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index d41653b..9d15d6a 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -52295,6 +52295,15 @@
   <int value="1" label="Remote"/>
 </enum>
 
+<enum name="SendWebPushMessageResult">
+  <int value="0" label="Successful"/>
+  <int value="1" label="Encryption failed"/>
+  <int value="2" label="Failed to create JWT"/>
+  <int value="3" label="Network error"/>
+  <int value="4" label="Server error"/>
+  <int value="5" label="Failed to parse response"/>
+</enum>
+
 <enum name="ServiceProcessEventType">
   <int value="0" label="SERVICE_EVENT_INITIALIZE"/>
   <int value="1" label="SERVICE_EVENT_ENABLED_ON_LAUNCH"/>
@@ -52484,6 +52493,7 @@
   <int value="31" label="LONG_RUNNING_MESSAGE"/>
   <int value="32" label="BACKGROUND_FETCH_SUCCESS"/>
   <int value="33" label="PERIODIC_SYNC"/>
+  <int value="34" label="CONTENT_DELETE"/>
 </enum>
 
 <enum name="ServiceWorkerPreparationType">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 25a7351..8ceaf5e4 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -17080,6 +17080,16 @@
   </summary>
 </histogram>
 
+<histogram name="Canvas.TextMetrics.SetFont" units="microseconds"
+    expires_after="M90">
+  <owner>yiyix@chromium.org</owner>
+  <owner>fserb@chromium.org</owner>
+  <summary>
+    Time spent in microseconds to perform calls to SetFont in Canvas for
+    Canvas2d. It's measured each time SetFont is called.
+  </summary>
+</histogram>
+
 <histogram name="CAPSUpdater.Step" enum="CAPSUpdaterStep"
     expires_after="2016-08-18">
   <obsolete>
@@ -18227,6 +18237,17 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.Apps.ExternalProtocolDialog"
+    enum="ArcIntentHandlerAction" expires_after="2019-09-17">
+  <owner>dominickn@chromium.org</owner>
+  <owner>melzhang@chromium.org</owner>
+  <owner>mxcai@chromium.org</owner>
+  <summary>
+    Records the response when the intent picker is shown to the user on Chrome
+    OS to allow them to open an external protocol in an app.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.Apps.IntentPickerAction"
     enum="ArcIntentHandlerAction">
   <owner>elijahtaylor@google.com</owner>
@@ -45416,6 +45437,16 @@
   </summary>
 </histogram>
 
+<histogram name="GCM.SendWebPushMessageResult" enum="SendWebPushMessageResult"
+    expires_after="2020-02-02">
+  <owner>alexchau@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    Result of sending web push messages. Recorded when the message has either
+    been sent, or has been dropped because an error happened.
+  </summary>
+</histogram>
+
 <histogram name="GCM.StoreDestroySucceeded" enum="BooleanSuccess">
   <owner>zea@chromium.org</owner>
   <summary>
@@ -87342,6 +87373,16 @@
   </summary>
 </histogram>
 
+<histogram name="OffscreenCanvas.TextMetrics.SetFont" units="microseconds"
+    expires_after="M90">
+  <owner>yiyix@chromium.org</owner>
+  <owner>fserb@chromium.org</owner>
+  <summary>
+    Time spent in microseconds to perform call SetFont in canvas for
+    OffscreenCanvas. It's measured each time SetFont is called.
+  </summary>
+</histogram>
+
 <histogram name="Omnibox.AggressiveHistoryURLProviderFieldTrialBeacon"
     enum="OmniboxAggressiveHistoryURLProviderFieldTrialBeacon"
     expires_after="2013-04-16">
@@ -120073,6 +120114,17 @@
   </summary>
 </histogram>
 
+<histogram name="ServiceWorker.ContentDeleteEvent.Time" units="ms"
+    expires_after="M88">
+  <owner>rayankans@chromium.org</owner>
+  <owner>platform-capabilities@chromium.org</owner>
+  <summary>
+    The time taken between dispatching a ContentDeleteEvent to a Service Worker
+    and receiving a message that it finished handling the event. Includes the
+    time for the waitUntil() promise to settle.
+  </summary>
+</histogram>
+
 <histogram name="ServiceWorker.ContextRequestHandlerStatus"
     enum="ServiceWorkerContextRequestHandlerStatus" expires_after="2019-03-13">
   <obsolete>
@@ -123812,6 +123864,30 @@
   </summary>
 </histogram>
 
+<histogram name="Sharing.ClickToCallSelectedAppIndex" units="index"
+    expires_after="2020-02-02">
+<!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
+
+  <owner>mvanouwerkerk@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    The index of the app selected by the user for Click to Call. Zero based.
+    Desktop only.
+  </summary>
+</histogram>
+
+<histogram name="Sharing.ClickToCallSelectedDeviceIndex" units="index"
+    expires_after="2020-02-02">
+<!-- Name completed by histogram_suffixes name="SharingClickToCallUi" -->
+
+  <owner>mvanouwerkerk@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    The index of the device selected by the user for Click to Call. Zero based.
+    Desktop only.
+  </summary>
+</histogram>
+
 <histogram name="Sharing.MessageReceivedType" enum="SharingMessageType"
     expires_after="2020-02-02">
   <owner>mvanouwerkerk@chromium.org</owner>
@@ -166125,6 +166201,13 @@
   <affected-histogram name="Setup.Install.Win32ApiError"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="SharingClickToCallUi" separator=".">
+  <suffix name="ContextMenu" label="Context menu"/>
+  <suffix name="Dialog" label="Dialog"/>
+  <affected-histogram name="Sharing.ClickToCallSelectedAppIndex"/>
+  <affected-histogram name="Sharing.ClickToCallSelectedDeviceIndex"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="ShillCumulativeTimeOnline" separator=".">
   <suffix name="Any" label="Any connection type"/>
   <suffix name="Cellular" label="Cellular connection"/>
diff --git a/tools/perf/contrib/memory_extras/memory_extras.py b/tools/perf/contrib/memory_extras/memory_extras.py
index b2bffdd..b4b7f37 100644
--- a/tools/perf/contrib/memory_extras/memory_extras.py
+++ b/tools/perf/contrib/memory_extras/memory_extras.py
@@ -2,15 +2,88 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import logging
+import py_utils
+
 from benchmarks import memory
-import page_sets
+from core import perf_benchmark
 
 from telemetry import benchmark
+from telemetry.page import page as page_module
+from telemetry.page import shared_page_state
+from telemetry import story as story_module
 
 
-# pylint: disable=protected-access
+_DUMP_WAIT_TIME = 3
+_ITERATIONS = 10
+
+
+class DesktopMemorySharedState(shared_page_state.SharedDesktopPageState):
+  def ShouldReuseBrowserForAllStoryRuns(self):
+    return True
+
+
+class DesktopMemoryPage(page_module.Page):
+
+  def __init__(self, url, page_set):
+    super(DesktopMemoryPage, self).__init__(
+        url=url, page_set=page_set,
+        shared_page_state_class=DesktopMemorySharedState,
+        name=url)
+
+  def _DumpMemory(self, action_runner, phase):
+    with action_runner.CreateInteraction(phase):
+      action_runner.Wait(_DUMP_WAIT_TIME)
+      action_runner.ForceGarbageCollection()
+      action_runner.SimulateMemoryPressureNotification('critical')
+      action_runner.Wait(_DUMP_WAIT_TIME)
+      action_runner.tab.browser.DumpMemory()
+
+  def RunPageInteractions(self, action_runner):
+    self._DumpMemory(action_runner, 'pre')
+    for _ in xrange(_ITERATIONS):
+      action_runner.ReloadPage()
+
+    tabs = action_runner.tab.browser.tabs
+    for _ in xrange(_ITERATIONS):
+      new_tab = tabs.New()
+      new_tab.action_runner.Navigate(self._url)
+      try:
+        new_tab.action_runner.WaitForNetworkQuiescence()
+      except py_utils.TimeoutException:
+        logging.warning('WaitForNetworkQuiescence() timeout')
+      new_tab.Close()
+
+    self._DumpMemory(action_runner, 'post')
+
+
+class DesktopMemoryPageSet(story_module.StorySet):
+  """ Desktop sites with interesting memory characteristics """
+
+  def __init__(self):
+    super(DesktopMemoryPageSet, self).__init__()
+
+    urls_list = [
+      'http://www.google.com',
+      "http://www.live.com",
+      "http://www.youtube.com",
+      "http://www.wikipedia.org",
+      "http://www.flickr.com/",
+      "http://www.cnn.com/",
+      "http://www.adobe.com/",
+      "http://www.aol.com/",
+      "http://www.cnet.com/",
+      "http://www.godaddy.com/",
+      "http://www.walmart.com/",
+      "http://www.skype.com/",
+    ]
+
+    for url in urls_list:
+      self.AddStory(DesktopMemoryPage(url, self))
+
+
 @benchmark.Info(emails=['etienneb@chromium.org'])
-class LongRunningMemoryBenchmarkSitesDesktop(memory._MemoryInfra):
+class LongRunningMemoryBenchmarkSitesDesktop(perf_benchmark.PerfBenchmark):
   """Measure memory usage on popular sites.
 
   This benchmark is intended to run locally over a long period of time. The
@@ -25,11 +98,13 @@
   }
 
   def CreateStorySet(self, options):
-    return page_sets.DesktopMemoryPageSet()
+    return DesktopMemoryPageSet()
+
+  def CreateCoreTimelineBasedMeasurementOptions(self):
+    return memory.CreateCoreTimelineBasedMemoryMeasurementOptions()
 
   def SetExtraBrowserOptions(self, options):
-    super(LongRunningMemoryBenchmarkSitesDesktop, self).SetExtraBrowserOptions(
-        options)
+    memory.SetExtraBrowserOptionsForMemoryMeasurement(options)
     options.AppendExtraBrowserArgs([
         '--memlog=all', '--memlog-sampling',
         '--memlog-stack-mode=native-with-thread-names'])
diff --git a/tools/perf/page_sets/desktop_memory.py b/tools/perf/page_sets/desktop_memory.py
deleted file mode 100644
index aef9de34d..0000000
--- a/tools/perf/page_sets/desktop_memory.py
+++ /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.
-import logging
-import py_utils
-
-from telemetry.page import page as page_module
-from telemetry.page import shared_page_state
-from telemetry import story as story_module
-
-_DUMP_WAIT_TIME = 3
-_ITERATIONS = 10
-
-
-class DesktopMemorySharedState(shared_page_state.SharedDesktopPageState):
-  def ShouldStopBrowserAfterStoryRun(self, story):
-    del story
-    return False  # Keep the same browser instance open across stories.
-
-
-class DesktopMemoryPage(page_module.Page):
-
-  def __init__(self, url, page_set):
-    super(DesktopMemoryPage, self).__init__(
-        url=url, page_set=page_set,
-        shared_page_state_class=DesktopMemorySharedState,
-        name=url)
-
-  def _DumpMemory(self, action_runner, phase):
-    with action_runner.CreateInteraction(phase):
-      action_runner.Wait(_DUMP_WAIT_TIME)
-      action_runner.ForceGarbageCollection()
-      action_runner.SimulateMemoryPressureNotification('critical')
-      action_runner.Wait(_DUMP_WAIT_TIME)
-      action_runner.tab.browser.DumpMemory()
-
-  def RunPageInteractions(self, action_runner):
-    self._DumpMemory(action_runner, 'pre')
-    for _ in xrange(_ITERATIONS):
-      action_runner.ReloadPage()
-
-    tabs = action_runner.tab.browser.tabs
-    for _ in xrange(_ITERATIONS):
-      new_tab = tabs.New()
-      new_tab.action_runner.Navigate(self._url)
-      try:
-        new_tab.action_runner.WaitForNetworkQuiescence()
-      except py_utils.TimeoutException:
-        logging.warning('WaitForNetworkQuiescence() timeout')
-      new_tab.Close()
-
-    self._DumpMemory(action_runner, 'post')
-
-
-class DesktopMemoryPageSet(story_module.StorySet):
-
-  """ Desktop sites with interesting memory characteristics """
-
-  def __init__(self):
-    super(DesktopMemoryPageSet, self).__init__()
-
-    urls_list = [
-      'http://www.google.com',
-      "http://www.live.com",
-      "http://www.youtube.com",
-      "http://www.wikipedia.org",
-      "http://www.flickr.com/",
-      "http://www.cnn.com/",
-      "http://www.adobe.com/",
-      "http://www.aol.com/",
-      "http://www.cnet.com/",
-      "http://www.godaddy.com/",
-      "http://www.walmart.com/",
-      "http://www.skype.com/",
-    ]
-
-    for url in urls_list:
-      self.AddStory(DesktopMemoryPage(url, self))
diff --git a/ui/accelerated_widget_mac/OWNERS b/ui/accelerated_widget_mac/OWNERS
index 4eb0cb4..bbcd5d6 100644
--- a/ui/accelerated_widget_mac/OWNERS
+++ b/ui/accelerated_widget_mac/OWNERS
@@ -1 +1,2 @@
 ccameron@chromium.org
+sdy@chromium.org
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 77a49d7..86e6336 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -16,6 +16,7 @@
 
 #include "base/command_line.h"
 #include "base/debug/leak_annotations.h"
+#include "base/memory/protected_memory_cfi.h"
 #include "base/no_destructor.h"
 #include "base/optional.h"
 #include "base/strings/string_number_conversions.h"
@@ -1965,25 +1966,58 @@
 
 }  //  namespace atk_object
 
+static PROTECTED_MEMORY_SECTION
+    base::ProtectedMemory<AtkTableCellInterface::GetTypeFunc>
+        g_atk_table_cell_get_type;
+
+static PROTECTED_MEMORY_SECTION
+    base::ProtectedMemory<AtkTableCellInterface::GetColumnHeaderCellsFunc>
+        g_atk_table_cell_get_column_header_cells;
+
+static PROTECTED_MEMORY_SECTION
+    base::ProtectedMemory<AtkTableCellInterface::GetRowHeaderCellsFunc>
+        g_atk_table_cell_get_row_header_cells;
+
+static PROTECTED_MEMORY_SECTION
+    base::ProtectedMemory<AtkTableCellInterface::GetRowColumnSpanFunc>
+        g_atk_table_cell_get_row_column_span;
+
 }  // namespace
 
 // static
 base::Optional<AtkTableCellInterface> AtkTableCellInterface::Get() {
   static base::Optional<AtkTableCellInterface> interface = base::nullopt;
+  static base::ProtectedMemory<GetTypeFunc>::Initializer
+      init_atk_table_cell_get_type(
+          &g_atk_table_cell_get_type,
+          reinterpret_cast<GetTypeFunc>(
+              dlsym(RTLD_DEFAULT, "atk_table_cell_get_type")));
+  static base::ProtectedMemory<GetColumnHeaderCellsFunc>::Initializer
+      init_atk_table_cell_get_column_header_cells(
+          &g_atk_table_cell_get_column_header_cells,
+          reinterpret_cast<GetColumnHeaderCellsFunc>(
+              dlsym(RTLD_DEFAULT, "atk_table_cell_get_column_header_cells")));
+  static base::ProtectedMemory<GetRowHeaderCellsFunc>::Initializer
+      init_atk_table_cell_get_row_header_cells(
+          &g_atk_table_cell_get_row_header_cells,
+          reinterpret_cast<GetRowHeaderCellsFunc>(
+              dlsym(RTLD_DEFAULT, "atk_table_cell_get_row_header_cells")));
+  static base::ProtectedMemory<GetRowColumnSpanFunc>::Initializer
+      init_atk_table_cell_get_row_column_span(
+          &g_atk_table_cell_get_row_column_span,
+          reinterpret_cast<GetRowColumnSpanFunc>(
+              dlsym(RTLD_DEFAULT, "atk_table_cell_get_row_column_span")));
+
   if (interface.has_value())
-    return interface->GetType ? interface : base::nullopt;
+    return **interface->GetType ? interface : base::nullopt;
 
   interface.emplace();
-  interface->GetType = reinterpret_cast<GetTypeFunc>(
-      dlsym(RTLD_DEFAULT, "atk_table_cell_get_type"));
-  interface->GetColumnHeaderCells = reinterpret_cast<GetColumnHeaderCellsFunc>(
-      dlsym(RTLD_DEFAULT, "atk_table_cell_get_column_header_cells"));
-  interface->GetRowHeaderCells = reinterpret_cast<GetRowHeaderCellsFunc>(
-      dlsym(RTLD_DEFAULT, "atk_table_cell_get_row_header_cells"));
-  interface->GetRowColumnSpan = reinterpret_cast<GetRowColumnSpanFunc>(
-      dlsym(RTLD_DEFAULT, "atk_table_cell_get_row_column_span"));
+  interface->GetType = &g_atk_table_cell_get_type;
+  interface->GetColumnHeaderCells = &g_atk_table_cell_get_column_header_cells;
+  interface->GetRowHeaderCells = &g_atk_table_cell_get_row_header_cells;
+  interface->GetRowColumnSpan = &g_atk_table_cell_get_row_column_span;
   interface->initialized = true;
-  return interface->GetType ? interface : base::nullopt;
+  return **interface->GetType ? interface : base::nullopt;
 }
 
 void AXPlatformNodeAuraLinux::EnsureGTypeInit() {
@@ -2106,8 +2140,9 @@
     // Run-time check to ensure AtkTableCell is supported (requires ATK 2.12).
     auto interface = AtkTableCellInterface::Get();
     if (interface.has_value()) {
-      g_type_add_interface_static(type, interface->GetType(),
-                                  &atk_table_cell::Info);
+      g_type_add_interface_static(
+          type, base::UnsanitizedCfiCall(*interface->GetType)(),
+          &atk_table_cell::Info);
     }
   }
 
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.h b/ui/accessibility/platform/ax_platform_node_auralinux.h
index 55ad15d..5debfad 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.h
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.h
@@ -13,6 +13,7 @@
 #include <utility>
 
 #include "base/macros.h"
+#include "base/memory/protected_memory.h"
 #include "base/optional.h"
 #include "base/strings/utf_offset_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
@@ -67,10 +68,11 @@
                                        gint* row_span,
                                        gint* col_span);
 
-  GetTypeFunc GetType = nullptr;
-  GetColumnHeaderCellsFunc GetColumnHeaderCells = nullptr;
-  GetRowHeaderCellsFunc GetRowHeaderCells = nullptr;
-  GetRowColumnSpanFunc GetRowColumnSpan = nullptr;
+  base::ProtectedMemory<GetTypeFunc>* GetType = nullptr;
+  base::ProtectedMemory<GetColumnHeaderCellsFunc>* GetColumnHeaderCells =
+      nullptr;
+  base::ProtectedMemory<GetRowHeaderCellsFunc>* GetRowHeaderCells = nullptr;
+  base::ProtectedMemory<GetRowColumnSpanFunc>* GetRowColumnSpan = nullptr;
   bool initialized = false;
 
   static base::Optional<AtkTableCellInterface> Get();
diff --git a/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js b/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
index f256c33..078a3a8 100644
--- a/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
+++ b/ui/file_manager/file_manager/background/js/runtime_loaded_test_util.js
@@ -438,23 +438,31 @@
  *     Object containing common key modifiers : shift, alt, and ctrl.
  * @param {number=} opt_button Mouse button number as per spec, e.g.: 2 for
  *     right-click.
+ * @param {Object=} opt_eventProperties Additional properties to pass to each
+ *     event, e.g.: clientX and clientY. right-click.
  * @return {boolean} True if the all events are sent to the target, false
  *     otherwise.
  */
 test.util.sync.fakeMouseClick =
-    (contentWindow, targetQuery, opt_keyModifiers, opt_button) => {
+    (contentWindow, targetQuery, opt_keyModifiers, opt_button,
+     opt_eventProperties) => {
       const modifiers = opt_keyModifiers || {};
-      const props = {
-        bubbles: true,
-        detail: 1,
-        composed: true,  // Allow the event to bubble past shadow DOM root.
-        ctrlKey: modifiers.ctrl,
-        shiftKey: modifiers.shift,
-        altKey: modifiers.alt,
-      };
+      const eventProperties = opt_eventProperties || {};
+
+      const props = Object.assign(
+          {
+            bubbles: true,
+            detail: 1,
+            composed: true,  // Allow the event to bubble past shadow DOM root.
+            ctrlKey: modifiers.ctrl,
+            shiftKey: modifiers.shift,
+            altKey: modifiers.alt,
+          },
+          eventProperties);
       if (opt_button !== undefined) {
         props.button = opt_button;
       }
+
       if (!targetQuery) {
         return false;
       }
@@ -646,6 +654,44 @@
   return test.util.sync.sendEvent(contentWindow, targetQuery, event);
 };
 
+/**
+ * Simulates a mouse right-click on the element specified by |targetQuery|.
+ * Optionally pass X,Y coordinates to be able to choose where the right-click
+ * should occur.
+ *
+ * @param {Window} contentWindow Window to be tested.
+ * @param {string} targetQuery Query to specify the element.
+ * @param {number=} opt_offsetBottom offset pixels applied to target element
+ *     bottom, can be negative to move above the bottom.
+ * @param {number=} opt_offsetRight offset pixels applied to target element
+ *     right can be negative to move inside the element.
+ * @return {boolean} True if the all events are sent to the target, false
+ *     otherwise.
+ */
+test.util.sync.rightClickOffset =
+    (contentWindow, targetQuery, opt_offsetBottom, opt_offsetRight) => {
+      const target = contentWindow.document &&
+          contentWindow.document.querySelector(targetQuery);
+      if (!target) {
+        return false;
+      }
+
+      // Calculate the offsets.
+      const targetRect = target.getBoundingClientRect();
+      const props = {
+        clientX: targetRect.right + (opt_offsetRight ? opt_offsetRight : 0),
+        clientY: targetRect.bottom + (opt_offsetBottom ? opt_offsetBottom : 0),
+      };
+
+      const keyModifiers = undefined;
+      const rightButton = 2;
+      if (!test.util.sync.fakeMouseClick(
+              contentWindow, targetQuery, keyModifiers, rightButton, props)) {
+        return false;
+      }
+
+      return true;
+    };
 
 /**
  * Sends a drag'n'drop set of events from |srcTarget| to |dstTarget|.
diff --git a/ui/file_manager/file_manager/foreground/elements/files_quick_view.html b/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
index 553784a1..b1af9ae 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
+++ b/ui/file_manager/file_manager/foreground/elements/files_quick_view.html
@@ -64,7 +64,7 @@
                 <div class="no-preview">[[noPlaybackText]]</div>
               </template>
               <template is="dom-if" if="[[contentUrl]]">
-                <files-safe-media tabindex="0" type="video" class="content no-close-on-click" controls autoplay="[[autoplay]]" src="[[contentUrl]]" poster="[[videoPoster]]"></files-safe-media>
+                <files-safe-media id="videoSafeMedia" tabindex="0" type="video" class="content no-close-on-click" controls autoplay="[[autoplay]]" src="[[contentUrl]]" poster="[[videoPoster]]"></files-safe-media>
               </template>
             </template>
             <!-- Audio -->
diff --git a/ui/file_manager/file_manager/foreground/elements/files_quick_view.js b/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
index 36116d67..4fe70b3 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
+++ b/ui/file_manager/file_manager/foreground/elements/files_quick_view.js
@@ -61,6 +61,11 @@
     this.audioArtwork = '';
     this.autoplay = false;
     this.browsable = false;
+    const video = this.$.contentPanel.querySelector('#videoSafeMedia');
+    if (video) {
+      video.src = '';
+      video.fire('src-changed');
+    }
   },
 
   /** @return {boolean} */
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index 4c7b3d6..d973f885 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -960,7 +960,11 @@
         !this.containsReadOnlyEntry_(entries, fileManager) &&
         !fileManager.directoryModel.isReadOnly() &&
         CommandUtil.hasCapability(entries, 'canDelete');
-    event.command.setHidden(false);
+
+    // Hide if there isn't anything selected, meaning user clicked in an empty
+    // space in the file list.
+    const noEntries = entries.length === 0;
+    event.command.setHidden(noEntries);
   }
 
   /**
@@ -1623,7 +1627,11 @@
       return;
     }
 
-    event.command.setHidden(false);
+    // Hide if there isn't anything selected, meaning user clicked in an empty
+    // space in the file list.
+    const noEntries = selection.entries.length === 0;
+    event.command.setHidden(noEntries);
+
     const isOnEligibleLocation =
         CommandHandler.IS_ZIP_ARCHIVER_PACKER_ENABLED_ ?
         true :
diff --git a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
index e2f0e02..9558b0d6 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
+++ b/ui/file_manager/file_manager/foreground/js/ui/BUILD.gn
@@ -387,17 +387,6 @@
   ]
 }
 
-js_unittest("list_container_unittest") {
-  deps = [
-    ":list_container",
-    "../../../common/js:util",
-    "//ui/file_manager/base/js:test_error_reporting",
-    "//ui/webui/resources/js:webui_resource_test",
-    "//ui/webui/resources/js/cr/ui:context_menu_handler",
-    "//ui/webui/resources/js/cr/ui:menu",
-  ]
-}
-
 js_library("location_line") {
   deps = [
     "../../../common/js:files_app_entry_types",
@@ -501,7 +490,6 @@
     ":file_table_list_unittest",
     ":file_table_unittest",
     ":file_tap_handler_unittest",
-    ":list_container_unittest",
     ":multi_menu_unittest",
   ]
 }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/list_container.js b/ui/file_manager/file_manager/foreground/js/ui/list_container.js
index f232d4b..2e100f7 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/list_container.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/list_container.js
@@ -297,13 +297,6 @@
    * @private
    */
   onContextMenu_(e) {
-    // Inhibit the context menu being shown if it only hosts
-    // disabled items https://crbug.com/917975
-    if (this.contextMenuHasActions_() === false) {
-      e.preventDefault();
-      e.stopPropagation();
-      return;
-    }
     if (!this.allowContextMenuByTouch_ && e.sourceCapabilities &&
         e.sourceCapabilities.firesTouchEvents) {
       this.focus();
diff --git a/ui/file_manager/file_manager/foreground/js/ui/list_container_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/list_container_unittest.js
deleted file mode 100644
index 58db0ac..0000000
--- a/ui/file_manager/file_manager/foreground/js/ui/list_container_unittest.js
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-'use strict';
-
-/** @type {cr.ui.Menu} */
-let contextMenu;
-
-/** @type {!ListContainer} */
-let listContainer;
-
-/** @type {!FileTable} */
-let table;
-
-/** @type {!FileGrid} */
-let grid;
-
-// Set up test components.
-function setUp() {
-  // Install cr.ui <command> elements and <cr-menu>s on the page.
-  document.body.innerHTML = [
-    '<style>',
-    '  .hide {',
-    '    display: none;',
-    '  }',
-    '</style>',
-    '<command id="default-task">',
-    '<cr-menu id="file-context-menu" hidden>',
-    '  <cr-menu-item id="item" command="#default-task"></cr-menu-item>',
-    '</cr-menu>',
-    '<div id="list-container">',
-    '  <div id="detail-table">',
-    '    <list id="file-list" contextmenu="#file-context-menu">',
-    '    </list>',
-    '  </div>',
-    '  <grid id="file-grid" contextmenu="#file-context-menu" hidden>',
-    '  </grid>',
-    '  <paper-progress class="loading-indicator" hidden></paper-progress>',
-    '</div>',
-  ].join('');
-
-  // Initialize cr.ui.Command with the <command>s.
-  cr.ui.decorate('command', cr.ui.Command);
-  // Setup the listContainer and its dependencies
-  contextMenu = util.queryDecoratedElement('#file-context-menu', cr.ui.Menu);
-  table = /** @type {!FileTable} */
-      (queryRequiredElement('#detail-table', undefined));
-  table.list = document.querySelector('#file-list');
-  grid = /** @type {!FileGrid} */
-      (queryRequiredElement('#file-grid', undefined));
-  listContainer = new ListContainer(
-      queryRequiredElement('#list-container', undefined), table, grid);
-  // Hook up enough ListContainer internals to handle touch tests
-  table.startBatchUpdates = table.endBatchUpdates = () => {};
-  table.setListThumbnailLoader = () => {};
-  grid.startBatchUpdates = grid.endBatchUpdates = () => {};
-  grid.setListThumbnailLoader = () => {};
-  listContainer.dataModel = /** @type {!FileListModel} */ ({});
-  listContainer.selectionModel = new cr.ui.ListSelectionModel();
-  listContainer.setCurrentListType(ListContainer.ListType.DETAIL);
-
-  cr.ui.contextMenuHandler.setContextMenu(table, contextMenu);
-}
-
-/**
- * Send a 'contextmenu' event to the element target of a query.
- * @param {string} targetQuery Query to specify the element.
- */
-function sendContextMenu(targetQuery) {
-  const event = new MouseEvent('contextmenu', {
-    bubbles: true,
-    composed: true,  // Allow the event to bubble past shadow DOM root.
-  });
-  const target = document.querySelector(targetQuery);
-  assertTrue(!!target);
-  return target.dispatchEvent(event);
-}
-
-/**
- *  Tests that sending a 'contextmenu' event will show the menu
- *  if it contains cr-menu-item(s) that are actionable.
- */
-function testShowMenuWithActionsOpensContextMenu() {
-  sendContextMenu('#file-list');
-  assertFalse(contextMenu.hasAttribute('hidden'));
-}
-
-/**
- *  Tests that sending a 'contextmenu' event will hide the menu
- *  if it doesn't contain cr-menu-item(s) that are actionable.
- */
-function testShowMenuWithNoActionsHidesContextMenu() {
-  const menuItem = document.querySelector('#item');
-
-  menuItem.setAttribute('hidden', '');
-  sendContextMenu('#file-list');
-  assertTrue(contextMenu.hasAttribute('hidden'));
-  menuItem.removeAttribute('hidden');
-
-  menuItem.setAttribute('disabled', 'disabled');
-  sendContextMenu('#file-list');
-  assertTrue(contextMenu.hasAttribute('hidden'));
-  menuItem.removeAttribute('disabled');
-
-  menuItem.setAttribute('class', 'hide');
-  sendContextMenu('#file-list');
-  assertTrue(contextMenu.hasAttribute('hidden'));
-}
diff --git a/ui/file_manager/integration_tests/file_manager/file_dialog.js b/ui/file_manager/integration_tests/file_manager/file_dialog.js
index 12e7888..cf7c66af 100644
--- a/ui/file_manager/integration_tests/file_manager/file_dialog.js
+++ b/ui/file_manager/integration_tests/file_manager/file_dialog.js
@@ -441,3 +441,52 @@
   chrome.test.assertEq('0', selectedFilter.value);
   chrome.test.assertEq('All files', selectedFilter.text);
 };
+
+/**
+ * Tests that context menu on File List for file picker dialog.
+ * File picker dialog displays fewer menu options than full Files app. For
+ * example copy/paste commands are disabled. Right-click on a file/folder should
+ * show context menu, whereas right-clicking on the blank parts of file list
+ * should NOT display the context menu.
+ *
+ * crbug.com/917975 crbug.com/983507.
+ */
+testcase.openFileDialogFileListShowContextMenu = async () => {
+  // Add entries to Downloads.
+  await addEntries(['local'], BASIC_LOCAL_ENTRY_SET);
+
+  // Open file picker dialog.
+  chrome.fileSystem.chooseEntry({type: 'openFile'}, (entry) => {});
+  const appId = await remoteCall.waitForWindow('dialog#');
+
+  // Wait for files to be displayed.
+  await remoteCall.waitForFiles(
+      appId, TestEntryInfo.getExpectedRows(BASIC_LOCAL_ENTRY_SET));
+
+  // Wait to finish initial load.
+  await remoteCall.waitFor('isFileManagerLoaded', appId, true);
+
+  // Right-click "photos" folder to show context menu.
+  await remoteCall.waitAndRightClick(appId, '#file-list [file-name="photos"]');
+
+  // Wait until the context menu appears.
+  const menuVisible = '#file-context-menu:not([hidden])';
+  await remoteCall.waitForElement(appId, menuVisible);
+
+  // Dismiss context menu.
+  const escKey = ['Escape', false, false, false];
+  await remoteCall.fakeKeyDown(appId, menuVisible, ...escKey);
+  await remoteCall.waitForElementLost(appId, menuVisible);
+
+  // Right-click 100px inside of #file-list (in an empty space).
+  const offsetBottom = -100;
+  const offsetRight = -100;
+  chrome.test.assertTrue(
+      await remoteCall.callRemoteTestUtil(
+          'rightClickOffset', appId, ['#file-list', offsetBottom, offsetRight]),
+      'right click failed');
+
+  // Check that context menu is NOT displayed because there is no visible menu
+  // items.
+  await remoteCall.waitForElement(appId, '#file-context-menu[hidden]');
+};
diff --git a/ui/file_manager/integration_tests/file_manager/format_dialog.js b/ui/file_manager/integration_tests/file_manager/format_dialog.js
index 68070ac..7c97102 100644
--- a/ui/file_manager/integration_tests/file_manager/format_dialog.js
+++ b/ui/file_manager/integration_tests/file_manager/format_dialog.js
@@ -56,10 +56,11 @@
   await openFormatDialog(appId, 'fake-usb');
 
   // Check the correct size is displayed.
-  const msg = await remoteCall.waitForElement(appId, [
+  const warning = await remoteCall.waitForElement(appId, [
     'files-format-dialog', '#warning-container:not([hidden]) #warning-message'
   ]);
-  chrome.test.assertEq('51 bytes of files will be deleted', msg.text.trim());
+  chrome.test.assertEq(
+      '51 bytes of files will be deleted', warning.text.trim());
 
   // Click format button.
   const formatButtonQuery = ['files-format-dialog', 'cr-button#format-button'];
diff --git a/ui/file_manager/integration_tests/remote_call.js b/ui/file_manager/integration_tests/remote_call.js
index 4a4a49b5..88f33c01 100644
--- a/ui/file_manager/integration_tests/remote_call.js
+++ b/ui/file_manager/integration_tests/remote_call.js
@@ -344,6 +344,26 @@
 };
 
 /**
+ * Shorthand for right-clicking an element.
+ * @param {AppWindow} appWindow Application window.
+ * @param {string|!Array<string>} query Query to specify the element.
+ *     If query is an array, |query[0]| specifies the first
+ *     element(s), |query[1]| specifies elements inside the shadow DOM of
+ *     the first element, and so on.
+ * @param {{shift: boolean, alt: boolean, ctrl: boolean}=} opt_keyModifiers
+ *     Object
+ * @param {Promise} Promise to be fulfilled with the clicked element.
+ */
+RemoteCall.prototype.waitAndRightClick =
+    async function(windowId, query, opt_keyModifiers) {
+  const element = await this.waitForElement(windowId, query);
+  const result = await this.callRemoteTestUtil(
+      'fakeMouseRightClick', windowId, [query, opt_keyModifiers]);
+  chrome.test.assertTrue(result, 'mouse right-click failed.');
+  return element;
+};
+
+/**
  * Shorthand for focusing an element.
  * @param {AppWindow} appWindow Application window.
  * @param {!Array<string>} query Query to specify the element to be focused.