diff --git a/BUILD.gn b/BUILD.gn
index 46c10d95..2bd7d27 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -325,6 +325,7 @@
       "//content/public/android:content_junit_tests",
       "//content/shell/android:content_shell_apk",
       "//device:device_junit_tests",
+      "//weblayer/shell/android:weblayer_shell_apk",
 
       # TODO(https://crbug.com/879065): remove once tests have been migrated to
       # the video_decode_accelerator_tests target.
diff --git a/DEPS b/DEPS
index 46909c1..d02574b 100644
--- a/DEPS
+++ b/DEPS
@@ -160,11 +160,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'ea07123fedc7ecfe6bc74fd94ad6c2cec34c211a',
+  'skia_revision': '3c8f9cb45ddefe1e06d75c33a83472ab95ddcfd2',
   # 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': '2334bc12561ce4921dad83678b344df2abf1c775',
+  'v8_revision': '0efdaafd71a5b60c7e3e79fced75a9c54e1d374d',
   # 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.
@@ -172,11 +172,11 @@
   # 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': '87b106a0944e5bc83bbee8de95a49c4be3de3825',
+  'angle_revision': '962503e75ac3f4aebc36c24c3b34297fc9ae800d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '793707262d8d92d6a96632d725dd6525227abf27',
+  'swiftshader_revision': '605f863173b69f9cbe40fca2ac1853eb299e8856',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -187,7 +187,7 @@
   #
   # Note this revision should be updated with
   # third_party/boringssl/roll_boringssl.py, not roll-dep.
-  'boringssl_revision': '44544d9d2d624cbfff9b1e77cb77f8dfc70d073c',
+  'boringssl_revision': '05cd93068b0a553afc48f69acbceae10c6a17593',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -211,7 +211,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '7d1d3b9a0e9310376a559ad2eac8a9dc4c60ce59',
+  'freetype_revision': '9adc3b35f1a6909c1785c42ae7b8cf369634b225',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling HarfBuzz
   # and whatever else without interference from each other.
@@ -223,7 +223,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': '83d2edf28b9db0d0d8a3dd59d997e6832b83b7e0',
+  'catapult_revision': '7ad424d601e29ecfb9a4c83aca4124e901563398',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -840,7 +840,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '0521d7ebd8730904da0ef0ca47042a4a46cb5a1a',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'cff0f2d04d272f024cfe5707bcf0a9c05d59a67f',
       'condition': 'checkout_linux',
   },
 
@@ -1447,7 +1447,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@4857c934a09c2c4e035ed5caf03120beafbe301c',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@9d4f04f712e2b790b8929357ff31a6e83a1dd118',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index b7c360b..009707d 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2518,7 +2518,7 @@
     'rlz_id': ['gab+watch@chromium.org',
                'robertshield+watch@chromium.org'],
     'runtime_enabled_features': ['jmedley+watch@chromium.org'],
-    'safe_browsing': ['drubery@chromium.org',
+    'safe_browsing': ['drubery+watch@chromium.org',
                       'timvolodine@chromium.org',
                       'vakh+watch@chromium.org',
                       'xinghuilu+watch@chromium.org'],
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index eb682c4..01e52c9 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -191,13 +191,13 @@
     "assistant/assistant_notification_controller.h",
     "assistant/assistant_notification_expiry_monitor.cc",
     "assistant/assistant_notification_expiry_monitor.h",
-    "assistant/assistant_prefs_controller.cc",
-    "assistant/assistant_prefs_controller.h",
     "assistant/assistant_screen_context_controller.cc",
     "assistant/assistant_screen_context_controller.h",
     "assistant/assistant_settings.cc",
     "assistant/assistant_setup_controller.cc",
     "assistant/assistant_setup_controller.h",
+    "assistant/assistant_state_controller.cc",
+    "assistant/assistant_state_controller.h",
     "assistant/assistant_suggestions_controller.cc",
     "assistant/assistant_suggestions_controller.h",
     "assistant/assistant_ui_controller.cc",
@@ -1638,8 +1638,8 @@
     "app_menu/notification_menu_view_unittest.cc",
     "app_menu/notification_overflow_view_unittest.cc",
     "assistant/assistant_notification_controller_unittest.cc",
-    "assistant/assistant_prefs_controller_unittest.cc",
     "assistant/assistant_screen_context_controller_unittest.cc",
+    "assistant/assistant_state_controller_unittest.cc",
     "assistant/model/assistant_query_history_unittest.cc",
     "assistant/ui/assistant_container_view_unittest.cc",
     "assistant/util/deep_link_util_unittest.cc",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index 1fdc88ff..46990bf6 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -37,7 +37,6 @@
 #include "ash/public/cpp/new_window_delegate.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/public/cpp/toast_data.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/rotator/window_rotation.h"
@@ -807,10 +806,6 @@
   Shell::Get()->ambient_controller()->Toggle();
 }
 
-bool CanHandleStartVoiceInteraction() {
-  return chromeos::features::IsAssistantEnabled();
-}
-
 void HandleToggleVoiceInteraction(const ui::Accelerator& accelerator) {
   if (accelerator.IsCmdDown() && accelerator.key_code() == ui::VKEY_SPACE) {
     base::RecordAction(
@@ -827,7 +822,7 @@
         base::UserMetricsAction("VoiceInteraction.Started.Assistant"));
   }
 
-  switch (VoiceInteractionController::Get()->allowed_state().value_or(
+  switch (AssistantState::Get()->allowed_state().value_or(
       mojom::AssistantAllowedState::ALLOWED)) {
     case mojom::AssistantAllowedState::DISALLOWED_BY_NONPRIMARY_USER:
       // Show a toast if the active user is not primary.
@@ -863,11 +858,6 @@
                 l10n_util::GetStringUTF16(
                     IDS_ASH_VOICE_INTERACTION_DISABLED_IN_DEMO_MODE_MESSAGE));
       return;
-    case mojom::AssistantAllowedState::DISALLOWED_BY_FLAG:
-      ShowToast(kVoiceInteractionErrorToastId,
-                l10n_util::GetStringUTF16(
-                    IDS_ASH_VOICE_INTERACTION_DISABLED_MESSAGE));
-      return;
     case mojom::AssistantAllowedState::DISALLOWED_BY_SUPERVISED_USER:
       // supervised user is deprecated, wait for the code clean up.
       NOTREACHED();
@@ -1579,7 +1569,7 @@
     case START_AMBIENT_MODE:
       return CanHandleStartAmbientMode();
     case START_VOICE_INTERACTION:
-      return CanHandleStartVoiceInteraction();
+      return true;
     case SWAP_PRIMARY_DISPLAY:
       return display::Screen::GetScreen()->GetNumDisplays() > 1;
     case SWITCH_IME:
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 8ea5e83..b965d18 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -32,7 +32,6 @@
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/shell_window_ids.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/root_window_controller.h"
 #include "ash/screen_util.h"
 #include "ash/session/session_controller_impl.h"
@@ -142,7 +141,7 @@
   shell->AddShellObserver(this);
   shell->overview_controller()->AddObserver(this);
   keyboard::KeyboardUIController::Get()->AddObserver(this);
-  VoiceInteractionController::Get()->AddLocalObserver(this);
+  AssistantState::Get()->AddObserver(this);
   shell->window_tree_host_manager()->AddObserver(this);
   shell->mru_window_tracker()->AddObserver(this);
   if (app_list_features::IsEmbeddedAssistantUIEnabled()) {
@@ -643,12 +642,12 @@
     app_list_view->OnScreenKeyboardShown(is_visible);
 }
 
-void AppListControllerImpl::OnVoiceInteractionStatusChanged(
+void AppListControllerImpl::OnAssistantStatusChanged(
     mojom::VoiceInteractionState state) {
   UpdateAssistantVisibility();
 }
 
-void AppListControllerImpl::OnVoiceInteractionSettingsEnabled(bool enabled) {
+void AppListControllerImpl::OnAssistantSettingsEnabled(bool enabled) {
   UpdateAssistantVisibility();
 }
 
@@ -1175,17 +1174,13 @@
 }
 
 bool AppListControllerImpl::IsAssistantAllowedAndEnabled() const {
-  if (!chromeos::features::IsAssistantEnabled())
-    return false;
-
   if (!Shell::Get()->assistant_controller()->IsAssistantReady())
     return false;
 
-  auto* controller = VoiceInteractionController::Get();
-  return controller->settings_enabled().value_or(false) &&
-         controller->allowed_state() == mojom::AssistantAllowedState::ALLOWED &&
-         controller->voice_interaction_state().value_or(
-             mojom::VoiceInteractionState::NOT_READY) !=
+  auto* state = AssistantState::Get();
+  return state->settings_enabled().value_or(false) &&
+         state->allowed_state() == mojom::AssistantAllowedState::ALLOWED &&
+         state->voice_interaction_state() !=
              mojom::VoiceInteractionState::NOT_READY;
 }
 
@@ -1447,7 +1442,7 @@
   }
   shell->mru_window_tracker()->RemoveObserver(this);
   shell->window_tree_host_manager()->RemoveObserver(this);
-  VoiceInteractionController::Get()->RemoveLocalObserver(this);
+  AssistantState::Get()->RemoveObserver(this);
   keyboard::KeyboardUIController::Get()->RemoveObserver(this);
   shell->overview_controller()->RemoveObserver(this);
   shell->RemoveShellObserver(this);
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index dffb2455..7bcd3d3 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -23,7 +23,6 @@
 #include "ash/home_screen/home_launcher_gesture_handler_observer.h"
 #include "ash/home_screen/home_screen_delegate.h"
 #include "ash/public/cpp/app_list/app_list_controller.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
 #include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/tablet_mode_observer.h"
@@ -59,7 +58,7 @@
       public TabletModeObserver,
       public KeyboardControllerObserver,
       public WallpaperControllerObserver,
-      public DefaultVoiceInteractionObserver,
+      public AssistantStateObserver,
       public WindowTreeHostManager::Observer,
       public ash::MruWindowTracker::Observer,
       public AssistantControllerObserver,
@@ -239,10 +238,9 @@
   // WallpaperControllerObserver:
   void OnWallpaperColorsChanged() override;
 
-  // mojom::VoiceInteractionObserver:
-  void OnVoiceInteractionStatusChanged(
-      mojom::VoiceInteractionState state) override;
-  void OnVoiceInteractionSettingsEnabled(bool enabled) override;
+  // AssistantStateObserver:
+  void OnAssistantStatusChanged(mojom::VoiceInteractionState state) override;
+  void OnAssistantSettingsEnabled(bool enabled) override;
   void OnAssistantFeatureAllowedChanged(
       mojom::AssistantAllowedState state) override;
 
diff --git a/ash/app_list/views/app_list_main_view.cc b/ash/app_list/views/app_list_main_view.cc
index 5d3152d3..ad2f55c89 100644
--- a/ash/app_list/views/app_list_main_view.cc
+++ b/ash/app_list/views/app_list_main_view.cc
@@ -223,7 +223,6 @@
 }
 
 void AppListMainView::AssistantButtonPressed() {
-  DCHECK(chromeos::features::IsAssistantEnabled());
   delegate_->StartAssistant();
 }
 
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index b04fe4b5..381c307 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -2450,9 +2450,7 @@
 // Tests selecting search result to show embedded Assistant UI.
 TEST_F(AppListViewFocusTest, ShowEmbeddedAssistantUI) {
   scoped_feature_list_.InitWithFeatures(
-      {chromeos::features::kAssistantFeature,
-       app_list_features::kEnableEmbeddedAssistantUI},
-      {});
+      {app_list_features::kEnableEmbeddedAssistantUI}, {});
   Show();
 
   // Initially the search box is inactive, hitting Enter to activate it.
@@ -2484,9 +2482,7 @@
 TEST_F(AppListViewTest, NoAnswerCardWhenEmbeddedAssistantUIEnabled) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {chromeos::features::kAssistantFeature,
-       app_list_features::kEnableEmbeddedAssistantUI},
-      {});
+      {app_list_features::kEnableEmbeddedAssistantUI}, {});
   ASSERT_TRUE(app_list_features::IsEmbeddedAssistantUIEnabled());
 
   Initialize(false /*is_tablet_mode*/);
@@ -2500,9 +2496,7 @@
 TEST_F(AppListViewTest, EscapeKeyEmbeddedAssistantUIToSearch) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {chromeos::features::kAssistantFeature,
-       app_list_features::kEnableEmbeddedAssistantUI},
-      {});
+      {app_list_features::kEnableEmbeddedAssistantUI}, {});
   ASSERT_TRUE(app_list_features::IsEmbeddedAssistantUIEnabled());
 
   Initialize(false /*is_tablet_mode*/);
@@ -2524,9 +2518,7 @@
 TEST_F(AppListViewTest, ClickOutsideEmbeddedAssistantUIToPeeking) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {chromeos::features::kAssistantFeature,
-       app_list_features::kEnableEmbeddedAssistantUI},
-      {});
+      {app_list_features::kEnableEmbeddedAssistantUI}, {});
   ASSERT_TRUE(app_list_features::IsEmbeddedAssistantUIEnabled());
 
   Initialize(false /*is_tablet_mode*/);
@@ -2561,9 +2553,7 @@
 TEST_F(AppListViewTest, ExpandArrowNotVisibleInEmbeddedAssistantUI) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
-      {chromeos::features::kAssistantFeature,
-       app_list_features::kEnableEmbeddedAssistantUI},
-      {});
+      {app_list_features::kEnableEmbeddedAssistantUI}, {});
   ASSERT_TRUE(app_list_features::IsEmbeddedAssistantUIEnabled());
 
   Initialize(false /*is_tablet_mode*/);
diff --git a/ash/app_list/views/assistant/assistant_page_view.cc b/ash/app_list/views/assistant/assistant_page_view.cc
index f266b8c5..929433a 100644
--- a/ash/app_list/views/assistant/assistant_page_view.cc
+++ b/ash/app_list/views/assistant/assistant_page_view.cc
@@ -17,6 +17,7 @@
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/assistant/ui/assistant_web_view.h"
 #include "ash/assistant/util/assistant_util.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/view_shadow.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "base/strings/utf_string_conversions.h"
@@ -157,6 +158,12 @@
   MaybeUpdateAppListState(child->GetHeightForWidth(width()));
 }
 
+void AssistantPageView::VisibilityChanged(views::View* starting_from,
+                                          bool is_visible) {
+  if (starting_from == this && !is_visible)
+    min_height_dip_ = ash::kMinHeightEmbeddedDip;
+}
+
 void AssistantPageView::OnMouseEvent(ui::MouseEvent* event) {
   switch (event->type()) {
     case ui::ET_MOUSE_PRESSED:
@@ -183,6 +190,12 @@
   }
 }
 
+void AssistantPageView::OnShown() {
+  // The preferred size might be different from the previous time, so updating
+  // to the correct size here.
+  SetSize(CalculatePreferredSize());
+}
+
 void AssistantPageView::OnAnimationStarted(ash::AppListState from_state,
                                            ash::AppListState to_state) {
   if (to_state != ash::AppListState::kStateEmbeddedAssistant)
@@ -247,8 +260,7 @@
 
   const bool prefer_voice =
       assistant_view_delegate_->IsTabletMode() ||
-      assistant_view_delegate_->GetState()->launch_with_mic_open().value_or(
-          false);
+      ash::AssistantState::Get()->launch_with_mic_open().value_or(false);
   if (!ash::assistant::util::IsVoiceEntryPoint(entry_point.value(),
                                                prefer_voice)) {
     NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
diff --git a/ash/app_list/views/assistant/assistant_page_view.h b/ash/app_list/views/assistant/assistant_page_view.h
index dc01c78..75dfe389 100644
--- a/ash/app_list/views/assistant/assistant_page_view.h
+++ b/ash/app_list/views/assistant/assistant_page_view.h
@@ -42,12 +42,14 @@
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void ChildPreferredSizeChanged(views::View* child) override;
   void ChildVisibilityChanged(views::View* child) override;
+  void VisibilityChanged(views::View* starting_from, bool is_visible) override;
 
   // ui::EventHandler:
   void OnMouseEvent(ui::MouseEvent* event) override;
   void OnGestureEvent(ui::GestureEvent* event) override;
 
   // AppListPage:
+  void OnShown() override;
   void OnAnimationStarted(ash::AppListState from_state,
                           ash::AppListState to_state) override;
   gfx::Rect GetPageBoundsForState(ash::AppListState state) const override;
diff --git a/ash/app_list/views/contents_view.cc b/ash/app_list/views/contents_view.cc
index 3e945a1..1fe3e14 100644
--- a/ash/app_list/views/contents_view.cc
+++ b/ash/app_list/views/contents_view.cc
@@ -299,9 +299,10 @@
   DCHECK_GE(assistant_page, 0);
 
   // Hide or Show results.
-  GetPageView(assistant_page)->SetVisible(show);
+  auto* page_view = GetPageView(assistant_page);
+  page_view->SetVisible(show);
   if (show)
-    GetPageView(assistant_page)->RequestFocus();
+    page_view->RequestFocus();
 
   const int search_results_page =
       GetPageIndexForState(ash::AppListState::kStateSearchResults);
@@ -311,8 +312,15 @@
   // No animation when transiting from/to |search_results_page| and in test.
   const bool animate = !AppListView::ShortAnimationsForTesting() &&
                        page_before_assistant_ != search_results_page;
+  const int current_page = pagination_model_.selected_page();
   SetActiveStateInternal(show ? assistant_page : page_before_assistant_,
                          animate);
+  // Sometimes the page stays in |assistant_page|, but the preferred bounds
+  // might change meanwhile.
+  if (show && current_page == assistant_page) {
+    page_view->SetBoundsRect(page_view->GetPageBoundsForState(
+        ash::AppListState::kStateEmbeddedAssistant));
+  }
   // If |page_before_assistant_| is kStateApps, we need to set app_list_view to
   // kPeeking and layout the suggestion chips.
   if (!show && page_before_assistant_ ==
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index c79264d..9910a19 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -695,9 +695,6 @@
       <message name="IDS_ASH_VOICE_INTERACTION_DISABLED_IN_PUBLIC_SESSION_MESSAGE" desc="Message content on the toast that appears when the voice interaction shortcut is pressed in a public session, which does not support voice interaction.">
         The Google Assistant is not available in a public session.
       </message>
-      <message name="IDS_ASH_VOICE_INTERACTION_DISABLED_MESSAGE" desc="Message content on the toast that appears when the voice interaction shortcut is pressed while the feature is disabled.">
-        The Google Assistant is disabled on this device.
-      </message>
       <message name="IDS_ASH_VOICE_INTERACTION_DISABLED_IN_GUEST_MESSAGE" desc="Message content on the toast that appears when the voice interaction shortcut is pressed in a guest session, which does not support voice interaction.">
         The Google Assistant is not available in a guest session.
       </message>
diff --git a/ash/assistant/assistant_controller.cc b/ash/assistant/assistant_controller.cc
index 2a0d86e3..c81e082 100644
--- a/ash/assistant/assistant_controller.cc
+++ b/ash/assistant/assistant_controller.cc
@@ -12,7 +12,6 @@
 #include "ash/public/cpp/android_intent_helper.h"
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/new_window_delegate.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
@@ -40,13 +39,12 @@
       assistant_alarm_timer_controller_(this),
       assistant_interaction_controller_(this),
       assistant_notification_controller_(this),
-      assistant_prefs_controller_(),
       assistant_screen_context_controller_(this),
       assistant_setup_controller_(this),
       assistant_suggestions_controller_(this),
       assistant_ui_controller_(this),
       view_delegate_(this) {
-  VoiceInteractionController::Get()->AddLocalObserver(this);
+  assistant_state_controller_.AddObserver(this);
   chromeos::CrasAudioHandler::Get()->AddAudioObserver(this);
   AddObserver(this);
 
@@ -58,7 +56,7 @@
 
   chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this);
   Shell::Get()->accessibility_controller()->RemoveObserver(this);
-  VoiceInteractionController::Get()->RemoveLocalObserver(this);
+  assistant_state_controller_.RemoveObserver(this);
   RemoveObserver(this);
 }
 
@@ -121,7 +119,7 @@
 }
 
 void AssistantController::StartSpeakerIdEnrollmentFlow() {
-  if (state()->consent_status().value_or(
+  if (assistant_state_controller_.consent_status().value_or(
           chromeos::assistant::prefs::ConsentStatus::kUnknown) ==
       chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted) {
     // If activity control has been accepted, launch the enrollment flow.
@@ -325,7 +323,7 @@
     observer.OnUrlOpened(url, from_server);
 }
 
-void AssistantController::OnVoiceInteractionStatusChanged(
+void AssistantController::OnAssistantStatusChanged(
     mojom::VoiceInteractionState state) {
   if (state == mojom::VoiceInteractionState::NOT_READY)
     assistant_ui_controller_.CloseUi(AssistantExitPoint::kUnspecified);
@@ -362,6 +360,11 @@
       ->BindRequest(std::move(receiver));
 }
 
+void AssistantController::BindStateController(
+    mojo::PendingReceiver<mojom::AssistantStateController> receiver) {
+  assistant_state_controller_.BindRequest(std::move(receiver));
+}
+
 void AssistantController::BindVolumeControl(
     mojo::PendingReceiver<mojom::AssistantVolumeControl> receiver) {
   Shell::Get()->assistant_controller()->BindRequest(std::move(receiver));
diff --git a/ash/assistant/assistant_controller.h b/ash/assistant/assistant_controller.h
index fbfe1eed..5a266e3 100644
--- a/ash/assistant/assistant_controller.h
+++ b/ash/assistant/assistant_controller.h
@@ -16,16 +16,15 @@
 #include "ash/assistant/assistant_controller_observer.h"
 #include "ash/assistant/assistant_interaction_controller.h"
 #include "ash/assistant/assistant_notification_controller.h"
-#include "ash/assistant/assistant_prefs_controller.h"
 #include "ash/assistant/assistant_screen_context_controller.h"
 #include "ash/assistant/assistant_setup_controller.h"
+#include "ash/assistant/assistant_state_controller.h"
 #include "ash/assistant/assistant_suggestions_controller.h"
 #include "ash/assistant/assistant_ui_controller.h"
 #include "ash/assistant/assistant_view_delegate_impl.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/public/cpp/assistant/assistant_image_downloader.h"
 #include "ash/public/cpp/assistant/assistant_interface_binder.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
 #include "ash/public/mojom/assistant_volume_control.mojom.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -46,16 +45,16 @@
 class AssistantAlarmTimerController;
 class AssistantInteractionController;
 class AssistantNotificationController;
-class AssistantPrefsController;
 class AssistantScreenContextController;
 class AssistantSetupController;
+class AssistantStateController;
 class AssistantSuggestionsController;
 class AssistantUiController;
 
 class ASH_EXPORT AssistantController
     : public chromeos::assistant::mojom::AssistantController,
       public AssistantControllerObserver,
-      public DefaultVoiceInteractionObserver,
+      public AssistantStateObserver,
       public mojom::AssistantVolumeControl,
       public chromeos::CrasAudioHandler::AudioObserver,
       public AccessibilityObserver,
@@ -117,8 +116,6 @@
   void GetNavigableContentsFactory(
       mojo::PendingReceiver<content::mojom::NavigableContentsFactory> receiver);
 
-  AssistantStateBase* state() { return &assistant_prefs_controller_; }
-
   AssistantAlarmTimerController* alarm_timer_controller() {
     return &assistant_alarm_timer_controller_;
   }
@@ -158,9 +155,8 @@
   void NotifyOpeningUrl(const GURL& url, bool in_background, bool from_server);
   void NotifyUrlOpened(const GURL& url, bool from_server);
 
-  // mojom::VoiceInteractionObserver:
-  void OnVoiceInteractionStatusChanged(
-      mojom::VoiceInteractionState state) override;
+  // AssistantStateObserver:
+  void OnAssistantStatusChanged(mojom::VoiceInteractionState state) override;
   void OnLockedFullScreenStateChanged(bool enabled) override;
 
   // AssistantInterfaceBinder implementation:
@@ -176,6 +172,8 @@
   void BindScreenContextController(
       mojo::PendingReceiver<mojom::AssistantScreenContextController> receiver)
       override;
+  void BindStateController(
+      mojo::PendingReceiver<mojom::AssistantStateController> receiver) override;
   void BindVolumeControl(
       mojo::PendingReceiver<mojom::AssistantVolumeControl> receiver) override;
 
@@ -196,7 +194,7 @@
   AssistantAlarmTimerController assistant_alarm_timer_controller_;
   AssistantInteractionController assistant_interaction_controller_;
   AssistantNotificationController assistant_notification_controller_;
-  AssistantPrefsController assistant_prefs_controller_;
+  AssistantStateController assistant_state_controller_;
   AssistantScreenContextController assistant_screen_context_controller_;
   AssistantSetupController assistant_setup_controller_;
   AssistantSuggestionsController assistant_suggestions_controller_;
diff --git a/ash/assistant/assistant_interaction_controller.cc b/ash/assistant/assistant_interaction_controller.cc
index 09e1a5b..0e67f3d 100644
--- a/ash/assistant/assistant_interaction_controller.cc
+++ b/ash/assistant/assistant_interaction_controller.cc
@@ -8,7 +8,6 @@
 
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/assistant/assistant_controller.h"
-#include "ash/assistant/assistant_prefs_controller.h"
 #include "ash/assistant/assistant_screen_context_controller.h"
 #include "ash/assistant/assistant_ui_controller.h"
 #include "ash/assistant/model/assistant_interaction_model_observer.h"
@@ -734,7 +733,7 @@
             assistant_controller_->ui_controller()->model()->visibility());
 
   const bool launch_with_mic_open =
-      assistant_controller_->state()->launch_with_mic_open().value_or(false);
+      AssistantState::Get()->launch_with_mic_open().value_or(false);
   const bool prefer_voice = launch_with_mic_open || IsTabletMode();
 
   // We don't explicitly start a new voice interaction if the entry point
diff --git a/ash/assistant/assistant_notification_controller.cc b/ash/assistant/assistant_notification_controller.cc
index 2025715..b3e1012 100644
--- a/ash/assistant/assistant_notification_controller.cc
+++ b/ash/assistant/assistant_notification_controller.cc
@@ -9,7 +9,6 @@
 
 #include "ash/assistant/assistant_controller.h"
 #include "ash/assistant/assistant_notification_expiry_monitor.h"
-#include "ash/assistant/assistant_prefs_controller.h"
 #include "ash/assistant/util/deep_link_util.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/public/cpp/vector_icons/vector_icons.h"
@@ -205,9 +204,8 @@
 void AssistantNotificationController::OnNotificationAdded(
     const AssistantNotification* notification) {
   // Do not show system notifications if the setting is disabled.
-  if (!assistant_controller_->state()->notification_enabled().value_or(true)) {
+  if (!AssistantState::Get()->notification_enabled().value_or(true))
     return;
-  }
 
   // We only show system notifications in the Message Center.
   if (!IsSystemNotification(notification))
@@ -220,9 +218,8 @@
 void AssistantNotificationController::OnNotificationUpdated(
     const AssistantNotification* notification) {
   // Do not show system notifications if the setting is disabled.
-  if (!assistant_controller_->state()->notification_enabled().value_or(true)) {
+  if (!AssistantState::Get()->notification_enabled().value_or(true))
     return;
-  }
 
   // If the notification that was updated is *not* a system notification, we
   // need to ensure that it is removed from the Message Center (given that it
diff --git a/ash/assistant/assistant_notification_controller_unittest.cc b/ash/assistant/assistant_notification_controller_unittest.cc
index 817ca06..e0af213 100644
--- a/ash/assistant/assistant_notification_controller_unittest.cc
+++ b/ash/assistant/assistant_notification_controller_unittest.cc
@@ -65,8 +65,6 @@
   ~AssistantNotificationControllerTest() override = default;
 
   void SetUp() override {
-    ASSERT_TRUE(chromeos::features::IsAssistantEnabled());
-
     AshTestBase::SetUp();
 
     controller_ =
diff --git a/ash/assistant/assistant_prefs_controller.cc b/ash/assistant/assistant_prefs_controller.cc
deleted file mode 100644
index 7a17d4b..0000000
--- a/ash/assistant/assistant_prefs_controller.cc
+++ /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.
-
-#include "ash/assistant/assistant_prefs_controller.h"
-
-#include "ash/session/session_controller_impl.h"
-#include "ash/shell.h"
-#include "ash/system/update/update_notification_controller.h"
-#include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
-#include "components/prefs/pref_change_registrar.h"
-#include "components/prefs/pref_registry_simple.h"
-
-namespace ash {
-
-AssistantPrefsController::AssistantPrefsController()
-    : session_observer_(this) {}
-
-AssistantPrefsController::~AssistantPrefsController() = default;
-
-void AssistantPrefsController::InitializeObserver(
-    AssistantStateObserver* observer) {
-  if (consent_status_.has_value())
-    observer->OnAssistantConsentStatusChanged(consent_status_.value());
-}
-
-void AssistantPrefsController::UpdateState() {
-  UpdateConsentStatus();
-  UpdateHotwordAlwaysOn();
-  UpdateLaunchWithMicOpen();
-  UpdateNotificationEnabled();
-}
-
-void AssistantPrefsController::OnActiveUserPrefServiceChanged(
-    PrefService* pref_service) {
-  pref_change_registrar_.reset();
-
-  // Skip for non-primary user prefs.
-  PrefService* primary_user_prefs =
-      Shell::Get()->session_controller()->GetPrimaryUserPrefService();
-  if (!primary_user_prefs || primary_user_prefs != pref_service)
-    return;
-
-  // Register preference changes.
-  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
-  pref_change_registrar_->Init(pref_service);
-  pref_change_registrar_->Add(
-      chromeos::assistant::prefs::kAssistantConsentStatus,
-      base::BindRepeating(&AssistantPrefsController::UpdateConsentStatus,
-                          base::Unretained(this)));
-  pref_change_registrar_->Add(
-      chromeos::assistant::prefs::kAssistantHotwordAlwaysOn,
-      base::BindRepeating(&AssistantPrefsController::UpdateHotwordAlwaysOn,
-                          base::Unretained(this)));
-  pref_change_registrar_->Add(
-      chromeos::assistant::prefs::kAssistantLaunchWithMicOpen,
-      base::BindRepeating(&AssistantPrefsController::UpdateLaunchWithMicOpen,
-                          base::Unretained(this)));
-  pref_change_registrar_->Add(
-      chromeos::assistant::prefs::kAssistantNotificationEnabled,
-      base::BindRepeating(&AssistantPrefsController::UpdateNotificationEnabled,
-                          base::Unretained(this)));
-
-  UpdateState();
-}
-
-void AssistantPrefsController::UpdateConsentStatus() {
-  auto consent_status = pref_change_registrar_->prefs()->GetInteger(
-      chromeos::assistant::prefs::kAssistantConsentStatus);
-  if (consent_status_.has_value() &&
-      consent_status_.value() == consent_status) {
-    return;
-  }
-  consent_status_ = consent_status;
-  for (auto& observer : observers_)
-    observer.OnAssistantConsentStatusChanged(consent_status_.value());
-}
-
-void AssistantPrefsController::UpdateHotwordAlwaysOn() {
-  auto hotword_always_on = pref_change_registrar_->prefs()->GetBoolean(
-      chromeos::assistant::prefs::kAssistantHotwordAlwaysOn);
-  if (hotword_always_on_.has_value() &&
-      hotword_always_on_.value() == hotword_always_on) {
-    return;
-  }
-  hotword_always_on_ = hotword_always_on;
-}
-
-void AssistantPrefsController::UpdateLaunchWithMicOpen() {
-  auto launch_with_mic_open = pref_change_registrar_->prefs()->GetBoolean(
-      chromeos::assistant::prefs::kAssistantLaunchWithMicOpen);
-  if (launch_with_mic_open_.has_value() &&
-      launch_with_mic_open_.value() == launch_with_mic_open) {
-    return;
-  }
-  launch_with_mic_open_ = launch_with_mic_open;
-}
-
-void AssistantPrefsController::UpdateNotificationEnabled() {
-  auto notification_enabled = pref_change_registrar_->prefs()->GetBoolean(
-      chromeos::assistant::prefs::kAssistantNotificationEnabled);
-  if (notification_enabled_.has_value() &&
-      notification_enabled_.value() == notification_enabled) {
-    return;
-  }
-  notification_enabled_ = notification_enabled;
-}
-
-}  // namespace ash
diff --git a/ash/assistant/assistant_prefs_controller.h b/ash/assistant/assistant_prefs_controller.h
deleted file mode 100644
index a487db3..0000000
--- a/ash/assistant/assistant_prefs_controller.h
+++ /dev/null
@@ -1,53 +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 ASH_ASSISTANT_ASSISTANT_PREFS_CONTROLLER_H_
-#define ASH_ASSISTANT_ASSISTANT_PREFS_CONTROLLER_H_
-
-#include "ash/ash_export.h"
-#include "ash/public/cpp/assistant/assistant_state_base.h"
-#include "ash/session/session_observer.h"
-#include "base/macros.h"
-#include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
-
-class PrefChangeRegistrar;
-
-namespace ash {
-
-// Provide access of Assistant related preferences to clients in ash.
-class ASH_EXPORT AssistantPrefsController : public SessionObserver,
-                                            public AssistantStateBase {
- public:
-  AssistantPrefsController();
-  ~AssistantPrefsController() override;
-
-  // AssistantStateBase:
-  void InitializeObserver(AssistantStateObserver* observer) override;
-
- private:
-  // Update pref cache and notify observers when primary user prefs becomes
-  // active.
-  void UpdateState();
-
-  // SessionObserver:
-  void OnActiveUserPrefServiceChanged(PrefService* pref_service) override;
-
-  // Called when the related preferences are obtained from the pref service.
-  void UpdateConsentStatus();
-  void UpdateHotwordAlwaysOn();
-  void UpdateLaunchWithMicOpen();
-  void UpdateNotificationEnabled();
-
-  // TODO(b/138679823): Move related logics into AssistantStateBase.
-  // Observes user profile prefs for the Assistant.
-  std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
-
-  ScopedSessionObserver session_observer_;
-
-  DISALLOW_COPY_AND_ASSIGN(AssistantPrefsController);
-};
-
-}  // namespace ash
-
-#endif  // ASH_ASSISTANT_ASSISTANT_PREFS_CONTROLLER_H_
diff --git a/ash/assistant/assistant_prefs_controller_unittest.cc b/ash/assistant/assistant_prefs_controller_unittest.cc
deleted file mode 100644
index 54146d57..0000000
--- a/ash/assistant/assistant_prefs_controller_unittest.cc
+++ /dev/null
@@ -1,96 +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 "ash/assistant/assistant_prefs_controller.h"
-
-#include <memory>
-
-#include "ash/assistant/assistant_controller.h"
-#include "ash/session/session_controller_impl.h"
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
-#include "chromeos/constants/chromeos_features.h"
-#include "components/prefs/pref_service.h"
-
-namespace ash {
-
-class TestAssistantPrefsObserver : public AssistantStateObserver {
- public:
-  TestAssistantPrefsObserver() = default;
-  ~TestAssistantPrefsObserver() override = default;
-
-  // AssistantPrefsObserver:
-  void OnAssistantConsentStatusChanged(int consent_status) override {
-    consent_status_ = consent_status;
-  }
-
-  int consent_status() { return consent_status_; }
-
- private:
-  int consent_status_ = chromeos::assistant::prefs::ConsentStatus::kUnknown;
-
-  DISALLOW_COPY_AND_ASSIGN(TestAssistantPrefsObserver);
-};
-
-class AssistantPrefsControllerTest : public AshTestBase {
- protected:
-  AssistantPrefsControllerTest() = default;
-  ~AssistantPrefsControllerTest() override = default;
-
-  void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        chromeos::features::kAssistantFeature);
-    ASSERT_TRUE(chromeos::features::IsAssistantEnabled());
-
-    AshTestBase::SetUp();
-
-    prefs_ = Shell::Get()->session_controller()->GetPrimaryUserPrefService();
-    DCHECK(prefs_);
-
-    observer_ = std::make_unique<TestAssistantPrefsObserver>();
-  }
-
-  PrefService* prefs() { return prefs_; }
-
-  TestAssistantPrefsObserver* observer() { return observer_.get(); }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-
-  PrefService* prefs_ = nullptr;
-  std::unique_ptr<TestAssistantPrefsObserver> observer_;
-
-  DISALLOW_COPY_AND_ASSIGN(AssistantPrefsControllerTest);
-};
-
-TEST_F(AssistantPrefsControllerTest, InitObserver) {
-  prefs()->SetInteger(
-      chromeos::assistant::prefs::kAssistantConsentStatus,
-      chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted);
-
-  // The observer class should get an instant notification about the current
-  // pref value.
-  Shell::Get()->assistant_controller()->state()->AddObserver(observer());
-  EXPECT_EQ(chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted,
-            observer()->consent_status());
-}
-
-TEST_F(AssistantPrefsControllerTest, NotifyConsentStatus) {
-  Shell::Get()->assistant_controller()->state()->AddObserver(observer());
-
-  prefs()->SetInteger(chromeos::assistant::prefs::kAssistantConsentStatus,
-                      chromeos::assistant::prefs::ConsentStatus::kUnauthorized);
-  EXPECT_EQ(chromeos::assistant::prefs::ConsentStatus::kUnauthorized,
-            observer()->consent_status());
-
-  prefs()->SetInteger(
-      chromeos::assistant::prefs::kAssistantConsentStatus,
-      chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted);
-  EXPECT_EQ(chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted,
-            observer()->consent_status());
-}
-
-}  // namespace ash
diff --git a/ash/assistant/assistant_screen_context_controller_unittest.cc b/ash/assistant/assistant_screen_context_controller_unittest.cc
index addb756..2223c092 100644
--- a/ash/assistant/assistant_screen_context_controller_unittest.cc
+++ b/ash/assistant/assistant_screen_context_controller_unittest.cc
@@ -14,7 +14,6 @@
 #include "ash/wm/desks/desks_util.h"
 #include "base/bind.h"
 #include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer.h"
@@ -46,10 +45,6 @@
   ~AssistantScreenContextControllerTest() override = default;
 
   void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        chromeos::features::kAssistantFeature);
-    ASSERT_TRUE(chromeos::features::IsAssistantEnabled());
-
     AshTestBase::SetUp();
 
     controller_ =
@@ -60,8 +55,6 @@
   ash::AssistantScreenContextController* controller() { return controller_; }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-
   AssistantScreenContextController* controller_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(AssistantScreenContextControllerTest);
diff --git a/ash/assistant/assistant_setup_controller.cc b/ash/assistant/assistant_setup_controller.cc
index 5363547..78500937 100644
--- a/ash/assistant/assistant_setup_controller.cc
+++ b/ash/assistant/assistant_setup_controller.cc
@@ -8,7 +8,6 @@
 #include "ash/assistant/assistant_ui_controller.h"
 #include "ash/assistant/util/deep_link_util.h"
 #include "ash/assistant/util/i18n_util.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "ash/shell.h"
 #include "base/bind.h"
@@ -58,7 +57,7 @@
 }
 
 void AssistantSetupController::OnOptInButtonPressed() {
-  if (assistant_controller_->state()->consent_status().value_or(
+  if (AssistantState::Get()->consent_status().value_or(
           chromeos::assistant::prefs::ConsentStatus::kUnknown) ==
       chromeos::assistant::prefs::ConsentStatus::kUnauthorized) {
     assistant_controller_->OpenUrl(assistant::util::CreateLocalizedGURL(
diff --git a/ash/assistant/assistant_state_controller.cc b/ash/assistant/assistant_state_controller.cc
new file mode 100644
index 0000000..9fa1ef2
--- /dev/null
+++ b/ash/assistant/assistant_state_controller.cc
@@ -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.
+
+#include "ash/assistant/assistant_state_controller.h"
+
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
+#include "chromeos/constants/chromeos_features.h"
+
+namespace ash {
+
+AssistantStateController::AssistantStateController()
+    : session_observer_(this) {}
+
+AssistantStateController::~AssistantStateController() = default;
+
+void AssistantStateController::OnActiveUserPrefServiceChanged(
+    PrefService* pref_service) {
+  // For non-primary prefs, calling the method with nullptr will reset the
+  // current registry.
+  PrefService* primary_user_prefs =
+      Shell::Get()->session_controller()->GetPrimaryUserPrefService();
+  RegisterPrefChanges(primary_user_prefs == pref_service ? primary_user_prefs
+                                                         : nullptr);
+}
+
+}  // namespace ash
diff --git a/ash/assistant/assistant_state_controller.h b/ash/assistant/assistant_state_controller.h
new file mode 100644
index 0000000..6acdeb2
--- /dev/null
+++ b/ash/assistant/assistant_state_controller.h
@@ -0,0 +1,33 @@
+// 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 ASH_ASSISTANT_ASSISTANT_STATE_CONTROLLER_H_
+#define ASH_ASSISTANT_ASSISTANT_STATE_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
+#include "ash/session/session_observer.h"
+#include "base/macros.h"
+
+namespace ash {
+
+// Provide access of Assistant related prefs and states to the clients.
+class ASH_EXPORT AssistantStateController : public AssistantState,
+                                            public SessionObserver {
+ public:
+  AssistantStateController();
+  ~AssistantStateController() override;
+
+ private:
+  // SessionObserver:
+  void OnActiveUserPrefServiceChanged(PrefService* pref_service) override;
+
+  ScopedSessionObserver session_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(AssistantStateController);
+};
+
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_ASSISTANT_STATE_CONTROLLER_H_
diff --git a/ash/assistant/assistant_state_controller_unittest.cc b/ash/assistant/assistant_state_controller_unittest.cc
new file mode 100644
index 0000000..286cf8d
--- /dev/null
+++ b/ash/assistant/assistant_state_controller_unittest.cc
@@ -0,0 +1,154 @@
+// 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 "ash/assistant/assistant_state_controller.h"
+
+#include <memory>
+
+#include "ash/assistant/assistant_controller.h"
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/macros.h"
+#include "components/prefs/pref_service.h"
+
+namespace ash {
+
+class TestAssistantStateObserver : public AssistantStateObserver {
+ public:
+  TestAssistantStateObserver() = default;
+  ~TestAssistantStateObserver() override = default;
+
+  // AssistantStateObserver:
+  void OnAssistantConsentStatusChanged(int consent_status) override {
+    consent_status_ = consent_status;
+  }
+  void OnAssistantHotwordAlwaysOn(bool hotword_always_on) override {
+    hotword_always_on_ = hotword_always_on;
+  }
+  void OnAssistantLaunchWithMicOpen(bool launch_with_mic_open) override {
+    launch_with_mic_open_ = launch_with_mic_open;
+  }
+  void OnAssistantNotificationEnabled(bool notification_enabled) override {
+    notification_enabled_ = notification_enabled;
+  }
+
+  int consent_status() const { return consent_status_; }
+  bool hotword_always_on() const { return hotword_always_on_; }
+  bool launch_with_mic_open() const { return launch_with_mic_open_; }
+  bool notification_enabled() const { return notification_enabled_; }
+
+ private:
+  int consent_status_ = chromeos::assistant::prefs::ConsentStatus::kUnknown;
+  bool hotword_always_on_ = false;
+  bool launch_with_mic_open_ = false;
+  bool notification_enabled_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(TestAssistantStateObserver);
+};
+
+class AssistantStateControllerTest : public AshTestBase {
+ protected:
+  AssistantStateControllerTest() = default;
+  ~AssistantStateControllerTest() override = default;
+
+  // AshTestBase:
+  void SetUp() override {
+    AshTestBase::SetUp();
+
+    prefs_ = Shell::Get()->session_controller()->GetPrimaryUserPrefService();
+    DCHECK(prefs_);
+
+    observer_ = std::make_unique<TestAssistantStateObserver>();
+  }
+
+  PrefService* prefs() { return prefs_; }
+
+  TestAssistantStateObserver* observer() { return observer_.get(); }
+
+ private:
+  PrefService* prefs_ = nullptr;
+  std::unique_ptr<TestAssistantStateObserver> observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(AssistantStateControllerTest);
+};
+
+TEST_F(AssistantStateControllerTest, InitObserver) {
+  prefs()->SetInteger(
+      chromeos::assistant::prefs::kAssistantConsentStatus,
+      chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted);
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantHotwordAlwaysOn,
+                      true);
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantLaunchWithMicOpen,
+                      true);
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantNotificationEnabled,
+                      true);
+
+  // The observer class should get an instant notification about the current
+  // pref value.
+  AssistantState::Get()->AddObserver(observer());
+  EXPECT_EQ(chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted,
+            observer()->consent_status());
+  EXPECT_EQ(observer()->hotword_always_on(), true);
+  EXPECT_EQ(observer()->launch_with_mic_open(), true);
+  EXPECT_EQ(observer()->notification_enabled(), true);
+  AssistantState::Get()->RemoveObserver(observer());
+}
+
+TEST_F(AssistantStateControllerTest, NotifyConsentStatus) {
+  AssistantState::Get()->AddObserver(observer());
+
+  prefs()->SetInteger(chromeos::assistant::prefs::kAssistantConsentStatus,
+                      chromeos::assistant::prefs::ConsentStatus::kUnauthorized);
+  EXPECT_EQ(chromeos::assistant::prefs::ConsentStatus::kUnauthorized,
+            observer()->consent_status());
+
+  prefs()->SetInteger(
+      chromeos::assistant::prefs::kAssistantConsentStatus,
+      chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted);
+  EXPECT_EQ(chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted,
+            observer()->consent_status());
+  AssistantState::Get()->RemoveObserver(observer());
+}
+
+TEST_F(AssistantStateControllerTest, NotifyHotwordAlwaysOn) {
+  AssistantState::Get()->AddObserver(observer());
+
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantHotwordAlwaysOn,
+                      false);
+  EXPECT_EQ(observer()->hotword_always_on(), false);
+
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantHotwordAlwaysOn,
+                      true);
+  EXPECT_EQ(observer()->hotword_always_on(), true);
+  AssistantState::Get()->RemoveObserver(observer());
+}
+
+TEST_F(AssistantStateControllerTest, NotifyLaunchWithMicOpen) {
+  AssistantState::Get()->AddObserver(observer());
+
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantLaunchWithMicOpen,
+                      false);
+  EXPECT_EQ(observer()->launch_with_mic_open(), false);
+
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantLaunchWithMicOpen,
+                      true);
+  EXPECT_EQ(observer()->launch_with_mic_open(), true);
+  AssistantState::Get()->RemoveObserver(observer());
+}
+
+TEST_F(AssistantStateControllerTest, NotifyNotificationEnabled) {
+  AssistantState::Get()->AddObserver(observer());
+
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantNotificationEnabled,
+                      false);
+  EXPECT_EQ(observer()->notification_enabled(), false);
+
+  prefs()->SetBoolean(chromeos::assistant::prefs::kAssistantNotificationEnabled,
+                      true);
+  EXPECT_EQ(observer()->notification_enabled(), true);
+  AssistantState::Get()->RemoveObserver(observer());
+}
+
+}  // namespace ash
diff --git a/ash/assistant/assistant_suggestions_controller.cc b/ash/assistant/assistant_suggestions_controller.cc
index 7aa617c..60cf1149 100644
--- a/ash/assistant/assistant_suggestions_controller.cc
+++ b/ash/assistant/assistant_suggestions_controller.cc
@@ -13,7 +13,6 @@
 #include "ash/assistant/util/deep_link_util.h"
 #include "ash/public/cpp/assistant/proactive_suggestions.h"
 #include "ash/public/cpp/assistant/proactive_suggestions_client.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "base/feature_list.h"
@@ -69,7 +68,7 @@
     : assistant_controller_(assistant_controller) {
   UpdateConversationStarters();
   assistant_controller_->AddObserver(this);
-  VoiceInteractionController::Get()->AddLocalObserver(this);
+  AssistantState::Get()->AddObserver(this);
 }
 
 AssistantSuggestionsController::~AssistantSuggestionsController() {
@@ -78,7 +77,7 @@
     client->SetDelegate(nullptr);
 
   assistant_controller_->RemoveObserver(this);
-  VoiceInteractionController::Get()->RemoveLocalObserver(this);
+  AssistantState::Get()->RemoveObserver(this);
 }
 
 void AssistantSuggestionsController::AddModelObserver(
@@ -118,8 +117,7 @@
     UpdateConversationStarters();
 }
 
-void AssistantSuggestionsController::OnVoiceInteractionContextEnabled(
-    bool enabled) {
+void AssistantSuggestionsController::OnAssistantContextEnabled(bool enabled) {
   UpdateConversationStarters();
 }
 
@@ -159,7 +157,7 @@
 
   // If enabled, always show the "What's on my screen?" conversation starter.
   if (kWhatsOnMyScreenChipEnabled.Get() &&
-      VoiceInteractionController::Get()->context_enabled().value_or(false)) {
+      AssistantState::Get()->context_enabled().value_or(false)) {
     AddConversationStarter(IDS_ASH_ASSISTANT_CHIP_WHATS_ON_MY_SCREEN,
                            assistant::util::CreateWhatsOnMyScreenDeepLink());
   }
diff --git a/ash/assistant/assistant_suggestions_controller.h b/ash/assistant/assistant_suggestions_controller.h
index 78451d4..68a2bd37 100644
--- a/ash/assistant/assistant_suggestions_controller.h
+++ b/ash/assistant/assistant_suggestions_controller.h
@@ -10,7 +10,7 @@
 #include "ash/assistant/assistant_controller_observer.h"
 #include "ash/assistant/model/assistant_suggestions_model.h"
 #include "ash/assistant/model/assistant_ui_model_observer.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/assistant/proactive_suggestions_client.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "base/macros.h"
@@ -23,7 +23,7 @@
 class AssistantSuggestionsController
     : public AssistantControllerObserver,
       public AssistantUiModelObserver,
-      public DefaultVoiceInteractionObserver,
+      public AssistantStateObserver,
       public ProactiveSuggestionsClient::Delegate {
  public:
   explicit AssistantSuggestionsController(
@@ -55,8 +55,8 @@
       scoped_refptr<ProactiveSuggestions> proactive_suggestions) override;
 
  private:
-  // DefaultVoiceInteractionObserver:
-  void OnVoiceInteractionContextEnabled(bool enabled) override;
+  // AssistantStateObserver:
+  void OnAssistantContextEnabled(bool enabled) override;
 
   void UpdateConversationStarters();
 
diff --git a/ash/assistant/assistant_ui_controller.cc b/ash/assistant/assistant_ui_controller.cc
index ed5bbed..25e575d 100644
--- a/ash/assistant/assistant_ui_controller.cc
+++ b/ash/assistant/assistant_ui_controller.cc
@@ -18,7 +18,6 @@
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/assistant/assistant_setup.h"
 #include "ash/public/cpp/toast_data.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -321,7 +320,7 @@
     AssistantVisibility old_visibility,
     base::Optional<AssistantEntryPoint> entry_point,
     base::Optional<AssistantExitPoint> exit_point) {
-  VoiceInteractionController::Get()->NotifyStatusChanged(
+  AssistantState::Get()->NotifyStatusChanged(
       new_visibility == AssistantVisibility::kVisible
           ? mojom::VoiceInteractionState::RUNNING
           : mojom::VoiceInteractionState::STOPPED);
@@ -402,16 +401,15 @@
   if (assistant_setup && assistant_setup->BounceOptInWindowIfActive())
     return;
 
-  auto* voice_interaction_controller = VoiceInteractionController::Get();
+  auto* assistant_state = AssistantState::Get();
 
-  if (!voice_interaction_controller->settings_enabled().value_or(false) ||
-      voice_interaction_controller->locked_full_screen_enabled().value_or(
-          false)) {
+  if (!assistant_state->settings_enabled().value_or(false) ||
+      assistant_state->locked_full_screen_enabled().value_or(false)) {
     return;
   }
 
   // TODO(dmblack): Show a more helpful message to the user.
-  if (VoiceInteractionController::Get()->voice_interaction_state() ==
+  if (assistant_state->voice_interaction_state() ==
       mojom::VoiceInteractionState::NOT_READY) {
     ShowToast(kUnboundServiceToastId, IDS_ASH_ASSISTANT_ERROR_GENERIC);
     return;
diff --git a/ash/assistant/assistant_view_delegate_impl.cc b/ash/assistant/assistant_view_delegate_impl.cc
index 107c56b..2795420 100644
--- a/ash/assistant/assistant_view_delegate_impl.cc
+++ b/ash/assistant/assistant_view_delegate_impl.cc
@@ -8,8 +8,8 @@
 #include "ash/assistant/assistant_controller_observer.h"
 #include "ash/assistant/assistant_interaction_controller.h"
 #include "ash/assistant/assistant_notification_controller.h"
-#include "ash/assistant/assistant_prefs_controller.h"
 #include "ash/assistant/assistant_suggestions_controller.h"
+#include "ash/public/cpp/assistant/assistant_state_base.h"
 #include "ash/shell.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 
@@ -50,16 +50,6 @@
   view_delegate_observers_.RemoveObserver(observer);
 }
 
-void AssistantViewDelegateImpl::AddStateObserver(
-    AssistantStateObserver* observer) {
-  assistant_controller_->state()->AddObserver(observer);
-}
-
-void AssistantViewDelegateImpl::RemoveStateObserver(
-    AssistantStateObserver* observer) {
-  assistant_controller_->state()->RemoveObserver(observer);
-}
-
 void AssistantViewDelegateImpl::AddInteractionModelObserver(
     AssistantInteractionModelObserver* observer) {
   assistant_controller_->interaction_controller()->AddModelObserver(observer);
@@ -113,10 +103,6 @@
   assistant_controller_->DownloadImage(url, std::move(callback));
 }
 
-AssistantStateBase* AssistantViewDelegateImpl::GetState() const {
-  return assistant_controller_->state();
-}
-
 ::wm::CursorManager* AssistantViewDelegateImpl::GetCursorManager() {
   return Shell::Get()->cursor_manager();
 }
diff --git a/ash/assistant/assistant_view_delegate_impl.h b/ash/assistant/assistant_view_delegate_impl.h
index 1effce5..defe6d0 100644
--- a/ash/assistant/assistant_view_delegate_impl.h
+++ b/ash/assistant/assistant_view_delegate_impl.h
@@ -29,8 +29,6 @@
   const AssistantUiModel* GetUiModel() const override;
   void AddObserver(AssistantViewDelegateObserver* observer) override;
   void RemoveObserver(AssistantViewDelegateObserver* observer) override;
-  void AddStateObserver(AssistantStateObserver* observer) override;
-  void RemoveStateObserver(AssistantStateObserver* observer) override;
   void AddInteractionModelObserver(
       AssistantInteractionModelObserver* observer) override;
   void RemoveInteractionModelObserver(
@@ -49,7 +47,6 @@
   void DownloadImage(
       const GURL& url,
       AssistantImageDownloader::DownloadCallback callback) override;
-  AssistantStateBase* GetState() const override;
   ::wm::CursorManager* GetCursorManager() override;
   void GetNavigableContentsFactoryForView(
       mojo::PendingReceiver<content::mojom::NavigableContentsFactory> receiver)
diff --git a/ash/assistant/test/assistant_ash_test_base.cc b/ash/assistant/test/assistant_ash_test_base.cc
index 68d86889..46a219d9 100644
--- a/ash/assistant/test/assistant_ash_test_base.cc
+++ b/ash/assistant/test/assistant_ash_test_base.cc
@@ -12,7 +12,6 @@
 #include "ash/app_list/views/contents_view.h"
 #include "ash/assistant/assistant_controller.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/shell.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 
@@ -29,7 +28,7 @@
   AshTestBase::SetUp();
 
   // Enable Assistant in settings.
-  VoiceInteractionController::Get()->NotifySettingsEnabled(true);
+  AssistantState::Get()->NotifySettingsEnabled(true);
 
   // Cache controller.
   controller_ = Shell::Get()->assistant_controller();
@@ -37,7 +36,7 @@
 
   // At this point our Assistant service is ready for use.
   // Indicate this by changing status from NOT_READY to STOPPED.
-  VoiceInteractionController::Get()->NotifyStatusChanged(
+  AssistantState::Get()->NotifyStatusChanged(
       mojom::VoiceInteractionState::STOPPED);
 
   DisableAnimations();
diff --git a/ash/assistant/ui/DEPS b/ash/assistant/ui/DEPS
index 3db41ee..bea464e 100644
--- a/ash/assistant/ui/DEPS
+++ b/ash/assistant/ui/DEPS
@@ -1,7 +1,6 @@
 noparent = True
 
 include_rules = [
-  "+ash/assistant/assistant_prefs_controller.h",
   "+ash/assistant/model",
   "+ash/assistant/ui",
   "+ash/assistant/util",
diff --git a/ash/assistant/ui/assistant_container_view_unittest.cc b/ash/assistant/ui/assistant_container_view_unittest.cc
index ed7c5173..581dc8176 100644
--- a/ash/assistant/ui/assistant_container_view_unittest.cc
+++ b/ash/assistant/ui/assistant_container_view_unittest.cc
@@ -8,7 +8,6 @@
 
 #include "ash/assistant/assistant_controller.h"
 #include "ash/assistant/assistant_ui_controller.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -32,9 +31,6 @@
   void SetUp() override {
     AshTestBase::SetUp();
 
-    // Enable Assistant in settings.
-    VoiceInteractionController::Get()->NotifySettingsEnabled(true);
-
     // Cache controller.
     controller_ = Shell::Get()->assistant_controller();
     DCHECK(controller_);
@@ -43,9 +39,12 @@
     ui_controller_ = controller_->ui_controller();
     DCHECK(ui_controller_);
 
+    // Enable Assistant in settings.
+    AssistantState::Get()->NotifySettingsEnabled(true);
+
     // After mocks are set up our Assistant service is ready for use. Indicate
     // this by changing status from NOT_READY to STOPPED.
-    VoiceInteractionController::Get()->NotifyStatusChanged(
+    AssistantState::Get()->NotifyStatusChanged(
         mojom::VoiceInteractionState::STOPPED);
   }
 
diff --git a/ash/assistant/ui/assistant_view_delegate.h b/ash/assistant/ui/assistant_view_delegate.h
index a5c7049..dde46c43 100644
--- a/ash/assistant/ui/assistant_view_delegate.h
+++ b/ash/assistant/ui/assistant_view_delegate.h
@@ -8,7 +8,6 @@
 #include <map>
 #include <string>
 
-#include "ash/assistant/assistant_prefs_controller.h"
 #include "ash/assistant/model/assistant_interaction_model.h"
 #include "ash/assistant/model/assistant_interaction_model_observer.h"
 #include "ash/assistant/model/assistant_notification_model.h"
@@ -22,7 +21,7 @@
 #include "ash/assistant/ui/dialog_plate/dialog_plate.h"
 #include "ash/assistant/ui/main_stage/assistant_opt_in_view.h"
 #include "ash/public/cpp/assistant/assistant_image_downloader.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "base/component_export.h"
 #include "base/observer_list_types.h"
 #include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
@@ -96,10 +95,6 @@
   virtual void AddObserver(AssistantViewDelegateObserver* observer) = 0;
   virtual void RemoveObserver(AssistantViewDelegateObserver* observer) = 0;
 
-  // Adds/removes the state observer associated with the view delegate.
-  virtual void AddStateObserver(AssistantStateObserver* observer) = 0;
-  virtual void RemoveStateObserver(AssistantStateObserver* observer) = 0;
-
   // Adds/removes the interaction model observer associated with the view
   // delegate.
   virtual void AddInteractionModelObserver(
@@ -135,8 +130,6 @@
       const GURL& url,
       AssistantImageDownloader::DownloadCallback callback) = 0;
 
-  virtual AssistantStateBase* GetState() const = 0;
-
   // Returns the cursor_manager.
   virtual ::wm::CursorManager* GetCursorManager() = 0;
 
diff --git a/ash/assistant/ui/main_stage/assistant_footer_view.cc b/ash/assistant/ui/main_stage/assistant_footer_view.cc
index 1406a30..d81e659 100644
--- a/ash/assistant/ui/main_stage/assistant_footer_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_footer_view.cc
@@ -45,11 +45,11 @@
               &AssistantFooterView::OnAnimationEnded,
               base::Unretained(this)))) {
   InitLayout();
-  delegate_->AddStateObserver(this);
+  AssistantState::Get()->AddObserver(this);
 }
 
 AssistantFooterView::~AssistantFooterView() {
-  delegate_->RemoveStateObserver(this);
+  AssistantState::Get()->RemoveObserver(this);
 }
 
 const char* AssistantFooterView::GetClassName() const {
@@ -69,7 +69,7 @@
 
   // Initial view state is based on user consent state.
   const bool consent_given =
-      delegate_->GetState()->consent_status().value_or(
+      AssistantState::Get()->consent_status().value_or(
           chromeos::assistant::prefs::ConsentStatus::kUnknown) ==
       chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted;
 
@@ -153,7 +153,7 @@
 bool AssistantFooterView::OnAnimationEnded(
     const ui::CallbackLayerAnimationObserver& observer) {
   const bool consent_given =
-      delegate_->GetState()->consent_status().value_or(
+      AssistantState::Get()->consent_status().value_or(
           chromeos::assistant::prefs::ConsentStatus::kUnknown) ==
       chromeos::assistant::prefs::ConsentStatus::kActivityControlAccepted;
 
diff --git a/ash/assistant/ui/main_stage/assistant_footer_view.h b/ash/assistant/ui/main_stage/assistant_footer_view.h
index 8a71075f..26fe0ed 100644
--- a/ash/assistant/ui/main_stage/assistant_footer_view.h
+++ b/ash/assistant/ui/main_stage/assistant_footer_view.h
@@ -8,7 +8,7 @@
 #include <memory>
 #include <string>
 
-#include "ash/assistant/assistant_prefs_controller.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "ui/views/view.h"
diff --git a/ash/assistant/ui/main_stage/assistant_opt_in_view.cc b/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
index 418460c..7fdb3f02 100644
--- a/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
@@ -90,11 +90,11 @@
 AssistantOptInView::AssistantOptInView(AssistantViewDelegate* delegate)
     : delegate_(delegate) {
   InitLayout();
-  delegate_->AddStateObserver(this);
+  AssistantState::Get()->AddObserver(this);
 }
 
 AssistantOptInView::~AssistantOptInView() {
-  delegate_->RemoveStateObserver(this);
+  AssistantState::Get()->RemoveObserver(this);
 }
 
 const char* AssistantOptInView::GetClassName() const {
@@ -152,7 +152,7 @@
   container_->AddChildView(label_);
   container_->SetFocusForPlatform();
 
-  UpdateLabel(delegate_->GetState()->consent_status().value_or(
+  UpdateLabel(AssistantState::Get()->consent_status().value_or(
       chromeos::assistant::prefs::ConsentStatus::kUnknown));
 }
 
diff --git a/ash/assistant/ui/main_stage/assistant_opt_in_view.h b/ash/assistant/ui/main_stage/assistant_opt_in_view.h
index a516a529..06005c8a 100644
--- a/ash/assistant/ui/main_stage/assistant_opt_in_view.h
+++ b/ash/assistant/ui/main_stage/assistant_opt_in_view.h
@@ -5,7 +5,7 @@
 #ifndef ASH_ASSISTANT_UI_MAIN_STAGE_ASSISTANT_OPT_IN_VIEW_H_
 #define ASH_ASSISTANT_UI_MAIN_STAGE_ASSISTANT_OPT_IN_VIEW_H_
 
-#include "ash/assistant/assistant_prefs_controller.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "ui/views/controls/button/button.h"
diff --git a/ash/mojo_interface_factory.cc b/ash/mojo_interface_factory.cc
index dd6f0dc5..c7f678f 100644
--- a/ash/mojo_interface_factory.cc
+++ b/ash/mojo_interface_factory.cc
@@ -12,7 +12,6 @@
 #include "ash/login/login_screen_controller.h"
 #include "ash/media/media_controller_impl.h"
 #include "ash/public/cpp/ash_features.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
 #include "ash/system/network/vpn_list.h"
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 3022f72..7b706feb 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -66,11 +66,12 @@
     "assistant/assistant_interface_binder.h",
     "assistant/assistant_setup.cc",
     "assistant/assistant_setup.h",
+    "assistant/assistant_state.cc",
+    "assistant/assistant_state.h",
     "assistant/assistant_state_base.cc",
     "assistant/assistant_state_base.h",
     "assistant/assistant_state_proxy.cc",
     "assistant/assistant_state_proxy.h",
-    "assistant/default_voice_interaction_observer.h",
     "assistant/proactive_suggestions.cc",
     "assistant/proactive_suggestions.h",
     "assistant/proactive_suggestions_client.cc",
@@ -210,8 +211,6 @@
     "update_types.h",
     "view_shadow.cc",
     "view_shadow.h",
-    "voice_interaction_controller.cc",
-    "voice_interaction_controller.h",
     "wallpaper_controller.cc",
     "wallpaper_controller.h",
     "wallpaper_controller_client.h",
@@ -234,6 +233,7 @@
     "//ash/public/cpp/vector_icons",
     "//chromeos/constants",
     "//chromeos/dbus/power:power_manager_proto",
+    "//chromeos/services/assistant/public/cpp:prefs",
     "//chromeos/services/network_config:in_process_instance",
     "//components/prefs",
     "//components/sync:rest_of_sync",
@@ -313,7 +313,6 @@
     "rounded_corner_decorator_unittest.cc",
     "shelf_model_unittest.cc",
     "view_shadow_unittest.cc",
-    "voice_interaction_controller_unittest.cc",
   ]
 
   deps = [
diff --git a/ash/public/cpp/app_list/app_list_features.cc b/ash/public/cpp/app_list/app_list_features.cc
index 122524ee0..7241f36 100644
--- a/ash/public/cpp/app_list/app_list_features.cc
+++ b/ash/public/cpp/app_list/app_list_features.cc
@@ -97,8 +97,7 @@
 }
 
 bool IsEmbeddedAssistantUIEnabled() {
-  return chromeos::features::IsAssistantEnabled() &&
-         base::FeatureList::IsEnabled(kEnableEmbeddedAssistantUI);
+  return base::FeatureList::IsEnabled(kEnableEmbeddedAssistantUI);
 }
 
 bool IsAppGridGhostEnabled() {
diff --git a/ash/public/cpp/assistant/assistant_interface_binder.h b/ash/public/cpp/assistant/assistant_interface_binder.h
index 21e76ee..500b331 100644
--- a/ash/public/cpp/assistant/assistant_interface_binder.h
+++ b/ash/public/cpp/assistant/assistant_interface_binder.h
@@ -7,6 +7,7 @@
 
 #include "ash/public/cpp/ash_public_export.h"
 #include "ash/public/mojom/assistant_controller.mojom.h"
+#include "ash/public/mojom/assistant_state_controller.mojom.h"
 #include "ash/public/mojom/assistant_volume_control.mojom.h"
 #include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -31,6 +32,8 @@
   virtual void BindScreenContextController(
       mojo::PendingReceiver<mojom::AssistantScreenContextController>
           receiver) = 0;
+  virtual void BindStateController(
+      mojo::PendingReceiver<mojom::AssistantStateController> receiver) = 0;
   virtual void BindVolumeControl(
       mojo::PendingReceiver<mojom::AssistantVolumeControl> receiver) = 0;
 
diff --git a/ash/public/cpp/assistant/assistant_state.cc b/ash/public/cpp/assistant/assistant_state.cc
new file mode 100644
index 0000000..a5f995425
--- /dev/null
+++ b/ash/public/cpp/assistant/assistant_state.cc
@@ -0,0 +1,141 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/assistant/assistant_state.h"
+
+#include <ostream>
+#include <sstream>
+
+namespace ash {
+namespace {
+
+AssistantState* g_assistant_state = nullptr;
+
+}  // namespace
+
+// static
+AssistantState* AssistantState::Get() {
+  return g_assistant_state;
+}
+
+AssistantState::AssistantState() {
+  DCHECK(!g_assistant_state);
+  g_assistant_state = this;
+}
+
+AssistantState::~AssistantState() {
+  DCHECK_EQ(g_assistant_state, this);
+  g_assistant_state = nullptr;
+}
+
+void AssistantState::BindRequest(
+    mojom::AssistantStateControllerRequest request) {
+  bindings_.AddBinding(this, std::move(request));
+}
+
+void AssistantState::NotifyStatusChanged(mojom::VoiceInteractionState state) {
+  if (voice_interaction_state_ == state)
+    return;
+
+  voice_interaction_state_ = state;
+
+  for (auto& observer : observers_)
+    observer.OnAssistantStatusChanged(state);
+  remote_observers_.ForAllPtrs(
+      [state](auto* observer) { observer->OnAssistantStatusChanged(state); });
+}
+
+void AssistantState::NotifySettingsEnabled(bool enabled) {
+  if (settings_enabled_.has_value() && settings_enabled_.value() == enabled)
+    return;
+
+  settings_enabled_ = enabled;
+  for (auto& observer : observers_)
+    observer.OnAssistantSettingsEnabled(enabled);
+  remote_observers_.ForAllPtrs([enabled](auto* observer) {
+    observer->OnAssistantSettingsEnabled(enabled);
+  });
+}
+
+void AssistantState::NotifyContextEnabled(bool enabled) {
+  if (context_enabled_.has_value() && context_enabled_.value() == enabled)
+    return;
+
+  context_enabled_ = enabled;
+  for (auto& observer : observers_)
+    observer.OnAssistantContextEnabled(enabled);
+  remote_observers_.ForAllPtrs([enabled](auto* observer) {
+    observer->OnAssistantContextEnabled(enabled);
+  });
+}
+
+void AssistantState::NotifyHotwordEnabled(bool enabled) {
+  if (hotword_enabled_.has_value() && hotword_enabled_.value() == enabled)
+    return;
+
+  hotword_enabled_ = enabled;
+  for (auto& observer : observers_)
+    observer.OnAssistantHotwordEnabled(enabled);
+  remote_observers_.ForAllPtrs([enabled](auto* observer) {
+    observer->OnAssistantHotwordEnabled(enabled);
+  });
+}
+
+void AssistantState::NotifyFeatureAllowed(mojom::AssistantAllowedState state) {
+  if (allowed_state_ == state)
+    return;
+
+  allowed_state_ = state;
+  for (auto& observer : observers_)
+    observer.OnAssistantFeatureAllowedChanged(state);
+  remote_observers_.ForAllPtrs([state](auto* observer) {
+    observer->OnAssistantFeatureAllowedChanged(state);
+  });
+}
+
+void AssistantState::NotifyLocaleChanged(const std::string& locale) {
+  if (locale_ == locale)
+    return;
+
+  locale_ = locale;
+  for (auto& observer : observers_)
+    observer.OnLocaleChanged(locale);
+  remote_observers_.ForAllPtrs(
+      [locale](auto* observer) { observer->OnLocaleChanged(locale); });
+}
+
+void AssistantState::NotifyArcPlayStoreEnabledChanged(bool enabled) {
+  if (arc_play_store_enabled_ == enabled)
+    return;
+
+  arc_play_store_enabled_ = enabled;
+
+  for (auto& observer : observers_)
+    observer.OnArcPlayStoreEnabledChanged(enabled);
+  remote_observers_.ForAllPtrs([enabled](auto* observer) {
+    observer->OnArcPlayStoreEnabledChanged(enabled);
+  });
+}
+
+void AssistantState::NotifyLockedFullScreenStateChanged(bool enabled) {
+  if (locked_full_screen_enabled_ == enabled)
+    return;
+
+  locked_full_screen_enabled_ = enabled;
+
+  for (auto& observer : observers_)
+    observer.OnLockedFullScreenStateChanged(enabled);
+  remote_observers_.ForAllPtrs([enabled](auto* observer) {
+    observer->OnLockedFullScreenStateChanged(enabled);
+  });
+}
+
+void AssistantState::AddMojomObserver(
+    mojom::AssistantStateObserverPtr observer) {
+  auto* observer_ptr = observer.get();
+  remote_observers_.AddPtr(std::move(observer));
+  InitializeObserverMojom(observer_ptr);
+}
+
+}  // namespace ash
diff --git a/ash/public/cpp/assistant/assistant_state.h b/ash/public/cpp/assistant/assistant_state.h
new file mode 100644
index 0000000..ca28c2cc
--- /dev/null
+++ b/ash/public/cpp/assistant/assistant_state.h
@@ -0,0 +1,52 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_ASSISTANT_ASSISTANT_STATE_H_
+#define ASH_PUBLIC_CPP_ASSISTANT_ASSISTANT_STATE_H_
+
+#include <string>
+
+#include "ash/public/cpp/assistant/assistant_state_base.h"
+#include "ash/public/mojom/assistant_state_controller.mojom.h"
+#include "ash/public/mojom/voice_interaction_controller.mojom.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
+
+namespace ash {
+
+// Interface for a class that holds Assistant related prefs and states.
+class ASH_PUBLIC_EXPORT AssistantState
+    : public AssistantStateBase,
+      public mojom::AssistantStateController {
+ public:
+  static AssistantState* Get();
+
+  AssistantState();
+  ~AssistantState() override;
+
+  void BindRequest(mojom::AssistantStateControllerRequest request);
+  void NotifyStatusChanged(mojom::VoiceInteractionState state);
+  void NotifySettingsEnabled(bool enabled);
+  void NotifyContextEnabled(bool enabled);
+  void NotifyHotwordEnabled(bool enabled);
+  void NotifyFeatureAllowed(mojom::AssistantAllowedState state);
+  void NotifyLocaleChanged(const std::string& locale);
+  void NotifyArcPlayStoreEnabledChanged(bool enabled);
+  void NotifyLockedFullScreenStateChanged(bool enabled);
+
+  // ash::mojom::AssistantStateController:
+  void AddMojomObserver(mojom::AssistantStateObserverPtr observer) override;
+
+ private:
+  mojo::BindingSet<mojom::AssistantStateController> bindings_;
+
+  mojo::InterfacePtrSet<mojom::AssistantStateObserver> remote_observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(AssistantState);
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_ASSISTANT_ASSISTANT_STATE_H_
diff --git a/ash/public/cpp/assistant/assistant_state_base.cc b/ash/public/cpp/assistant/assistant_state_base.cc
index 96ff462..e259eb3e 100644
--- a/ash/public/cpp/assistant/assistant_state_base.cc
+++ b/ash/public/cpp/assistant/assistant_state_base.cc
@@ -9,6 +9,8 @@
 
 #include "ash/public/cpp/accelerators.h"
 #include "base/strings/string_piece_forward.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
 
 namespace ash {
 namespace {
@@ -33,7 +35,7 @@
 std::string AssistantStateBase::ToString() const {
   std::stringstream result;
   result << "AssistantState:";
-  PRINT_VALUE(voice_interaction_state);
+  result << voice_interaction_state_;
   PRINT_VALUE(settings_enabled);
   PRINT_VALUE(context_enabled);
   PRINT_VALUE(hotword_enabled);
@@ -53,4 +55,114 @@
   observers_.RemoveObserver(observer);
 }
 
+void AssistantStateBase::RegisterPrefChanges(PrefService* pref_service) {
+  pref_change_registrar_.reset();
+
+  if (!pref_service)
+    return;
+
+  // Register preference changes.
+  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
+  pref_change_registrar_->Init(pref_service);
+  pref_change_registrar_->Add(
+      chromeos::assistant::prefs::kAssistantConsentStatus,
+      base::BindRepeating(&AssistantStateBase::UpdateConsentStatus,
+                          base::Unretained(this)));
+  pref_change_registrar_->Add(
+      chromeos::assistant::prefs::kAssistantHotwordAlwaysOn,
+      base::BindRepeating(&AssistantStateBase::UpdateHotwordAlwaysOn,
+                          base::Unretained(this)));
+  pref_change_registrar_->Add(
+      chromeos::assistant::prefs::kAssistantLaunchWithMicOpen,
+      base::BindRepeating(&AssistantStateBase::UpdateLaunchWithMicOpen,
+                          base::Unretained(this)));
+  pref_change_registrar_->Add(
+      chromeos::assistant::prefs::kAssistantNotificationEnabled,
+      base::BindRepeating(&AssistantStateBase::UpdateNotificationEnabled,
+                          base::Unretained(this)));
+
+  UpdateConsentStatus();
+  UpdateHotwordAlwaysOn();
+  UpdateLaunchWithMicOpen();
+  UpdateNotificationEnabled();
+}
+
+void AssistantStateBase::InitializeObserver(AssistantStateObserver* observer) {
+  if (consent_status_.has_value())
+    observer->OnAssistantConsentStatusChanged(consent_status_.value());
+  if (hotword_always_on_.has_value())
+    observer->OnAssistantHotwordAlwaysOn(hotword_always_on_.value());
+  if (launch_with_mic_open_.has_value())
+    observer->OnAssistantLaunchWithMicOpen(launch_with_mic_open_.value());
+  if (notification_enabled_.has_value())
+    observer->OnAssistantNotificationEnabled(notification_enabled_.value());
+
+  InitializeObserverMojom(observer);
+}
+
+void AssistantStateBase::InitializeObserverMojom(
+    mojom::AssistantStateObserver* observer) {
+  observer->OnAssistantStatusChanged(voice_interaction_state_);
+  if (settings_enabled_.has_value())
+    observer->OnAssistantSettingsEnabled(settings_enabled_.value());
+  if (context_enabled_.has_value())
+    observer->OnAssistantContextEnabled(context_enabled_.value());
+  if (hotword_enabled_.has_value())
+    observer->OnAssistantHotwordEnabled(hotword_enabled_.value());
+  if (allowed_state_.has_value())
+    observer->OnAssistantFeatureAllowedChanged(allowed_state_.value());
+  if (locale_.has_value())
+    observer->OnLocaleChanged(locale_.value());
+  if (arc_play_store_enabled_.has_value())
+    observer->OnArcPlayStoreEnabledChanged(arc_play_store_enabled_.value());
+}
+
+void AssistantStateBase::UpdateConsentStatus() {
+  auto consent_status = pref_change_registrar_->prefs()->GetInteger(
+      chromeos::assistant::prefs::kAssistantConsentStatus);
+  if (consent_status_.has_value() &&
+      consent_status_.value() == consent_status) {
+    return;
+  }
+  consent_status_ = consent_status;
+  for (auto& observer : observers_)
+    observer.OnAssistantConsentStatusChanged(consent_status_.value());
+}
+
+void AssistantStateBase::UpdateHotwordAlwaysOn() {
+  auto hotword_always_on = pref_change_registrar_->prefs()->GetBoolean(
+      chromeos::assistant::prefs::kAssistantHotwordAlwaysOn);
+  if (hotword_always_on_.has_value() &&
+      hotword_always_on_.value() == hotword_always_on) {
+    return;
+  }
+  hotword_always_on_ = hotword_always_on;
+  for (auto& observer : observers_)
+    observer.OnAssistantHotwordAlwaysOn(hotword_always_on_.value());
+}
+
+void AssistantStateBase::UpdateLaunchWithMicOpen() {
+  auto launch_with_mic_open = pref_change_registrar_->prefs()->GetBoolean(
+      chromeos::assistant::prefs::kAssistantLaunchWithMicOpen);
+  if (launch_with_mic_open_.has_value() &&
+      launch_with_mic_open_.value() == launch_with_mic_open) {
+    return;
+  }
+  launch_with_mic_open_ = launch_with_mic_open;
+  for (auto& observer : observers_)
+    observer.OnAssistantLaunchWithMicOpen(launch_with_mic_open_.value());
+}
+
+void AssistantStateBase::UpdateNotificationEnabled() {
+  auto notification_enabled = pref_change_registrar_->prefs()->GetBoolean(
+      chromeos::assistant::prefs::kAssistantNotificationEnabled);
+  if (notification_enabled_.has_value() &&
+      notification_enabled_.value() == notification_enabled) {
+    return;
+  }
+  notification_enabled_ = notification_enabled;
+  for (auto& observer : observers_)
+    observer.OnAssistantNotificationEnabled(notification_enabled_.value());
+}
+
 }  // namespace ash
diff --git a/ash/public/cpp/assistant/assistant_state_base.h b/ash/public/cpp/assistant/assistant_state_base.h
index c3768745..23765990 100644
--- a/ash/public/cpp/assistant/assistant_state_base.h
+++ b/ash/public/cpp/assistant/assistant_state_base.h
@@ -7,35 +7,57 @@
 
 #include <string>
 
+#include "ash/public/mojom/assistant_state_controller.mojom.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "base/macros.h"
 #include "base/optional.h"
+#include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
+
+class PrefChangeRegistrar;
+class PrefService;
 
 namespace ash {
 
 // A checked observer which receives Assistant state change.
-class ASH_PUBLIC_EXPORT AssistantStateObserver : public base::CheckedObserver {
+class ASH_PUBLIC_EXPORT AssistantStateObserver
+    : public mojom::AssistantStateObserver,
+      public base::CheckedObserver {
  public:
   AssistantStateObserver() = default;
   ~AssistantStateObserver() override = default;
 
   virtual void OnAssistantConsentStatusChanged(int consent_status) {}
+  virtual void OnAssistantHotwordAlwaysOn(bool hotword_always_on) {}
+  virtual void OnAssistantLaunchWithMicOpen(bool launch_with_mic_open) {}
+  virtual void OnAssistantNotificationEnabled(bool notification_enabled) {}
+
+  // mojom::AssistantStateObserver:
+  void OnAssistantStatusChanged(
+      ash::mojom::VoiceInteractionState state) override {}
+  void OnAssistantSettingsEnabled(bool enabled) override {}
+  void OnAssistantContextEnabled(bool enabled) override {}
+  void OnAssistantHotwordEnabled(bool enabled) override {}
+  void OnAssistantFeatureAllowedChanged(
+      ash::mojom::AssistantAllowedState state) override {}
+  void OnArcPlayStoreEnabledChanged(bool enabled) override {}
+  void OnLocaleChanged(const std::string& locale) override {}
+  void OnLockedFullScreenStateChanged(bool enabled) override {}
 
  private:
   DISALLOW_COPY_AND_ASSIGN(AssistantStateObserver);
 };
 
 // Plain data class that holds Assistant related prefs and states. This is
-// shared by both the controller that controlls these values and client proxy
-// that caches these values locally. Please do not use this object directly,
-// most likely you want to use |AssistantStateProxy|.
+// shared by both the controller that controls these values and client proxy
+// that caches these values locally. Please do not use this object directly.
+// For ash/browser use |AssistantState| and for other threads use
+// |AssistantStateProxy|.
 class ASH_PUBLIC_EXPORT AssistantStateBase {
  public:
   AssistantStateBase();
   virtual ~AssistantStateBase();
 
-  const base::Optional<mojom::VoiceInteractionState>& voice_interaction_state()
-      const {
+  const mojom::VoiceInteractionState& voice_interaction_state() const {
     return voice_interaction_state_;
   }
 
@@ -83,13 +105,24 @@
 
   void AddObserver(AssistantStateObserver* observer);
   void RemoveObserver(AssistantStateObserver* observer);
-  virtual void InitializeObserver(AssistantStateObserver* observer) {}
+
+  void RegisterPrefChanges(PrefService* pref_service);
 
  protected:
-  base::Optional<mojom::VoiceInteractionState> voice_interaction_state_;
+  void InitializeObserver(AssistantStateObserver* observer);
+  void InitializeObserverMojom(mojom::AssistantStateObserver* observer);
+
+  // Called when the related preferences are obtained from the pref service.
+  void UpdateConsentStatus();
+  void UpdateHotwordAlwaysOn();
+  void UpdateLaunchWithMicOpen();
+  void UpdateNotificationEnabled();
+
+  mojom::VoiceInteractionState voice_interaction_state_ =
+      mojom::VoiceInteractionState::NOT_READY;
 
   // TODO(b/138679823): Maybe remove Optional for preference values.
-  // Whether voice interaction is enabled in system settings. nullopt if the
+  // Whether the Assistant is enabled in system settings. nullopt if the
   // data is not available yet.
   base::Optional<bool> settings_enabled_;
 
@@ -113,7 +146,7 @@
   // Whether notification is enabled.
   base::Optional<bool> notification_enabled_;
 
-  // Whether voice interaction feature is allowed or disallowed for what reason.
+  // Whether the Assistant feature is allowed or disallowed for what reason.
   // nullopt if the data is not available yet.
   base::Optional<mojom::AssistantAllowedState> allowed_state_;
 
@@ -126,6 +159,9 @@
   // available yet.
   base::Optional<bool> locked_full_screen_enabled_;
 
+  // Observes user profile prefs for the Assistant.
+  std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
+
   base::ObserverList<AssistantStateObserver> observers_;
 
  private:
diff --git a/ash/public/cpp/assistant/assistant_state_proxy.cc b/ash/public/cpp/assistant/assistant_state_proxy.cc
index e4058327..e36d4fa4 100644
--- a/ash/public/cpp/assistant/assistant_state_proxy.cc
+++ b/ash/public/cpp/assistant/assistant_state_proxy.cc
@@ -12,68 +12,43 @@
 namespace ash {
 
 AssistantStateProxy::AssistantStateProxy()
-    : voice_interaction_observer_binding_(this) {}
+    : assistant_state_observer_binding_(this) {}
 
 AssistantStateProxy::~AssistantStateProxy() = default;
 
 void AssistantStateProxy::Init(
-    mojo::PendingRemote<mojom::VoiceInteractionController>
-        voice_interaction_controller) {
-  voice_interaction_controller_.Bind(std::move(voice_interaction_controller));
+    mojo::PendingRemote<mojom::AssistantStateController>
+        assistant_state_controller) {
+  assistant_state_controller_.Bind(std::move(assistant_state_controller));
 
-  ash::mojom::VoiceInteractionObserverPtr ptr;
-  voice_interaction_observer_binding_.Bind(mojo::MakeRequest(&ptr));
-  voice_interaction_controller_->AddObserver(std::move(ptr));
+  mojom::AssistantStateObserverPtr ptr;
+  assistant_state_observer_binding_.Bind(mojo::MakeRequest(&ptr));
+  assistant_state_controller_->AddMojomObserver(std::move(ptr));
 }
 
-void AssistantStateProxy::AddObserver(
-    DefaultVoiceInteractionObserver* observer) {
-  if (voice_interaction_state_.has_value())
-    observer->OnVoiceInteractionStatusChanged(voice_interaction_state_.value());
-  if (settings_enabled_.has_value())
-    observer->OnVoiceInteractionSettingsEnabled(settings_enabled_.value());
-  if (context_enabled_.has_value())
-    observer->OnVoiceInteractionContextEnabled(context_enabled_.value());
-  if (hotword_enabled_.has_value())
-    observer->OnVoiceInteractionHotwordEnabled(hotword_enabled_.value());
-  if (allowed_state_.has_value())
-    observer->OnAssistantFeatureAllowedChanged(allowed_state_.value());
-  if (locale_.has_value())
-    observer->OnLocaleChanged(locale_.value());
-  if (arc_play_store_enabled_.has_value())
-    observer->OnArcPlayStoreEnabledChanged(arc_play_store_enabled_.value());
-
-  observers_.AddObserver(observer);
-}
-
-void AssistantStateProxy::RemoveObserver(
-    DefaultVoiceInteractionObserver* observer) {
-  observers_.RemoveObserver(observer);
-}
-
-void AssistantStateProxy::OnVoiceInteractionStatusChanged(
+void AssistantStateProxy::OnAssistantStatusChanged(
     ash::mojom::VoiceInteractionState state) {
   voice_interaction_state_ = state;
   for (auto& observer : observers_)
-    observer.OnVoiceInteractionStatusChanged(voice_interaction_state_.value());
+    observer.OnAssistantStatusChanged(voice_interaction_state_);
 }
 
-void AssistantStateProxy::OnVoiceInteractionSettingsEnabled(bool enabled) {
+void AssistantStateProxy::OnAssistantSettingsEnabled(bool enabled) {
   settings_enabled_ = enabled;
   for (auto& observer : observers_)
-    observer.OnVoiceInteractionSettingsEnabled(settings_enabled_.value());
+    observer.OnAssistantSettingsEnabled(settings_enabled_.value());
 }
 
-void AssistantStateProxy::OnVoiceInteractionContextEnabled(bool enabled) {
+void AssistantStateProxy::OnAssistantContextEnabled(bool enabled) {
   context_enabled_ = enabled;
   for (auto& observer : observers_)
-    observer.OnVoiceInteractionContextEnabled(context_enabled_.value());
+    observer.OnAssistantContextEnabled(context_enabled_.value());
 }
 
-void AssistantStateProxy::OnVoiceInteractionHotwordEnabled(bool enabled) {
+void AssistantStateProxy::OnAssistantHotwordEnabled(bool enabled) {
   hotword_enabled_ = enabled;
   for (auto& observer : observers_)
-    observer.OnVoiceInteractionHotwordEnabled(hotword_enabled_.value());
+    observer.OnAssistantHotwordEnabled(hotword_enabled_.value());
 }
 
 void AssistantStateProxy::OnAssistantFeatureAllowedChanged(
diff --git a/ash/public/cpp/assistant/assistant_state_proxy.h b/ash/public/cpp/assistant/assistant_state_proxy.h
index cce62d9..005c320 100644
--- a/ash/public/cpp/assistant/assistant_state_proxy.h
+++ b/ash/public/cpp/assistant/assistant_state_proxy.h
@@ -9,7 +9,7 @@
 #include <vector>
 
 #include "ash/public/cpp/assistant/assistant_state_base.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
+#include "ash/public/mojom/assistant_state_controller.mojom.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "base/callback.h"
 #include "base/macros.h"
@@ -25,34 +25,29 @@
 // will fire if this client already have data.
 class ASH_PUBLIC_EXPORT AssistantStateProxy
     : public AssistantStateBase,
-      public mojom::VoiceInteractionObserver {
+      public mojom::AssistantStateObserver {
  public:
   AssistantStateProxy();
   ~AssistantStateProxy() override;
 
-  void Init(mojo::PendingRemote<mojom::VoiceInteractionController>
-                voice_interaction_controller);
-  void AddObserver(DefaultVoiceInteractionObserver* observer);
-  void RemoveObserver(DefaultVoiceInteractionObserver* observer);
+  void Init(mojo::PendingRemote<mojom::AssistantStateController>
+                assistant_state_controller);
 
  private:
-  // mojom::VoiceInteractionObserver:
-  void OnVoiceInteractionStatusChanged(
-      mojom::VoiceInteractionState state) override;
-  void OnVoiceInteractionSettingsEnabled(bool enabled) override;
-  void OnVoiceInteractionContextEnabled(bool enabled) override;
-  void OnVoiceInteractionHotwordEnabled(bool enabled) override;
+  // mojom::AssistantStateObserver:
+  void OnAssistantStatusChanged(mojom::VoiceInteractionState state) override;
+  void OnAssistantSettingsEnabled(bool enabled) override;
+  void OnAssistantContextEnabled(bool enabled) override;
+  void OnAssistantHotwordEnabled(bool enabled) override;
   void OnAssistantFeatureAllowedChanged(
       mojom::AssistantAllowedState state) override;
   void OnLocaleChanged(const std::string& locale) override;
   void OnArcPlayStoreEnabledChanged(bool enabled) override;
   void OnLockedFullScreenStateChanged(bool enabled) override;
 
-  base::ObserverList<DefaultVoiceInteractionObserver> observers_;
-
-  mojom::VoiceInteractionControllerPtr voice_interaction_controller_;
-  mojo::Binding<mojom::VoiceInteractionObserver>
-      voice_interaction_observer_binding_;
+  mojom::AssistantStateControllerPtr assistant_state_controller_;
+  mojo::Binding<mojom::AssistantStateObserver>
+      assistant_state_observer_binding_;
 
   DISALLOW_COPY_AND_ASSIGN(AssistantStateProxy);
 };
diff --git a/ash/public/cpp/assistant/default_voice_interaction_observer.h b/ash/public/cpp/assistant/default_voice_interaction_observer.h
deleted file mode 100644
index f697e07..0000000
--- a/ash/public/cpp/assistant/default_voice_interaction_observer.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_PUBLIC_CPP_ASSISTANT_DEFAULT_VOICE_INTERACTION_OBSERVER_H_
-#define ASH_PUBLIC_CPP_ASSISTANT_DEFAULT_VOICE_INTERACTION_OBSERVER_H_
-
-#include <string>
-
-#include "ash/public/mojom/voice_interaction_controller.mojom.h"
-#include "base/macros.h"
-#include "base/observer_list_types.h"
-
-namespace ash {
-
-// Provides a default empty implementation of
-// ash::mojom::VoiceInteractionObserver interface. Child class only need to
-// override the methods they are actually interested in.
-class ASH_PUBLIC_EXPORT DefaultVoiceInteractionObserver
-    : public mojom::VoiceInteractionObserver,
-      public base::CheckedObserver {
- public:
-  ~DefaultVoiceInteractionObserver() override = default;
-
-  // mojom::VoiceInteractionObserver:
-  void OnVoiceInteractionStatusChanged(
-      ash::mojom::VoiceInteractionState state) override {}
-  void OnVoiceInteractionSettingsEnabled(bool enabled) override {}
-  void OnVoiceInteractionContextEnabled(bool enabled) override {}
-  void OnVoiceInteractionHotwordEnabled(bool enabled) override {}
-  void OnAssistantFeatureAllowedChanged(
-      ash::mojom::AssistantAllowedState state) override {}
-  void OnLocaleChanged(const std::string& locale) override {}
-  void OnArcPlayStoreEnabledChanged(bool enabled) override {}
-  void OnLockedFullScreenStateChanged(bool enabled) override {}
-
- protected:
-  DefaultVoiceInteractionObserver() = default;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DefaultVoiceInteractionObserver);
-};
-
-}  // namespace ash
-
-#endif  // ASH_PUBLIC_CPP_ASSISTANT_DEFAULT_VOICE_INTERACTION_OBSERVER_H_
diff --git a/ash/public/cpp/manifest.cc b/ash/public/cpp/manifest.cc
index 012178fd..8bde2f45 100644
--- a/ash/public/cpp/manifest.cc
+++ b/ash/public/cpp/manifest.cc
@@ -10,7 +10,6 @@
 #include "ash/public/mojom/cros_display_config.mojom.h"
 #include "ash/public/mojom/ime_controller.mojom.h"
 #include "ash/public/mojom/tray_action.mojom.h"
-#include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "ash/public/mojom/vpn_list.mojom.h"
 #include "base/no_destructor.h"
 #include "chromeos/services/multidevice_setup/public/mojom/constants.mojom.h"
diff --git a/ash/public/cpp/voice_interaction_controller.cc b/ash/public/cpp/voice_interaction_controller.cc
deleted file mode 100644
index 2705dab..0000000
--- a/ash/public/cpp/voice_interaction_controller.cc
+++ /dev/null
@@ -1,179 +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 "ash/public/cpp/voice_interaction_controller.h"
-
-#include <utility>
-
-#include "chromeos/constants/chromeos_features.h"
-
-namespace ash {
-namespace {
-VoiceInteractionController* g_voice_interaction_cotroller = nullptr;
-}
-
-// static
-VoiceInteractionController* VoiceInteractionController::Get() {
-  return g_voice_interaction_cotroller;
-}
-
-VoiceInteractionController::VoiceInteractionController() {
-  DCHECK(!g_voice_interaction_cotroller);
-  g_voice_interaction_cotroller = this;
-  if (chromeos::features::IsAssistantEnabled())
-    voice_interaction_state_ = mojom::VoiceInteractionState::NOT_READY;
-}
-
-VoiceInteractionController::~VoiceInteractionController() {
-  DCHECK_EQ(g_voice_interaction_cotroller, this);
-  g_voice_interaction_cotroller = nullptr;
-}
-
-void VoiceInteractionController::BindRequest(
-    mojom::VoiceInteractionControllerRequest request) {
-  bindings_.AddBinding(this, std::move(request));
-}
-
-void VoiceInteractionController::NotifyStatusChanged(
-    mojom::VoiceInteractionState state) {
-  if (voice_interaction_state_ == state)
-    return;
-
-  voice_interaction_state_ = state;
-  observers_.ForAllPtrs([state](auto* observer) {
-    observer->OnVoiceInteractionStatusChanged(state);
-  });
-  for (auto& observer : local_observers_)
-    observer.OnVoiceInteractionStatusChanged(state);
-}
-
-void VoiceInteractionController::NotifySettingsEnabled(bool enabled) {
-  if (settings_enabled_.has_value() && settings_enabled_.value() == enabled)
-    return;
-
-  settings_enabled_ = enabled;
-  observers_.ForAllPtrs([enabled](auto* observer) {
-    observer->OnVoiceInteractionSettingsEnabled(enabled);
-  });
-  for (auto& observer : local_observers_)
-    observer.OnVoiceInteractionSettingsEnabled(enabled);
-}
-
-void VoiceInteractionController::NotifyContextEnabled(bool enabled) {
-  if (context_enabled_.has_value() && context_enabled_.value() == enabled)
-    return;
-
-  context_enabled_ = enabled;
-  observers_.ForAllPtrs([enabled](auto* observer) {
-    observer->OnVoiceInteractionContextEnabled(enabled);
-  });
-  for (auto& observer : local_observers_)
-    observer.OnVoiceInteractionContextEnabled(enabled);
-}
-
-void VoiceInteractionController::NotifyHotwordEnabled(bool enabled) {
-  if (hotword_enabled_.has_value() && hotword_enabled_.value() == enabled)
-    return;
-
-  hotword_enabled_ = enabled;
-  observers_.ForAllPtrs([enabled](auto* observer) {
-    observer->OnVoiceInteractionHotwordEnabled(enabled);
-  });
-  for (auto& observer : local_observers_)
-    observer.OnVoiceInteractionHotwordEnabled(enabled);
-}
-
-void VoiceInteractionController::NotifyFeatureAllowed(
-    mojom::AssistantAllowedState state) {
-  if (allowed_state_ == state)
-    return;
-
-  allowed_state_ = state;
-  observers_.ForAllPtrs([state](auto* observer) {
-    observer->OnAssistantFeatureAllowedChanged(state);
-  });
-  for (auto& observer : local_observers_)
-    observer.OnAssistantFeatureAllowedChanged(state);
-}
-
-void VoiceInteractionController::NotifyLocaleChanged(
-    const std::string& locale) {
-  if (locale_ == locale)
-    return;
-
-  locale_ = locale;
-  observers_.ForAllPtrs(
-      [locale](auto* observer) { observer->OnLocaleChanged(locale); });
-  for (auto& observer : local_observers_)
-    observer.OnLocaleChanged(locale);
-}
-
-void VoiceInteractionController::NotifyArcPlayStoreEnabledChanged(
-    bool enabled) {
-  if (arc_play_store_enabled_ == enabled)
-    return;
-
-  arc_play_store_enabled_ = enabled;
-
-  observers_.ForAllPtrs([enabled](auto* observer) {
-    observer->OnArcPlayStoreEnabledChanged(enabled);
-  });
-  for (auto& observer : local_observers_)
-    observer.OnArcPlayStoreEnabledChanged(enabled);
-}
-
-void VoiceInteractionController::NotifyLockedFullScreenStateChanged(
-    bool enabled) {
-  if (locked_full_screen_enabled_ == enabled)
-    return;
-
-  locked_full_screen_enabled_ = enabled;
-
-  observers_.ForAllPtrs([enabled](auto* observer) {
-    observer->OnLockedFullScreenStateChanged(enabled);
-  });
-  for (auto& observer : local_observers_)
-    observer.OnLockedFullScreenStateChanged(enabled);
-}
-
-void VoiceInteractionController::AddObserver(
-    mojom::VoiceInteractionObserverPtr observer) {
-  InitObserver(observer.get());
-  observers_.AddPtr(std::move(observer));
-}
-
-void VoiceInteractionController::AddLocalObserver(
-    DefaultVoiceInteractionObserver* observer) {
-  InitObserver(observer);
-  local_observers_.AddObserver(observer);
-}
-
-void VoiceInteractionController::RemoveLocalObserver(
-    DefaultVoiceInteractionObserver* observer) {
-  local_observers_.RemoveObserver(observer);
-}
-
-void VoiceInteractionController::InitObserver(
-    mojom::VoiceInteractionObserver* observer) {
-  if (voice_interaction_state_.has_value())
-    observer->OnVoiceInteractionStatusChanged(voice_interaction_state_.value());
-  if (settings_enabled_.has_value())
-    observer->OnVoiceInteractionSettingsEnabled(settings_enabled_.value());
-  if (context_enabled_.has_value())
-    observer->OnVoiceInteractionContextEnabled(context_enabled_.value());
-  if (hotword_enabled_.has_value())
-    observer->OnVoiceInteractionHotwordEnabled(hotword_enabled_.value());
-  if (allowed_state_.has_value())
-    observer->OnAssistantFeatureAllowedChanged(allowed_state_.value());
-  if (locale_.has_value())
-    observer->OnLocaleChanged(locale_.value());
-  if (arc_play_store_enabled_.has_value())
-    observer->OnArcPlayStoreEnabledChanged(arc_play_store_enabled_.value());
-}
-
-void VoiceInteractionController::FlushForTesting() {
-  observers_.FlushForTesting();
-}
-
-}  // namespace ash
diff --git a/ash/public/cpp/voice_interaction_controller.h b/ash/public/cpp/voice_interaction_controller.h
deleted file mode 100644
index cc7f222..0000000
--- a/ash/public/cpp/voice_interaction_controller.h
+++ /dev/null
@@ -1,81 +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 ASH_PUBLIC_CPP_VOICE_INTERACTION_CONTROLLER_H_
-#define ASH_PUBLIC_CPP_VOICE_INTERACTION_CONTROLLER_H_
-
-#include <string>
-
-#include "ash/public/cpp/ash_public_export.h"
-#include "ash/public/cpp/assistant/assistant_state_base.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
-#include "ash/public/mojom/voice_interaction_controller.mojom.h"
-#include "base/observer_list.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-#include "mojo/public/cpp/bindings/interface_ptr_set.h"
-
-namespace ash {
-
-// TODO(b/138679823): Merge this with AssistantPrefsController.
-class ASH_PUBLIC_EXPORT VoiceInteractionController
-    : public mojom::VoiceInteractionController,
-      public AssistantStateBase {
- public:
-  static VoiceInteractionController* Get();
-
-  VoiceInteractionController();
-  ~VoiceInteractionController() override;
-
-  void BindRequest(mojom::VoiceInteractionControllerRequest request);
-
-  // Called when the voice interaction state is changed.
-  virtual void NotifyStatusChanged(mojom::VoiceInteractionState state);
-
-  // Called when the voice interaction settings is enabled/disabled.
-  virtual void NotifySettingsEnabled(bool enabled);
-
-  // Called when the voice interaction context is enabled/disabled.
-  // If context is enabled the screenshot will be passed in voice
-  // interaction session.
-  virtual void NotifyContextEnabled(bool enabled);
-
-  // Called when the hotword listening is enabled/disabled.
-  virtual void NotifyHotwordEnabled(bool enabled);
-
-  // Notify if voice interaction feature is allowed or not. e.g. not allowed
-  // if disabled by policy.
-  virtual void NotifyFeatureAllowed(mojom::AssistantAllowedState state);
-
-  // Called when the locale is changed.
-  virtual void NotifyLocaleChanged(const std::string& locale);
-
-  // Called when Google Play Store is enabled/disabled.
-  virtual void NotifyArcPlayStoreEnabledChanged(bool enabled);
-
-  // Called when locked full screen state is enabled/disabled.
-  virtual void NotifyLockedFullScreenStateChanged(bool enabled);
-
-  // ash::mojom::VoiceInteractionController:
-  void AddObserver(mojom::VoiceInteractionObserverPtr observer) override;
-
-  // Adding local observers in the same process.
-  void AddLocalObserver(DefaultVoiceInteractionObserver* observer);
-  void RemoveLocalObserver(DefaultVoiceInteractionObserver* observer);
-  void InitObserver(mojom::VoiceInteractionObserver* observer);
-
-  void FlushForTesting();
-
- private:
-  mojo::BindingSet<mojom::VoiceInteractionController> bindings_;
-
-  mojo::InterfacePtrSet<mojom::VoiceInteractionObserver> observers_;
-
-  base::ObserverList<DefaultVoiceInteractionObserver> local_observers_;
-
-  DISALLOW_COPY_AND_ASSIGN(VoiceInteractionController);
-};
-
-}  // namespace ash
-
-#endif  // ASH_PUBLIC_CPP_VOICE_INTERACTION_CONTROLLER_H_
diff --git a/ash/public/cpp/voice_interaction_controller_unittest.cc b/ash/public/cpp/voice_interaction_controller_unittest.cc
deleted file mode 100644
index e743cdd..0000000
--- a/ash/public/cpp/voice_interaction_controller_unittest.cc
+++ /dev/null
@@ -1,143 +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 "ash/public/cpp/voice_interaction_controller.h"
-
-#include <memory>
-#include <utility>
-
-#include "ash/public/mojom/voice_interaction_controller.mojom.h"
-#include "base/test/task_environment.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace ash {
-namespace {
-
-class TestVoiceInteractionObserver : public mojom::VoiceInteractionObserver {
- public:
-  TestVoiceInteractionObserver() : voice_interaction_binding_(this) {}
-
-  ~TestVoiceInteractionObserver() override = default;
-
-  // mojom::VoiceInteractionObserver overrides:
-  void OnVoiceInteractionStatusChanged(
-      mojom::VoiceInteractionState state) override {
-    state_ = state;
-  }
-  void OnVoiceInteractionSettingsEnabled(bool enabled) override {
-    settings_enabled_ = enabled;
-  }
-  void OnVoiceInteractionContextEnabled(bool enabled) override {
-    context_enabled_ = enabled;
-  }
-  void OnVoiceInteractionHotwordEnabled(bool enabled) override {
-    hotword_enabled_ = enabled;
-  }
-  void OnAssistantFeatureAllowedChanged(
-      mojom::AssistantAllowedState state) override {}
-  void OnLocaleChanged(const std::string& locale) override {}
-  void OnArcPlayStoreEnabledChanged(bool enabled) override {
-    arc_play_store_enabled_ = enabled;
-  }
-  void OnLockedFullScreenStateChanged(bool enabled) override {}
-
-  mojom::VoiceInteractionState voice_interaction_state() const {
-    return state_;
-  }
-  bool settings_enabled() const { return settings_enabled_; }
-  bool context_enabled() const { return context_enabled_; }
-  bool hotword_enabled() const { return hotword_enabled_; }
-  bool arc_play_store_enabled() const { return arc_play_store_enabled_; }
-
-  void SetVoiceInteractionController(VoiceInteractionController* controller) {
-    mojom::VoiceInteractionObserverPtr ptr;
-    voice_interaction_binding_.Bind(mojo::MakeRequest(&ptr));
-    controller->AddObserver(std::move(ptr));
-  }
-
- private:
-  mojom::VoiceInteractionState state_ = mojom::VoiceInteractionState::STOPPED;
-  bool settings_enabled_ = false;
-  bool context_enabled_ = false;
-  bool hotword_enabled_ = false;
-  bool arc_play_store_enabled_ = false;
-
-  mojo::Binding<mojom::VoiceInteractionObserver> voice_interaction_binding_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestVoiceInteractionObserver);
-};
-
-class VoiceInteractionControllerTest : public testing::Test {
- public:
-  VoiceInteractionControllerTest()
-      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
-  ~VoiceInteractionControllerTest() override = default;
-
-  void SetUp() override {
-    controller_ = std::make_unique<VoiceInteractionController>();
-    observer_ = std::make_unique<TestVoiceInteractionObserver>();
-    observer_->SetVoiceInteractionController(controller());
-  }
-
-  void TearDown() override { observer_.reset(); }
-
- protected:
-  VoiceInteractionController* controller() { return controller_.get(); }
-
-  TestVoiceInteractionObserver* observer() { return observer_.get(); }
-
- private:
-  base::test::TaskEnvironment task_environment_;
-  std::unique_ptr<VoiceInteractionController> controller_;
-  std::unique_ptr<TestVoiceInteractionObserver> observer_;
-
-  DISALLOW_COPY_AND_ASSIGN(VoiceInteractionControllerTest);
-};
-
-}  // namespace
-
-TEST_F(VoiceInteractionControllerTest, NotifyStatusChanged) {
-  controller()->NotifyStatusChanged(mojom::VoiceInteractionState::RUNNING);
-  controller()->FlushForTesting();
-
-  // The cached state should be updated.
-  EXPECT_EQ(mojom::VoiceInteractionState::RUNNING,
-            controller()->voice_interaction_state());
-  // The observers should be notified.
-  EXPECT_EQ(mojom::VoiceInteractionState::RUNNING,
-            observer()->voice_interaction_state());
-}
-
-TEST_F(VoiceInteractionControllerTest, NotifySettingsEnabled) {
-  controller()->NotifySettingsEnabled(true);
-  controller()->FlushForTesting();
-  // The cached state should be updated.
-  EXPECT_TRUE(controller()->settings_enabled());
-  // The observers should be notified.
-  EXPECT_TRUE(observer()->settings_enabled());
-}
-
-TEST_F(VoiceInteractionControllerTest, NotifyContextEnabled) {
-  controller()->NotifyContextEnabled(true);
-  controller()->FlushForTesting();
-  // The observers should be notified.
-  EXPECT_TRUE(observer()->context_enabled());
-}
-
-TEST_F(VoiceInteractionControllerTest, NotifyHotwordEnabled) {
-  controller()->NotifyHotwordEnabled(true);
-  controller()->FlushForTesting();
-  // The observers should be notified.
-  EXPECT_TRUE(observer()->hotword_enabled());
-}
-
-TEST_F(VoiceInteractionControllerTest, NotifyArcPlayStoreEnabledChanged) {
-  controller()->NotifyArcPlayStoreEnabledChanged(true);
-  controller()->FlushForTesting();
-  // The observers should be notified.
-  EXPECT_TRUE(observer()->arc_play_store_enabled());
-}
-
-}  // namespace ash
diff --git a/ash/public/mojom/BUILD.gn b/ash/public/mojom/BUILD.gn
index 0872da0f..306ca48a 100644
--- a/ash/public/mojom/BUILD.gn
+++ b/ash/public/mojom/BUILD.gn
@@ -9,6 +9,7 @@
 
   sources = [
     "assistant_controller.mojom",
+    "assistant_state_controller.mojom",
     "assistant_volume_control.mojom",
     "constants.mojom",
     "cros_display_config.mojom",
diff --git a/ash/public/mojom/assistant_state_controller.mojom b/ash/public/mojom/assistant_state_controller.mojom
new file mode 100644
index 0000000..4b7424e
--- /dev/null
+++ b/ash/public/mojom/assistant_state_controller.mojom
@@ -0,0 +1,44 @@
+// 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.
+
+module ash.mojom;
+
+import "ash/public/mojom/voice_interaction_controller.mojom";
+
+// Allows observing changes to Assistant status and settings.
+interface AssistantStateObserver {
+  // Called when Assistant state changes.
+  OnAssistantStatusChanged(VoiceInteractionState state);
+
+  // Called when the Assistant is enabled/disabled in settings.
+  OnAssistantSettingsEnabled(bool enabled);
+
+  // Called when the Assistant service is allowed/disallowed to access
+  // the "context" (text and graphic content that is currently on screen).
+  OnAssistantContextEnabled(bool enabled);
+
+  // Called when hotword listening is enabled/disabled.
+  OnAssistantHotwordEnabled(bool enabled);
+
+  // Called when assistant feature allowed state has changed.
+  OnAssistantFeatureAllowedChanged(AssistantAllowedState state);
+
+  // Called when Google Play Store is enabled/disabled.
+  OnArcPlayStoreEnabledChanged(bool enabled);
+
+  // Called when locale is changed in pref. The locale is in the format can be
+  // "en-US" or simply "en". When locale is not set in pref, it returns empty
+  // string.
+  OnLocaleChanged(string locale);
+
+  // Called when locked full screen state has changed.
+  OnLockedFullScreenStateChanged(bool enabled);
+};
+
+// Interface for ash client (Assistant service) to connect to state controller,
+// which notifies changes of Assistant status and settings.
+interface AssistantStateController {
+  // Add an observer.
+  AddMojomObserver(AssistantStateObserver observer);
+};
diff --git a/ash/public/mojom/voice_interaction_controller.mojom b/ash/public/mojom/voice_interaction_controller.mojom
index b296b6da..8f19c8eb 100644
--- a/ash/public/mojom/voice_interaction_controller.mojom
+++ b/ash/public/mojom/voice_interaction_controller.mojom
@@ -29,8 +29,6 @@
   DISALLOWED_BY_POLICY,
   // Disallowed because user's locale is not compatible.
   DISALLOWED_BY_LOCALE,
-  // Disallowed because the feature flag is off.
-  DISALLOWED_BY_FLAG,
   // Disallowed because current user is not primary user.
   DISALLOWED_BY_NONPRIMARY_USER,
   // Disallowed because current user is supervised user.
diff --git a/ash/shelf/back_button_unittest.cc b/ash/shelf/back_button_unittest.cc
index 0721a21..687e214 100644
--- a/ash/shelf/back_button_unittest.cc
+++ b/ash/shelf/back_button_unittest.cc
@@ -10,6 +10,7 @@
 #include "ash/app_list/test/app_list_test_helper.h"
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/shelf/shelf.h"
+#include "ash/shelf/shelf_navigation_widget.h"
 #include "ash/shelf/shelf_view.h"
 #include "ash/shelf/shelf_view_test_api.h"
 #include "ash/shelf/shelf_widget.h"
@@ -84,7 +85,12 @@
 TEST_F(BackButtonTest, BackKeySequenceGenerated) {
   // Enter tablet mode; the back button is not visible in non tablet mode.
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
-  test_api()->RunMessageLoopUntilAnimationsDone();
+  // Wait for the navigation widget's animation.
+  test_api()->RunMessageLoopUntilAnimationsDone(
+      GetPrimaryShelf()
+          ->shelf_widget()
+          ->navigation_widget()
+          ->get_bounds_animator_for_testing());
 
   AcceleratorControllerImpl* controller =
       Shell::Get()->accelerator_controller();
diff --git a/ash/shelf/home_button_controller.cc b/ash/shelf/home_button_controller.cc
index cd31b2a..a481952 100644
--- a/ash/shelf/home_button_controller.cc
+++ b/ash/shelf/home_button_controller.cc
@@ -7,7 +7,6 @@
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/assistant/assistant_controller.h"
 #include "ash/home_screen/home_screen_controller.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shelf/assistant_overlay.h"
@@ -49,15 +48,13 @@
   shell->app_list_controller()->AddObserver(this);
   shell->session_controller()->AddObserver(this);
   shell->tablet_mode_controller()->AddObserver(this);
-  VoiceInteractionController::Get()->AddLocalObserver(this);
+  AssistantState::Get()->AddObserver(this);
 
   // Initialize voice interaction overlay and sync the flags if active user
   // session has already started. This could happen when an external monitor
   // is plugged in.
-  if (shell->session_controller()->IsActiveUserSessionStarted() &&
-      chromeos::features::IsAssistantEnabled()) {
+  if (shell->session_controller()->IsActiveUserSessionStarted())
     InitializeVoiceInteractionOverlay();
-  }
 }
 
 HomeButtonController::~HomeButtonController() {
@@ -70,7 +67,8 @@
   if (shell->tablet_mode_controller())
     shell->tablet_mode_controller()->RemoveObserver(this);
   shell->session_controller()->RemoveObserver(this);
-  VoiceInteractionController::Get()->RemoveLocalObserver(this);
+  if (AssistantState::Get())
+    AssistantState::Get()->RemoveObserver(this);
 }
 
 bool HomeButtonController::MaybeHandleGestureEvent(ui::GestureEvent* event) {
@@ -134,17 +132,16 @@
 }
 
 bool HomeButtonController::IsVoiceInteractionAvailable() {
-  VoiceInteractionController* controller = VoiceInteractionController::Get();
-  bool settings_enabled = controller->settings_enabled().value_or(false);
+  AssistantStateBase* state = AssistantState::Get();
+  bool settings_enabled = state->settings_enabled().value_or(false);
   bool feature_allowed =
-      controller->allowed_state() == mojom::AssistantAllowedState::ALLOWED;
+      state->allowed_state() == mojom::AssistantAllowedState::ALLOWED;
 
   return assistant_overlay_ && feature_allowed && settings_enabled;
 }
 
 bool HomeButtonController::IsVoiceInteractionRunning() {
-  return VoiceInteractionController::Get()->voice_interaction_state().value_or(
-             mojom::VoiceInteractionState::STOPPED) ==
+  return AssistantState::Get()->voice_interaction_state() ==
          mojom::VoiceInteractionState::RUNNING;
 }
 
@@ -164,7 +161,7 @@
   // Initialize voice interaction overlay when primary user session becomes
   // active.
   if (Shell::Get()->session_controller()->IsUserPrimary() &&
-      !assistant_overlay_ && chromeos::features::IsAssistantEnabled()) {
+      !assistant_overlay_) {
     InitializeVoiceInteractionOverlay();
   }
 }
@@ -173,7 +170,7 @@
   button_->AnimateInkDrop(views::InkDropState::DEACTIVATED, nullptr);
 }
 
-void HomeButtonController::OnVoiceInteractionStatusChanged(
+void HomeButtonController::OnAssistantStatusChanged(
     mojom::VoiceInteractionState state) {
   button_->OnVoiceInteractionAvailabilityChanged();
 
@@ -187,14 +184,6 @@
           base::TimeTicks::Now() - voice_interaction_start_timestamp_);
       break;
     case mojom::VoiceInteractionState::NOT_READY:
-      // If we are showing the bursting or waiting animation, no need to do
-      // anything. Otherwise show the waiting animation now.
-      // NOTE: No waiting animation for native assistant.
-      if (!chromeos::features::IsAssistantEnabled() &&
-          !assistant_overlay_->IsBursting() &&
-          !assistant_overlay_->IsWaiting()) {
-        assistant_overlay_->WaitingAnimation();
-      }
       break;
     case mojom::VoiceInteractionState::RUNNING:
       // we start hiding the animation if it is running.
@@ -212,7 +201,7 @@
   }
 }
 
-void HomeButtonController::OnVoiceInteractionSettingsEnabled(bool enabled) {
+void HomeButtonController::OnAssistantSettingsEnabled(bool enabled) {
   button_->OnVoiceInteractionAvailabilityChanged();
 }
 
diff --git a/ash/shelf/home_button_controller.h b/ash/shelf/home_button_controller.h
index f05d5e2..30a9a4c 100644
--- a/ash/shelf/home_button_controller.h
+++ b/ash/shelf/home_button_controller.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "ash/app_list/app_list_controller_observer.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/tablet_mode_observer.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "ash/session/session_observer.h"
@@ -33,7 +33,7 @@
 class HomeButtonController : public AppListControllerObserver,
                              public SessionObserver,
                              public TabletModeObserver,
-                             public DefaultVoiceInteractionObserver {
+                             public AssistantStateObserver {
  public:
   explicit HomeButtonController(HomeButton* button);
   ~HomeButtonController() override;
@@ -61,10 +61,9 @@
   // TabletModeObserver:
   void OnTabletModeStarted() override;
 
-  // mojom::VoiceInteractionObserver:
-  void OnVoiceInteractionStatusChanged(
-      mojom::VoiceInteractionState state) override;
-  void OnVoiceInteractionSettingsEnabled(bool enabled) override;
+  // AssistantStateObserver:
+  void OnAssistantStatusChanged(mojom::VoiceInteractionState state) override;
+  void OnAssistantSettingsEnabled(bool enabled) override;
 
   void OnAppListShown();
   void OnAppListDismissed();
diff --git a/ash/shelf/home_button_unittest.cc b/ash/shelf/home_button_unittest.cc
index d8af29e..192518cf 100644
--- a/ash/shelf/home_button_unittest.cc
+++ b/ash/shelf/home_button_unittest.cc
@@ -13,11 +13,11 @@
 #include "ash/assistant/assistant_ui_controller.h"
 #include "ash/assistant/model/assistant_ui_model.h"
 #include "ash/assistant/test/test_assistant_service.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_constants.h"
+#include "ash/shelf/shelf_navigation_widget.h"
 #include "ash/shelf/shelf_view.h"
 #include "ash/shelf/shelf_view_test_api.h"
 #include "ash/shelf/shelf_widget.h"
@@ -64,6 +64,8 @@
     return GetPrimaryShelf()->shelf_widget()->GetHomeButton();
   }
 
+  AssistantState* assistant_state() const { return AssistantState::Get(); }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(HomeButtonTest);
 };
@@ -147,11 +149,20 @@
 
   ShelfViewTestAPI test_api(GetPrimaryShelf()->GetShelfViewForTesting());
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
-  test_api.RunMessageLoopUntilAnimationsDone();
+  // Wait for the navigation widget's animation.
+  test_api.RunMessageLoopUntilAnimationsDone(
+      GetPrimaryShelf()
+          ->shelf_widget()
+          ->navigation_widget()
+          ->get_bounds_animator_for_testing());
   EXPECT_GT(home_button()->bounds().x(), 0);
 
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
-  test_api.RunMessageLoopUntilAnimationsDone();
+  test_api.RunMessageLoopUntilAnimationsDone(
+      GetPrimaryShelf()
+          ->shelf_widget()
+          ->navigation_widget()
+          ->get_bounds_animator_for_testing());
   // Visual space around the home button is set at the widget level.
   EXPECT_EQ(0, home_button()->bounds().x());
 }
@@ -163,11 +174,10 @@
   CreateUserSessions(2);
 
   // Enable voice interaction in system settings.
-  VoiceInteractionController::Get()->NotifySettingsEnabled(true);
-  VoiceInteractionController::Get()->NotifyFeatureAllowed(
+  assistant_state()->NotifySettingsEnabled(true);
+  assistant_state()->NotifyFeatureAllowed(
       mojom::AssistantAllowedState::ALLOWED);
-  VoiceInteractionController::Get()->NotifyStatusChanged(
-      mojom::VoiceInteractionState::STOPPED);
+  assistant_state()->NotifyStatusChanged(mojom::VoiceInteractionState::STOPPED);
 
   ui::GestureEvent long_press =
       CreateGestureEvent(ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
@@ -193,11 +203,11 @@
 
 TEST_F(HomeButtonTest, LongPressGestureWithSecondaryUser) {
   // Disallowed by secondary user.
-  VoiceInteractionController::Get()->NotifyFeatureAllowed(
+  assistant_state()->NotifyFeatureAllowed(
       mojom::AssistantAllowedState::DISALLOWED_BY_NONPRIMARY_USER);
 
   // Enable voice interaction in system settings.
-  VoiceInteractionController::Get()->NotifySettingsEnabled(true);
+  assistant_state()->NotifySettingsEnabled(true);
 
   ui::GestureEvent long_press =
       CreateGestureEvent(ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
@@ -224,8 +234,8 @@
 
   // Simulate a user who has already completed setup flow, but disabled voice
   // interaction in settings.
-  VoiceInteractionController::Get()->NotifySettingsEnabled(false);
-  VoiceInteractionController::Get()->NotifyFeatureAllowed(
+  assistant_state()->NotifySettingsEnabled(false);
+  assistant_state()->NotifyFeatureAllowed(
       mojom::AssistantAllowedState::ALLOWED);
 
   ui::GestureEvent long_press =
diff --git a/ash/shelf/scrollable_shelf_view.cc b/ash/shelf/scrollable_shelf_view.cc
index 3af6071..d98a8e6 100644
--- a/ash/shelf/scrollable_shelf_view.cc
+++ b/ash/shelf/scrollable_shelf_view.cc
@@ -442,31 +442,36 @@
 }
 
 bool ScrollableShelfView::ShouldHandleGestures(const ui::GestureEvent& event) {
-  if (!cross_main_axis_scrolling_ && !event.IsScrollGestureEvent())
-    return true;
+  // ScrollableShelfView only handles the gesture scrolling along the main axis.
+  // For other gesture events, including the scrolling across the main axis,
+  // they are handled by ShelfView.
+
+  if (scroll_status_ == kNotInScroll && !event.IsScrollGestureEvent())
+    return false;
 
   if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
-    CHECK_EQ(false, cross_main_axis_scrolling_);
+    CHECK_EQ(scroll_status_, kNotInScroll);
 
     float main_offset = event.details().scroll_x_hint();
     float cross_offset = event.details().scroll_y_hint();
     if (!GetShelf()->IsHorizontalAlignment())
       std::swap(main_offset, cross_offset);
 
-    cross_main_axis_scrolling_ = std::abs(main_offset) < std::abs(cross_offset);
+    scroll_status_ = std::abs(main_offset) < std::abs(cross_offset)
+                         ? kAcrossMainAxisScroll
+                         : kAlongMainAxisScroll;
   }
 
-  // Gesture scrollings perpendicular to the main axis should be handled by
-  // ShelfView.
-  bool should_handle_gestures = !cross_main_axis_scrolling_;
+  bool should_handle_gestures = scroll_status_ == kAlongMainAxisScroll;
 
-  if (should_handle_gestures && event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
+  if (scroll_status_ == kAlongMainAxisScroll &&
+      event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
     scroll_offset_before_main_axis_scrolling_ = scroll_offset_;
     layout_strategy_before_main_axis_scrolling_ = layout_strategy_;
   }
 
   if (event.type() == ui::ET_GESTURE_END) {
-    cross_main_axis_scrolling_ = false;
+    scroll_status_ = kNotInScroll;
 
     if (should_handle_gestures) {
       scroll_offset_before_main_axis_scrolling_ = gfx::Vector2dF();
diff --git a/ash/shelf/scrollable_shelf_view.h b/ash/shelf/scrollable_shelf_view.h
index 2abf48fd..a53a544c 100644
--- a/ash/shelf/scrollable_shelf_view.h
+++ b/ash/shelf/scrollable_shelf_view.h
@@ -56,6 +56,21 @@
  private:
   class GradientLayerDelegate;
 
+  enum ScrollStatus {
+    // Indicates whether the gesture scrolling is across the main axis.
+    // That is, whether it is scrolling vertically for bottom shelf, or
+    // whether it is scrolling horizontally for left/right shelf.
+    kAcrossMainAxisScroll,
+
+    // Indicates whether the gesture scrolling is along the main axis.
+    // That is, whether it is scrolling horizontally for bottom shelf, or
+    // whether it is scrolling vertically for left/right shelf.
+    kAlongMainAxisScroll,
+
+    // Not in scrolling.
+    kNotInScroll
+  };
+
   // Returns the maximum scroll distance.
   int CalculateScrollUpperBound() const;
 
@@ -137,10 +152,7 @@
 
   gfx::Vector2dF scroll_offset_;
 
-  // Indicates whether the gesture scrolling is across the main axis.
-  // That is, whether it is scrolling vertically for bottom shelf, or
-  // whether it is scrolling horizontally for left/right shelf.
-  bool cross_main_axis_scrolling_ = false;
+  ScrollStatus scroll_status_ = kNotInScroll;
 
   // Gesture states are preserved when the gesture scrolling along the main axis
   // (that is, whether it is scrolling horizontally for bottom shelf, or whether
diff --git a/ash/shelf/shelf_navigation_widget.cc b/ash/shelf/shelf_navigation_widget.cc
index e1b88f0..18e2178 100644
--- a/ash/shelf/shelf_navigation_widget.cc
+++ b/ash/shelf/shelf_navigation_widget.cc
@@ -12,7 +12,6 @@
 #include "ash/shelf/shelf_view.h"
 #include "ash/shelf/shelf_widget.h"
 #include "ash/shell.h"
-#include "ash/shell_observer.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -20,7 +19,6 @@
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/views/animation/bounds_animator.h"
 #include "ui/views/background.h"
-#include "ui/views/layout/box_layout.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -34,30 +32,42 @@
          Shell::Get()->tablet_mode_controller()->InTabletMode();
 }
 
+// Returns the bounds for the first button shown in this view (the back
+// button in tablet mode, the home button otherwise).
+gfx::Rect GetFirstButtonBounds() {
+  return gfx::Rect(0, 0, ShelfConstants::control_size(),
+                   ShelfConstants::control_size());
+}
+
+// Returns the bounds for the second button shown in this view (which is
+// always the home button and only in tablet mode, which implies a horizontal
+// shelf).
+gfx::Rect GetSecondButtonBounds() {
+  return gfx::Rect(
+      ShelfConstants::control_size() + ShelfConstants::button_spacing(), 0,
+      ShelfConstants::control_size(), ShelfConstants::control_size());
+}
+
 }  // namespace
 
 class ShelfNavigationWidget::Delegate : public views::AccessiblePaneView,
-                                        public views::WidgetDelegate,
-                                        public ShellObserver {
+                                        public views::WidgetDelegate {
  public:
   Delegate(Shelf* shelf, ShelfView* shelf_view);
   ~Delegate() override;
 
-  // views::View
+  // views::View:
   FocusTraversable* GetPaneFocusTraversable() override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
-  // views::AccessiblePaneView
+  // views::AccessiblePaneView:
   View* GetDefaultFocusableChild() override;
 
-  // views::WidgetDelegate
+  // views::WidgetDelegate:
   bool CanActivate() const override;
   views::Widget* GetWidget() override { return View::GetWidget(); }
   const views::Widget* GetWidget() const override { return View::GetWidget(); }
 
-  // ShellObserver
-  void OnShelfAlignmentChanged(aura::Window* root_window) override;
-
   BackButton* back_button() const { return back_button_; }
   HomeButton* home_button() const { return home_button_; }
 
@@ -66,11 +76,8 @@
   }
 
  private:
-  void UpdateLayoutManager();
-
   BackButton* back_button_ = nullptr;
   HomeButton* home_button_ = nullptr;
-  Shelf* shelf_ = nullptr;
   // When true, the default focus of the navigation widget is the last
   // focusable child.
   bool default_last_focusable_child_ = false;
@@ -78,8 +85,7 @@
   DISALLOW_COPY_AND_ASSIGN(Delegate);
 };
 
-ShelfNavigationWidget::Delegate::Delegate(Shelf* shelf, ShelfView* shelf_view)
-    : shelf_(shelf) {
+ShelfNavigationWidget::Delegate::Delegate(Shelf* shelf, ShelfView* shelf_view) {
   set_allow_deactivate_on_esc(true);
 
   const int control_size = ShelfConstants::control_size();
@@ -101,14 +107,9 @@
   SetBackground(views::CreateRoundedRectBackground(
       kShelfControlPermanentHighlightBackground,
       ShelfConstants::control_border_radius()));
-  UpdateLayoutManager();
-
-  Shell::Get()->AddShellObserver(this);
 }
 
-ShelfNavigationWidget::Delegate::~Delegate() {
-  Shell::Get()->RemoveShellObserver(this);
-}
+ShelfNavigationWidget::Delegate::~Delegate() = default;
 
 bool ShelfNavigationWidget::Delegate::CanActivate() const {
   // We don't want mouse clicks to activate us, but we need to allow
@@ -132,33 +133,22 @@
                                        : GetFirstFocusableChild();
 }
 
-void ShelfNavigationWidget::Delegate::OnShelfAlignmentChanged(
-    aura::Window* root_window) {
-  UpdateLayoutManager();
-}
-
-void ShelfNavigationWidget::Delegate::UpdateLayoutManager() {
-  const views::BoxLayout::Orientation orientation =
-      shelf_->IsHorizontalAlignment()
-          ? views::BoxLayout::Orientation::kHorizontal
-          : views::BoxLayout::Orientation::kVertical;
-  SetLayoutManager(std::make_unique<views::BoxLayout>(
-      orientation, gfx::Insets(), ShelfConstants::button_spacing()));
-}
-
 ShelfNavigationWidget::ShelfNavigationWidget(Shelf* shelf,
                                              ShelfView* shelf_view)
     : shelf_(shelf),
       delegate_(new ShelfNavigationWidget::Delegate(shelf, shelf_view)),
       bounds_animator_(std::make_unique<views::BoundsAnimator>(delegate_)) {
   DCHECK(shelf_);
+  bounds_animator_->SetAnimationDuration(kBackButtonOpacityAnimationDurationMs);
   Shell::Get()->tablet_mode_controller()->AddObserver(this);
+  Shell::Get()->AddShellObserver(this);
 }
 
 ShelfNavigationWidget::~ShelfNavigationWidget() {
   // Shell destroys the TabletModeController before destroying all root windows.
   if (Shell::Get()->tablet_mode_controller())
     Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
+  Shell::Get()->RemoveShellObserver(this);
 }
 
 void ShelfNavigationWidget::Initialize(aura::Window* container) {
@@ -174,9 +164,9 @@
   set_focus_on_creation(false);
   GetFocusManager()->set_arrow_key_traversal_enabled_for_widget(true);
   SetContentsView(delegate_);
-  GetBackButton()->SetVisible(IsTabletMode());
-  GetBackButton()->layer()->SetOpacity(IsTabletMode() ? 1 : 0);
+  GetBackButton()->SetBoundsRect(GetFirstButtonBounds());
   SetSize(GetIdealSize());
+  UpdateLayout();
 }
 
 gfx::Size ShelfNavigationWidget::GetIdealSize() const {
@@ -218,46 +208,41 @@
 }
 
 void ShelfNavigationWidget::OnTabletModeStarted() {
-  // Show the back button right away so that the animation is visible.
-  GetBackButton()->SetVisible(true);
-  GetBackButton()->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
-
-  ui::ScopedLayerAnimationSettings settings(
-      GetBackButton()->layer()->GetAnimator());
-  settings.SetTransitionDuration(
-      base::TimeDelta::FromMilliseconds(kBackButtonOpacityAnimationDurationMs));
-  settings.AddObserver(this);
-  GetBackButton()->layer()->SetOpacity(1);
-
-  bounds_animator_->SetAnimationDuration(kBackButtonOpacityAnimationDurationMs);
-  bounds_animator_->AnimateViewTo(
-      GetHomeButton(),
-      gfx::Rect(
-          ShelfConstants::control_size() + ShelfConstants::button_spacing(), 0,
-          ShelfConstants::control_size(), ShelfConstants::control_size()));
+  UpdateLayout();
 }
 
 void ShelfNavigationWidget::OnTabletModeEnded() {
-  ui::ScopedLayerAnimationSettings settings(
-      GetBackButton()->layer()->GetAnimator());
-  settings.SetTransitionDuration(
-      base::TimeDelta::FromMilliseconds(kBackButtonOpacityAnimationDurationMs));
-  settings.AddObserver(this);
-  GetBackButton()->layer()->SetOpacity(0);
+  UpdateLayout();
+}
 
-  GetBackButton()->SetFocusBehavior(views::View::FocusBehavior::NEVER);
-  bounds_animator_->SetAnimationDuration(kBackButtonOpacityAnimationDurationMs);
-  bounds_animator_->AnimateViewTo(
-      GetHomeButton(), gfx::Rect(0, 0, ShelfConstants::control_size(),
-                                 ShelfConstants::control_size()));
+void ShelfNavigationWidget::OnShelfAlignmentChanged(aura::Window* root_window) {
+  UpdateLayout();
 }
 
 void ShelfNavigationWidget::OnImplicitAnimationsCompleted() {
   // Hide the back button once it has become fully transparent.
-  if (!IsTabletMode()) {
+  if (!IsTabletMode())
     GetBackButton()->SetVisible(false);
-    delegate_->InvalidateLayout();
-  }
+}
+
+void ShelfNavigationWidget::UpdateLayout() {
+  const bool tablet_mode = IsTabletMode();
+  // Show the back button right away so that the animation is visible.
+  if (tablet_mode)
+    GetBackButton()->SetVisible(true);
+  GetBackButton()->SetFocusBehavior(tablet_mode
+                                        ? views::View::FocusBehavior::ALWAYS
+                                        : views::View::FocusBehavior::NEVER);
+  ui::ScopedLayerAnimationSettings settings(
+      GetBackButton()->layer()->GetAnimator());
+  settings.SetTransitionDuration(
+      base::TimeDelta::FromMilliseconds(kBackButtonOpacityAnimationDurationMs));
+  settings.AddObserver(this);
+  GetBackButton()->layer()->SetOpacity(tablet_mode ? 1 : 0);
+
+  bounds_animator_->AnimateViewTo(
+      GetHomeButton(),
+      tablet_mode ? GetSecondButtonBounds() : GetFirstButtonBounds());
 }
 
 }  // namespace ash
diff --git a/ash/shelf/shelf_navigation_widget.h b/ash/shelf/shelf_navigation_widget.h
index 6ee10e94..48abd57 100644
--- a/ash/shelf/shelf_navigation_widget.h
+++ b/ash/shelf/shelf_navigation_widget.h
@@ -7,6 +7,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/tablet_mode_observer.h"
+#include "ash/shell_observer.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/views/accessible_pane_view.h"
 #include "ui/views/widget/widget.h"
@@ -30,6 +31,7 @@
 // the back button.
 class ASH_EXPORT ShelfNavigationWidget : public views::Widget,
                                          public TabletModeObserver,
+                                         public ShellObserver,
                                          public ui::ImplicitAnimationObserver {
  public:
   ShelfNavigationWidget(Shelf* shelf, ShelfView* shelf_view);
@@ -42,7 +44,7 @@
   // tablet mode is on.
   gfx::Size GetIdealSize() const;
 
-  // views::Widget
+  // views::Widget:
   bool OnNativeWidgetActivationChanged(bool active) override;
 
   // Getters for the back and home buttons.
@@ -57,16 +59,27 @@
   // focused when activating this widget.
   void SetDefaultLastFocusableChild(bool default_last_focusable_child);
 
-  // TabletModeObserver
+  // TabletModeObserver:
   void OnTabletModeStarted() override;
   void OnTabletModeEnded() override;
 
-  // ui::ImplicitAnimationObserver
+  // ShellObserver:
+  void OnShelfAlignmentChanged(aura::Window* root_window) override;
+
+  // ui::ImplicitAnimationObserver:
   void OnImplicitAnimationsCompleted() override;
 
+  views::BoundsAnimator* get_bounds_animator_for_testing() {
+    return bounds_animator_.get();
+  }
+
  private:
   class Delegate;
 
+  // Updates this widget's layout according to current constraints: tablet
+  // mode and shelf orientation.
+  void UpdateLayout();
+
   Shelf* shelf_ = nullptr;
   Delegate* delegate_ = nullptr;
   std::unique_ptr<views::BoundsAnimator> bounds_animator_;
diff --git a/ash/shelf/shelf_view_test_api.cc b/ash/shelf/shelf_view_test_api.cc
index e896cd9..2e4801d2 100644
--- a/ash/shelf/shelf_view_test_api.cc
+++ b/ash/shelf/shelf_view_test_api.cc
@@ -89,19 +89,25 @@
   shelf_view_->bounds_animator_->SetAnimationDuration(duration_ms);
 }
 
-void ShelfViewTestAPI::RunMessageLoopUntilAnimationsDone() {
-  if (!shelf_view_->bounds_animator_->IsAnimating())
-    return;
-
+void ShelfViewTestAPI::RunMessageLoopUntilAnimationsDone(
+    views::BoundsAnimator* bounds_animator) {
   std::unique_ptr<TestAPIAnimationObserver> observer(
       new TestAPIAnimationObserver());
-  shelf_view_->bounds_animator_->AddObserver(observer.get());
+
+  bounds_animator->AddObserver(observer.get());
 
   // This nested loop will quit when TestAPIAnimationObserver's
   // OnBoundsAnimatorDone is called.
   base::RunLoop().Run();
 
-  shelf_view_->bounds_animator_->RemoveObserver(observer.get());
+  bounds_animator->RemoveObserver(observer.get());
+}
+
+void ShelfViewTestAPI::RunMessageLoopUntilAnimationsDone() {
+  if (!shelf_view_->bounds_animator_->IsAnimating())
+    return;
+
+  RunMessageLoopUntilAnimationsDone(shelf_view_->bounds_animator_.get());
 }
 
 gfx::Rect ShelfViewTestAPI::GetMenuAnchorRect(const views::View& source,
diff --git a/ash/shelf/shelf_view_test_api.h b/ash/shelf/shelf_view_test_api.h
index a2245b1e..2a16b06 100644
--- a/ash/shelf/shelf_view_test_api.h
+++ b/ash/shelf/shelf_view_test_api.h
@@ -15,6 +15,7 @@
 }
 
 namespace views {
+class BoundsAnimator;
 class View;
 }
 
@@ -60,7 +61,13 @@
   // Sets animation duration in milliseconds for test.
   void SetAnimationDuration(int duration_ms);
 
-  // Runs message loop and waits until all add/remove animations are done.
+  // Runs message loop and waits until all add/remove animations are done for
+  // the given bounds animator.
+  void RunMessageLoopUntilAnimationsDone(
+      views::BoundsAnimator* bounds_animator);
+
+  // Runs message loop and waits until all add/remove animations are done on
+  // the shelf view.
   void RunMessageLoopUntilAnimationsDone();
 
   // Gets the anchor point that would be used for a context menu with these
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index 6bc96d5..cd24d75 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -78,6 +78,7 @@
 #include "ui/events/event_utils.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/geometry/point.h"
+#include "ui/views/animation/bounds_animator.h"
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/animation/test/ink_drop_host_view_test_api.h"
 #include "ui/views/bubble/bubble_frame_view.h"
@@ -2128,7 +2129,12 @@
   // to finish in order for the BackButton to move out from under the
   // HomeButton.
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
-  test_api_->RunMessageLoopUntilAnimationsDone();
+
+  // We need to wait for the navigation widget's animation to be done.
+  test_api_->RunMessageLoopUntilAnimationsDone(
+      shelf_view_->shelf_widget()
+          ->navigation_widget()
+          ->get_bounds_animator_for_testing());
 
   views::View* back_button = shelf_view_->shelf_widget()->GetBackButton();
   generator->MoveMouseTo(back_button->GetBoundsInScreen().CenterPoint());
diff --git a/ash/shell.cc b/ash/shell.cc
index a5fb322..0d531de1 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -72,7 +72,6 @@
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shell_window_ids.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/root_window_controller.h"
 #include "ash/screenshot_delegate.h"
 #include "ash/session/session_controller_impl.h"
@@ -354,7 +353,6 @@
 }
 
 AssistantController* Shell::assistant_controller() {
-  DCHECK(chromeos::features::IsAssistantEnabled());
   return assistant_controller_.get();
 }
 
@@ -663,8 +661,7 @@
   // Destroy |assistant_controller_| earlier than |tablet_mode_controller_| so
   // that the former will destroy the Assistant view hierarchy which has a
   // dependency on the latter.
-  if (chromeos::features::IsAssistantEnabled())
-    assistant_controller_.reset();
+  assistant_controller_.reset();
 
   // Destroy tablet mode controller early on since it has some observers which
   // need to be removed.
@@ -753,7 +750,6 @@
   laser_pointer_controller_.reset();
   partial_magnification_controller_.reset();
   highlighter_controller_.reset();
-  voice_interaction_controller_.reset();
   key_accessibility_enabler_.reset();
 
   display_speaker_controller_.reset();
@@ -980,8 +976,6 @@
       display::Screen::GetScreen()->GetPrimaryDisplay());
 
   accelerator_controller_ = std::make_unique<AcceleratorControllerImpl>();
-  voice_interaction_controller_ =
-      std::make_unique<VoiceInteractionController>();
 
   shelf_controller_ = std::make_unique<ShelfController>();
 
@@ -1056,9 +1050,7 @@
 
   magnification_controller_ = std::make_unique<MagnificationController>();
   mru_window_tracker_ = std::make_unique<MruWindowTracker>();
-  assistant_controller_ = chromeos::features::IsAssistantEnabled()
-                              ? std::make_unique<AssistantController>()
-                              : nullptr;
+  assistant_controller_ = std::make_unique<AssistantController>();
 
   // |assistant_controller_| is put before |ambient_controller_| as it will be
   // used by the latter.
diff --git a/ash/shell.h b/ash/shell.h
index 6e99eb1f..7072104e 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -186,7 +186,6 @@
 class TrayBluetoothHelper;
 class VideoActivityNotifier;
 class VideoDetector;
-class VoiceInteractionController;
 class VpnList;
 class WallpaperControllerImpl;
 class WaylandServerController;
@@ -684,7 +683,6 @@
   std::unique_ptr<ToastManagerImpl> toast_manager_;
   std::unique_ptr<TouchDevicesController> touch_devices_controller_;
   std::unique_ptr<TrayAction> tray_action_;
-  std::unique_ptr<VoiceInteractionController> voice_interaction_controller_;
   std::unique_ptr<VpnList> vpn_list_;
   std::unique_ptr<WallpaperControllerImpl> wallpaper_controller_;
   std::unique_ptr<WindowCycleController> window_cycle_controller_;
diff --git a/ash/system/palette/palette_tool.cc b/ash/system/palette/palette_tool.cc
index f49c48e..f3f5185 100644
--- a/ash/system/palette/palette_tool.cc
+++ b/ash/system/palette/palette_tool.cc
@@ -24,9 +24,7 @@
   tool_manager->AddTool(std::make_unique<CaptureRegionMode>(tool_manager));
   tool_manager->AddTool(std::make_unique<CaptureScreenAction>(tool_manager));
   tool_manager->AddTool(std::make_unique<CreateNoteAction>(tool_manager));
-  if (chromeos::features::IsAssistantEnabled()) {
-    tool_manager->AddTool(std::make_unique<MetalayerMode>(tool_manager));
-  }
+  tool_manager->AddTool(std::make_unique<MetalayerMode>(tool_manager));
   tool_manager->AddTool(std::make_unique<LaserPointerMode>(tool_manager));
   tool_manager->AddTool(std::make_unique<MagnifierMode>(tool_manager));
 }
diff --git a/ash/system/palette/palette_tray_unittest.cc b/ash/system/palette/palette_tray_unittest.cc
index cdc3b9e..098fb57c 100644
--- a/ash/system/palette/palette_tray_unittest.cc
+++ b/ash/system/palette/palette_tray_unittest.cc
@@ -13,8 +13,8 @@
 #include "ash/highlighter/highlighter_controller_test_api.h"
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/ash_switches.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/stylus_utils.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
@@ -231,10 +231,10 @@
 }
 
 // Base class for tests that rely on Assistant enabled.
-class PaletteTrayTestWithVoiceInteraction : public PaletteTrayTest {
+class PaletteTrayTestWithAssistant : public PaletteTrayTest {
  public:
-  PaletteTrayTestWithVoiceInteraction() = default;
-  ~PaletteTrayTestWithVoiceInteraction() override = default;
+  PaletteTrayTestWithAssistant() = default;
+  ~PaletteTrayTestWithAssistant() override = default;
 
   // PaletteTrayTest:
   void SetUp() override {
@@ -269,6 +269,8 @@
     return highlighter_test_api_->IsShowingHighlighter();
   }
 
+  AssistantState* assistant_state() const { return AssistantState::Get(); }
+
   void DragAndAssertMetalayer(const std::string& context,
                               const gfx::Point& origin,
                               int event_flags,
@@ -317,22 +319,20 @@
  private:
   base::SimpleTestTickClock simulated_clock_;
 
-  DISALLOW_COPY_AND_ASSIGN(PaletteTrayTestWithVoiceInteraction);
+  DISALLOW_COPY_AND_ASSIGN(PaletteTrayTestWithAssistant);
 };
 
-TEST_F(PaletteTrayTestWithVoiceInteraction, MetalayerToolViewCreated) {
+TEST_F(PaletteTrayTestWithAssistant, MetalayerToolViewCreated) {
   EXPECT_TRUE(
       test_api_->palette_tool_manager()->HasTool(PaletteToolId::METALAYER));
 }
 
-TEST_F(PaletteTrayTestWithVoiceInteraction, MetalayerToolActivatesHighlighter) {
+TEST_F(PaletteTrayTestWithAssistant, MetalayerToolActivatesHighlighter) {
   ui::ScopedAnimationDurationScaleMode animation_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
-  VoiceInteractionController::Get()->NotifyStatusChanged(
-      mojom::VoiceInteractionState::RUNNING);
-  VoiceInteractionController::Get()->NotifySettingsEnabled(true);
-  VoiceInteractionController::Get()->NotifyContextEnabled(true);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyStatusChanged(mojom::VoiceInteractionState::RUNNING);
+  assistant_state()->NotifySettingsEnabled(true);
+  assistant_state()->NotifyContextEnabled(true);
 
   ui::test::EventGenerator* generator = GetEventGenerator();
   generator->EnterPenPointerMode();
@@ -394,8 +394,7 @@
   // Disabling metalayer support in the delegate should disable the palette
   // tool.
   test_api_->palette_tool_manager()->ActivateTool(PaletteToolId::METALAYER);
-  VoiceInteractionController::Get()->NotifyContextEnabled(false);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyContextEnabled(false);
   EXPECT_FALSE(metalayer_enabled());
 
   // With the metalayer disabled again, press/drag does not activate the
@@ -405,15 +404,13 @@
                          false /* no highlighter on press */);
 }
 
-TEST_F(PaletteTrayTestWithVoiceInteraction,
-       StylusBarrelButtonActivatesHighlighter) {
+TEST_F(PaletteTrayTestWithAssistant, StylusBarrelButtonActivatesHighlighter) {
   ui::ScopedAnimationDurationScaleMode animation_duration_mode(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
-  VoiceInteractionController::Get()->NotifyStatusChanged(
+  assistant_state()->NotifyStatusChanged(
       mojom::VoiceInteractionState::NOT_READY);
-  VoiceInteractionController::Get()->NotifySettingsEnabled(false);
-  VoiceInteractionController::Get()->NotifyContextEnabled(false);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifySettingsEnabled(false);
+  assistant_state()->NotifyContextEnabled(false);
 
   ui::test::EventGenerator* generator = GetEventGenerator();
   generator->EnterPenPointerMode();
@@ -433,23 +430,19 @@
                              false /* no highlighter on press */);
 
   // Enable one of the two user prefs, should not be sufficient.
-  VoiceInteractionController::Get()->NotifyContextEnabled(true);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyContextEnabled(true);
   WaitDragAndAssertMetalayer("one pref enabled", origin,
                              ui::EF_LEFT_MOUSE_BUTTON, false /* no metalayer */,
                              false /* no highlighter on press */);
 
   // Enable the other user pref, still not sufficient.
-  VoiceInteractionController::Get()->NotifySettingsEnabled(true);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifySettingsEnabled(true);
   WaitDragAndAssertMetalayer("two prefs enabled", origin,
                              ui::EF_LEFT_MOUSE_BUTTON, false /* no metalayer */,
                              false /* no highlighter on press */);
 
   // Once the service is ready, the button should start working.
-  VoiceInteractionController::Get()->NotifyStatusChanged(
-      mojom::VoiceInteractionState::RUNNING);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyStatusChanged(mojom::VoiceInteractionState::RUNNING);
 
   // Press and drag with no button, still no highlighter.
   WaitDragAndAssertMetalayer("all enabled, no button ", origin, ui::EF_NONE,
@@ -513,8 +506,7 @@
 
   // Disable the metalayer support.
   // This should deactivate both the palette tool and the highlighter.
-  VoiceInteractionController::Get()->NotifyContextEnabled(false);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyContextEnabled(false);
   EXPECT_FALSE(test_api_->palette_tool_manager()->IsToolActive(
       PaletteToolId::METALAYER));
 
diff --git a/ash/system/palette/palette_welcome_bubble.cc b/ash/system/palette/palette_welcome_bubble.cc
index e4b2431f..59fd4cfb 100644
--- a/ash/system/palette/palette_welcome_bubble.cc
+++ b/ash/system/palette/palette_welcome_bubble.cc
@@ -58,10 +58,9 @@
 
   void Init() override {
     SetLayoutManager(std::make_unique<views::FillLayout>());
+    // TODO(crbug.com/996312): Check if the board type is eligibile here.
     auto* label = new views::Label(l10n_util::GetStringUTF16(
-        chromeos::features::IsAssistantEnabled()
-            ? IDS_ASH_STYLUS_WARM_WELCOME_BUBBLE_WITH_ASSISTANT_DESCRIPTION
-            : IDS_ASH_STYLUS_WARM_WELCOME_BUBBLE_DESCRIPTION));
+        IDS_ASH_STYLUS_WARM_WELCOME_BUBBLE_WITH_ASSISTANT_DESCRIPTION));
     label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     label->SetMultiLine(true);
     label->SizeToFit(kBubbleContentLabelPreferredWidthDp);
diff --git a/ash/system/palette/tools/metalayer_mode.cc b/ash/system/palette/tools/metalayer_mode.cc
index 2eaa9f9..af764054 100644
--- a/ash/system/palette/tools/metalayer_mode.cc
+++ b/ash/system/palette/tools/metalayer_mode.cc
@@ -4,8 +4,8 @@
 
 #include "ash/system/palette/tools/metalayer_mode.h"
 
+#include "ash/assistant/assistant_controller.h"
 #include "ash/public/cpp/toast_data.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -37,13 +37,14 @@
 
 MetalayerMode::MetalayerMode(Delegate* delegate) : CommonPaletteTool(delegate) {
   Shell::Get()->AddPreTargetHandler(this);
-  VoiceInteractionController::Get()->AddLocalObserver(this);
+  AssistantState::Get()->AddObserver(this);
   Shell::Get()->highlighter_controller()->AddObserver(this);
 }
 
 MetalayerMode::~MetalayerMode() {
   Shell::Get()->highlighter_controller()->RemoveObserver(this);
-  VoiceInteractionController::Get()->RemoveLocalObserver(this);
+  if (AssistantState::Get())
+    AssistantState::Get()->RemoveObserver(this);
   Shell::Get()->RemovePreTargetHandler(this);
 }
 
@@ -149,18 +150,18 @@
   event->StopPropagation();
 }
 
-void MetalayerMode::OnVoiceInteractionStatusChanged(
+void MetalayerMode::OnAssistantStatusChanged(
     mojom::VoiceInteractionState state) {
   voice_interaction_state_ = state;
   UpdateState();
 }
 
-void MetalayerMode::OnVoiceInteractionSettingsEnabled(bool enabled) {
+void MetalayerMode::OnAssistantSettingsEnabled(bool enabled) {
   voice_interaction_enabled_ = enabled;
   UpdateState();
 }
 
-void MetalayerMode::OnVoiceInteractionContextEnabled(bool enabled) {
+void MetalayerMode::OnAssistantContextEnabled(bool enabled) {
   voice_interaction_context_enabled_ = enabled;
   UpdateState();
 }
diff --git a/ash/system/palette/tools/metalayer_mode.h b/ash/system/palette/tools/metalayer_mode.h
index 254844a..7e52041 100644
--- a/ash/system/palette/tools/metalayer_mode.h
+++ b/ash/system/palette/tools/metalayer_mode.h
@@ -7,7 +7,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/highlighter/highlighter_controller.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "ash/system/palette/common_palette_tool.h"
 #include "base/memory/weak_ptr.h"
@@ -22,7 +22,7 @@
 // menu, but also by the stylus button click.
 class ASH_EXPORT MetalayerMode : public CommonPaletteTool,
                                  public ui::EventHandler,
-                                 public DefaultVoiceInteractionObserver,
+                                 public AssistantStateObserver,
                                  public HighlighterController::Observer {
  public:
   explicit MetalayerMode(Delegate* delegate);
@@ -64,11 +64,10 @@
   // ui::EventHandler:
   void OnTouchEvent(ui::TouchEvent* event) override;
 
-  // mojom::VoiceInteractionObserver:
-  void OnVoiceInteractionStatusChanged(
-      mojom::VoiceInteractionState state) override;
-  void OnVoiceInteractionSettingsEnabled(bool enabled) override;
-  void OnVoiceInteractionContextEnabled(bool enabled) override;
+  // AssistantStateObserver:
+  void OnAssistantStatusChanged(mojom::VoiceInteractionState state) override;
+  void OnAssistantSettingsEnabled(bool enabled) override;
+  void OnAssistantContextEnabled(bool enabled) override;
   void OnAssistantFeatureAllowedChanged(
       mojom::AssistantAllowedState state) override;
 
diff --git a/ash/system/palette/tools/metalayer_unittest.cc b/ash/system/palette/tools/metalayer_unittest.cc
index 8a0e9c8e..385ecd83 100644
--- a/ash/system/palette/tools/metalayer_unittest.cc
+++ b/ash/system/palette/tools/metalayer_unittest.cc
@@ -6,7 +6,7 @@
 
 #include "ash/highlighter/highlighter_controller.h"
 #include "ash/highlighter/highlighter_controller_test_api.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "ash/shell.h"
 #include "ash/system/palette/mock_palette_tool_delegate.h"
@@ -49,6 +49,8 @@
     AshTestBase::TearDown();
   }
 
+  AssistantState* assistant_state() { return AssistantState::Get(); }
+
  protected:
   std::unique_ptr<HighlighterControllerTestApi> highlighter_test_api_;
   std::unique_ptr<MockPaletteToolDelegate> palette_tool_delegate_;
@@ -71,7 +73,6 @@
       mojom::AssistantAllowedState::ALLOWED,
       mojom::AssistantAllowedState::DISALLOWED_BY_POLICY,
       mojom::AssistantAllowedState::DISALLOWED_BY_LOCALE,
-      mojom::AssistantAllowedState::DISALLOWED_BY_FLAG,
       mojom::AssistantAllowedState::DISALLOWED_BY_NONPRIMARY_USER,
       mojom::AssistantAllowedState::DISALLOWED_BY_SUPERVISED_USER,
       mojom::AssistantAllowedState::DISALLOWED_BY_INCOGNITO,
@@ -88,12 +89,10 @@
           const bool ready = state != mojom::VoiceInteractionState::NOT_READY;
           const bool selectable = allowed && enabled && context && ready;
 
-          VoiceInteractionController::Get()->NotifyStatusChanged(state);
-          VoiceInteractionController::Get()->NotifySettingsEnabled(enabled);
-          VoiceInteractionController::Get()->NotifyContextEnabled(context);
-          VoiceInteractionController::Get()->NotifyFeatureAllowed(
-              allowed_state);
-          VoiceInteractionController::Get()->FlushForTesting();
+          assistant_state()->NotifyStatusChanged(state);
+          assistant_state()->NotifySettingsEnabled(enabled);
+          assistant_state()->NotifyContextEnabled(context);
+          assistant_state()->NotifyFeatureAllowed(allowed_state);
 
           std::unique_ptr<views::View> view =
               base::WrapUnique(tool_->CreateView());
@@ -139,30 +138,24 @@
 
 // Verifies that disabling the metalayer support disables the tool.
 TEST_F(MetalayerToolTest, MetalayerUnsupportedDisablesPaletteTool) {
-  VoiceInteractionController::Get()->NotifyStatusChanged(
-      mojom::VoiceInteractionState::RUNNING);
-  VoiceInteractionController::Get()->NotifySettingsEnabled(true);
-  VoiceInteractionController::Get()->NotifyContextEnabled(true);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyStatusChanged(mojom::VoiceInteractionState::RUNNING);
+  assistant_state()->NotifySettingsEnabled(true);
+  assistant_state()->NotifyContextEnabled(true);
 
   // Disabling the user prefs individually should disable the tool.
   tool_->OnEnable();
   EXPECT_CALL(*palette_tool_delegate_.get(),
               DisableTool(PaletteToolId::METALAYER));
-  VoiceInteractionController::Get()->NotifySettingsEnabled(false);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifySettingsEnabled(false);
   testing::Mock::VerifyAndClearExpectations(palette_tool_delegate_.get());
-  VoiceInteractionController::Get()->NotifySettingsEnabled(true);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifySettingsEnabled(true);
 
   tool_->OnEnable();
   EXPECT_CALL(*palette_tool_delegate_.get(),
               DisableTool(PaletteToolId::METALAYER));
-  VoiceInteractionController::Get()->NotifyContextEnabled(false);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyContextEnabled(false);
   testing::Mock::VerifyAndClearExpectations(palette_tool_delegate_.get());
-  VoiceInteractionController::Get()->NotifyContextEnabled(true);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyContextEnabled(true);
 
   // Test VoiceInteractionState changes.
   tool_->OnEnable();
@@ -172,20 +165,16 @@
   EXPECT_CALL(*palette_tool_delegate_.get(),
               DisableTool(PaletteToolId::METALAYER))
       .Times(0);
-  VoiceInteractionController::Get()->NotifyStatusChanged(
-      mojom::VoiceInteractionState::STOPPED);
-  VoiceInteractionController::Get()->NotifyStatusChanged(
-      mojom::VoiceInteractionState::RUNNING);
-  VoiceInteractionController::Get()->FlushForTesting();
+  assistant_state()->NotifyStatusChanged(mojom::VoiceInteractionState::STOPPED);
+  assistant_state()->NotifyStatusChanged(mojom::VoiceInteractionState::RUNNING);
   testing::Mock::VerifyAndClearExpectations(palette_tool_delegate_.get());
 
   // Changing the state to NOT_READY should disable the tool.
   EXPECT_CALL(*palette_tool_delegate_.get(),
               DisableTool(PaletteToolId::METALAYER))
       .Times(testing::AtLeast(1));
-  VoiceInteractionController::Get()->NotifyStatusChanged(
+  assistant_state()->NotifyStatusChanged(
       mojom::VoiceInteractionState::NOT_READY);
-  VoiceInteractionController::Get()->FlushForTesting();
   testing::Mock::VerifyAndClearExpectations(palette_tool_delegate_.get());
 }
 
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index d313dea..9a65621 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -469,6 +469,8 @@
 void OverviewSession::InitiateDrag(OverviewItem* item,
                                    const gfx::PointF& location_in_screen,
                                    bool is_touch_dragging) {
+  if (Shell::Get()->split_view_controller()->IsDividerAnimating())
+    return;
   highlight_controller_->SetFocusHighlightVisibility(false);
   window_drag_controller_ = std::make_unique<OverviewWindowDragController>(
       this, item, is_touch_dragging);
@@ -932,10 +934,6 @@
   for (auto& grid : grid_list_)
     grid->UpdateCannotSnapWarningVisibility();
 
-  // Notify |split_view_drag_indicators_| if split view mode ended.
-  if (split_view_drag_indicators_ && state == SplitViewState::kNoSnap)
-    split_view_drag_indicators_->OnSplitViewModeEnded();
-
   // Transfer focus from |window| to |overview_focus_widget_| to match the
   // behavior of entering overview mode in the beginning.
   DCHECK(overview_focus_widget_);
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index 5fa8b57..afe2a8f 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -4434,6 +4434,34 @@
             window_util::GetFocusedWindow());
 }
 
+// Test that you cannot drag from overview during the split view divider
+// animation.
+TEST_F(SplitViewOverviewSessionTest,
+       CannotDragFromOverviewDuringSplitViewDividerAnimation) {
+  std::unique_ptr<aura::Window> snapped_window = CreateTestWindow();
+  std::unique_ptr<aura::Window> overview_window = CreateTestWindow();
+  ToggleOverview();
+  split_view_controller()->SnapWindow(snapped_window.get(),
+                                      SplitViewController::LEFT);
+
+  gfx::Point divider_drag_point =
+      split_view_controller()
+          ->split_view_divider()
+          ->GetDividerBoundsInScreen(/*is_dragging=*/false)
+          .CenterPoint();
+  split_view_controller()->StartResize(divider_drag_point);
+  divider_drag_point.Offset(20, 0);
+  split_view_controller()->Resize(divider_drag_point);
+  split_view_controller()->EndResize(divider_drag_point);
+  ASSERT_TRUE(IsDividerAnimating());
+
+  OverviewItem* overview_item = GetOverviewItemForWindow(overview_window.get());
+  overview_session()->InitiateDrag(overview_item,
+                                   overview_item->target_bounds().CenterPoint(),
+                                   /*is_touch_dragging=*/true);
+  EXPECT_FALSE(overview_item->IsDragItem());
+}
+
 // Test the split view and overview functionalities in clamshell mode. Split
 // view is only active when overview is active in clamshell mode.
 class SplitViewOverviewSessionInClamshellTest
diff --git a/ash/wm/overview/overview_window_drag_controller.cc b/ash/wm/overview/overview_window_drag_controller.cc
index bb8616ae..0167b1e9 100644
--- a/ash/wm/overview/overview_window_drag_controller.cc
+++ b/ash/wm/overview/overview_window_drag_controller.cc
@@ -110,7 +110,9 @@
       display_count_(Shell::GetAllRootWindows().size()),
       is_touch_dragging_(is_touch_dragging),
       should_allow_split_view_(ShouldAllowSplitView()),
-      virtual_desks_bar_enabled_(GetVirtualDesksBarEnabled(item)) {}
+      virtual_desks_bar_enabled_(GetVirtualDesksBarEnabled(item)) {
+  DCHECK(!Shell::Get()->split_view_controller()->IsDividerAnimating());
+}
 
 OverviewWindowDragController::~OverviewWindowDragController() = default;
 
@@ -670,6 +672,7 @@
   DCHECK_NE(snap_position, SplitViewController::NONE);
 
   // |item_| will be deleted after SplitViewController::SnapWindow().
+  DCHECK(!Shell::Get()->split_view_controller()->IsDividerAnimating());
   split_view_controller_->SnapWindow(item_->GetWindow(), snap_position,
                                      /*use_divider_spawn_animation=*/true);
   item_ = nullptr;
diff --git a/ash/wm/splitview/split_view_drag_indicators.cc b/ash/wm/splitview/split_view_drag_indicators.cc
index 09166b6..5d081ad0a 100644
--- a/ash/wm/splitview/split_view_drag_indicators.cc
+++ b/ash/wm/splitview/split_view_drag_indicators.cc
@@ -276,26 +276,23 @@
 
   ~SplitViewDragIndicatorsView() override {}
 
-  // Called by parent widget when the state machine changes.
+  // Called by parent widget when the state machine changes. Handles setting the
+  // opacity and bounds of the highlights and labels.
   void OnIndicatorTypeChanged(IndicatorState indicator_state) {
     DCHECK_NE(indicator_state_, indicator_state);
     previous_indicator_state_ = indicator_state_;
     indicator_state_ = indicator_state;
-    Update();
-  }
 
-  // Handles setting the opacity and bounds of the highlights and labels.
-  void Update() {
-    left_rotated_view_->OnIndicatorTypeChanged(indicator_state_,
+    left_rotated_view_->OnIndicatorTypeChanged(indicator_state,
                                                previous_indicator_state_);
-    right_rotated_view_->OnIndicatorTypeChanged(indicator_state_,
+    right_rotated_view_->OnIndicatorTypeChanged(indicator_state,
                                                 previous_indicator_state_);
-    left_highlight_view_->OnIndicatorTypeChanged(indicator_state_,
+    left_highlight_view_->OnIndicatorTypeChanged(indicator_state,
                                                  previous_indicator_state_);
-    right_highlight_view_->OnIndicatorTypeChanged(indicator_state_,
+    right_highlight_view_->OnIndicatorTypeChanged(indicator_state,
                                                   previous_indicator_state_);
 
-    if (indicator_state_ != IndicatorState::kNone ||
+    if (indicator_state != IndicatorState::kNone ||
         IsPreviewAreaState(previous_indicator_state_))
       Layout(previous_indicator_state_ != IndicatorState::kNone);
   }
@@ -633,14 +630,6 @@
   indicators_view_->OnIndicatorTypeChanged(current_indicator_state_);
 }
 
-void SplitViewDragIndicators::OnSplitViewModeEnded() {
-  if (current_indicator_state_ == IndicatorState::kNone ||
-      IsPreviewAreaState(current_indicator_state_)) {
-    return;
-  }
-  indicators_view_->Update();
-}
-
 void SplitViewDragIndicators::OnDisplayBoundsChanged() {
   aura::Window* root_window = widget_->GetNativeView()->GetRootWindow();
   widget_->SetBounds(GetWorkAreaBoundsNoOverlapWithShelf(root_window));
diff --git a/ash/wm/splitview/split_view_drag_indicators.h b/ash/wm/splitview/split_view_drag_indicators.h
index a6b4c4a..e2f1710 100644
--- a/ash/wm/splitview/split_view_drag_indicators.h
+++ b/ash/wm/splitview/split_view_drag_indicators.h
@@ -82,11 +82,6 @@
   void SetIndicatorState(IndicatorState indicator_state,
                          const gfx::Point& event_location);
 
-  // Called by owner of this class when split view mode ends, so that this class
-  // can show the drag indicators which are usually hidden in split view mode.
-  // https://crbug.com/946601
-  void OnSplitViewModeEnded();
-
   // Called by owner of this class when display bounds changes are observed, so
   // that this class can relayout accordingly.
   void OnDisplayBoundsChanged();
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
index 0c7ae37..2e21d0d5 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
@@ -41,9 +41,9 @@
 constexpr float kIndicatorsThresholdRatio = 0.1;
 
 // Duration of a drag that it will be considered as an intended drag. Must be at
-// least the duration of the split view divider snap animation, or else there
-// will be an issue similar to https://crbug.com/946601 but involving dragging a
-// snapped window from the top.
+// least the duration of the split view divider snap animation, or else issues
+// like crbug.com/946601, crbug.com/997764, and https://crbug.com/997765, which
+// all refer to dragging from overview, will apply to dragging from the top.
 constexpr base::TimeDelta kIsWindowMovedTimeoutMs =
     base::TimeDelta::FromMilliseconds(300);
 
diff --git a/build/config/fuchsia/BUILD.gn b/build/config/fuchsia/BUILD.gn
index a661b24..888be8c 100644
--- a/build/config/fuchsia/BUILD.gn
+++ b/build/config/fuchsia/BUILD.gn
@@ -56,73 +56,6 @@
   libs = [ "zircon" ]
 }
 
-# Writes an extended version of storage-full.blk to fvm.extended.blk.
-blobstore_extended_path = "$root_out_dir/storage-extended.blk"
-action("blobstore_extended") {
-  # The file is grown by 1GB, which should be large enough to hold packaged
-  # binaries and assets. The value should be increased if the size becomes a
-  # limitation in the future.
-  _extend_size = "1073741824"  # 1GB
-
-  _blobstore_path =
-      "${fuchsia_sdk}/../images/${current_cpu}/qemu/storage-full.blk"
-
-  script = "//build/config/fuchsia/extend_fvm.py"
-
-  inputs = [
-    _blobstore_path,
-    "${fuchsia_sdk}/tools/fvm",
-  ]
-  outputs = [
-    blobstore_extended_path,
-  ]
-
-  args = [
-    rebase_path("${fuchsia_sdk}/tools/fvm", root_build_dir),
-    rebase_path(_blobstore_path, root_build_dir),
-    rebase_path(blobstore_extended_path, root_build_dir),
-    _extend_size,
-  ]
-}
-
-#  _________________________________________
-# / Create a compressed copy-on-write (COW) \
-# \ image based on storage-extended.blk.    /
-#  -----------------------------------------
-#         \   ^__^
-#          \  (oo)\_______
-#             (__)\       )\/\
-#                 ||----w |
-#                 ||     ||
-action("blobstore_extended_qcow2") {
-  script = "//build/gn_run_binary.py"
-
-  deps = [
-    ":blobstore_extended",
-  ]
-  inputs = [
-    blobstore_extended_path,
-  ]
-  outputs = [
-    blobstore_qcow_path,
-  ]
-  data = [
-    blobstore_qcow_path,
-  ]
-
-  args = [
-    rebase_path("${qemu_root}/bin/qemu-img", root_build_dir),
-    "convert",
-    "-f",
-    "raw",
-    "-O",
-    "qcow2",
-    "-c",
-    rebase_path(blobstore_extended_path, root_build_dir),
-    rebase_path(blobstore_qcow_path, root_build_dir),
-  ]
-}
-
 # Settings for executables.
 config("executable_config") {
   ldflags = [ "-pie" ]
diff --git a/build/config/fuchsia/rules.gni b/build/config/fuchsia/rules.gni
index 06ea04c8..f7edfdc 100644
--- a/build/config/fuchsia/rules.gni
+++ b/build/config/fuchsia/rules.gni
@@ -10,8 +10,6 @@
 import("//build/config/sysroot.gni")
 import("//build/util/generate_wrapper.gni")
 
-blobstore_qcow_path = "$root_out_dir/fvm.blk.qcow2"
-
 # Generates a script which deploys and executes a package on a device.
 #
 # Parameters:
@@ -71,7 +69,6 @@
       wrapper_script = script_path
 
       deps = [
-        "//build/config/fuchsia:blobstore_extended_qcow2",
         invoker.package,
       ]
 
@@ -79,14 +76,22 @@
         deps += invoker.deps
       }
 
+      # Declares the files that are needed for test execution on the
+      # swarming test client.
       data = [
         _manifest_path,
         "//build/fuchsia/",
         "//build/util/lib/",
         "//third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer",
         "${qemu_root}/",
-        "${fuchsia_sdk}/",
-        "${boot_image_root}/",
+        "${fuchsia_sdk}/tools/fvm",
+        "${fuchsia_sdk}/tools/pm",
+        "${fuchsia_sdk}/tools/symbolize",
+        "${fuchsia_sdk}/tools/zbi",
+        "${fuchsia_sdk}/.build-id/",
+        "${boot_image_root}/qemu/qemu-kernel.kernel",
+        "${boot_image_root}/qemu/storage-full.blk",
+        "${boot_image_root}/qemu/zircon-a.zbi",
       ]
 
       data_deps = [
diff --git a/build/fuchsia/boot_data.py b/build/fuchsia/boot_data.py
index 1cb3aef3..2c20b81 100644
--- a/build/fuchsia/boot_data.py
+++ b/build/fuchsia/boot_data.py
@@ -39,7 +39,6 @@
 # Specifies boot files intended for use by anything (incl. physical devices).
 TARGET_TYPE_GENERIC = 'generic'
 
-
 def _GetPubKeyPath(output_dir):
   """Returns a path to the generated SSH public key."""
 
@@ -74,18 +73,6 @@
     os.remove(known_hosts_path)
 
 
-def _MakeQcowDisk(output_dir, disk_path):
-  """Creates a QEMU copy-on-write version of |disk_path| in the output
-  directory."""
-
-  qimg_path = os.path.join(common.GetQemuRootForPlatform(), 'bin', 'qemu-img')
-  output_path = os.path.join(output_dir,
-                             os.path.basename(disk_path) + '.qcow2')
-  subprocess.check_call([qimg_path, 'create', '-q', '-f', 'qcow2',
-                         '-b', disk_path, output_path])
-  return output_path
-
-
 def GetTargetFile(filename, target_arch, target_type):
   """Computes a path to |filename| in the Fuchsia boot image directory specific
   to |target_type| and |target_arch|."""
@@ -124,7 +111,7 @@
   assert os.path.exists(GetTargetFile('zircon-a.zbi', arch, platform)), \
       'This checkout is missing the files necessary for\n' \
       'booting this configuration of Fuchsia.\n' \
-      'To check out the files, add an entry to the\n' \
-      'entry in your .gclient file like this:\n\n' \
+      'To check out the files, add this entry to the "custom_vars"\n' \
+      'section of your .gclient file:\n\n' \
       '    "checkout_fuchsia_boot_images": "%s.%s"\n\n' % \
            (platform, arch)
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 566f893..44c08d9 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8904069313901062272
\ No newline at end of file
+8904041490669619712
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index b6bd538..e527b7a 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8904068489467873856
\ No newline at end of file
+8904041744819176656
\ No newline at end of file
diff --git a/build/fuchsia/qemu_target.py b/build/fuchsia/qemu_target.py
index 31ef779..4e6ff19b 100644
--- a/build/fuchsia/qemu_target.py
+++ b/build/fuchsia/qemu_target.py
@@ -7,12 +7,14 @@
 import boot_data
 import common
 import logging
-import target
+import md5
 import os
 import platform
+import shutil
 import socket
 import subprocess
 import sys
+import target
 import tempfile
 import time
 
@@ -25,6 +27,9 @@
 HOST_IP_ADDRESS = '192.168.3.2'
 GUEST_MAC_ADDRESS = '52:54:00:63:5e:7b'
 
+# Capacity of the system's blobstore volume.
+EXTENDED_BLOBSTORE_SIZE = 1073741824  # 1GB
+
 
 class QemuTarget(target.Target):
   def __init__(self, output_dir, target_cpu, cpu_cores, system_log_file,
@@ -79,8 +84,8 @@
         # any changes.
         '-snapshot',
         '-drive', 'file=%s,format=qcow2,if=none,id=blobstore,snapshot=on' %
-                    EnsurePathExists(
-                        os.path.join(self._output_dir, 'fvm.blk.qcow2')),
+                    _EnsureBlobstoreQcowAndReturnPath(self._output_dir,
+                                                      self._GetTargetSdkArch()),
         '-device', 'virtio-blk-pci,drive=blobstore',
 
         # Use stdio for the guest OS only; don't attach the QEMU interactive
@@ -180,3 +185,60 @@
 
   def _GetSshConfigPath(self):
     return boot_data.GetSSHConfigPath(self._output_dir)
+
+
+def _ComputeFileHash(filename):
+  hasher = md5.new()
+  with open(filename, 'rb') as f:
+    buf = f.read(4096)
+    while buf:
+      hasher.update(buf)
+      buf = f.read(4096)
+
+  return hasher.hexdigest()
+
+
+def _EnsureBlobstoreQcowAndReturnPath(output_dir, target_arch):
+  """Returns a file containing the Fuchsia blobstore in a QCOW format,
+  with extra buffer space added for growth."""
+
+  qimg_tool = os.path.join(common.GetQemuRootForPlatform(), 'bin', 'qemu-img')
+  fvm_tool = os.path.join(common.SDK_ROOT, 'tools', 'fvm')
+  blobstore_path = boot_data.GetTargetFile('storage-full.blk', target_arch,
+                                           'qemu')
+  qcow_path = os.path.join(output_dir, 'gen', 'blobstore.qcow')
+
+  # Check a hash of the blobstore to determine if we can re-use an existing
+  # extended version of it.
+  blobstore_hash_path = os.path.join(output_dir, 'gen', 'blobstore.hash')
+  current_blobstore_hash = _ComputeFileHash(blobstore_path)
+
+  if os.path.exists(blobstore_hash_path) and os.path.exists(qcow_path):
+    if current_blobstore_hash == open(blobstore_hash_path, 'r').read():
+      return qcow_path
+
+  # Add some extra room for growth to the Blobstore volume.
+  # Fuchsia is unable to automatically extend FVM volumes at runtime so the
+  # volume enlargement must be performed prior to QEMU startup.
+
+  # The 'fvm' tool only supports extending volumes in-place, so make a
+  # temporary copy of 'blobstore.bin' before it's mutated.
+  extended_blobstore = tempfile.NamedTemporaryFile()
+  shutil.copyfile(blobstore_path, extended_blobstore.name)
+  subprocess.check_call([fvm_tool, extended_blobstore.name, 'extend',
+                         '--length', str(EXTENDED_BLOBSTORE_SIZE),
+                         blobstore_path])
+
+  # Construct a QCOW image from the extended, temporary FVM volume.
+  # The result will be retained in the build output directory for re-use.
+  subprocess.check_call([qimg_tool, 'convert', '-f', 'raw', '-O', 'qcow2',
+                         '-c', extended_blobstore.name, qcow_path])
+
+  # Write out a hash of the original blobstore file, so that subsequent runs
+  # can trivially check if a cached extended FVM volume is available for reuse.
+  with open(blobstore_hash_path, 'w') as blobstore_hash_file:
+    blobstore_hash_file.write(current_blobstore_hash)
+
+  return qcow_path
+
+
diff --git a/build/fuchsia/update_sdk.py b/build/fuchsia/update_sdk.py
index a51b102e..0a69669 100755
--- a/build/fuchsia/update_sdk.py
+++ b/build/fuchsia/update_sdk.py
@@ -133,6 +133,9 @@
 
 
 def DownloadSdkBootImages(sdk_hash, boot_image_names):
+  if not boot_image_names:
+    return
+
   all_device_types = ['generic', 'qemu']
   all_archs = ['x64', 'arm64']
 
@@ -168,15 +171,12 @@
     action='store_true',
     help='Enable debug-level logging.')
   parser.add_argument('--boot-images',
-    type=str,
-    required=True,
+    type=str, nargs='?',
     help='List of boot images to download, represented as a comma separated '
-         'list. Wildcards are allowed.')
+         'list. Wildcards are allowed. '
+         'If omitted, no boot images will be downloaded.')
   args = parser.parse_args()
 
-  if not args.boot_images:
-    raise Exception('--boot-images must be non-empty.')
-
   logging.basicConfig(level=logging.DEBUG if args.verbose else logging.WARNING)
 
   # Quietly exit if there's no SDK support for this platform.
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
index 93f643f..d0a616f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
@@ -4,11 +4,14 @@
 
 package org.chromium.chrome.browser.tasks.tab_groups;
 
+import android.app.Activity;
 import android.support.annotation.StringRes;
 import android.view.View;
 
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
@@ -72,23 +75,25 @@
 
     /**
      * Start a TabModelSelectorTabObserver to show IPH for TabGroups.
-     * @param selector The selector that owns the Tabs that should be observed.
      */
-    public static void startObservingForTabGroupsIPH(TabModelSelector selector) {
+    public static void startObservingForCreationIPH() {
         if (sTabModelSelectorTabObserver != null) return;
+
+        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+        if (!(activity instanceof ChromeTabbedActivity)) return;
+        TabModelSelector selector = ((ChromeTabbedActivity) activity).getTabModelSelector();
+
         sTabModelSelectorTabObserver = new TabModelSelectorTabObserver(selector) {
             @Override
             public void onDidFinishNavigation(Tab tab, NavigationHandle navigationHandle) {
-                if (!navigationHandle.isInMainFrame() || navigationHandle.pageTransition() == null)
-                    return;
+                if (!navigationHandle.isInMainFrame()) return;
                 if (tab.isIncognito()) return;
-                if (navigationHandle.pageTransition() == null) return;
-
-                int coreTransitionType =
-                        navigationHandle.pageTransition() & PageTransition.CORE_MASK;
+                Integer transition = navigationHandle.pageTransition();
                 // Searching from omnibox results in PageTransition.GENERATED.
                 if (navigationHandle.isValidSearchFormUrl()
-                        || coreTransitionType == PageTransition.GENERATED) {
+                        || (transition != null
+                                && (transition & PageTransition.CORE_MASK)
+                                        == PageTransition.GENERATED)) {
                     maybeShowIPH(FeatureConstants.TAB_GROUPS_QUICKLY_COMPARE_PAGES_FEATURE,
                             tab.getView());
                     sTabModelSelectorTabObserver.destroy();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
index fb18034..214223187 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
@@ -4,18 +4,14 @@
 
 package org.chromium.chrome.browser.tasks.tab_management;
 
-import android.app.Activity;
 import android.content.Context;
 import android.support.annotation.Nullable;
 
-import org.chromium.base.ApplicationStatus;
-import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.browser.tasks.tab_groups.TabGroupUtils;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -50,7 +46,6 @@
         mMediator = new TabGridSheetMediator(mContext, bottomSheetController,
                 this::resetWithListOfTabs, mToolbarPropertyModel, tabModelSelector,
                 tabCreatorManager, themeColorProvider);
-        startObservingForCreationIPH();
     }
 
     /**
@@ -98,12 +93,4 @@
             }
         }
     }
-
-    private void startObservingForCreationIPH() {
-        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
-        if (!(activity instanceof ChromeTabbedActivity)) return;
-
-        TabGroupUtils.startObservingForTabGroupsIPH(
-                ((ChromeTabbedActivity) activity).getTabModelSelector());
-    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
index 7914f67..9ca6b1e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupUtils;
 import org.chromium.chrome.browser.toolbar.bottom.BottomControlsCoordinator;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -100,6 +101,8 @@
                 ((ChromeTabbedActivity) activity).getOverviewModeBehavior(), mThemeColorProvider);
         mActivityLifecycleDispatcher = activity.getLifecycleDispatcher();
         mActivityLifecycleDispatcher.register(this);
+
+        TabGroupUtils.startObservingForCreationIPH();
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
index 5e1775c..916c3d37 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.tasks.tab_management;
 
+import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.browser.ThemeColorProvider;
@@ -71,7 +72,7 @@
     private final TabModelSelectorTabObserver mTabModelSelectorTabObserver;
     private final TabModelSelectorObserver mTabModelSelectorObserver;
     private boolean mIsTabGroupUiVisible;
-    private boolean mIsClosingAGroup;
+    private boolean mIsShowingOverViewMode;
     private final TabGroupModelFilter.Observer mTabGroupModelFilterObserver;
 
     TabGroupUiMediator(
@@ -125,17 +126,20 @@
 
             @Override
             public void tabClosureUndone(Tab tab) {
-                if (!mIsTabGroupUiVisible) resetTabStripWithRelatedTabsForId(tab.getId());
+                if (!mIsTabGroupUiVisible && !mIsShowingOverViewMode)
+                    resetTabStripWithRelatedTabsForId(tab.getId());
             }
         };
         mOverviewModeObserver = new EmptyOverviewModeObserver() {
             @Override
             public void onOverviewModeStartedShowing(boolean showToolbar) {
+                mIsShowingOverViewMode = true;
                 resetTabStripWithRelatedTabsForId(Tab.INVALID_TAB_ID);
             }
 
             @Override
             public void onOverviewModeFinishedHiding() {
+                mIsShowingOverViewMode = false;
                 Tab tab = mTabModelSelector.getCurrentTab();
                 if (tab == null) return;
                 resetTabStripWithRelatedTabsForId(tab.getId());
@@ -261,4 +265,9 @@
         mThemeColorProvider.removeTintObserver(mTintObserver);
         mTabModelSelectorTabObserver.destroy();
     }
+
+    @VisibleForTesting
+    boolean getIsShowingOverViewModeForTesting() {
+        return mIsShowingOverViewMode;
+    }
 }
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java
index a8208f7..9f4f728 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java
@@ -488,6 +488,68 @@
     }
 
     @Test
+    public void tabClosureUndone_UiVisible_NotShowingOverviewMode() {
+        // Assume mTab2 is selected, and it has related tabs mTab2 and mTab3.
+        initAndAssertProperties(mTab2);
+        // OverviewMode is hiding by default.
+        assertThat(mTabGroupUiMediator.getIsShowingOverViewModeForTesting(), equalTo(false));
+
+        // Simulate that another member of this group, newTab, is being undone from closure.
+        Tab newTab = prepareTab(TAB4_ID, TAB4_ID);
+        doReturn(new ArrayList<>(Arrays.asList(mTab2, mTab3, newTab)))
+                .when(mTabGroupModelFilter)
+                .getRelatedTabList(TAB4_ID);
+
+        mTabModelObserverArgumentCaptor.getValue().tabClosureUndone(newTab);
+
+        // Since the strip is already visible, no resetting.
+        mVisibilityControllerInOrder.verify(mVisibilityController, never())
+                .setBottomControlsVisible(anyBoolean());
+    }
+
+    @Test
+    public void tabClosureUndone_UiNotVisible_NotShowingOverviewMode() {
+        // Assume mTab1 is selected. Since mTab1 is now a single tab, the strip is invisible.
+        initAndAssertProperties(mTab1);
+        // OverviewMode is hiding by default.
+        assertThat(mTabGroupUiMediator.getIsShowingOverViewModeForTesting(), equalTo(false));
+
+        // Simulate that newTab which was a tab in the same group as mTab1 is being undone from
+        // closure.
+        Tab newTab = prepareTab(TAB4_ID, TAB4_ID);
+        doReturn(new ArrayList<>(Arrays.asList(mTab1, newTab)))
+                .when(mTabGroupModelFilter)
+                .getRelatedTabList(TAB4_ID);
+
+        mTabModelObserverArgumentCaptor.getValue().tabClosureUndone(newTab);
+
+        // Strip should reset to be visible.
+        mVisibilityControllerInOrder.verify(mVisibilityController)
+                .setBottomControlsVisible(eq(true));
+    }
+
+    @Test
+    public void tabClosureUndone_UiNotVisible_ShowingOverviewMode() {
+        // Assume mTab1 is selected.
+        initAndAssertProperties(mTab1);
+        // OverviewMode is hiding by default.
+        assertThat(mTabGroupUiMediator.getIsShowingOverViewModeForTesting(), equalTo(false));
+
+        // Simulate the overview mode is showing, which hides the strip.
+        mOverviewModeObserverArgumentCaptor.getValue().onOverviewModeStartedShowing(true);
+        assertThat(mTabGroupUiMediator.getIsShowingOverViewModeForTesting(), equalTo(true));
+        mVisibilityControllerInOrder.verify(mVisibilityController).setBottomControlsVisible(false);
+
+        // Simulate that we undo a group closure of {mTab2, mTab3}.
+        mTabModelObserverArgumentCaptor.getValue().tabClosureUndone(mTab3);
+        mTabModelObserverArgumentCaptor.getValue().tabClosureUndone(mTab2);
+
+        // Since overview mode is showing, we should not show strip.
+        mVisibilityControllerInOrder.verify(mVisibilityController, never())
+                .setBottomControlsVisible(anyBoolean());
+    }
+
+    @Test
     public void overViewStartedShowing() {
         initAndAssertProperties(mTab1);
 
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedConfiguration.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedConfiguration.java
index 5adcb6a..95b1b29 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedConfiguration.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedConfiguration.java
@@ -37,6 +37,31 @@
     /** Default value for whether to consumer synthetic tokens on restore. */
     public static final boolean CONSUME_SYNTHETIC_TOKENS_WHILE_RESTORING_DEFAULT = false;
 
+    private static final String FEED_ACTION_SERVER_ENDPOINT = "feed_action_server_endpoint";
+    /** Default value for the endpoint used for recording uploaded actions to the server. */
+    public static final String FEED_ACTION_SERVER_ENDPOINT_DEFAULT =
+            "https://www.google.com/httpservice/retry/ClankActionUploadService/ClankActionUpload";
+
+    private static final String FEED_ACTION_SERVER_MAX_ACTIONS_PER_REQUEST =
+            "feed_action_server_max_actions_per_request";
+    /**
+     * Default value for maximum number of actions to be uploaded to the enpoint in a single
+     * request.
+     */
+    public static final long FEED_ACTION_SERVER_MAX_ACTIONS_PER_REQUEST_DEFAULT = 20;
+
+    private static final String FEED_ACTION_SERVER_MAX_SIZE_PER_REQUEST =
+            "feed_action_server_max_size_per_request";
+    /**
+     * Default value for maximum size in bytes of the request to be uploaded to the enpoint in a
+     * single request.
+     */
+    public static final long FEED_ACTION_SERVER_MAX_SIZE_PER_REQUEST_DEFAULT = 1000;
+
+    private static final String FEED_ACTION_SERVER_METHOD = "feed_action_server_method";
+    /** Default value for the HTTP method call to the feed action server (put/post/etc). */
+    public static final String FEED_ACTION_SERVER_METHOD_DEFAULT = "POST";
+
     private static final String FEED_SERVER_ENDPOINT = "feed_server_endpoint";
     /** Default value for server endpoint. */
     public static final String FEED_SERVER_ENDPOINT_DEFAULT =
@@ -160,6 +185,45 @@
                 CONSUME_SYNTHETIC_TOKENS_WHILE_RESTORING_DEFAULT);
     }
 
+    /** @return The endpoint used for recording uploaded actions to the server. */
+    @VisibleForTesting
+    static String getFeedActionServerEndpoint() {
+        String paramValue = ChromeFeatureList.getFieldTrialParamByFeature(
+                ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS, FEED_ACTION_SERVER_ENDPOINT);
+        return TextUtils.isEmpty(paramValue) ? FEED_ACTION_SERVER_ENDPOINT_DEFAULT : paramValue;
+    }
+
+    /**
+     * @return Maximum number of actions to be uploaded to the endpoint in a single request.
+     */
+    @VisibleForTesting
+    static long getFeedActionServerMaxActionsPerRequest() {
+        return (long) ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS,
+                FEED_ACTION_SERVER_MAX_ACTIONS_PER_REQUEST,
+                (int) FEED_ACTION_SERVER_MAX_ACTIONS_PER_REQUEST_DEFAULT);
+    }
+
+    /**
+     * @return Maximum size in bytes of the request to be uploaded to the endpoint in a single
+     *         request.
+     */
+    @VisibleForTesting
+    static long getFeedActionServerMaxSizePerRequest() {
+        return (long) ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS,
+                FEED_ACTION_SERVER_MAX_SIZE_PER_REQUEST,
+                (int) FEED_ACTION_SERVER_MAX_SIZE_PER_REQUEST_DEFAULT);
+    }
+
+    /** @return The HTTP method call to the feed action server (put/post/etc). */
+    @VisibleForTesting
+    static String getFeedActionServerMethod() {
+        String paramValue = ChromeFeatureList.getFieldTrialParamByFeature(
+                ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS, FEED_ACTION_SERVER_METHOD);
+        return TextUtils.isEmpty(paramValue) ? FEED_ACTION_SERVER_METHOD_DEFAULT : paramValue;
+    }
+
     /** @return Feed server endpoint to use to fetch content suggestions. */
     @VisibleForTesting
     static String getFeedServerEndpoint() {
@@ -354,6 +418,14 @@
                         FeedConfiguration.getConsumeSyntheticTokens())
                 .put(ConfigKey.CONSUME_SYNTHETIC_TOKENS_WHILE_RESTORING,
                         FeedConfiguration.getConsumeSyntheticTokensWhileRestoring())
+                .put(ConfigKey.FEED_ACTION_SERVER_ENDPOINT,
+                        FeedConfiguration.getFeedActionServerEndpoint())
+                .put(ConfigKey.FEED_ACTION_SERVER_MAX_ACTIONS_PER_REQUEST,
+                        FeedConfiguration.getFeedActionServerMaxActionsPerRequest())
+                .put(ConfigKey.FEED_ACTION_SERVER_MAX_SIZE_PER_REQUEST,
+                        FeedConfiguration.getFeedActionServerMaxSizePerRequest())
+                .put(ConfigKey.FEED_ACTION_SERVER_METHOD,
+                        FeedConfiguration.getFeedActionServerMethod())
                 .put(ConfigKey.FEED_SERVER_ENDPOINT, FeedConfiguration.getFeedServerEndpoint())
                 .put(ConfigKey.FEED_SERVER_METHOD, FeedConfiguration.getFeedServerMethod())
                 .put(ConfigKey.FEED_SERVER_RESPONSE_LENGTH_PREFIXED,
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index 1754cff..8c963c8 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -244,11 +244,6 @@
 
             return mDelegate.onInterceptTouchEvent(ev);
         }
-
-        @Override
-        public boolean wasLastSideSwipeGestureConsumed() {
-            return mStream.willHandleHorizontalSwipe();
-        }
     }
 
     /**
diff --git a/chrome/android/java/res/layout/navigation_bubble.xml b/chrome/android/java/res/layout/navigation_bubble.xml
index f4c7ee29..55e6d54 100644
--- a/chrome/android/java/res/layout/navigation_bubble.xml
+++ b/chrome/android/java/res/layout/navigation_bubble.xml
@@ -11,6 +11,7 @@
     android:layout_width="wrap_content"
     android:gravity="center_vertical"
     android:background="@drawable/navigation_bubble_shadow"
+    android:animateLayoutChanges="true"
     android:orientation="horizontal">
 
     <org.chromium.ui.widget.ChromeImageView
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
index d9748a0e..eb0da82 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
@@ -102,27 +102,12 @@
             // invoke |wasLastSideSwipeGestureConsumed| which may be expensive less often.
             if (mNavigationHandler.isStopped()) return true;
 
-            if (wasLastSideSwipeGestureConsumed()) {
-                mNavigationHandler.reset();
-                return true;
-            }
             return mNavigationHandler.onScroll(
                     e1.getX(), distanceX, distanceY, e2.getX(), e2.getY());
         }
     }
 
     /**
-     * Checks if the gesture event was consumed by one of children views, in which case
-     * history navigation should not proceed. Whatever the child view does with the gesture
-     * events should take precedence and not be disturbed by the navigation.
-     *
-     * @return {@code true} if gesture event is consumed by one of the children.
-     */
-    public boolean wasLastSideSwipeGestureConsumed() {
-        return false;
-    }
-
-    /**
      * Cancel navigation UI with animation effect.
      */
     public void release() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationBubble.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationBubble.java
index 6330b39..7ac7ed0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationBubble.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationBubble.java
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 package org.chromium.chrome.browser.gesturenav;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.os.Build;
@@ -17,15 +18,40 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.util.ColorUtils;
 
 /**
  * View class for a bubble used in gesture navigation UI that consists of an icon
  * and an optional text.
  */
 public class NavigationBubble extends LinearLayout {
-    private ImageView mIcon;
-    private final ColorStateList mBlueTint;
+    private static final int COLOR_TRANSITION_DURATION_MS = 250;
+
+    private final ValueAnimator mColorAnimator;
+    private final int mBlue;
+    private final int mBlack;
+
+    private class ColorUpdateListener implements ValueAnimator.AnimatorUpdateListener {
+        private int mStart;
+        private int mEnd;
+
+        private void setTransitionColors(int start, int end) {
+            mStart = start;
+            mEnd = end;
+        }
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            float fraction = (float) animation.getAnimatedValue();
+            ApiCompatibilityUtils.setImageTintList(mIcon,
+                    ColorStateList.valueOf(ColorUtils.getColorWithOverlay(mStart, mEnd, fraction)));
+        }
+    }
+
+    private final ColorUpdateListener mColorUpdateListener;
+
     private TextView mText;
+    private ImageView mIcon;
     private AnimationListener mListener;
 
     /**
@@ -44,7 +70,12 @@
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
             setLayerType(View.LAYER_TYPE_SOFTWARE, null);
         }
-        mBlueTint = context.getResources().getColorStateList(R.color.blue_mode_tint);
+        mBlack = ApiCompatibilityUtils.getColor(getResources(), android.R.color.black);
+        mBlue = ApiCompatibilityUtils.getColor(getResources(), R.color.default_icon_color_blue);
+
+        mColorUpdateListener = new ColorUpdateListener();
+        mColorAnimator = ValueAnimator.ofFloat(0, 1).setDuration(COLOR_TRANSITION_DURATION_MS);
+        mColorAnimator.addUpdateListener(mColorUpdateListener);
     }
 
     @Override
@@ -115,11 +146,9 @@
      */
     public void setImageTint(boolean navigate) {
         assert mIcon != null;
-        if (navigate) {
-            ApiCompatibilityUtils.setImageTintList(mIcon, mBlueTint);
-        } else {
-            ApiCompatibilityUtils.setImageTintList(mIcon, mText.getTextColors());
-        }
+        mColorUpdateListener.setTransitionColors(
+                navigate ? mBlack : mBlue, navigate ? mBlue : mBlack);
+        mColorAnimator.start();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
index 27cf673..714630f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
@@ -247,11 +247,6 @@
         return mNewTabPageLayout;
     }
 
-    @Override
-    public boolean wasLastSideSwipeGestureConsumed() {
-        return mRecyclerView.isCardBeingSwiped();
-    }
-
     /**
      * Sets the {@link FakeboxDelegate} associated with the new tab page.
      * @param fakeboxDelegate The {@link FakeboxDelegate} used to determine whether the URL bar
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
index 69bb24a..579529d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsRecyclerView.java
@@ -81,8 +81,6 @@
 
     private Runnable mCloseContextMenuCallback;
 
-    private boolean mIsCardBeingSwiped;
-
     public SuggestionsRecyclerView(Context context) {
         this(context, null);
     }
@@ -406,8 +404,6 @@
         @Override
         public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder,
                 float dX, float dY, int actionState, boolean isCurrentlyActive) {
-            mIsCardBeingSwiped = isCurrentlyActive && dX != 0.f;
-
             // In some cases a removed child may call this method when unrelated items are
             // interacted with (https://crbug.com/664466, b/32900699), but in that case
             // getSiblingDismissalViewHolders() below will return an empty list.
@@ -420,14 +416,6 @@
         }
     }
 
-    /**
-     * Tells if one of card views is being swiped now.
-     * @return {@code true} if a card view is being swiped.
-     */
-    public boolean isCardBeingSwiped() {
-        return mIsCardBeingSwiped;
-    }
-
     private List<ViewHolder> getDismissalGroupViewHolders(ViewHolder viewHolder) {
         int position = viewHolder.getAdapterPosition();
         if (position == NO_POSITION) return Collections.emptyList();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedConfigurationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedConfigurationTest.java
index de505e7..2f504c1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedConfigurationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedConfigurationTest.java
@@ -42,6 +42,14 @@
                 FeedConfiguration.getConsumeSyntheticTokens());
         Assert.assertEquals(FeedConfiguration.CONSUME_SYNTHETIC_TOKENS_WHILE_RESTORING_DEFAULT,
                 FeedConfiguration.getConsumeSyntheticTokensWhileRestoring());
+        Assert.assertEquals(FeedConfiguration.FEED_ACTION_SERVER_ENDPOINT_DEFAULT,
+                FeedConfiguration.getFeedActionServerEndpoint());
+        Assert.assertEquals(FeedConfiguration.FEED_ACTION_SERVER_MAX_ACTIONS_PER_REQUEST_DEFAULT,
+                FeedConfiguration.getFeedActionServerMaxActionsPerRequest());
+        Assert.assertEquals(FeedConfiguration.FEED_ACTION_SERVER_MAX_SIZE_PER_REQUEST_DEFAULT,
+                FeedConfiguration.getFeedActionServerMaxSizePerRequest());
+        Assert.assertEquals(FeedConfiguration.FEED_ACTION_SERVER_METHOD_DEFAULT,
+                FeedConfiguration.getFeedActionServerMethod());
         Assert.assertEquals(FeedConfiguration.FEED_SERVER_ENDPOINT_DEFAULT,
                 FeedConfiguration.getFeedServerEndpoint());
         Assert.assertEquals(FeedConfiguration.FEED_SERVER_METHOD_DEFAULT,
@@ -131,6 +139,48 @@
     @Feature({"Feed"})
     @CommandLineFlags.
     Add({"enable-features=InterestFeedContentSuggestions<Trial", "force-fieldtrials=Trial/Group",
+            "force-fieldtrial-params=Trial.Group:feed_action_server_endpoint/"
+                    + "https%3A%2F%2Ffeed%2Egoogle%2Ecom%2Fpath"})
+    public void
+    testFeedActionServerEndpoint() {
+        Assert.assertEquals(
+                "https://feed.google.com/path", FeedConfiguration.getFeedActionServerEndpoint());
+    }
+
+    @Test
+    @Feature({"Feed"})
+    @CommandLineFlags.
+    Add({"enable-features=InterestFeedContentSuggestions<Trial", "force-fieldtrials=Trial/Group",
+            "force-fieldtrial-params=Trial.Group:feed_action_server_max_actions_per_request/1234"})
+    public void
+    testFeedActionServerMaxActionsPerRequest() {
+        Assert.assertEquals(1234, FeedConfiguration.getFeedActionServerMaxActionsPerRequest());
+    }
+
+    @Test
+    @Feature({"Feed"})
+    @CommandLineFlags.
+    Add({"enable-features=InterestFeedContentSuggestions<Trial", "force-fieldtrials=Trial/Group",
+            "force-fieldtrial-params=Trial.Group:feed_action_server_max_size_per_request/1234"})
+    public void
+    testFeedActionServerMaxSizePerRequest() {
+        Assert.assertEquals(1234, FeedConfiguration.getFeedActionServerMaxSizePerRequest());
+    }
+
+    @Test
+    @Feature({"Feed"})
+    @CommandLineFlags.
+    Add({"enable-features=InterestFeedContentSuggestions<Trial", "force-fieldtrials=Trial/Group",
+            "force-fieldtrial-params=Trial.Group:feed_action_server_method/PUT"})
+    public void
+    testFeedActionServerMethod() {
+        Assert.assertEquals("PUT", FeedConfiguration.getFeedActionServerMethod());
+    }
+
+    @Test
+    @Feature({"Feed"})
+    @CommandLineFlags.
+    Add({"enable-features=InterestFeedContentSuggestions<Trial", "force-fieldtrials=Trial/Group",
             "force-fieldtrial-params=Trial.Group:feed_server_endpoint/"
                     + "https%3A%2F%2Ffeed%2Egoogle%2Ecom%2Fpath"})
     public void
@@ -351,6 +401,18 @@
                 configuration.getValueOrDefault(ConfigKey.CONSUME_SYNTHETIC_TOKENS, true));
         Assert.assertFalse(configuration.getValueOrDefault(
                 ConfigKey.CONSUME_SYNTHETIC_TOKENS_WHILE_RESTORING, true));
+        Assert.assertEquals(FeedConfiguration.FEED_ACTION_SERVER_ENDPOINT_DEFAULT,
+                configuration.getValueOrDefault(ConfigKey.FEED_ACTION_SERVER_ENDPOINT, ""));
+        Assert.assertEquals(
+                (long) FeedConfiguration.FEED_ACTION_SERVER_MAX_ACTIONS_PER_REQUEST_DEFAULT,
+                configuration.getValueOrDefault(
+                        ConfigKey.FEED_ACTION_SERVER_MAX_ACTIONS_PER_REQUEST, 0));
+        Assert.assertEquals(
+                (long) FeedConfiguration.FEED_ACTION_SERVER_MAX_SIZE_PER_REQUEST_DEFAULT,
+                configuration.getValueOrDefault(
+                        ConfigKey.FEED_ACTION_SERVER_MAX_SIZE_PER_REQUEST, 0));
+        Assert.assertEquals(FeedConfiguration.FEED_ACTION_SERVER_METHOD_DEFAULT,
+                configuration.getValueOrDefault(ConfigKey.FEED_ACTION_SERVER_METHOD, ""));
         Assert.assertEquals(FeedConfiguration.FEED_SERVER_ENDPOINT_DEFAULT,
                 configuration.getValueOrDefault(ConfigKey.FEED_SERVER_ENDPOINT, ""));
         Assert.assertEquals(FeedConfiguration.FEED_SERVER_METHOD_DEFAULT,
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index e18a782..fc2e5e7 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-77.0.3864.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-78.0.3893.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index 926218b..d7cd2147 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -541,6 +541,15 @@
   // Initialize D-Bus clients that depend on feature list.
   chromeos::InitializeFeatureListDependentDBus();
 #endif
+
+#if defined(OS_ANDROID)
+  startup_data_->CreateProfilePrefService();
+#endif
+
+  if (base::FeatureList::IsEnabled(
+          features::kWriteBasicSystemProfileToPersistentHistogramsFile)) {
+    startup_data_->RecordCoreSystemProfile();
+  }
 }
 
 bool ChromeMainDelegate::ShouldCreateFeatureList() {
@@ -548,16 +557,6 @@
   return false;
 }
 
-void ChromeMainDelegate::PostTaskSchedulerStart() {
-#if defined(OS_ANDROID)
-  startup_data_->CreateProfilePrefService();
-#endif
-  if (base::FeatureList::IsEnabled(
-          features::kWriteBasicSystemProfileToPersistentHistogramsFile)) {
-    startup_data_->RecordCoreSystemProfile();
-  }
-}
-
 #endif
 
 void ChromeMainDelegate::PostFieldTrialInitialization() {
diff --git a/chrome/app/chrome_main_delegate.h b/chrome/app/chrome_main_delegate.h
index 108f6bb1..eace225 100644
--- a/chrome/app/chrome_main_delegate.h
+++ b/chrome/app/chrome_main_delegate.h
@@ -65,7 +65,6 @@
 #if !defined(CHROME_MULTIPLE_DLL_CHILD)
   void PostEarlyInitialization(bool is_running_tests) override;
   bool ShouldCreateFeatureList() override;
-  void PostTaskSchedulerStart() override;
 #endif  // !defined(CHROME_MULTIPLE_DLL_CHILD)
   void PostFieldTrialInitialization() override;
 
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index b89ea2e..be474a74 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -2432,7 +2432,7 @@
   </if>
 
   <!-- Strings translating ONC (Open Network Configuration) properties -->
-  <!-- and translatable values. See onc_spec.html for more information. -->
+  <!-- and translatable values. See onc_spec.md for more information. -->
   <if expr="chromeos">
     <message name="IDS_ONC_IPV4_ADDRESS" desc="ONC Property label for ipv4-IPAddress">
       IP address
@@ -2461,6 +2461,9 @@
     <message name="IDS_ONC_CELLULAR_ACTIVATION_STATE_PARTIALLY_ACTIVATED" desc="ONC Property value when Cellular.ActivationState = PartiallyActivated">
       Partially activated
     </message>
+    <message name="IDS_ONC_CELLULAR_ACTIVATION_STATE_NO_SERVICE" desc="ONC Property value when Cellular.ActivationState = NoService">
+      No service
+    </message>
     <message name="IDS_ONC_CELLULAR_APN_ACCESS_POINT_NAME" desc="ONC Property label for APN-AccessPointName">
       Access point name
     </message>
@@ -2629,6 +2632,9 @@
     <message name="IDS_ONC_VPN_IPSEC_PSK" desc="ONC Property label for VPN.IPSec.PSK">
       Pre-shared key
     </message>
+    <message name="IDS_ONC_VPN_OPENVPN_EXTRA_HOSTS" desc="ONC Property label for VPN.OpenVPN.ExtraHosts">
+      Extra hosts
+    </message>
     <message name="IDS_ONC_VPN_OPENVPN_OTP" desc="ONC Property label for VPN.OpenVPN.OTP">
       OTP
     </message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index aa07715..38ffffd 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3633,11 +3633,6 @@
 #endif  // defined(OS_ANDROID)
 
 #if defined(OS_CHROMEOS)
-    {"enable-native-google-assistant",
-     flag_descriptions::kEnableGoogleAssistantName,
-     flag_descriptions::kEnableGoogleAssistantDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kAssistantFeature)},
-
     {"enable-assistant-dsp", flag_descriptions::kEnableGoogleAssistantDspName,
      flag_descriptions::kEnableGoogleAssistantDspDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::assistant::features::kEnableDspHotword)},
@@ -4413,6 +4408,11 @@
     {"zero-state-files", flag_descriptions::kZeroStateFilesName,
      flag_descriptions::kZeroStateFilesDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(app_list_features::kEnableZeroStateMixedTypesRanker)},
+
+    {"new-overview-tablet-layout",
+     flag_descriptions::kNewOverviewTabletLayoutName,
+     flag_descriptions::kNewOverviewTabletLayoutDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kNewOverviewLayout)},
 #endif  // defined(OS_CHROMEOS)
 
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
diff --git a/chrome/browser/availability/availability_prober.cc b/chrome/browser/availability/availability_prober.cc
index 22df74e..a5ee1c79 100644
--- a/chrome/browser/availability/availability_prober.cc
+++ b/chrome/browser/availability/availability_prober.cc
@@ -15,6 +15,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
 #include "base/time/default_clock.h"
 #include "base/time/default_tick_clock.h"
 #include "build/build_config.h"
@@ -22,6 +23,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/network_service_instance.h"
 #include "net/base/load_flags.h"
@@ -57,6 +59,8 @@
   switch (name) {
     case AvailabilityProber::ClientName::kLitepages:
       return "Litepages";
+    case AvailabilityProber::ClientName::kLitepagesOriginCheck:
+      return "LitepagesOriginCheck";
   }
   NOTREACHED();
   return std::string();
@@ -276,7 +280,6 @@
       url_loader_factory_(url_loader_factory),
       weak_factory_(this) {
   DCHECK(delegate_);
-  DCHECK(pref_service_);
 
   // The NetworkConnectionTracker can only be used directly on the UI thread.
   // Otherwise we use the cross-thread call.
@@ -288,8 +291,11 @@
         base::BindOnce(&AvailabilityProber::AddSelfAsNetworkConnectionObserver,
                        weak_factory_.GetWeakPtr()));
   }
-  cached_probe_results_ =
-      pref_service_->GetDictionary(pref_key_)->CreateDeepCopy();
+
+  if (pref_service_) {
+    cached_probe_results_ =
+        pref_service_->GetDictionary(pref_key_)->CreateDeepCopy();
+  }
 }
 
 AvailabilityProber::~AvailabilityProber() {
@@ -582,10 +588,13 @@
 
 void AvailabilityProber::SetOnCompleteCallback(
     AvailabilityProberOnCompleteCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   on_complete_callback_ = std::move(callback);
 }
 
 void AvailabilityProber::RecordProbeResult(bool success) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   AvailabilityProberCacheEntry entry;
   entry.set_is_success(success);
   entry.set_last_modified(
@@ -597,21 +606,36 @@
     return;
   }
 
-  DictionaryPrefUpdate update(pref_service_, pref_key_);
-  update->SetKey(GetCacheKeyForCurrentNetwork(), std::move(encoded.value()));
+  base::DictionaryValue* update_dict = cached_probe_results_.get();
+  if (pref_service_) {
+    DictionaryPrefUpdate update(pref_service_, pref_key_);
+    update_dict = update.Get();
+  }
 
-  if (update.Get()->DictSize() > max_cache_entries_)
-    RemoveOldestDictionaryEntry(update.Get());
+  update_dict->SetKey(GetCacheKeyForCurrentNetwork(),
+                      std::move(encoded.value()));
 
-  cached_probe_results_ = update.Get()->CreateDeepCopy();
+  if (update_dict->DictSize() > max_cache_entries_)
+    RemoveOldestDictionaryEntry(update_dict);
 
-  if (on_complete_callback_)
-    on_complete_callback_.Run(success);
+  cached_probe_results_ = update_dict->CreateDeepCopy();
 
   base::BooleanHistogram::FactoryGet(
       AppendNameToHistogram(kSuccessHistogram),
       base::HistogramBase::kUmaTargetedHistogramFlag)
       ->Add(success);
+
+  // The callback may delete |this| so run it in a post task.
+  if (on_complete_callback_) {
+    base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+                   base::BindOnce(&AvailabilityProber::RunCallback,
+                                  weak_factory_.GetWeakPtr(), success));
+  }
+}
+
+void AvailabilityProber::RunCallback(bool success) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::move(on_complete_callback_).Run(success);
 }
 
 std::string AvailabilityProber::GetCacheKeyForCurrentNetwork() const {
diff --git a/chrome/browser/availability/availability_prober.h b/chrome/browser/availability/availability_prober.h
index a211aeb..a5c3b2d 100644
--- a/chrome/browser/availability/availability_prober.h
+++ b/chrome/browser/availability/availability_prober.h
@@ -75,7 +75,9 @@
   enum class ClientName {
     kLitepages = 0,
 
-    kMaxValue = kLitepages,
+    kLitepagesOriginCheck = 1,
+
+    kMaxValue = kLitepagesOriginCheck,
   };
 
   // This enum describes the different algorithms that can be used to calculate
@@ -174,7 +176,7 @@
   void OnConnectionChanged(network::mojom::ConnectionType type) override;
 
   // Sets a repeating callback to notify the completion of a probe and whether
-  // it was successful.
+  // it was successful. It is safe to delete |this| during the callback.
   void SetOnCompleteCallback(AvailabilityProberOnCompleteCallback callback);
 
  protected:
@@ -207,6 +209,7 @@
   void RecordProbeResult(bool success);
   std::string GetCacheKeyForCurrentNetwork() const;
   std::string AppendNameToHistogram(const std::string& histogram) const;
+  void RunCallback(bool success);
 #if defined(OS_ANDROID)
   void OnApplicationStateChange(base::android::ApplicationState new_state);
 #endif
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 273cf0e..f0503453 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -546,10 +546,14 @@
     "arc/input_method_manager/arc_input_method_manager_service.h",
     "arc/input_method_manager/input_connection_impl.cc",
     "arc/input_method_manager/input_connection_impl.h",
+    "arc/instance_throttle/arc_active_window_throttle_observer.cc",
+    "arc/instance_throttle/arc_active_window_throttle_observer.h",
     "arc/instance_throttle/arc_instance_throttle.cc",
     "arc/instance_throttle/arc_instance_throttle.h",
     "arc/instance_throttle/arc_throttle_observer.cc",
     "arc/instance_throttle/arc_throttle_observer.h",
+    "arc/instance_throttle/window_throttle_observer_base.cc",
+    "arc/instance_throttle/window_throttle_observer_base.h",
     "arc/intent_helper/arc_external_protocol_dialog.cc",
     "arc/intent_helper/arc_external_protocol_dialog.h",
     "arc/intent_helper/arc_intent_picker_app_fetcher.cc",
@@ -2396,6 +2400,7 @@
     "arc/input_method_manager/input_connection_impl_unittest.cc",
     "arc/input_method_manager/test_input_method_manager_bridge.cc",
     "arc/input_method_manager/test_input_method_manager_bridge.h",
+    "arc/instance_throttle/arc_active_window_throttle_observer_unittest.cc",
     "arc/instance_throttle/arc_instance_throttle_unittest.cc",
     "arc/instance_throttle/arc_throttle_observer_unittest.cc",
     "arc/intent_helper/arc_external_protocol_dialog_unittest.cc",
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
index f7883c15..54645ed 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
@@ -327,7 +327,8 @@
 // Verifies that pressing right arrow button with search button should move
 // focus to the next ShelfItem instead of the last one
 // (see https://crbug.com/947683).
-IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ShelfIconFocusForward) {
+// This test is flaky, see http://crbug.com/997628
+IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, DISABLED_ShelfIconFocusForward) {
   const std::string title("MockApp");
   ChromeLauncherController* controller = ChromeLauncherController::instance();
 
diff --git a/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.cc b/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.cc
new file mode 100644
index 0000000..7b3377c
--- /dev/null
+++ b/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.cc
@@ -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.
+
+#include "chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.h"
+
+#include "components/arc/arc_util.h"
+
+namespace arc {
+
+ArcActiveWindowThrottleObserver::ArcActiveWindowThrottleObserver()
+    : WindowThrottleObserverBase(ArcThrottleObserver::PriorityLevel::CRITICAL,
+                                 "ArcWindowIsActiveWindow") {}
+
+bool ArcActiveWindowThrottleObserver::ProcessWindowActivation(
+    ActivationReason reason,
+    aura::Window* gained_active,
+    aura::Window* lost_active) {
+  return IsArcAppWindow(gained_active);
+}
+
+}  // namespace arc
diff --git a/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.h b/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.h
new file mode 100644
index 0000000..95b507d
--- /dev/null
+++ b/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.h
@@ -0,0 +1,32 @@
+// 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_CHROMEOS_ARC_INSTANCE_THROTTLE_ARC_ACTIVE_WINDOW_THROTTLE_OBSERVER_H_
+#define CHROME_BROWSER_CHROMEOS_ARC_INSTANCE_THROTTLE_ARC_ACTIVE_WINDOW_THROTTLE_OBSERVER_H_
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/arc/instance_throttle/arc_throttle_observer.h"
+#include "chrome/browser/chromeos/arc/instance_throttle/window_throttle_observer_base.h"
+
+namespace arc {
+
+// This class observes window activations and sets the state to active if the
+// currently active window is an ARC window.
+class ArcActiveWindowThrottleObserver : public WindowThrottleObserverBase {
+ public:
+  ArcActiveWindowThrottleObserver();
+  ~ArcActiveWindowThrottleObserver() override = default;
+
+  // WindowThrottleObserverBase:
+  bool ProcessWindowActivation(ActivationReason reason,
+                               aura::Window* gained_active,
+                               aura::Window* lost_active) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ArcActiveWindowThrottleObserver);
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_CHROMEOS_ARC_INSTANCE_THROTTLE_ARC_ACTIVE_WINDOW_THROTTLE_OBSERVER_H_
diff --git a/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer_unittest.cc b/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer_unittest.cc
new file mode 100644
index 0000000..54d6e38
--- /dev/null
+++ b/chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer_unittest.cc
@@ -0,0 +1,61 @@
+// 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/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.h"
+
+#include "ash/public/cpp/app_types.h"
+#include "base/bind.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+
+namespace arc {
+
+class ArcActiveWindowThrottleObserverTest : public testing::Test {
+ public:
+  ArcActiveWindowThrottleObserverTest()
+      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
+
+ protected:
+  ArcActiveWindowThrottleObserver* window_observer() {
+    return &window_observer_;
+  }
+
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  ArcActiveWindowThrottleObserver window_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArcActiveWindowThrottleObserverTest);
+};
+
+TEST_F(ArcActiveWindowThrottleObserverTest, TestConstructDestruct) {}
+
+TEST_F(ArcActiveWindowThrottleObserverTest, TestOnWindowActivated) {
+  aura::test::TestWindowDelegate dummy_delegate;
+  aura::Window* arc_window = aura::test::CreateTestWindowWithDelegate(
+      &dummy_delegate, 1, gfx::Rect(), nullptr);
+  aura::Window* chrome_window = aura::test::CreateTestWindowWithDelegate(
+      &dummy_delegate, 2, gfx::Rect(), nullptr);
+  arc_window->SetProperty(aura::client::kAppType,
+                          static_cast<int>(ash::AppType::ARC_APP));
+  chrome_window->SetProperty(aura::client::kAppType,
+                             static_cast<int>(ash::AppType::BROWSER));
+
+  EXPECT_FALSE(window_observer()->active());
+
+  window_observer()->OnWindowActivated(
+      ArcActiveWindowThrottleObserver::ActivationReason::INPUT_EVENT,
+      arc_window, chrome_window);
+  EXPECT_TRUE(window_observer()->active());
+
+  window_observer()->OnWindowActivated(
+      ArcActiveWindowThrottleObserver::ActivationReason::INPUT_EVENT,
+      chrome_window, arc_window);
+  EXPECT_FALSE(window_observer()->active());
+}
+
+}  // namespace arc
diff --git a/chrome/browser/chromeos/arc/instance_throttle/arc_instance_throttle.cc b/chrome/browser/chromeos/arc/instance_throttle/arc_instance_throttle.cc
index 341febd..0a91406 100644
--- a/chrome/browser/chromeos/arc/instance_throttle/arc_instance_throttle.cc
+++ b/chrome/browser/chromeos/arc/instance_throttle/arc_instance_throttle.cc
@@ -119,7 +119,7 @@
 std::vector<ArcThrottleObserver*> ArcInstanceThrottle::GetAllObservers() {
   if (!observers_for_testing_.empty())
     return observers_for_testing_;
-  return {};  // Pointers to member observers will go here
+  return {&active_window_throttle_observer_};
 }
 
 void ArcInstanceThrottle::NotifyObserverStateChangedForTesting() {
diff --git a/chrome/browser/chromeos/arc/instance_throttle/arc_instance_throttle.h b/chrome/browser/chromeos/arc/instance_throttle/arc_instance_throttle.h
index ec14261b..fe1b6a7 100644
--- a/chrome/browser/chromeos/arc/instance_throttle/arc_instance_throttle.h
+++ b/chrome/browser/chromeos/arc/instance_throttle/arc_instance_throttle.h
@@ -11,6 +11,7 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/arc/instance_throttle/arc_active_window_throttle_observer.h"
 #include "chrome/browser/chromeos/arc/instance_throttle/arc_throttle_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
 
@@ -72,6 +73,9 @@
   ArcThrottleObserver::PriorityLevel level_{
       ArcThrottleObserver::PriorityLevel::UNKNOWN};
 
+  // Throttle Observers
+  ArcActiveWindowThrottleObserver active_window_throttle_observer_;
+
   base::WeakPtrFactory<ArcInstanceThrottle> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ArcInstanceThrottle);
diff --git a/chrome/browser/chromeos/arc/instance_throttle/arc_throttle_observer.h b/chrome/browser/chromeos/arc/instance_throttle/arc_throttle_observer.h
index e9eeb10..2abf55a 100644
--- a/chrome/browser/chromeos/arc/instance_throttle/arc_throttle_observer.h
+++ b/chrome/browser/chromeos/arc/instance_throttle/arc_throttle_observer.h
@@ -29,8 +29,9 @@
   ArcThrottleObserver(PriorityLevel level, const std::string& name);
   virtual ~ArcThrottleObserver();
 
-  // Starts observing (overridden in derived classes to register self as
-  // observer for a particular condition)
+  // Starts observing. This is overridden in derived classes to register self as
+  // observer for a particular condition. However, the base method should be
+  // called in overridden methods, so that the callback_ member is initialized.
   virtual void StartObserving(ArcBridgeService* arc_bridge_service,
                               content::BrowserContext* content,
                               const ObserverStateChangedCallback& callback);
diff --git a/chrome/browser/chromeos/arc/instance_throttle/window_throttle_observer_base.cc b/chrome/browser/chromeos/arc/instance_throttle/window_throttle_observer_base.cc
new file mode 100644
index 0000000..5a2fe86a
--- /dev/null
+++ b/chrome/browser/chromeos/arc/instance_throttle/window_throttle_observer_base.cc
@@ -0,0 +1,95 @@
+// 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/chromeos/arc/instance_throttle/window_throttle_observer_base.h"
+
+#include "ash/public/cpp/shell_window_ids.h"
+#include "chrome/browser/chromeos/arc/instance_throttle/arc_throttle_observer.h"
+#include "components/exo/wm_helper.h"
+#include "ui/aura/window.h"
+#include "ui/wm/public/activation_change_observer.h"
+
+namespace arc {
+namespace {
+
+// Returns true if the window is in the app list window container.
+bool IsAppListWindow(const aura::Window* window) {
+  if (!window)
+    return false;
+
+  const aura::Window* parent = window->parent();
+  return parent &&
+         parent->id() == ash::ShellWindowId::kShellWindowId_AppListContainer;
+}
+
+// Returns true if this window activation should be ignored (app list
+// opening/closing), or false if it should be processed.
+bool ShouldIgnoreWindowActivation(
+    wm::ActivationChangeObserver::ActivationReason reason,
+    aura::Window* gained_active,
+    aura::Window* lost_active) {
+  // The current active window defines whether the instance should be
+  // throttled or not i.e. if it's a native window then throttle Android, if
+  // it's an Android window then unthrottle it.
+  //
+  // However, the app list needs to be handled differently. The app list is
+  // deemed as an temporary overlay over the user's main window for browsing
+  // through or selecting an app. Consequently, if the app list is brought
+  // over a Chrome window then IsAppListWindow(gained_active) is true and this
+  // event is ignored. The instance continues to be throttled. Once the app
+  // list is closed then IsAppListWindow(lost_active) is true and the instance
+  // continues to be throttled. Similarly, if the app list is brought over an
+  // Android window, the instance continues to be unthrottled.
+  //
+  // On launching an app from the app list on Chrome OS the following events
+  // happen -
+  //
+  // - When an Android app icon is clicked the instance is unthrottled. This
+  // logic resides in |LaunchAppWithIntent| in
+  // src/chrome/browser/ui/app_list/arc/arc_app_utils.cc.
+  //
+  // - Between the time the app opens there is a narrow slice of time where
+  // this callback is triggered with |lost_active| equal to the app list
+  // window and the gained active possibly a native window. Without the check
+  // below the instance will be throttled again, further delaying the app
+  // launch. If the app was a native one then the instance was throttled
+  // anyway.
+  if (IsAppListWindow(lost_active) || IsAppListWindow(gained_active))
+    return true;
+  return false;
+}
+
+}  // namespace
+
+WindowThrottleObserverBase::WindowThrottleObserverBase(
+    ArcThrottleObserver::PriorityLevel level,
+    std::string name)
+    : ArcThrottleObserver(level, name) {}
+
+void WindowThrottleObserverBase::StartObserving(
+    ArcBridgeService* arc_bridge_service,
+    content::BrowserContext* context,
+    const ObserverStateChangedCallback& callback) {
+  ArcThrottleObserver::StartObserving(arc_bridge_service, context, callback);
+  if (!exo::WMHelper::HasInstance())  // for unit testing
+    return;
+  exo::WMHelper::GetInstance()->AddActivationObserver(this);
+}
+
+void WindowThrottleObserverBase::StopObserving() {
+  ArcThrottleObserver::StopObserving();
+  if (!exo::WMHelper::HasInstance())
+    return;
+  exo::WMHelper::GetInstance()->RemoveActivationObserver(this);
+}
+
+void WindowThrottleObserverBase::OnWindowActivated(ActivationReason reason,
+                                                   aura::Window* gained_active,
+                                                   aura::Window* lost_active) {
+  if (ShouldIgnoreWindowActivation(reason, gained_active, lost_active))
+    return;
+  SetActive(ProcessWindowActivation(reason, gained_active, lost_active));
+}
+
+}  // namespace arc
diff --git a/chrome/browser/chromeos/arc/instance_throttle/window_throttle_observer_base.h b/chrome/browser/chromeos/arc/instance_throttle/window_throttle_observer_base.h
new file mode 100644
index 0000000..a4c1aa80
--- /dev/null
+++ b/chrome/browser/chromeos/arc/instance_throttle/window_throttle_observer_base.h
@@ -0,0 +1,56 @@
+// 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_CHROMEOS_ARC_INSTANCE_THROTTLE_WINDOW_THROTTLE_OBSERVER_BASE_H_
+#define CHROME_BROWSER_CHROMEOS_ARC_INSTANCE_THROTTLE_WINDOW_THROTTLE_OBSERVER_BASE_H_
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/arc/instance_throttle/arc_throttle_observer.h"
+#include "ui/wm/public/activation_change_observer.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace aura {
+class Window;
+}
+
+namespace arc {
+
+class ArcBridgeService;
+
+// Base class for locks that observe changes in window activation.
+class WindowThrottleObserverBase : public ArcThrottleObserver,
+                                   public wm::ActivationChangeObserver {
+ public:
+  WindowThrottleObserverBase(ArcThrottleObserver::PriorityLevel level,
+                             std::string name);
+  ~WindowThrottleObserverBase() override = default;
+
+  // ArcThrottleObserver:
+  void StartObserving(ArcBridgeService* arc_bridge_service,
+                      content::BrowserContext* context,
+                      const ObserverStateChangedCallback& callback) override;
+  void StopObserving() override;
+
+  // wm::ActivationChangeObserver:
+  void OnWindowActivated(ActivationReason reason,
+                         aura::Window* gained_active,
+                         aura::Window* lost_active) override;
+
+ protected:
+  // Returns true if the window activation should set the state to active, and
+  // false if the window activation should set state to inactive.
+  virtual bool ProcessWindowActivation(ActivationReason reason,
+                                       aura::Window* gained_active,
+                                       aura::Window* lost_active) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(WindowThrottleObserverBase);
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_CHROMEOS_ARC_INSTANCE_THROTTLE_WINDOW_THROTTLE_OBSERVER_BASE_H_
diff --git a/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.cc b/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.cc
index 1623338..de92088 100644
--- a/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.cc
+++ b/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.cc
@@ -8,7 +8,7 @@
 #include <utility>
 
 #include "ash/public/cpp/ash_pref_names.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "base/bind.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/arc/arc_util.h"
@@ -49,9 +49,7 @@
   arc::ArcSessionManager::Get()->AddObserver(this);
   g_voice_interaction_controller_client_instance = this;
 
-  if (chromeos::features::IsAssistantEnabled()) {
-    voice_interaction_state_ = ash::mojom::VoiceInteractionState::NOT_READY;
-  }
+  voice_interaction_state_ = ash::mojom::VoiceInteractionState::NOT_READY;
 }
 
 VoiceInteractionControllerClient::~VoiceInteractionControllerClient() {
@@ -76,43 +74,42 @@
 void VoiceInteractionControllerClient::NotifyStatusChanged(
     ash::mojom::VoiceInteractionState state) {
   voice_interaction_state_ = state;
-  ash::VoiceInteractionController::Get()->NotifyStatusChanged(state);
+  ash::AssistantState::Get()->NotifyStatusChanged(state);
   for (auto& observer : observers_)
     observer.OnStateChanged(state);
 }
 
 void VoiceInteractionControllerClient::NotifyLockedFullScreenStateChanged(
     bool enabled) {
-  ash::VoiceInteractionController::Get()->NotifyLockedFullScreenStateChanged(
-      enabled);
+  ash::AssistantState::Get()->NotifyLockedFullScreenStateChanged(enabled);
 }
 
 void VoiceInteractionControllerClient::NotifySettingsEnabled() {
   DCHECK(profile_);
   PrefService* prefs = profile_->GetPrefs();
   bool enabled = prefs->GetBoolean(prefs::kVoiceInteractionEnabled);
-  ash::VoiceInteractionController::Get()->NotifySettingsEnabled(enabled);
+  ash::AssistantState::Get()->NotifySettingsEnabled(enabled);
 }
 
 void VoiceInteractionControllerClient::NotifyContextEnabled() {
   DCHECK(profile_);
   PrefService* prefs = profile_->GetPrefs();
   bool enabled = prefs->GetBoolean(prefs::kVoiceInteractionContextEnabled);
-  ash::VoiceInteractionController::Get()->NotifyContextEnabled(enabled);
+  ash::AssistantState::Get()->NotifyContextEnabled(enabled);
 }
 
 void VoiceInteractionControllerClient::NotifyHotwordEnabled() {
   DCHECK(profile_);
   PrefService* prefs = profile_->GetPrefs();
   bool enabled = prefs->GetBoolean(prefs::kVoiceInteractionHotwordEnabled);
-  ash::VoiceInteractionController::Get()->NotifyHotwordEnabled(enabled);
+  ash::AssistantState::Get()->NotifyHotwordEnabled(enabled);
 }
 
 void VoiceInteractionControllerClient::NotifyFeatureAllowed() {
   DCHECK(profile_);
   ash::mojom::AssistantAllowedState state =
       assistant::IsAssistantAllowedForProfile(profile_);
-  ash::VoiceInteractionController::Get()->NotifyFeatureAllowed(state);
+  ash::AssistantState::Get()->NotifyFeatureAllowed(state);
 }
 
 void VoiceInteractionControllerClient::NotifyLocaleChanged() {
@@ -123,7 +120,7 @@
   std::string out_locale =
       profile_->GetPrefs()->GetString(language::prefs::kApplicationLocale);
 
-  ash::VoiceInteractionController::Get()->NotifyLocaleChanged(out_locale);
+  ash::AssistantState::Get()->NotifyLocaleChanged(out_locale);
 }
 
 void VoiceInteractionControllerClient::ActiveUserChanged(
@@ -205,8 +202,7 @@
 
 void VoiceInteractionControllerClient::OnArcPlayStoreEnabledChanged(
     bool enabled) {
-  ash::VoiceInteractionController::Get()->NotifyArcPlayStoreEnabledChanged(
-      enabled);
+  ash::AssistantState::Get()->NotifyArcPlayStoreEnabledChanged(enabled);
 }
 
 }  // namespace arc
diff --git a/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h b/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h
index 7e6f1587..58c430b 100644
--- a/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h
+++ b/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h
@@ -16,6 +16,7 @@
 
 namespace arc {
 
+// TODO(b/138679823): Remove the class and use AssistantState instead.
 // The client of VoiceInteractionController. It monitors various user session
 // states and notifies Ash side.  It can also be used to notify some specific
 // state changes that does not have an observer interface.
diff --git a/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client_unittest.cc b/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client_unittest.cc
index 0129709..a6f5cb14 100644
--- a/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client_unittest.cc
+++ b/chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client_unittest.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h"
 
-#include "ash/public/cpp/voice_interaction_controller.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "base/bind.h"
 #include "base/files/scoped_temp_dir.h"
 #include "chrome/browser/chromeos/arc/arc_session_manager.h"
@@ -90,22 +90,22 @@
   ASSERT_EQ(false, prefs->GetBoolean(prefs::kVoiceInteractionEnabled));
   prefs->SetBoolean(prefs::kVoiceInteractionEnabled, true);
   ASSERT_EQ(true, prefs->GetBoolean(prefs::kVoiceInteractionEnabled));
-  EXPECT_EQ(true, ash::VoiceInteractionController::Get()->settings_enabled());
+  EXPECT_EQ(true, ash::AssistantState::Get()->settings_enabled());
 
   ASSERT_EQ(false, prefs->GetBoolean(prefs::kVoiceInteractionContextEnabled));
   prefs->SetBoolean(prefs::kVoiceInteractionContextEnabled, true);
   ASSERT_EQ(true, prefs->GetBoolean(prefs::kVoiceInteractionContextEnabled));
-  EXPECT_EQ(true, ash::VoiceInteractionController::Get()->context_enabled());
+  EXPECT_EQ(true, ash::AssistantState::Get()->context_enabled());
 
   ASSERT_EQ(false, prefs->GetBoolean(prefs::kVoiceInteractionHotwordEnabled));
   prefs->SetBoolean(prefs::kVoiceInteractionHotwordEnabled, true);
   ASSERT_EQ(true, prefs->GetBoolean(prefs::kVoiceInteractionHotwordEnabled));
-  EXPECT_EQ(true, ash::VoiceInteractionController::Get()->hotword_enabled());
+  EXPECT_EQ(true, ash::AssistantState::Get()->hotword_enabled());
 
   ASSERT_EQ("", prefs->GetString(language::prefs::kApplicationLocale));
   prefs->SetString(language::prefs::kApplicationLocale, "en-CA");
   ASSERT_EQ("en-CA", prefs->GetString(language::prefs::kApplicationLocale));
-  EXPECT_EQ("en-CA", ash::VoiceInteractionController::Get()->locale());
+  EXPECT_EQ("en-CA", ash::AssistantState::Get()->locale());
 }
 
 }  // namespace arc
diff --git a/chrome/browser/chromeos/assistant/assistant_util.cc b/chrome/browser/chromeos/assistant/assistant_util.cc
index 6404c5b6c..08e1ee8 100644
--- a/chrome/browser/chromeos/assistant/assistant_util.cc
+++ b/chrome/browser/chromeos/assistant/assistant_util.cc
@@ -25,9 +25,6 @@
 
 ash::mojom::AssistantAllowedState IsAssistantAllowedForProfile(
     const Profile* profile) {
-  if (!chromeos::features::IsAssistantEnabled())
-    return ash::mojom::AssistantAllowedState::DISALLOWED_BY_FLAG;
-
   if (!chromeos::ProfileHelper::IsPrimaryProfile(profile))
     return ash::mojom::AssistantAllowedState::DISALLOWED_BY_NONPRIMARY_USER;
 
diff --git a/chrome/browser/chromeos/assistant/assistant_util_unittest.cc b/chrome/browser/chromeos/assistant/assistant_util_unittest.cc
index 70b1f0f..df38dd4 100644
--- a/chrome/browser/chromeos/assistant/assistant_util_unittest.cc
+++ b/chrome/browser/chromeos/assistant/assistant_util_unittest.cc
@@ -8,7 +8,6 @@
 
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/scoped_command_line.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -144,7 +143,6 @@
 
   void SetUp() override {
     command_line_ = std::make_unique<base::test::ScopedCommandLine>();
-    feature_list_.InitAndEnableFeature(chromeos::features::kAssistantFeature);
 
     ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
     profile_manager_ = std::make_unique<TestingProfileManager>(
@@ -184,7 +182,6 @@
 
  private:
   std::unique_ptr<base::test::ScopedCommandLine> command_line_;
-  base::test::ScopedFeatureList feature_list_;
   content::BrowserTaskEnvironment task_environment_;
   base::ScopedTempDir data_dir_;
   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc b/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc
index a49df584..61abaa0 100644
--- a/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc
+++ b/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc
@@ -96,6 +96,7 @@
       l10n_util::GetStringUTF16(IDS_DOWNLOAD_LINK_CANCEL))});
   notification_->set_never_timeout(true);
   notification_->set_progress(progress_percent);
+  notification_->set_pinned(true);
 
   ForceRedisplay();
 }
@@ -119,6 +120,7 @@
   notification_->set_buttons({});
   notification_->set_never_timeout(true);
   notification_->set_progress(-1);  // Infinite progress bar
+  notification_->set_pinned(false);
 
   ForceRedisplay();
 }
@@ -141,6 +143,7 @@
           : IDS_CROSTINI_IMPORT_NOTIFICATION_MESSAGE_DONE));
   notification_->set_buttons({});
   notification_->set_never_timeout(false);
+  notification_->set_pinned(false);
 
   ForceRedisplay();
 }
@@ -201,6 +204,7 @@
   notification_->set_message(message);
   notification_->set_buttons({});
   notification_->set_never_timeout(false);
+  notification_->set_pinned(false);
 
   ForceRedisplay();
 }
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc b/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc
index 3aaca4e..a7915eb 100644
--- a/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc
@@ -141,6 +141,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 0);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // 20% PACK = 10% overall.
   SendExportProgress(vm_tools::cicerone::
@@ -149,6 +150,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 10);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // 20% DOWNLOAD = 60% overall.
   SendExportProgress(
@@ -158,6 +160,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 60);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // Close notification and update progress. Should not update notification.
   notification->Close(false);
@@ -168,12 +171,14 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 60);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // Done.
   SendExportProgress(
       vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE);
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::DONE);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   // CrostiniExportImport should've created the exported file.
   task_environment_.RunUntilIdle();
   EXPECT_TRUE(base::PathExists(tarball_));
@@ -189,6 +194,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 0);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // STREAMING 10% bytes done + 30% files done = 20% overall.
   SendExportProgress(
@@ -201,6 +207,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 20);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // STREAMING 66% bytes done + 55% files done then floored = 60% overall.
   SendExportProgress(
@@ -213,6 +220,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 60);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // Close notification and update progress. Should not update notification.
   notification->Close(false);
@@ -226,12 +234,14 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 60);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // Done.
   SendExportProgress(
       vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE);
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::DONE);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   // CrostiniExportImport should've created the exported file.
   task_environment_.RunUntilIdle();
   EXPECT_TRUE(base::PathExists(tarball_));
@@ -249,6 +259,7 @@
       vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_FAILED);
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::FAILED);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   // CrostiniExportImport should cleanup the file if an export fails.
   task_environment_.RunUntilIdle();
   EXPECT_FALSE(base::PathExists(tarball_));
@@ -264,6 +275,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 0);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // CANCELLING:
   crostini_export_import_->CancelOperation(ExportImportType::EXPORT,
@@ -271,6 +283,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::CANCELLING);
   EXPECT_EQ(notification->get_notification()->progress(), -1);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   EXPECT_TRUE(base::PathExists(tarball_));
 
   // STREAMING: should not be displayed as cancel is in progress
@@ -284,6 +297,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::CANCELLING);
   EXPECT_EQ(notification->get_notification()->progress(), -1);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   EXPECT_TRUE(base::PathExists(tarball_));
 
   // CANCELLED:
@@ -305,6 +319,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 0);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // CANCELLING:
   crostini_export_import_->CancelOperation(ExportImportType::EXPORT,
@@ -312,6 +327,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::CANCELLING);
   EXPECT_EQ(notification->get_notification()->progress(), -1);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   EXPECT_TRUE(base::PathExists(tarball_));
 
   // DONE: Completed before cancel processed, file should be deleted.
@@ -333,6 +349,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 0);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // 20% UPLOAD = 10% overall.
   SendImportProgress(
@@ -342,6 +359,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 10);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // 20% UNPACK = 60% overall.
   SendImportProgress(
@@ -351,6 +369,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 60);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // Close notification and update progress. Should not update notification.
   notification->Close(false);
@@ -361,12 +380,14 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 60);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // Done.
   SendImportProgress(
       vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_DONE);
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::DONE);
+  EXPECT_FALSE(notification->get_notification()->pinned());
 }
 
 TEST_F(CrostiniExportImportTest, TestImportFail) {
@@ -381,6 +402,7 @@
       vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_FAILED);
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::FAILED);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   std::string msg("Restoring couldn't be completed due to an error");
   EXPECT_EQ(notification->get_notification()->message(),
             base::UTF8ToUTF16(msg));
@@ -396,6 +418,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 0);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // CANCELLING:
   crostini_export_import_->CancelOperation(ExportImportType::IMPORT,
@@ -403,6 +426,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::CANCELLING);
   EXPECT_EQ(notification->get_notification()->progress(), -1);
+  EXPECT_FALSE(notification->get_notification()->pinned());
 
   // STREAMING: should not be displayed as cancel is in progress
   SendImportProgress(
@@ -412,6 +436,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::CANCELLING);
   EXPECT_EQ(notification->get_notification()->progress(), -1);
+  EXPECT_FALSE(notification->get_notification()->pinned());
 
   // CANCELLED:
   SendImportProgress(
@@ -430,6 +455,7 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::RUNNING);
   EXPECT_EQ(notification->get_notification()->progress(), 0);
+  EXPECT_TRUE(notification->get_notification()->pinned());
 
   // CANCELLING:
   crostini_export_import_->CancelOperation(ExportImportType::IMPORT,
@@ -437,12 +463,14 @@
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::CANCELLING);
   EXPECT_EQ(notification->get_notification()->progress(), -1);
+  EXPECT_FALSE(notification->get_notification()->pinned());
 
   // DONE: Cancel couldn't be processed in time, done is displayed instead.
   SendImportProgress(
       vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_DONE);
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::DONE);
+  EXPECT_FALSE(notification->get_notification()->pinned());
 }
 
 TEST_F(CrostiniExportImportTest, TestImportFailArchitecture) {
@@ -458,6 +486,7 @@
           ImportLxdContainerProgressSignal_Status_FAILED_ARCHITECTURE);
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::FAILED);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   std::string msg(
       "Cannot import container architecture type arch_con with this device "
       "which is arch_dev. You can try restoring this container into a "
@@ -483,6 +512,7 @@
       });
   EXPECT_EQ(notification->status(),
             CrostiniExportImportNotification::Status::FAILED);
+  EXPECT_FALSE(notification->get_notification()->pinned());
   std::string msg =
       "Cannot restore due to lack of storage space. Free up 15.0 GB from the "
       "device and try again.";
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 03dca09..a9ea66d 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -17,7 +17,6 @@
 #include "ash/public/cpp/split_view.h"
 #include "ash/public/cpp/split_view_test_api.h"
 #include "ash/public/cpp/tablet_mode.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/mojom/constants.mojom.h"
 #include "ash/shell.h"
@@ -1561,16 +1560,12 @@
 
 AutotestPrivateSetAssistantEnabledFunction::
     AutotestPrivateSetAssistantEnabledFunction() {
-  mojo::PendingRemote<ash::mojom::VoiceInteractionController> remote;
-  ash::VoiceInteractionController::Get()->BindRequest(
-      remote.InitWithNewPipeAndPassReceiver());
-  assistant_state_.Init(std::move(remote));
-  assistant_state_.AddObserver(this);
+  ash::AssistantState::Get()->AddObserver(this);
 }
 
 AutotestPrivateSetAssistantEnabledFunction::
     ~AutotestPrivateSetAssistantEnabledFunction() {
-  assistant_state_.RemoveObserver(this);
+  ash::AssistantState::Get()->RemoveObserver(this);
 }
 
 ExtensionFunction::ResponseAction
@@ -1594,7 +1589,7 @@
                        ? ash::mojom::VoiceInteractionState::STOPPED
                        : ash::mojom::VoiceInteractionState::NOT_READY;
 
-  if (assistant_state_.voice_interaction_state() == new_state)
+  if (ash::AssistantState::Get()->voice_interaction_state() == new_state)
     return RespondNow(NoArguments());
 
   // Assistant service has not responded yet, set up a delayed timer to wait for
@@ -1608,9 +1603,10 @@
   return RespondLater();
 }
 
-void AutotestPrivateSetAssistantEnabledFunction::
-    OnVoiceInteractionStatusChanged(ash::mojom::VoiceInteractionState state) {
-  DCHECK(expected_state_);
+void AutotestPrivateSetAssistantEnabledFunction::OnAssistantStatusChanged(
+    ash::mojom::VoiceInteractionState state) {
+  if (!expected_state_)
+    return;
 
   // The service could go through |NOT_READY| then to |STOPPED| during enable
   // flow if this API is called before the initial state is reported.
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
index d109598..bd8af6bc 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
@@ -9,8 +9,7 @@
 #include <string>
 #include <vector>
 
-#include "ash/public/cpp/assistant/assistant_state_proxy.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/window_state_type.h"
 #include "base/compiler_specific.h"
 #include "base/timer/timer.h"
@@ -20,6 +19,7 @@
 #include "chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom.h"
 #include "chromeos/services/machine_learning/public/mojom/model.mojom.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "mojo/public/cpp/bindings/binding.h"
 #include "ui/snapshot/screenshot_grabber.h"
 
 namespace crostini {
@@ -462,7 +462,7 @@
 // pref which will indirectly bring up or shut down the Assistant service.
 class AutotestPrivateSetAssistantEnabledFunction
     : public ExtensionFunction,
-      public ash::DefaultVoiceInteractionObserver {
+      public ash::AssistantStateObserver {
  public:
   AutotestPrivateSetAssistantEnabledFunction();
   DECLARE_EXTENSION_FUNCTION("autotestPrivate.setAssistantEnabled",
@@ -472,15 +472,14 @@
   ~AutotestPrivateSetAssistantEnabledFunction() override;
   ResponseAction Run() override;
 
-  // ash::DefaultVoiceInteractionObserver overrides:
-  void OnVoiceInteractionStatusChanged(
+  // ash::AssistantStateObserver overrides:
+  void OnAssistantStatusChanged(
       ash::mojom::VoiceInteractionState state) override;
 
   // Called when the Assistant service does not respond in a timely fashion. We
   // will respond with an error.
   void Timeout();
 
-  ash::AssistantStateProxy assistant_state_;
   base::Optional<ash::mojom::VoiceInteractionState> expected_state_;
   base::OneShotTimer timeout_timer_;
 };
diff --git a/chrome/browser/chromeos/extensions/info_private_api.cc b/chrome/browser/chromeos/extensions/info_private_api.cc
index 9cc5f69..dafa0e0 100644
--- a/chrome/browser/chromeos/extensions/info_private_api.cc
+++ b/chrome/browser/chromeos/extensions/info_private_api.cc
@@ -194,10 +194,6 @@
 // Key which corresponds to the assistantStatus property in JS.
 const char kPropertyAssistantStatus[] = "assistantStatus";
 
-// Value to which assistantStatus property is set when the device does not
-// support Assistant.
-const char kAssistantStatusUnsupported[] = "unsupported";
-
 // Value to which assistantStatus property is set when the device supports
 // Assistant.
 const char kAssistantStatusSupported[] = "supported";
@@ -389,9 +385,7 @@
   }
 
   if (property_name == kPropertyAssistantStatus) {
-    return std::make_unique<base::Value>(
-        chromeos::features::IsAssistantEnabled() ? kAssistantStatusSupported
-                                                 : kAssistantStatusUnsupported);
+    return std::make_unique<base::Value>(kAssistantStatusSupported);
   }
 
   if (property_name == kPropertyClientId) {
diff --git a/chrome/browser/chromeos/extensions/info_private_apitest.cc b/chrome/browser/chromeos/extensions/info_private_apitest.cc
index 0b3ccd9..8ce32b8b 100644
--- a/chrome/browser/chromeos/extensions/info_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/info_private_apitest.cc
@@ -141,25 +141,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeOSInfoPrivateTest, AssistantSupported) {
-  // Enable native Assistant.
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(chromeos::features::kAssistantFeature);
-
   ASSERT_TRUE(RunPlatformAppTestWithArg("chromeos_info_private/extended",
                                         "assistant supported"))
       << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(ChromeOSInfoPrivateTest, AssistantUnsupported) {
-  // Disable native Assistant.
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(chromeos::features::kAssistantFeature);
-
-  ASSERT_TRUE(RunPlatformAppTestWithArg("chromeos_info_private/extended",
-                                        "assistant unsupported"))
-      << message_;
-}
-
 IN_PROC_BROWSER_TEST_F(ChromeOSInfoPrivateTest, StylusUnsupported) {
   ASSERT_TRUE(RunPlatformAppTestWithArg("chromeos_info_private/extended",
                                         "stylus unsupported"))
diff --git a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
index 7061dc41..f037e934 100644
--- a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
+++ b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
@@ -376,9 +376,6 @@
   void SetUp() override {
     LOG(INFO) << "OOBE end-to-end test  started with params "
               << params_.ToString();
-
-    if (params_.arc_state != ArcState::kNotAvailable)
-      feature_list_.InitAndEnableFeature(features::kAssistantFeature);
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
diff --git a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.cc b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.cc
index 2f84ad0..f7d3538 100644
--- a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.cc
+++ b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.cc
@@ -45,10 +45,10 @@
   }
 
 #if BUILDFLAG(ENABLE_CROS_ASSISTANT)
-  if (chromeos::features::IsAssistantEnabled() &&
-      ::assistant::IsAssistantAllowedForProfile(
+  if (::assistant::IsAssistantAllowedForProfile(
           ProfileManager::GetActiveUserProfile()) ==
-          ash::mojom::AssistantAllowedState::ALLOWED) {
+          ash::mojom::AssistantAllowedState::ALLOWED &&
+      !skip_for_testing_) {
     view_->Show();
     return;
   }
diff --git a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h
index f8f75a8..5e5f368 100644
--- a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h
+++ b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h
@@ -29,10 +29,15 @@
   void Hide() override;
   void OnUserAction(const std::string& action_id) override;
 
+  void SetSkipForTesting() { skip_for_testing_ = true; }
+
  private:
   AssistantOptInFlowScreenView* view_;
   base::RepeatingClosure exit_callback_;
 
+  // Skip the screen for testing if set to true.
+  bool skip_for_testing_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(AssistantOptInFlowScreen);
 };
 
diff --git a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc
index a3d4dc07..938b02a 100644
--- a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc
@@ -12,7 +12,6 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/strings/string_piece.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h"
 #include "chrome/browser/chromeos/login/login_wizard.h"
 #include "chrome/browser/chromeos/login/oobe_screen.h"
@@ -322,13 +321,7 @@
   AssistantOptInFlowTest() = default;
   ~AssistantOptInFlowTest() override = default;
 
-  virtual void InitializeFeatureList() {
-    feature_list_.InitAndEnableFeature(features::kAssistantFeature);
-  }
-
   void SetUp() override {
-    InitializeFeatureList();
-
     base::FilePath test_data_dir;
     base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
     https_server_.ServeFilesFromDirectory(test_data_dir);
@@ -451,8 +444,6 @@
   // request..
   bool fail_next_value_prop_url_request_ = false;
 
-  base::test::ScopedFeatureList feature_list_;
-
  private:
   std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
     auto response = std::make_unique<BasicHttpResponse>();
@@ -485,31 +476,6 @@
   LoginManagerMixin login_manager_{&mixin_host_, {test_user_}};
 };
 
-class AssistantOptInFlowTestWithDisabledAssistant
-    : public AssistantOptInFlowTest {
- public:
-  AssistantOptInFlowTestWithDisabledAssistant() = default;
-  ~AssistantOptInFlowTestWithDisabledAssistant() override = default;
-
-  void InitializeFeatureList() override {
-    feature_list_.InitAndDisableFeature(features::kAssistantFeature);
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(AssistantOptInFlowTestWithDisabledAssistant,
-                       ExitImmediately) {
-  assistant_optin_flow_screen_->Show();
-  WaitForScreenExit();
-
-  ExpectCollectedOptIns({});
-  PrefService* const prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
-  EXPECT_EQ(
-      chromeos::assistant::prefs::ConsentStatus::kUnknown,
-      prefs->GetInteger(chromeos::assistant::prefs::kAssistantConsentStatus));
-  EXPECT_FALSE(prefs->GetBoolean(arc::prefs::kVoiceInteractionHotwordEnabled));
-  EXPECT_FALSE(prefs->GetBoolean(arc::prefs::kVoiceInteractionContextEnabled));
-}
-
 IN_PROC_BROWSER_TEST_F(AssistantOptInFlowTest, Basic) {
   arc::VoiceInteractionControllerClient::Get()->NotifyStatusChanged(
       ash::mojom::VoiceInteractionState::STOPPED);
diff --git a/chrome/browser/chromeos/login/sync_consent_interactive_ui_test.cc b/chrome/browser/chromeos/login/sync_consent_interactive_ui_test.cc
index c89cf61..f157e1a 100644
--- a/chrome/browser/chromeos/login/sync_consent_interactive_ui_test.cc
+++ b/chrome/browser/chromeos/login/sync_consent_interactive_ui_test.cc
@@ -7,9 +7,9 @@
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h"
 #include "chrome/browser/chromeos/login/screens/sync_consent_screen.h"
 #include "chrome/browser/chromeos/login/test/fake_gaia_mixin.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
@@ -18,6 +18,7 @@
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
+#include "chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
 #include "chrome/grit/generated_resources.h"
@@ -141,6 +142,12 @@
                                   FakeGaiaMixin::kEmptyUserServices);
 
     test::CreateOobeScreenWaiter("sync-consent")->Wait();
+
+    // Skip the Assistant opt-in flow screen to avoid it blocking the test.
+    auto* screen = static_cast<AssistantOptInFlowScreen*>(
+        WizardController::default_controller()->GetScreen(
+            AssistantOptInFlowScreenView::kScreenId));
+    screen->SetSkipForTesting();
   }
 
  protected:
@@ -263,17 +270,6 @@
 // we use WithParamInterface<bool> here.
 class SyncConsentPolicyDisabledTest : public SyncConsentTest,
                                       public testing::WithParamInterface<bool> {
- public:
-  SyncConsentPolicyDisabledTest() {
-    // Assistant feature contains an OOBE page which is irrelevant for this
-    // test.
-    feature_list_.InitAndDisableFeature(features::kAssistantFeature);
-  }
-  ~SyncConsentPolicyDisabledTest() = default;
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-  DISALLOW_COPY_AND_ASSIGN(SyncConsentPolicyDisabledTest);
 };
 
 IN_PROC_BROWSER_TEST_P(SyncConsentPolicyDisabledTest,
diff --git a/chrome/browser/devtools/devtools_file_helper.cc b/chrome/browser/devtools/devtools_file_helper.cc
index 5c062c3..15cedcc5 100644
--- a/chrome/browser/devtools/devtools_file_helper.cc
+++ b/chrome/browser/devtools/devtools_file_helper.cc
@@ -77,15 +77,17 @@
   void Show(ui::SelectFileDialog::Type type,
             const base::FilePath& default_path) {
     AddRef();  // Balanced in the three listener outcomes.
+    base::FilePath::StringType ext;
+    ui::SelectFileDialog::FileTypeInfo file_type_info;
+    if (type == ui::SelectFileDialog::SELECT_SAVEAS_FILE &&
+        default_path.Extension().length() > 0) {
+      ext = default_path.Extension().substr(1);
+      file_type_info.extensions.resize(1);
+      file_type_info.extensions[0].push_back(ext);
+    }
     select_file_dialog_->SelectFile(
-      type,
-      base::string16(),
-      default_path,
-      NULL,
-      0,
-      base::FilePath::StringType(),
-      platform_util::GetTopLevel(web_contents_->GetNativeView()),
-      NULL);
+        type, base::string16(), default_path, &file_type_info, 0, ext,
+        platform_util::GetTopLevel(web_contents_->GetNativeView()), nullptr);
   }
 
   // ui::SelectFileDialog::Listener implementation.
diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client.cc b/chrome/browser/extensions/api/chrome_extensions_api_client.cc
index 82adf99..fe9d024 100644
--- a/chrome/browser/extensions/api/chrome_extensions_api_client.cc
+++ b/chrome/browser/extensions/api/chrome_extensions_api_client.cc
@@ -221,7 +221,8 @@
     content::BrowserContext* context,
     const ExtensionId& extension_id,
     int tab_id,
-    int action_count) {
+    int action_count,
+    bool clear_badge_text) {
   const Extension* extension =
       ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
           extension_id);
@@ -232,6 +233,13 @@
   DCHECK(action);
 
   action->SetDNRActionCount(tab_id, action_count);
+
+  // The badge text should be cleared if |action| contains explicitly set badge
+  // text for the |tab_id| when the preference is then toggled on. In this case,
+  // the matched action count should take precedence over the badge text.
+  if (clear_badge_text)
+    action->ClearBadgeText(tab_id);
+
   content::WebContents* tab_contents = nullptr;
   if (ExtensionTabUtil::GetTabById(
           tab_id, context, true /* include_incognito */, &tab_contents) &&
@@ -241,6 +249,25 @@
   }
 }
 
+void ChromeExtensionsAPIClient::ClearActionCount(
+    content::BrowserContext* context,
+    const Extension& extension) {
+  ExtensionAction* action =
+      ExtensionActionManager::Get(context)->GetExtensionAction(extension);
+  DCHECK(action);
+
+  action->ClearDNRActionCountForAllTabs();
+
+  std::vector<content::WebContents*> contents_to_notify =
+      ExtensionTabUtil::GetAllActiveWebContentsForContext(
+          context, true /* include_incognito */);
+
+  for (auto* active_contents : contents_to_notify) {
+    ExtensionActionAPI::Get(context)->NotifyChange(action, active_contents,
+                                                   context);
+  }
+}
+
 AppViewGuestDelegate* ChromeExtensionsAPIClient::CreateAppViewGuestDelegate()
     const {
   return new ChromeAppViewGuestDelegate();
diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client.h b/chrome/browser/extensions/api/chrome_extensions_api_client.h
index f1b8197f..f5ff000a 100644
--- a/chrome/browser/extensions/api/chrome_extensions_api_client.h
+++ b/chrome/browser/extensions/api/chrome_extensions_api_client.h
@@ -42,7 +42,10 @@
   void UpdateActionCount(content::BrowserContext* context,
                          const ExtensionId& extension_id,
                          int tab_id,
-                         int action_count) override;
+                         int action_count,
+                         bool clear_badge_text) override;
+  void ClearActionCount(content::BrowserContext* context,
+                        const Extension& extension) override;
   AppViewGuestDelegate* CreateAppViewGuestDelegate() const override;
   ExtensionOptionsGuestDelegate* CreateExtensionOptionsGuestDelegate(
       ExtensionOptionsGuest* guest) const override;
diff --git a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
index 3a510a0..e704227 100644
--- a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
@@ -29,6 +29,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.h"
 #include "chrome/browser/extensions/extension_action.h"
 #include "chrome/browser/extensions/extension_action_manager.h"
 #include "chrome/browser/extensions/extension_action_runner.h"
@@ -494,6 +495,18 @@
     ASSERT_EQ("success", ExecuteScriptInBackgroundPage(extension_id, script));
   }
 
+  void SetActionsAsBadgeText(const ExtensionId& extension_id, bool pref) {
+    const char* pref_string = pref ? "true" : "false";
+    static constexpr char kSetActionCountAsBadgeTextScript[] = R"(
+      chrome.declarativeNetRequest.setActionCountAsBadgeText(%s);
+      window.domAutomationController.send("done");
+    )";
+
+    ExecuteScriptInBackgroundPage(
+        extension_id,
+        base::StringPrintf(kSetActionCountAsBadgeTextScript, pref_string));
+  }
+
   std::set<GURL> GetAndResetRequestsToServer() {
     base::AutoLock lock(requests_to_server_lock_);
     std::set<GURL> results = requests_to_server_;
@@ -2834,13 +2847,7 @@
       query_badge_text_from_ext(extension_id, first_tab_id);
   EXPECT_EQ(default_badge_text, queried_badge_text);
 
-  ExtensionPrefs::Get(profile())->SetDNRUseActionCountAsBadgeText(extension_id,
-                                                                  true);
-  // TODO(crbug.com/979068): Remove the navigation once we update extension
-  // action immediately upon toggling preference.
-  ui_test_utils::NavigateToURL(browser(), page_url);
-  ASSERT_TRUE(WasFrameWithScriptLoaded(GetMainFrame()));
-
+  SetActionsAsBadgeText(extension_id, true);
   // Since the preference is on for the current tab, attempting to query the
   // badge text from the extension should return the placeholder text instead of
   // the matched action count.
@@ -2851,13 +2858,7 @@
   // The displayed badge text should show "0" as no actions have been matched.
   EXPECT_EQ("0", action->GetDisplayBadgeText(first_tab_id));
 
-  ExtensionPrefs::Get(profile())->SetDNRUseActionCountAsBadgeText(extension_id,
-                                                                  false);
-  // TODO(crbug.com/979068): Remove the navigation once we update extension
-  // action immediately upon toggling preference.
-  ui_test_utils::NavigateToURL(browser(), page_url);
-  ASSERT_TRUE(WasFrameWithScriptLoaded(GetMainFrame()));
-
+  SetActionsAsBadgeText(extension_id, false);
   // Switching the preference off should cause the extension queried badge text
   // to be the explicitly set badge text for this tab if it exists. In this
   // case, the queried badge text should be the default badge text.
@@ -2867,6 +2868,68 @@
   // The displayed badge text should be the default badge text now that the
   // preference is off.
   EXPECT_EQ(default_badge_text, action->GetDisplayBadgeText(first_tab_id));
+
+  // Verify that turning off the preference deletes the DNR action count within
+  // the extension action.
+  EXPECT_FALSE(action->HasDNRActionCount(first_tab_id));
+}
+
+// Test that enabling the setActionCountAsBadgeText preference will update
+// all browsers sharing the same browser context.
+IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
+                       ActionCountPreferenceMultipleWindows) {
+  // Load the extension with a background script so scripts can be run from its
+  // generated background page.
+  set_has_background_script(true);
+
+  ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules(
+      {}, "test_extension", {URLPattern::kAllUrlsPattern}));
+
+  const ExtensionId& extension_id = last_loaded_extension_id();
+  const Extension* dnr_extension = extension_registry()->GetExtensionById(
+      extension_id, extensions::ExtensionRegistry::ENABLED);
+
+  ExtensionAction* extension_action =
+      ExtensionActionManager::Get(web_contents()->GetBrowserContext())
+          ->GetExtensionAction(*dnr_extension);
+
+  const GURL page_url = embedded_test_server()->GetURL(
+      "norulesmatched.com", "/pages_with_script/index.html");
+  ui_test_utils::NavigateToURL(browser(), page_url);
+  ASSERT_TRUE(WasFrameWithScriptLoaded(GetMainFrame()));
+
+  int first_browser_tab_id = ExtensionTabUtil::GetTabId(web_contents());
+  EXPECT_EQ("", extension_action->GetDisplayBadgeText(first_browser_tab_id));
+
+  // Now create a new browser with the same profile as |browser()| and navigate
+  // to |page_url|.
+  Browser* second_browser = CreateBrowser(profile());
+  ui_test_utils::NavigateToURL(second_browser, page_url);
+  ASSERT_TRUE(WasFrameWithScriptLoaded(GetMainFrame(second_browser)));
+  content::WebContents* second_browser_contents =
+      second_browser->tab_strip_model()->GetActiveWebContents();
+
+  int second_browser_tab_id =
+      ExtensionTabUtil::GetTabId(second_browser_contents);
+  EXPECT_EQ("", extension_action->GetDisplayBadgeText(second_browser_tab_id));
+
+  // Set up an observer to listen for ExtensionAction updates for the active web
+  // contents of both browser windows.
+  TestExtensionActionAPIObserver test_api_observer(
+      profile(), extension_id, {web_contents(), second_browser_contents});
+
+  SetActionsAsBadgeText(extension_id, true);
+
+  // Wait until ExtensionActionAPI::NotifyChange is called, then perform a
+  // sanity check on the browser action's badge text.
+  test_api_observer.Wait();
+
+  EXPECT_EQ("0", extension_action->GetDisplayBadgeText(first_browser_tab_id));
+
+  // The badge text for the second browser window should also update to the
+  // matched action count because the second browser shares the same browser
+  // context as the first.
+  EXPECT_EQ("0", extension_action->GetDisplayBadgeText(second_browser_tab_id));
 }
 
 // Test that the action matched badge text for an extension is visible in an
diff --git a/chrome/browser/extensions/api/extension_action/extension_action_api.cc b/chrome/browser/extensions/api/extension_action/extension_action_api.cc
index 9c5b346..28bdacc 100644
--- a/chrome/browser/extensions/api/extension_action/extension_action_api.cc
+++ b/chrome/browser/extensions/api/extension_action/extension_action_api.cc
@@ -166,8 +166,14 @@
 
   if (event_name) {
     std::unique_ptr<base::ListValue> args(new base::ListValue());
+    // The action APIs (browserAction, pageAction, action) are only available
+    // to blessed extension contexts. As such, we deterministically know that
+    // the right context type here is blessed.
+    constexpr Feature::Context context_type =
+        Feature::BLESSED_EXTENSION_CONTEXT;
     ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-        ExtensionTabUtil::GetScrubTabBehavior(extension, web_contents);
+        ExtensionTabUtil::GetScrubTabBehavior(extension, context_type,
+                                              web_contents);
     args->Append(ExtensionTabUtil::CreateTabObject(
                      web_contents, scrub_tab_behavior, extension)
                      ->ToValue());
diff --git a/chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.cc b/chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.cc
index 2083a40..054f6a3 100644
--- a/chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.cc
+++ b/chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.cc
@@ -13,6 +13,14 @@
   scoped_observer_.Add(ExtensionActionAPI::Get(context));
 }
 
+TestExtensionActionAPIObserver::TestExtensionActionAPIObserver(
+    content::BrowserContext* context,
+    const ExtensionId& extension_id,
+    const std::set<content::WebContents*>& contents_to_observe)
+    : TestExtensionActionAPIObserver(context, extension_id) {
+  contents_to_observe_ = contents_to_observe;
+}
+
 TestExtensionActionAPIObserver::~TestExtensionActionAPIObserver() = default;
 
 void TestExtensionActionAPIObserver::Wait() {
@@ -25,7 +33,10 @@
     content::BrowserContext* browser_context) {
   if (extension_action->extension_id() == extension_id_) {
     last_web_contents_ = web_contents;
-    run_loop_.QuitWhenIdle();
+    contents_to_observe_.erase(web_contents);
+
+    if (contents_to_observe_.empty())
+      run_loop_.QuitWhenIdle();
   }
 }
 
diff --git a/chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.h b/chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.h
index 11b35f5..981da2f 100644
--- a/chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.h
+++ b/chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_TEST_EXTENSION_ACTION_API_OBSERVER_H_
 #define CHROME_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_TEST_EXTENSION_ACTION_API_OBSERVER_H_
 
+#include <set>
+
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/scoped_observer.h"
@@ -23,9 +25,15 @@
  public:
   TestExtensionActionAPIObserver(content::BrowserContext* context,
                                  const ExtensionId& extension_id);
+  TestExtensionActionAPIObserver(
+      content::BrowserContext* context,
+      const ExtensionId& extension_id,
+      const std::set<content::WebContents*>& contents_to_observe);
   ~TestExtensionActionAPIObserver() override;
 
-  // Waits till the extension action is updated.
+  // Waits until the extension action is updated and the update is seen for all
+  // web contents in |contents_to_observe_| if |contents_to_observe_| is not
+  // empty.
   void Wait();
 
   // Returns the web contents for which the extension action was updated. Must
@@ -47,6 +55,9 @@
   ScopedObserver<ExtensionActionAPI, ExtensionActionAPI::Observer>
       scoped_observer_;
 
+  // An optional set of web contents to observe for extension action updates.
+  std::set<content::WebContents*> contents_to_observe_;
+
   DISALLOW_COPY_AND_ASSIGN(TestExtensionActionAPIObserver);
 };
 
diff --git a/chrome/browser/extensions/api/sessions/sessions_api.cc b/chrome/browser/extensions/api/sessions/sessions_api.cc
index d0acced..882f22b3 100644
--- a/chrome/browser/extensions/api/sessions/sessions_api.cc
+++ b/chrome/browser/extensions/api/sessions/sessions_api.cc
@@ -81,7 +81,8 @@
     int index,
     bool pinned,
     bool active,
-    const Extension* extension) {
+    const Extension* extension,
+    Feature::Context context) {
   api::tabs::Tab tab_struct;
 
   const GURL& url = current_navigation.virtual_url();
@@ -102,7 +103,7 @@
   tab_struct.active = active;
 
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension, url);
+      ExtensionTabUtil::GetScrubTabBehavior(extension, context, url);
   if (scrub_tab_behavior != ExtensionTabUtil::kDontScrubTab) {
     ExtensionTabUtil::ScrubTabForExtension(extension, nullptr, &tab_struct,
                                            scrub_tab_behavior);
@@ -154,7 +155,7 @@
   return CreateTabModelHelper(tab.navigations[tab.current_navigation_index],
                               base::NumberToString(tab.id.id()),
                               tab.tabstrip_index, tab.pinned, active,
-                              extension());
+                              extension(), source_context_type());
 }
 
 std::unique_ptr<api::windows::Window>
@@ -234,7 +235,7 @@
   std::string session_id = SessionId(session_tag, tab.tab_id.id()).ToString();
   return CreateTabModelHelper(
       tab.navigations[tab.normalized_navigation_index()], session_id, tab_index,
-      tab.pinned, active, extension());
+      tab.pinned, active, extension(), source_context_type());
 }
 
 std::unique_ptr<api::windows::Window>
@@ -396,7 +397,8 @@
 ExtensionFunction::ResponseValue SessionsRestoreFunction::GetRestoredTabResult(
     content::WebContents* contents) {
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension(), contents);
+      ExtensionTabUtil::GetScrubTabBehavior(extension(), source_context_type(),
+                                            contents);
   std::unique_ptr<api::tabs::Tab> tab(ExtensionTabUtil::CreateTabObject(
       contents, scrub_tab_behavior, extension()));
   std::unique_ptr<api::sessions::Session> restored_session(
@@ -415,7 +417,8 @@
   }
   std::unique_ptr<base::DictionaryValue> window_value(
       ExtensionTabUtil::CreateWindowValueForExtension(
-          *browser, extension(), ExtensionTabUtil::kPopulateTabs));
+          *browser, extension(), ExtensionTabUtil::kPopulateTabs,
+          source_context_type()));
   std::unique_ptr<api::windows::Window> window(
       api::windows::Window::FromValue(*window_value));
   return ArgumentList(Restore::Results::Create(*CreateSessionModelHelper(
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index c7a137a9..b7199b5 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -269,10 +269,11 @@
 std::unique_ptr<api::tabs::Tab> CreateTabObjectHelper(
     WebContents* contents,
     const Extension* extension,
+    Feature::Context context,
     TabStripModel* tab_strip,
     int tab_index) {
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension, contents);
+      ExtensionTabUtil::GetScrubTabBehavior(extension, context, contents);
   return ExtensionTabUtil::CreateTabObject(contents, scrub_tab_behavior,
                                            extension, tab_strip, tab_index);
 }
@@ -322,8 +323,8 @@
       extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs
                                 : ExtensionTabUtil::kDontPopulateTabs;
   std::unique_ptr<base::DictionaryValue> windows =
-      ExtensionTabUtil::CreateWindowValueForExtension(*browser, extension(),
-                                                      populate_tab_behavior);
+      ExtensionTabUtil::CreateWindowValueForExtension(
+          *browser, extension(), populate_tab_behavior, source_context_type());
   return RespondNow(OneArgument(std::move(windows)));
 }
 
@@ -345,8 +346,8 @@
       extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs
                                 : ExtensionTabUtil::kDontPopulateTabs;
   std::unique_ptr<base::DictionaryValue> windows =
-      ExtensionTabUtil::CreateWindowValueForExtension(*browser, extension(),
-                                                      populate_tab_behavior);
+      ExtensionTabUtil::CreateWindowValueForExtension(
+          *browser, extension(), populate_tab_behavior, source_context_type());
   return RespondNow(OneArgument(std::move(windows)));
 }
 
@@ -379,8 +380,8 @@
       extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs
                                 : ExtensionTabUtil::kDontPopulateTabs;
   std::unique_ptr<base::DictionaryValue> windows =
-      ExtensionTabUtil::CreateWindowValueForExtension(*browser, extension(),
-                                                      populate_tab_behavior);
+      ExtensionTabUtil::CreateWindowValueForExtension(
+          *browser, extension(), populate_tab_behavior, source_context_type());
   return RespondNow(OneArgument(std::move(windows)));
 }
 
@@ -401,7 +402,8 @@
       continue;
     }
     window_list->Append(ExtensionTabUtil::CreateWindowValueForExtension(
-        *controller->GetBrowser(), extension(), populate_tab_behavior));
+        *controller->GetBrowser(), extension(), populate_tab_behavior,
+        source_context_type()));
   }
 
   return RespondNow(OneArgument(std::move(window_list)));
@@ -658,7 +660,8 @@
     result = std::make_unique<base::Value>();
   } else {
     result = ExtensionTabUtil::CreateWindowValueForExtension(
-        *new_window, extension(), ExtensionTabUtil::kPopulateTabs);
+        *new_window, extension(), ExtensionTabUtil::kPopulateTabs,
+        source_context_type());
   }
 
   return RespondNow(OneArgument(std::move(result)));
@@ -788,7 +791,8 @@
     browser->window()->FlashFrame(*params->update_info.draw_attention);
 
   return RespondNow(OneArgument(ExtensionTabUtil::CreateWindowValueForExtension(
-      *browser, extension(), ExtensionTabUtil::kDontPopulateTabs)));
+      *browser, extension(), ExtensionTabUtil::kDontPopulateTabs,
+      source_context_type())));
 }
 
 ExtensionFunction::ResponseAction WindowsRemoveFunction::Run() {
@@ -842,9 +846,9 @@
   WebContents* contents = tab_strip->GetActiveWebContents();
   if (!contents)
     return RespondNow(Error(tabs_constants::kNoSelectedTabError));
-  return RespondNow(
-      ArgumentList(tabs::Get::Results::Create(*CreateTabObjectHelper(
-          contents, extension(), tab_strip, tab_strip->active_index()))));
+  return RespondNow(ArgumentList(tabs::Get::Results::Create(
+      *CreateTabObjectHelper(contents, extension(), source_context_type(),
+                             tab_strip, tab_strip->active_index()))));
 }
 
 ExtensionFunction::ResponseAction TabsGetAllInWindowFunction::Run() {
@@ -861,8 +865,8 @@
   if (!GetBrowserFromWindowID(this, window_id, &browser, &error))
     return RespondNow(Error(error));
 
-  return RespondNow(
-      OneArgument(ExtensionTabUtil::CreateTabList(browser, extension())));
+  return RespondNow(OneArgument(ExtensionTabUtil::CreateTabList(
+      browser, extension(), source_context_type())));
 }
 
 ExtensionFunction::ResponseAction TabsQueryFunction::Run() {
@@ -1031,9 +1035,9 @@
       if (loading_status_set && loading != web_contents->IsLoading())
         continue;
 
-      result->Append(
-          CreateTabObjectHelper(web_contents, extension(), tab_strip, i)
-              ->ToValue());
+      result->Append(CreateTabObjectHelper(web_contents, extension(),
+                                           source_context_type(), tab_strip, i)
+                         ->ToValue());
     }
   }
 
@@ -1097,9 +1101,9 @@
     return RespondNow(Error(kUnknownErrorDoNotUse));
   }
 
-  return RespondNow(
-      ArgumentList(tabs::Get::Results::Create(*CreateTabObjectHelper(
-          new_contents, extension(), new_tab_strip, new_tab_index))));
+  return RespondNow(ArgumentList(tabs::Get::Results::Create(
+      *CreateTabObjectHelper(new_contents, extension(), source_context_type(),
+                             new_tab_strip, new_tab_index))));
 }
 
 ExtensionFunction::ResponseAction TabsGetFunction::Run() {
@@ -1117,7 +1121,8 @@
   }
 
   return RespondNow(ArgumentList(tabs::Get::Results::Create(
-      *CreateTabObjectHelper(contents, extension(), tab_strip, tab_index))));
+      *CreateTabObjectHelper(contents, extension(), source_context_type(),
+                             tab_strip, tab_index))));
 }
 
 ExtensionFunction::ResponseAction TabsGetCurrentFunction::Run() {
@@ -1128,8 +1133,8 @@
   WebContents* caller_contents = GetSenderWebContents();
   std::unique_ptr<base::ListValue> results;
   if (caller_contents && ExtensionTabUtil::GetTabId(caller_contents) >= 0) {
-    results = tabs::Get::Results::Create(
-        *CreateTabObjectHelper(caller_contents, extension(), nullptr, -1));
+    results = tabs::Get::Results::Create(*CreateTabObjectHelper(
+        caller_contents, extension(), source_context_type(), nullptr, -1));
   }
   return RespondNow(results ? ArgumentList(std::move(results)) : NoArguments());
 }
@@ -1177,7 +1182,8 @@
   selection.set_active(active_index);
   browser->tab_strip_model()->SetSelectionFromModel(std::move(selection));
   return RespondNow(OneArgument(ExtensionTabUtil::CreateWindowValueForExtension(
-      *browser, extension(), ExtensionTabUtil::kPopulateTabs)));
+      *browser, extension(), ExtensionTabUtil::kPopulateTabs,
+      source_context_type())));
 }
 
 bool TabsHighlightFunction::HighlightTab(TabStripModel* tabstrip,
@@ -1371,8 +1377,8 @@
   if (!has_callback())
     return NoArguments();
 
-  return ArgumentList(tabs::Get::Results::Create(
-      *CreateTabObjectHelper(web_contents_, extension(), nullptr, -1)));
+  return ArgumentList(tabs::Get::Results::Create(*CreateTabObjectHelper(
+      web_contents_, extension(), source_context_type(), nullptr, -1)));
 }
 
 void TabsUpdateFunction::OnExecuteCodeFinished(
@@ -1506,6 +1512,7 @@
 
       if (has_callback()) {
         tab_values->Append(CreateTabObjectHelper(web_contents_raw, extension(),
+                                                 source_context_type(),
                                                  target_tab_strip, *new_index)
                                ->ToValue());
       }
@@ -1527,6 +1534,7 @@
 
   if (has_callback()) {
     tab_values->Append(CreateTabObjectHelper(contents, extension(),
+                                             source_context_type(),
                                              source_tab_strip, *new_index)
                            ->ToValue());
   }
@@ -2122,8 +2130,9 @@
 
   // Create the Tab object and return it in case of success.
   if (contents) {
-    return RespondNow(ArgumentList(tabs::Discard::Results::Create(
-        *CreateTabObjectHelper(contents, extension(), nullptr, -1))));
+    return RespondNow(
+        ArgumentList(tabs::Discard::Results::Create(*CreateTabObjectHelper(
+            contents, extension(), source_context_type(), nullptr, -1))));
   }
 
   // Return appropriate error message otherwise.
diff --git a/chrome/browser/extensions/api/tabs/tabs_event_router.cc b/chrome/browser/extensions/api/tabs/tabs_event_router.cc
index 404da57..f86ebb9 100644
--- a/chrome/browser/extensions/api/tabs/tabs_event_router.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_event_router.cc
@@ -48,7 +48,8 @@
     Event* event,
     const base::DictionaryValue* listener_filter) {
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension, contents);
+      ExtensionTabUtil::GetScrubTabBehavior(extension, target_context,
+                                            contents);
   std::unique_ptr<api::tabs::Tab> tab_object =
       ExtensionTabUtil::CreateTabObject(contents, scrub_tab_behavior,
                                         extension);
@@ -77,7 +78,8 @@
                                  const base::DictionaryValue* listener_filter) {
   event->event_args->Clear();
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension, contents);
+      ExtensionTabUtil::GetScrubTabBehavior(extension, target_context,
+                                            contents);
   std::unique_ptr<base::DictionaryValue> tab_value =
       ExtensionTabUtil::CreateTabObject(contents, scrub_tab_behavior, extension)
           ->ToValue();
diff --git a/chrome/browser/extensions/api/tabs/windows_event_router.cc b/chrome/browser/extensions/api/tabs/windows_event_router.cc
index b33004e..c890c88 100644
--- a/chrome/browser/extensions/api/tabs/windows_event_router.cc
+++ b/chrome/browser/extensions/api/tabs/windows_event_router.cc
@@ -216,9 +216,13 @@
     return;
 
   std::unique_ptr<base::ListValue> args(new base::ListValue());
+  // Since we don't populate tab info here, the context type doesn't matter.
+  constexpr ExtensionTabUtil::PopulateTabBehavior populate_behavior =
+      ExtensionTabUtil::kDontPopulateTabs;
+  constexpr Feature::Context context_type = Feature::UNSPECIFIED_CONTEXT;
   args->Append(ExtensionTabUtil::CreateWindowValueForExtension(
-      *window_controller->GetBrowser(), nullptr,
-      ExtensionTabUtil::kDontPopulateTabs));
+      *window_controller->GetBrowser(), nullptr, populate_behavior,
+      context_type));
   DispatchEvent(events::WINDOWS_ON_CREATED, windows::OnCreated::kEventName,
                 window_controller, std::move(args));
 }
diff --git a/chrome/browser/extensions/extension_action.h b/chrome/browser/extensions/extension_action.h
index 44e146b..b98080a5 100644
--- a/chrome/browser/extensions/extension_action.h
+++ b/chrome/browser/extensions/extension_action.h
@@ -118,6 +118,10 @@
   void SetBadgeText(int tab_id, const std::string& text) {
     SetValue(&badge_text_, tab_id, text);
   }
+
+  // Clear this action's badge text on a specific tab.
+  void ClearBadgeText(int tab_id) { badge_text_.erase(tab_id); }
+
   // Get the badge text that has been set using SetBadgeText for a tab, or the
   // default if no badge text was set.
   std::string GetExplicitlySetBadgeText(int tab_id) const {
@@ -153,6 +157,8 @@
   int GetDNRActionCount(int tab_id) const {
     return GetValue(&dnr_action_count_, tab_id);
   }
+  // Clear this ExtensionAction's DNR matched action count for all tabs.
+  void ClearDNRActionCountForAllTabs() { dnr_action_count_.clear(); }
 
   // Get the badge text displayed for a tab, calculated based on both
   // |badge_text_| and |dnr_action_count_|. Returns in order of priority:
diff --git a/chrome/browser/extensions/extension_context_menu_browsertest.cc b/chrome/browser/extensions/extension_context_menu_browsertest.cc
index 6389e53..40a7627 100644
--- a/chrome/browser/extensions/extension_context_menu_browsertest.cc
+++ b/chrome/browser/extensions/extension_context_menu_browsertest.cc
@@ -726,9 +726,6 @@
 #endif
 
 IN_PROC_BROWSER_TEST_P(ExtensionContextMenuBrowserTest, MAYBE_IncognitoSplit) {
-  // TODO(crbug.com/939664): Not yet implemented.
-  if (GetParam() == ContextType::kServiceWorker)
-    return;
   ExtensionTestMessageListener created("created item regular", false);
   ExtensionTestMessageListener created_incognito("created item incognito",
                                                  false);
diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc
index 9c7060f..a92dcf7b 100644
--- a/chrome/browser/extensions/extension_tab_util.cc
+++ b/chrome/browser/extensions/extension_tab_util.cc
@@ -120,8 +120,13 @@
 
 ExtensionTabUtil::ScrubTabBehavior GetScrubTabBehaviorImpl(
     const Extension* extension,
+    Feature::Context context,
     const GURL& url,
     int tab_id) {
+  if (context == Feature::Context::WEBUI_CONTEXT) {
+    return ExtensionTabUtil::kDontScrubTab;
+  }
+
   bool has_permission = false;
 
   if (extension) {
@@ -314,7 +319,7 @@
 
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
       ExtensionTabUtil::GetScrubTabBehavior(
-          function->extension(),
+          function->extension(), function->source_context_type(),
           navigate_params.navigated_or_inserted_contents);
 
   // Return data about the newly created tab.
@@ -451,13 +456,14 @@
 
 std::unique_ptr<base::ListValue> ExtensionTabUtil::CreateTabList(
     const Browser* browser,
-    const Extension* extension) {
+    const Extension* extension,
+    Feature::Context context) {
   std::unique_ptr<base::ListValue> tab_list(new base::ListValue());
   TabStripModel* tab_strip = browser->tab_strip_model();
   for (int i = 0; i < tab_strip->count(); ++i) {
     WebContents* web_contents = tab_strip->GetWebContentsAt(i);
     ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-        ExtensionTabUtil::GetScrubTabBehavior(extension, web_contents);
+        ExtensionTabUtil::GetScrubTabBehavior(extension, context, web_contents);
     tab_list->Append(CreateTabObject(web_contents, scrub_tab_behavior,
                                      extension, tab_strip, i)
                          ->ToValue());
@@ -471,7 +477,8 @@
 ExtensionTabUtil::CreateWindowValueForExtension(
     const Browser& browser,
     const Extension* extension,
-    PopulateTabBehavior populate_tab_behavior) {
+    PopulateTabBehavior populate_tab_behavior,
+    Feature::Context context) {
   auto result = std::make_unique<base::DictionaryValue>();
 
   result->SetInteger(tabs_constants::kIdKey, browser.session_id().id());
@@ -510,7 +517,8 @@
   result->SetInteger(tabs_constants::kHeightKey, bounds.height());
 
   if (populate_tab_behavior == kPopulateTabs)
-    result->Set(tabs_constants::kTabsKey, CreateTabList(&browser, extension));
+    result->Set(tabs_constants::kTabsKey,
+                CreateTabList(&browser, extension, context));
 
   return result;
 }
@@ -550,16 +558,19 @@
 // static
 ExtensionTabUtil::ScrubTabBehavior ExtensionTabUtil::GetScrubTabBehavior(
     const Extension* extension,
+    Feature::Context context,
     content::WebContents* contents) {
-  return GetScrubTabBehaviorImpl(extension, contents->GetURL(),
+  return GetScrubTabBehaviorImpl(extension, context, contents->GetURL(),
                                  GetTabId(contents));
 }
 
 // static
 ExtensionTabUtil::ScrubTabBehavior ExtensionTabUtil::GetScrubTabBehavior(
     const Extension* extension,
+    Feature::Context context,
     const GURL& url) {
-  return GetScrubTabBehaviorImpl(extension, url, api::tabs::TAB_ID_NONE);
+  return GetScrubTabBehaviorImpl(extension, context, url,
+                                 api::tabs::TAB_ID_NONE);
 }
 
 // static
@@ -583,6 +594,7 @@
   }
 }
 
+// static
 bool ExtensionTabUtil::GetTabStripModel(const WebContents* web_contents,
                                         TabStripModel** tab_strip_model,
                                         int* tab_index) {
@@ -666,6 +678,30 @@
                     nullptr, contents, nullptr);
 }
 
+// static
+std::vector<content::WebContents*>
+ExtensionTabUtil::GetAllActiveWebContentsForContext(
+    content::BrowserContext* browser_context,
+    bool include_incognito) {
+  std::vector<content::WebContents*> active_contents;
+
+  Profile* profile = Profile::FromBrowserContext(browser_context);
+  Profile* incognito_profile =
+      include_incognito && profile->HasOffTheRecordProfile()
+          ? profile->GetOffTheRecordProfile()
+          : nullptr;
+  for (auto* target_browser : *BrowserList::GetInstance()) {
+    if (target_browser->profile() == profile ||
+        target_browser->profile() == incognito_profile) {
+      TabStripModel* target_tab_strip = target_browser->tab_strip_model();
+
+      active_contents.push_back(target_tab_strip->GetActiveWebContents());
+    }
+  }
+
+  return active_contents;
+}
+
 GURL ExtensionTabUtil::ResolvePossiblyRelativeURL(const std::string& url_string,
                                                   const Extension* extension) {
   GURL url = GURL(url_string);
diff --git a/chrome/browser/extensions/extension_tab_util.h b/chrome/browser/extensions/extension_tab_util.h
index 8894512..be96c5ea 100644
--- a/chrome/browser/extensions/extension_tab_util.h
+++ b/chrome/browser/extensions/extension_tab_util.h
@@ -7,9 +7,11 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/callback.h"
 #include "chrome/common/extensions/api/tabs.h"
+#include "extensions/common/features/feature.h"
 #include "ui/base/window_open_disposition.h"
 
 class Browser;
@@ -87,7 +89,8 @@
   static int GetWindowIdOfTab(const content::WebContents* web_contents);
   static std::unique_ptr<base::ListValue> CreateTabList(
       const Browser* browser,
-      const Extension* extension);
+      const Extension* extension,
+      Feature::Context context);
 
   static Browser* GetBrowserFromWindowID(
       const ChromeExtensionFunctionDetails& details,
@@ -121,12 +124,14 @@
   // Creates a DictionaryValue representing the window for the given |browser|,
   // and scrubs any privacy-sensitive data that |extension| does not have
   // access to. |populate_tab_behavior| determines whether tabs will be
-  // populated in the result.
+  // populated in the result. |context| is used to determine the
+  // ScrubTabBehavior for the populated tabs data.
   // TODO(devlin): Convert this to a api::Windows::Window object.
   static std::unique_ptr<base::DictionaryValue> CreateWindowValueForExtension(
       const Browser& browser,
       const Extension* extension,
-      PopulateTabBehavior populate_tab_behavior);
+      PopulateTabBehavior populate_tab_behavior,
+      Feature::Context context);
 
   // Creates a tab MutedInfo object (see chrome/common/extensions/api/tabs.json)
   // with information about the mute state of a browser tab.
@@ -141,10 +146,12 @@
   // extension and web contents. This is the preferred way to get
   // ScrubTabBehavior.
   static ScrubTabBehavior GetScrubTabBehavior(const Extension* extension,
+                                              Feature::Context context,
                                               content::WebContents* contents);
   // Only use this if there is no access to a specific WebContents, such as when
   // the tab has been closed and there is no active WebContents anymore.
   static ScrubTabBehavior GetScrubTabBehavior(const Extension* extension,
+                                              Feature::Context context,
                                               const GURL& url);
 
   // Removes any privacy-sensitive fields from a Tab object if appropriate,
@@ -166,7 +173,7 @@
   // be NULL and will not be set within the function.
   static bool GetTabById(int tab_id,
                          content::BrowserContext* browser_context,
-                         bool incognito_enabled,
+                         bool include_incognito,
                          Browser** browser,
                          TabStripModel** tab_strip,
                          content::WebContents** contents,
@@ -175,6 +182,10 @@
                          content::BrowserContext* browser_context,
                          bool include_incognito,
                          content::WebContents** contents);
+  // Returns all active web contents for the given |browser_context|.
+  static std::vector<content::WebContents*> GetAllActiveWebContentsForContext(
+      content::BrowserContext* browser_context,
+      bool include_incognito);
 
   // Takes |url_string| and returns a GURL which is either valid and absolute
   // or invalid. If |url_string| is not directly interpretable as a valid (it is
diff --git a/chrome/browser/extensions/extension_tab_util_unittest.cc b/chrome/browser/extensions/extension_tab_util_unittest.cc
index 4a15687..fd2ede39 100644
--- a/chrome/browser/extensions/extension_tab_util_unittest.cc
+++ b/chrome/browser/extensions/extension_tab_util_unittest.cc
@@ -42,8 +42,9 @@
   auto extension = ExtensionBuilder("test").AddPermission("tabs").Build();
 
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension.get(),
-                                            GURL("http://www.google.com"));
+      ExtensionTabUtil::GetScrubTabBehavior(
+          extension.get(), Feature::Context::UNSPECIFIED_CONTEXT,
+          GURL("http://www.google.com"));
   EXPECT_EQ(ExtensionTabUtil::kScrubTabUrlToOrigin, scrub_tab_behavior);
 
   // Unset the delegate.
@@ -55,16 +56,18 @@
                        .AddPermission("tabs")
                        .Build();
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension.get(),
-                                            GURL("http://www.google.com"));
+      ExtensionTabUtil::GetScrubTabBehavior(
+          extension.get(), Feature::Context::UNSPECIFIED_CONTEXT,
+          GURL("http://www.google.com"));
   EXPECT_EQ(ExtensionTabUtil::kDontScrubTab, scrub_tab_behavior);
 }
 
 TEST(ExtensionTabUtilTest, ScrubTabBehaviorForNoPermission) {
   auto extension = ExtensionBuilder("Extension with no permissions").Build();
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(extension.get(),
-                                            GURL("http://www.google.com"));
+      ExtensionTabUtil::GetScrubTabBehavior(
+          extension.get(), Feature::Context::UNSPECIFIED_CONTEXT,
+          GURL("http://www.google.com"));
   EXPECT_EQ(ExtensionTabUtil::kScrubTabFully, scrub_tab_behavior);
 }
 
@@ -74,15 +77,25 @@
                        .Build();
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
       ExtensionTabUtil::GetScrubTabBehavior(
-          extension.get(), GURL("http://www.google.com/some/path"));
+          extension.get(), Feature::Context::UNSPECIFIED_CONTEXT,
+          GURL("http://www.google.com/some/path"));
   EXPECT_EQ(ExtensionTabUtil::kDontScrubTab, scrub_tab_behavior);
 }
 
 TEST(ExtensionTabUtilTest, ScrubTabBehaviorForNoExtension) {
   ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
-      ExtensionTabUtil::GetScrubTabBehavior(nullptr,
-                                            GURL("http://www.google.com"));
+      ExtensionTabUtil::GetScrubTabBehavior(
+          nullptr, Feature::Context::UNSPECIFIED_CONTEXT,
+          GURL("http://www.google.com"));
   EXPECT_EQ(ExtensionTabUtil::kScrubTabFully, scrub_tab_behavior);
 }
 
+TEST(ExtensionTabUtilTest, ScrubTabBehaviorForWebUI) {
+  ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
+      ExtensionTabUtil::GetScrubTabBehavior(nullptr,
+                                            Feature::Context::WEBUI_CONTEXT,
+                                            GURL("http://www.google.com"));
+  EXPECT_EQ(ExtensionTabUtil::kDontScrubTab, scrub_tab_behavior);
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 126e32f..59b7ccc 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -1632,6 +1632,193 @@
   EXPECT_TRUE(finished_listener.WaitUntilSatisfied());
 }
 
+namespace {
+
+constexpr char kIncognitoManifest[] =
+    R"({
+          "name": "Incognito Test Extension",
+          "version": "0.1",
+          "manifest_version": 2,
+          "permissions": ["tabs"],
+          "background": {"service_worker": "worker.js"},
+          "incognito": "%s"
+        })";
+
+constexpr char kQueryWorkerScript[] =
+    R"(var inIncognitoContext = chrome.extension.inIncognitoContext;
+       var incognitoStr =
+           inIncognitoContext ? 'incognito' : 'regular';
+       chrome.test.sendMessage('Script started ' + incognitoStr, function() {
+         chrome.tabs.query({}, function(tabs) {
+           let urls = tabs.map(tab => tab.url);
+           chrome.test.sendMessage(JSON.stringify(urls));
+         });
+       });)";
+
+constexpr char kTabsOnUpdatedScript[] =
+    R"(var inIncognitoContext = chrome.extension.inIncognitoContext;
+       var incognitoStr =
+           inIncognitoContext ? 'incognito' : 'regular';
+       var urls = [];
+
+       chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+         if (changeInfo.status === 'complete') {
+           urls.push(tab.url);
+         }
+       });
+
+       chrome.test.sendMessage('Script started ' + incognitoStr, function() {
+           chrome.test.sendMessage(JSON.stringify(urls));
+       });)";
+
+}  // anonymous namespace
+
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, TabsQuerySplit) {
+  ExtensionTestMessageListener ready_regular("Script started regular", true);
+  ExtensionTestMessageListener ready_incognito("Script started incognito",
+                                               true);
+  // Open an incognito window.
+  Browser* browser_incognito =
+      OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
+  ASSERT_TRUE(browser_incognito);
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(base::StringPrintf(kIncognitoManifest, "split"));
+  test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), kQueryWorkerScript);
+
+  const Extension* extension = LoadExtensionIncognito(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  // Wait for the extension's service workers to be ready.
+  ASSERT_TRUE(ready_regular.WaitUntilSatisfied());
+  ASSERT_TRUE(ready_incognito.WaitUntilSatisfied());
+
+  // Load a new tab in both browsers.
+  ui_test_utils::NavigateToURL(browser(), GURL("chrome:version"));
+  ui_test_utils::NavigateToURL(browser_incognito, GURL("chrome:about"));
+
+  {
+    ExtensionTestMessageListener tabs_listener(false);
+    // The extension waits for the reply to the "ready" sendMessage call
+    // and replies with the URLs of the tabs.
+    ready_regular.Reply("");
+    EXPECT_TRUE(tabs_listener.WaitUntilSatisfied());
+    EXPECT_EQ(R"(["chrome://version/"])", tabs_listener.message());
+  }
+  {
+    ExtensionTestMessageListener tabs_listener(false);
+    // Reply to the original message and wait for the return message.
+    ready_incognito.Reply("");
+    EXPECT_TRUE(tabs_listener.WaitUntilSatisfied());
+    EXPECT_EQ(R"(["chrome://about/"])", tabs_listener.message());
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, TabsQuerySpanning) {
+  ExtensionTestMessageListener ready_listener("Script started regular", true);
+
+  // Open an incognito window.
+  Browser* browser_incognito =
+      OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
+  ASSERT_TRUE(browser_incognito);
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(base::StringPrintf(kIncognitoManifest, "spanning"));
+  test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), kQueryWorkerScript);
+
+  const Extension* extension = LoadExtensionIncognito(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  // Wait for the extension's service worker to be ready.
+  ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
+
+  // Load a new tab in both browsers.
+  ui_test_utils::NavigateToURL(browser(), GURL("chrome:version"));
+  ui_test_utils::NavigateToURL(browser_incognito, GURL("chrome:about"));
+
+  ExtensionTestMessageListener tabs_listener(false);
+  // The extension waits for the reply to the "ready" sendMessage call
+  // and replies with the URLs of the tabs.
+  ready_listener.Reply("");
+  EXPECT_TRUE(tabs_listener.WaitUntilSatisfied());
+  EXPECT_EQ(R"(["chrome://version/","chrome://about/"])",
+            tabs_listener.message());
+}
+
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, TabsOnUpdatedSplit) {
+  ExtensionTestMessageListener ready_regular("Script started regular", true);
+  ExtensionTestMessageListener ready_incognito("Script started incognito",
+                                               true);
+  // Open an incognito window.
+  Browser* browser_incognito =
+      OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
+  ASSERT_TRUE(browser_incognito);
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(base::StringPrintf(kIncognitoManifest, "split"));
+  test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), kTabsOnUpdatedScript);
+
+  const Extension* extension = LoadExtensionIncognito(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  // Wait for the extension's service workers to be ready.
+  ASSERT_TRUE(ready_regular.WaitUntilSatisfied());
+  ASSERT_TRUE(ready_incognito.WaitUntilSatisfied());
+
+  // Load a new tab in both browsers.
+  ui_test_utils::NavigateToURL(browser(), GURL("chrome:version"));
+  ui_test_utils::NavigateToURL(browser_incognito, GURL("chrome:about"));
+
+  {
+    ExtensionTestMessageListener tabs_listener(false);
+    // The extension waits for the reply to the "ready" sendMessage call
+    // and replies with the URLs of the tabs.
+    ready_regular.Reply("");
+    EXPECT_TRUE(tabs_listener.WaitUntilSatisfied());
+    EXPECT_EQ(R"(["chrome://version/"])", tabs_listener.message());
+  }
+  {
+    ExtensionTestMessageListener tabs_listener(false);
+    // The extension waits for the reply to the "ready" sendMessage call
+    // and replies with the URLs of the tabs.
+    ready_incognito.Reply("");
+    EXPECT_TRUE(tabs_listener.WaitUntilSatisfied());
+    EXPECT_EQ(R"(["chrome://about/"])", tabs_listener.message());
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
+                       TabsOnUpdatedSpanning) {
+  ExtensionTestMessageListener ready_listener("Script started regular", true);
+
+  // Open an incognito window.
+  Browser* browser_incognito =
+      OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
+  ASSERT_TRUE(browser_incognito);
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(base::StringPrintf(kIncognitoManifest, "spanning"));
+  test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), kTabsOnUpdatedScript);
+
+  const Extension* extension = LoadExtensionIncognito(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  // Wait for the extension's service worker to be ready.
+  ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
+
+  // Load a new tab in both browsers.
+  ui_test_utils::NavigateToURL(browser(), GURL("chrome:version"));
+  ui_test_utils::NavigateToURL(browser_incognito, GURL("chrome:about"));
+
+  ExtensionTestMessageListener tabs_listener(false);
+  // The extension waits for the reply to the "ready" sendMessage call
+  // and replies with the URLs of the tabs.
+  ready_listener.Reply("");
+  EXPECT_TRUE(tabs_listener.WaitUntilSatisfied());
+  EXPECT_EQ(R"(["chrome://version/","chrome://about/"])",
+            tabs_listener.message());
+}
+
 // Tests the restriction on registering service worker scripts at root scope.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
                        ServiceWorkerScriptRootScope) {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index f2688120..3aa3e4e 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1377,11 +1377,6 @@
     "expiry_milestone": 75
   },
   {
-    "name": "enable-native-google-assistant",
-    "owners": [ "croissant-eng" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-native-notifications",
     "owners": [ "peter", "finnur" ],
     "expiry_milestone": 76
@@ -2385,6 +2380,11 @@
     "expiry_milestone": 75
   },
   {
+    "name": "new-overview-tablet-layout",
+    "owners": [ "sammiequon" ],
+    "expiry_milestone": 85
+  },
+  {
     "name": "new-password-form-parsing",
     "owners": [ "dvadym" ],
     "expiry_milestone": 76
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ec4b120e..efdfb03 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3258,11 +3258,6 @@
     "Enable a D-Bus service for accessing gesture properties, which are used "
     "to configure input devices.";
 
-const char kEnableGoogleAssistantName[] = "Enable Google Assistant";
-const char kEnableGoogleAssistantDescription[] =
-    "Enable an experimental Assistant implementation that will work on all "
-    "Chromebooks.";
-
 const char kEnableGoogleAssistantDspName[] =
     "Enable Google Assistant with hardware-based hotword";
 const char kEnableGoogleAssistantDspDescription[] =
@@ -3419,6 +3414,12 @@
     "If enabled, notification is displayed when device is connected to a "
     "network behind captive portal.";
 
+const char kNewOverviewTabletLayoutName[] = "New overview tablet mode layout";
+const char kNewOverviewTabletLayoutDescription[] =
+    "If enabled, in tablet mode, overview items will be laid out in two rows "
+    "with six items showing at a time. Users can scroll through to see hidden "
+    "items.";
+
 const char kNewZipUnpackerName[] = "ZIP Archiver (unpacking)";
 const char kNewZipUnpackerDescription[] =
     "Use the ZIP Archiver for mounting/unpacking ZIP files";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 121c498..68a2c7f 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1949,9 +1949,6 @@
 extern const char kEnableGesturePropertiesDBusServiceName[];
 extern const char kEnableGesturePropertiesDBusServiceDescription[];
 
-extern const char kEnableGoogleAssistantName[];
-extern const char kEnableGoogleAssistantDescription[];
-
 extern const char kEnableGoogleAssistantDspName[];
 extern const char kEnableGoogleAssistantDspDescription[];
 
@@ -2048,6 +2045,9 @@
 extern const char kNetworkPortalNotificationName[];
 extern const char kNetworkPortalNotificationDescription[];
 
+extern const char kNewOverviewTabletLayoutName[];
+extern const char kNewOverviewTabletLayoutDescription[];
+
 extern const char kNewZipUnpackerName[];
 extern const char kNewZipUnpackerDescription[];
 
diff --git a/chrome/browser/net/network_context_configuration_browsertest.cc b/chrome/browser/net/network_context_configuration_browsertest.cc
index 6e1b81ef..f898ac4 100644
--- a/chrome/browser/net/network_context_configuration_browsertest.cc
+++ b/chrome/browser/net/network_context_configuration_browsertest.cc
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <atomic>
 #include <string>
+#include <unordered_set>
 #include <vector>
 
 #include "base/bind.h"
@@ -636,6 +638,10 @@
            content::IsInProcessNetworkService();
   }
 
+  void UpdateChromePolicy(const policy::PolicyMap& policy_map) {
+    provider_.UpdateChromePolicy(policy_map);
+  }
+
   enum class WayToEnableSSLConfig { kViaPrefs, kViaPolicy };
 
   // This helper function enables the kSSLVersionMin pref and tests that this
@@ -676,13 +682,11 @@
                  policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_CLOUD,
                  std::make_unique<base::Value>(switches::kSSLVersionTLSv11),
                  nullptr);
-
       base::RunLoop run_loop;
       PrefChangeRegistrar pref_change_registrar;
       pref_change_registrar.Init(g_browser_process->local_state());
       pref_change_registrar.Add(prefs::kSSLVersionMin, run_loop.QuitClosure());
-      provider_.UpdateChromePolicy(values);
-
+      UpdateChromePolicy(values);
       run_loop.Run();
     }
 
@@ -1797,6 +1801,153 @@
   }
 };
 
+class NetworkContextConfigurationProxySettingsBrowserTest
+    : public NetworkContextConfigurationHttpPacBrowserTest {
+ public:
+  const size_t kDefaultMaxConnectionsPerProxy = 32;
+
+  NetworkContextConfigurationProxySettingsBrowserTest() = default;
+  ~NetworkContextConfigurationProxySettingsBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+        &NetworkContextConfigurationProxySettingsBrowserTest::TrackConnections,
+        base::Unretained(this)));
+
+    expected_connections_loop_ptr_.store(nullptr);
+
+    NetworkContextConfigurationHttpPacBrowserTest::SetUpOnMainThread();
+  }
+
+  virtual size_t GetExpectedMaxConnectionsPerProxy() const {
+    return kDefaultMaxConnectionsPerProxy;
+  }
+
+  std::unique_ptr<net::test_server::HttpResponse> TrackConnections(
+      const net::test_server::HttpRequest& request) {
+    if (!base::StartsWith(request.relative_url, "/hung",
+                          base::CompareCase::INSENSITIVE_ASCII))
+      return nullptr;
+
+    // Record the number of connections we're seeing.
+    CHECK(observed_request_urls_.find(request.GetURL().spec()) ==
+          observed_request_urls_.end());
+    observed_request_urls_.emplace(request.GetURL().spec());
+    CHECK_GE(GetExpectedMaxConnectionsPerProxy(),
+             observed_request_urls_.size());
+
+    // Once we've seen at least as many connections as we expect, we can quit
+    // the loop on the main test thread. The test may choose to wait for
+    // longer to see if there are any additional unexpected connections.
+    if (GetExpectedMaxConnectionsPerProxy() == observed_request_urls_.size() &&
+        expected_connections_loop_ptr_.load() != nullptr) {
+      expected_connections_loop_ptr_.load()->Quit();
+    }
+
+    // To test the number of connections per proxy, we'll hang all responses.
+    return std::make_unique<net::test_server::HungResponse>();
+  }
+
+  void RunMaxConnectionsPerProxyTest() {
+    // At this point in the test, we've set up a proxy that points to our
+    // embedded test server. We've also set things up to hang all incoming
+    // requests and record how many concurrent connections we have running. To
+    // detect the maximum number of requests per proxy, we now just have to
+    // attempt to make as many requests as possible to ensure we don't see an
+    // incorrect number of concurrent requests.
+
+    // First of all, we're going to want to wait for at least as many
+    // connections as we expect.
+    base::RunLoop expected_connections_run_loop;
+    expected_connections_loop_ptr_.store(&expected_connections_run_loop);
+
+    std::vector<std::unique_ptr<network::SimpleURLLoader>> loaders(
+        GetExpectedMaxConnectionsPerProxy());
+    for (unsigned int i = 0; i < GetExpectedMaxConnectionsPerProxy() + 1; ++i) {
+      std::unique_ptr<network::ResourceRequest> request =
+          std::make_unique<network::ResourceRequest>();
+      request->url =
+          embedded_test_server()->GetURL(base::StringPrintf("foo%u.test", i),
+                                         base::StringPrintf("/hung_%u", i));
+
+      content::SimpleURLLoaderTestHelper simple_loader_helper;
+      std::unique_ptr<network::SimpleURLLoader> simple_loader =
+          network::SimpleURLLoader::Create(std::move(request),
+                                           TRAFFIC_ANNOTATION_FOR_TESTS);
+
+      simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+          loader_factory(), simple_loader_helper.GetCallback());
+      loaders.emplace_back(std::move(simple_loader));
+    }
+    expected_connections_run_loop.Run();
+
+    // Then wait for any remaining connections that we should NOT get.
+    base::RunLoop unexpected_connections_run_loop;
+    base::RunLoop::ScopedRunTimeoutForTest run_timeout(
+        base::TimeDelta::FromMilliseconds(100),
+        base::BindLambdaForTesting(
+            [&]() { unexpected_connections_run_loop.Quit(); }));
+    unexpected_connections_run_loop.Run();
+
+    // Stop the server.
+    ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+  }
+
+ private:
+  std::atomic<base::RunLoop*> expected_connections_loop_ptr_{nullptr};
+
+  // In RunMaxConnectionsPerProxyTest(), we'll make several network requests
+  // that hang. These hung requests are assumed to last for the duration of the
+  // test. This member, which is only accessed from the server's IO thread,
+  // records each observed request to ensure we see only as many connections as
+  // we expect.
+  std::unordered_set<std::string> observed_request_urls_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkContextConfigurationProxySettingsBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(NetworkContextConfigurationProxySettingsBrowserTest,
+                       MaxConnectionsPerProxy) {
+  RunMaxConnectionsPerProxyTest();
+}
+
+class NetworkContextConfigurationManagedProxySettingsBrowserTest
+    : public NetworkContextConfigurationProxySettingsBrowserTest {
+ public:
+  const size_t kTestMaxConnectionsPerProxy = 37;
+
+  NetworkContextConfigurationManagedProxySettingsBrowserTest() = default;
+  ~NetworkContextConfigurationManagedProxySettingsBrowserTest() override =
+      default;
+
+  void SetUpInProcessBrowserTestFixture() override {
+    NetworkContextConfigurationProxySettingsBrowserTest::
+        SetUpInProcessBrowserTestFixture();
+    policy::PolicyMap policies;
+    policies.Set(policy::key::kMaxConnectionsPerProxy,
+                 policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
+                 policy::POLICY_SOURCE_CLOUD,
+                 std::make_unique<base::Value>(
+                     static_cast<int>(kTestMaxConnectionsPerProxy)),
+                 /*external_data_fetcher=*/nullptr);
+    UpdateChromePolicy(policies);
+  }
+
+  size_t GetExpectedMaxConnectionsPerProxy() const override {
+    return kTestMaxConnectionsPerProxy;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(
+      NetworkContextConfigurationManagedProxySettingsBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_P(
+    NetworkContextConfigurationManagedProxySettingsBrowserTest,
+    MaxConnectionsPerProxy) {
+  RunMaxConnectionsPerProxyTest();
+}
+
 // Instantiates tests with a prefix indicating which NetworkContext is being
 // tested, and a suffix of "/0" if the network service is disabled, "/1" if it's
 // enabled, and "/2" if it's enabled and restarted.
@@ -1855,5 +2006,9 @@
     NetworkContextConfigurationFtpPacBrowserTest);
 INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
     NetworkContextConfigurationHttpsStrippingPacBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+    NetworkContextConfigurationProxySettingsBrowserTest);
+INSTANTIATE_TEST_CASES_FOR_TEST_FIXTURE(
+    NetworkContextConfigurationManagedProxySettingsBrowserTest);
 
 }  // namespace
diff --git a/chrome/browser/net/system_network_context_manager_browsertest.cc b/chrome/browser/net/system_network_context_manager_browsertest.cc
index 8d78516..f5b5805 100644
--- a/chrome/browser/net/system_network_context_manager_browsertest.cc
+++ b/chrome/browser/net/system_network_context_manager_browsertest.cc
@@ -4,14 +4,11 @@
 
 #include "chrome/browser/net/system_network_context_manager.h"
 
-#include <memory>
 #include <string>
 #include <vector>
 
-#include "base/deferred_sequenced_task_runner.h"
 #include "base/feature_list.h"
 #include "base/optional.h"
-#include "base/test/bind_test_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "build/build_config.h"
@@ -21,23 +18,12 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
-#include "components/policy/core/browser/browser_policy_connector.h"
-#include "components/policy/core/common/mock_configuration_policy_provider.h"
-#include "components/policy/core/common/policy_map.h"
-#include "components/policy/core/common/policy_types.h"
-#include "components/policy/policy_constants.h"
 #include "components/prefs/pref_service.h"
 #include "components/version_info/version_info.h"
-#include "content/public/browser/network_service_instance.h"
-#include "content/public/browser/system_connector.h"
 #include "content/public/common/content_switches.h"
-#include "content/public/common/network_service_util.h"
-#include "content/public/common/service_names.mojom.h"
 #include "content/public/common/user_agent.h"
-#include "net/socket/client_socket_pool_manager.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/network_service.mojom.h"
-#include "services/network/public/mojom/network_service_test.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
@@ -273,77 +259,6 @@
 #endif  // defined(OS_CHROMEOS)
 }
 
-IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerBrowsertest,
-                       DefaultMaxConnectionsPerProxy) {
-  int max_connections = 0;
-  if (content::IsInProcessNetworkService()) {
-    base::RunLoop run_loop;
-    content::GetNetworkTaskRunner()->PostTask(
-        FROM_HERE, base::BindLambdaForTesting([&run_loop, &max_connections] {
-          max_connections =
-              net::ClientSocketPoolManager::max_sockets_per_proxy_server(
-                  net::HttpNetworkSession::NORMAL_SOCKET_POOL);
-          run_loop.Quit();
-        }));
-    run_loop.Run();
-  } else {
-    network::mojom::NetworkServiceTestPtr network_service_test;
-    content::GetSystemConnector()->BindInterface(
-        content::mojom::kNetworkServiceName, &network_service_test);
-
-    mojo::ScopedAllowSyncCallForTesting allow_sync_call;
-
-    bool available =
-        network_service_test->GetMaxConnectionsPerProxy(&max_connections);
-    EXPECT_TRUE(available);
-  }
-  EXPECT_EQ(net::DefaultMaxValues::kDefaultMaxSocketsPerProxyServer,
-            max_connections);
-}
-
-class SystemNetworkContextManagerMaxConnectionsPerProxyBrowsertest
-    : public SystemNetworkContextManagerBrowsertest {
- public:
-  const int kTestMaxConnectionsPerProxy = 42;
-
-  SystemNetworkContextManagerMaxConnectionsPerProxyBrowsertest() = default;
-  ~SystemNetworkContextManagerMaxConnectionsPerProxyBrowsertest() override =
-      default;
-
-  void SetUpInProcessBrowserTestFixture() override {
-    EXPECT_CALL(provider_, IsInitializationComplete(testing::_))
-        .WillRepeatedly(testing::Return(true));
-    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
-    policy::PolicyMap policies;
-    policies.Set(policy::key::kMaxConnectionsPerProxy,
-                 policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD,
-                 std::make_unique<base::Value>(kTestMaxConnectionsPerProxy),
-                 /*external_data_fetcher=*/nullptr);
-    provider_.UpdateChromePolicy(policies);
-  }
-
- private:
-  policy::MockConfigurationPolicyProvider provider_;
-};
-
-// Flaky on Linux Tests. https://crbug.com/993059
-IN_PROC_BROWSER_TEST_F(
-    SystemNetworkContextManagerMaxConnectionsPerProxyBrowsertest,
-    DISABLED_MaxConnectionsPerProxy) {
-  network::mojom::NetworkServiceTestPtr network_service_test;
-  content::GetSystemConnector()->BindInterface(
-      content::mojom::kNetworkServiceName, &network_service_test);
-
-  mojo::ScopedAllowSyncCallForTesting allow_sync_call;
-
-  int max_connections = 0;
-  bool available =
-      network_service_test->GetMaxConnectionsPerProxy(&max_connections);
-  EXPECT_TRUE(available);
-  EXPECT_EQ(kTestMaxConnectionsPerProxy, max_connections);
-}
-
 class SystemNetworkContextManagerStubResolverBrowsertest
     : public SystemNetworkContextManagerBrowsertest,
       public testing::WithParamInterface<bool> {
diff --git a/chrome/browser/notifications/proto/notification_data.proto b/chrome/browser/notifications/proto/notification_data.proto
index e15c0a6d4..3a57635 100644
--- a/chrome/browser/notifications/proto/notification_data.proto
+++ b/chrome/browser/notifications/proto/notification_data.proto
@@ -21,8 +21,15 @@
   UNHELPFUL = 2;
 }
 
+// Icon type.
+enum IconType {
+  UNKNOWN_ICON_TYPE = 0;
+  SMALL_ICON = 1;
+  LARGE_ICON = 2;
+}
+
 // Stores data used to display a notification in the UI.
-// Next tag: 7
+// Next tag: 6
 message NotificationData {
   // Represents the button on the notification.
   // Next tag: 4
@@ -32,6 +39,11 @@
     optional string id = 3;
   }
 
+  message IconUuidBundle {
+    optional IconType type = 1;
+    optional string uuid = 2;
+  }
+
   // Title of the notification.
   optional string title = 1;
 
@@ -44,11 +56,7 @@
   // A list of buttons on the notification.
   repeated Button buttons = 4;
 
-  // The unique identifier of the small icon on notification, which must be
-  // loaded asynchronously into memory.
-  optional string small_icon_uuid = 5;
-
-  // The unique identifier of the large icon on notification, which must be
-  // loaded asynchronously into memory.
-  optional string large_icon_uuid = 6;
+  // A bundle of icons type and uuid, which must be loaded asynchronously into
+  // memory.
+  repeated IconUuidBundle icons_uuid = 5;
 }
diff --git a/chrome/browser/notifications/scheduler/internal/BUILD.gn b/chrome/browser/notifications/scheduler/internal/BUILD.gn
index be96a48..af7f0c4 100644
--- a/chrome/browser/notifications/scheduler/internal/BUILD.gn
+++ b/chrome/browser/notifications/scheduler/internal/BUILD.gn
@@ -17,8 +17,6 @@
     "collection_store.h",
     "display_decider.cc",
     "display_decider.h",
-    "distribution_policy.cc",
-    "distribution_policy.h",
     "icon_converter.h",
     "icon_converter_result.cc",
     "icon_converter_result.h",
@@ -81,7 +79,6 @@
   sources = [
     "background_task_coordinator_unittest.cc",
     "display_decider_unittest.cc",
-    "distribution_policy_unittest.cc",
     "icon_converter_unittest.cc",
     "icon_store_unittest.cc",
     "impression_history_tracker_unittest.cc",
diff --git a/chrome/browser/notifications/scheduler/internal/background_task_coordinator_unittest.cc b/chrome/browser/notifications/scheduler/internal/background_task_coordinator_unittest.cc
index 4320a79..6a46320 100644
--- a/chrome/browser/notifications/scheduler/internal/background_task_coordinator_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/background_task_coordinator_unittest.cc
@@ -19,7 +19,7 @@
 #include "chrome/browser/notifications/scheduler/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using testing::_;
+using ::testing::_;
 
 namespace notifications {
 namespace {
@@ -27,6 +27,11 @@
 using Notifications = BackgroundTaskCoordinator::Notifications;
 using ClientStates = BackgroundTaskCoordinator::ClientStates;
 
+const char kNow[] = "04/25/1984 06:00:00 AM";
+const char kDeliverTimeWindowStart[] = "04/25/1984 08:00:00 AM";
+const char kDeliverTimeWindowEnd[] = "04/25/1984 08:50:00 AM";
+const char kTommorow[] = "04/26/1984 00:00:00 AM";
+
 const char kGuid[] = "1234";
 const std::vector<test::ImpressionTestData> kSingleClientImpressionTestData = {
     {SchedulerClientType::kTest1,
@@ -69,20 +74,29 @@
     background_task_ = background_task.get();
     coordinator_ = BackgroundTaskCoordinator::Create(std::move(background_task),
                                                      &config_, &clock_);
+    clock_.SetNow(kNow);
   }
 
   test::MockNotificationBackgroundTaskScheduler* background_task() {
     return background_task_;
   }
-
   SchedulerConfig* config() { return &config_; }
-
-  void SetNow(const char* now_str) { clock_.SetNow(now_str); }
+  test::FakeClock* clock() { return &clock_; }
 
   base::Time GetTime(const char* time_str) {
     return test::FakeClock::GetTime(time_str);
   }
 
+  NotificationEntry CreateNotification(SchedulerClientType type,
+                                       const std::string& guid,
+                                       const char* deliver_window_start,
+                                       const char* deliver_window_end) {
+    NotificationEntry entry(type, guid);
+    entry.schedule_params.deliver_time_start = GetTime(deliver_window_start);
+    entry.schedule_params.deliver_time_end = GetTime(deliver_window_end);
+    return entry;
+  }
+
   void ScheduleTask(const TestData& test_data) {
     test_data_ = test_data;
     test::AddImpressionTestData(test_data_.impression_test_data,
@@ -100,22 +114,6 @@
                                          std::move(client_states));
   }
 
-  void TestScheduleNewNotification(const char* now,
-                                   const char* expected_task_start_time) {
-    SetNow(now);
-    EXPECT_CALL(*background_task(), Cancel()).Times(0);
-    auto expected_window_start =
-        GetTime(expected_task_start_time) - GetTime(now);
-    EXPECT_CALL(*background_task(),
-                Schedule(_, expected_window_start,
-                         expected_window_start +
-                             config()->background_task_window_duration));
-
-    NotificationEntry entry(SchedulerClientType::kTest1, kGuid);
-    TestData test_data{kSingleClientImpressionTestData, {entry}};
-    ScheduleTask(test_data);
-  }
-
  private:
   base::test::TaskEnvironment task_environment_;
   test::FakeClock clock_;
@@ -138,5 +136,131 @@
   ScheduleTask(test_data);
 }
 
+// Test to schedule one notification.
+TEST_F(BackgroundTaskCoordinatorTest, OneNotification) {
+  TestData test_data;
+  test_data.impression_test_data = kSingleClientImpressionTestData;
+  test_data.notification_entries = {
+      CreateNotification(SchedulerClientType::kTest1, kGuid,
+                         kDeliverTimeWindowStart, kDeliverTimeWindowEnd)};
+  EXPECT_CALL(*background_task(),
+              Schedule(_, GetTime(kDeliverTimeWindowStart) - GetTime(kNow), _));
+  EXPECT_CALL(*background_task(), Cancel()).Times(0);
+  ScheduleTask(test_data);
+}
+
+// Verifies that the daily throttle for a particular notification type will
+// block notification to show.
+TEST_F(BackgroundTaskCoordinatorTest, ThrottlePerType) {
+  TestData test_data;
+  test_data.impression_test_data = kSingleClientImpressionTestData;
+  test_data.impression_test_data.front().current_max_daily_show = 0;
+  test_data.notification_entries = {
+      CreateNotification(SchedulerClientType::kTest1, kGuid,
+                         kDeliverTimeWindowStart, kDeliverTimeWindowEnd)};
+  EXPECT_CALL(*background_task(), Schedule(_, _, _)).Times(0);
+  EXPECT_CALL(*background_task(), Cancel()).Times(0);
+  ScheduleTask(test_data);
+}
+
+// Verifies that the daily throttle for all notification types will
+// block notification to show.
+TEST_F(BackgroundTaskCoordinatorTest, ThrottleAllType) {
+  TestData test_data;
+  test_data.impression_test_data = kSingleClientImpressionTestData;
+  test_data.impression_test_data.front().current_max_daily_show = 1;
+  config()->max_daily_shown_all_type = 0;
+  test_data.notification_entries = {
+      CreateNotification(SchedulerClientType::kTest1, kGuid,
+                         kDeliverTimeWindowStart, kDeliverTimeWindowEnd)};
+  EXPECT_CALL(*background_task(), Schedule(_, _, _)).Times(0);
+  EXPECT_CALL(*background_task(), Cancel()).Times(0);
+  ScheduleTask(test_data);
+}
+
+// Verifies that a notification scheduled to show after today will still trigger
+// a background task even if it is throttled today.
+TEST_F(BackgroundTaskCoordinatorTest, ThrottlePerTypeNextDay) {
+  TestData test_data;
+  test_data.impression_test_data = kSingleClientImpressionTestData;
+  test_data.impression_test_data.front().current_max_daily_show = 1;
+  Impression impression_today(SchedulerClientType::kTest1, "guid",
+                              clock()->Now() - base::TimeDelta::FromMinutes(5));
+  test_data.impression_test_data.front().impressions = {impression_today};
+  test_data.notification_entries = {
+      CreateNotification(SchedulerClientType::kTest1, kGuid,
+                         "04/25/1984 23:59:00 PM", "04/26/1984 08:00:00 AM")};
+  EXPECT_CALL(*background_task(),
+              Schedule(_, GetTime(kTommorow) - GetTime(kNow), _));
+  EXPECT_CALL(*background_task(), Cancel()).Times(0);
+  ScheduleTask(test_data);
+}
+
+// Verfies that notification with their deliver window expired will not trigger
+// a background task.
+TEST_F(BackgroundTaskCoordinatorTest, DeliverWindowPassed) {
+  TestData test_data;
+  test_data.impression_test_data = kSingleClientImpressionTestData;
+  test_data.notification_entries = {
+      CreateNotification(SchedulerClientType::kTest1, kGuid,
+                         "04/24/1984 23:59:00 PM", "04/24/1984 08:00:00 AM")};
+  EXPECT_CALL(*background_task(), Schedule(_, _, _)).Times(0);
+  EXPECT_CALL(*background_task(), Cancel()).Times(0);
+  ScheduleTask(test_data);
+}
+
+// Verfies that notification suppression will block the notification to be
+// shown.
+TEST_F(BackgroundTaskCoordinatorTest, Suppression) {
+  TestData test_data;
+  test_data.impression_test_data = kSingleClientImpressionTestData;
+  test_data.notification_entries = {
+      CreateNotification(SchedulerClientType::kTest1, kGuid,
+                         kDeliverTimeWindowStart, kDeliverTimeWindowEnd)};
+  test_data.impression_test_data.front().suppression_info =
+      SuppressionInfo(clock()->Now() - base::TimeDelta::FromHours(1),
+                      base::TimeDelta::FromDays(7));
+  EXPECT_CALL(*background_task(), Schedule(_, _, _)).Times(0);
+  EXPECT_CALL(*background_task(), Cancel()).Times(0);
+  ScheduleTask(test_data);
+}
+
+// Verfies that notification will trigger background task if its deliver time
+// window is after the suppression expiration time.
+TEST_F(BackgroundTaskCoordinatorTest, DeliverTimeAfterSuppressionExpired) {
+  TestData test_data;
+  test_data.impression_test_data = kSingleClientImpressionTestData;
+  test_data.notification_entries = {
+      CreateNotification(SchedulerClientType::kTest1, kGuid,
+                         "04/26/1984 05:00:00 AM", "04/26/1984 23:59:00 PM")};
+  // Suppression will expire at 04/26/1984 06:00:00 AM.
+  test_data.impression_test_data.front().suppression_info =
+      SuppressionInfo(clock()->Now() - base::TimeDelta::FromDays(1),
+                      base::TimeDelta::FromDays(2));
+  EXPECT_CALL(
+      *background_task(),
+      Schedule(_, GetTime("04/26/1984 06:00:00 AM") - GetTime(kNow), _));
+  EXPECT_CALL(*background_task(), Cancel()).Times(0);
+  ScheduleTask(test_data);
+}
+
+// Test to schedule multiple notifications from multiple clients.
+TEST_F(BackgroundTaskCoordinatorTest, MutipleNotifications) {
+  TestData test_data;
+  test_data.impression_test_data = kClientsImpressionTestData;
+  NotificationEntry entry0 =
+      CreateNotification(SchedulerClientType::kTest1, "guid0",
+                         kDeliverTimeWindowStart, kDeliverTimeWindowEnd);
+  NotificationEntry entry1 =
+      CreateNotification(SchedulerClientType::kTest2, "guid1",
+                         "04/27/1984 05:00:00 AM", "04/27/1984 23:59:00 PM");
+
+  test_data.notification_entries = {entry0, entry1};
+  EXPECT_CALL(*background_task(),
+              Schedule(_, GetTime(kDeliverTimeWindowStart) - GetTime(kNow), _));
+  EXPECT_CALL(*background_task(), Cancel()).Times(0);
+  ScheduleTask(test_data);
+}
+
 }  // namespace
 }  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/internal/display_decider_unittest.cc b/chrome/browser/notifications/scheduler/internal/display_decider_unittest.cc
index 8d2e5af7..09366fd 100644
--- a/chrome/browser/notifications/scheduler/internal/display_decider_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/display_decider_unittest.cc
@@ -11,7 +11,6 @@
 
 #include "base/strings/stringprintf.h"
 #include "base/test/task_environment.h"
-#include "chrome/browser/notifications/scheduler/internal/distribution_policy.h"
 #include "chrome/browser/notifications/scheduler/internal/notification_entry.h"
 #include "chrome/browser/notifications/scheduler/internal/scheduler_config.h"
 #include "chrome/browser/notifications/scheduler/public/notification_scheduler_types.h"
diff --git a/chrome/browser/notifications/scheduler/internal/distribution_policy.cc b/chrome/browser/notifications/scheduler/internal/distribution_policy.cc
deleted file mode 100644
index 7319635..0000000
--- a/chrome/browser/notifications/scheduler/internal/distribution_policy.cc
+++ /dev/null
@@ -1,46 +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 "chrome/browser/notifications/scheduler/internal/distribution_policy.h"
-
-#include "base/logging.h"
-
-namespace notifications {
-namespace {
-
-// Evenly distributes notifications to show in morning and evening. Morning
-// will have one more to show if the total quota is odd.
-class EvenDistributionHigherMorning : public DistributionPolicy {
- public:
-  EvenDistributionHigherMorning() = default;
-  ~EvenDistributionHigherMorning() override = default;
-
- private:
-  // DistributionPolicy implementation.
-  int MaxToShow(SchedulerTaskTime task_start_time, int quota) override {
-    DCHECK_GE(quota, 0);
-    switch (task_start_time) {
-      case SchedulerTaskTime::kUnknown:
-        NOTREACHED();
-        return quota;
-      case SchedulerTaskTime::kMorning:
-        return quota / 2 + quota % 2;
-      case SchedulerTaskTime::kEvening:
-        // The task running in the evening should flush all the remaining
-        // notifications.
-        return quota;
-    }
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(EvenDistributionHigherMorning);
-};
-
-}  // namespace
-
-// static
-std::unique_ptr<DistributionPolicy> DistributionPolicy::Create() {
-  return std::make_unique<EvenDistributionHigherMorning>();
-}
-
-}  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/internal/distribution_policy.h b/chrome/browser/notifications/scheduler/internal/distribution_policy.h
deleted file mode 100644
index a2b86905..0000000
--- a/chrome/browser/notifications/scheduler/internal/distribution_policy.h
+++ /dev/null
@@ -1,37 +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 CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_INTERNAL_DISTRIBUTION_POLICY_H_
-#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_INTERNAL_DISTRIBUTION_POLICY_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "chrome/browser/notifications/scheduler/public/notification_scheduler_types.h"
-
-namespace notifications {
-
-// Defines how to distribute notifications to show in different background tasks
-// in a day.
-// TODO(xingliu): Delete this.
-class DistributionPolicy {
- public:
-  // Creates the default distribution policy.
-  static std::unique_ptr<DistributionPolicy> Create();
-
-  DistributionPolicy() = default;
-  virtual ~DistributionPolicy() = default;
-
-  // Returns the maximum number of notifications to show in the background task
-  // starts at |task_start_time|. Suppose we at most can show |quota| number of
-  // new notifications during the current background task.
-  virtual int MaxToShow(SchedulerTaskTime task_start_time, int quota) = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DistributionPolicy);
-};
-
-}  // namespace notifications
-
-#endif  // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_INTERNAL_DISTRIBUTION_POLICY_H_
diff --git a/chrome/browser/notifications/scheduler/internal/distribution_policy_unittest.cc b/chrome/browser/notifications/scheduler/internal/distribution_policy_unittest.cc
deleted file mode 100644
index 0606e61b..0000000
--- a/chrome/browser/notifications/scheduler/internal/distribution_policy_unittest.cc
+++ /dev/null
@@ -1,42 +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 "chrome/browser/notifications/scheduler/internal/distribution_policy.h"
-
-#include "base/logging.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace notifications {
-namespace {
-
-int MaxToShow(DistributionPolicy* policy,
-              SchedulerTaskTime task_start_time,
-              int quota) {
-  DCHECK(policy);
-  return policy->MaxToShow(task_start_time, quota);
-}
-
-TEST(DistributionPolicyTest, EvenDistributionHigherMorning) {
-  auto policy = DistributionPolicy::Create();
-
-  EXPECT_EQ(MaxToShow(policy.get(), SchedulerTaskTime::kMorning, 5 /* quota */),
-            3);
-  EXPECT_EQ(MaxToShow(policy.get(), SchedulerTaskTime::kMorning, 4 /* quota */),
-            2);
-
-  // Evening task should flush all remaining quota.
-  EXPECT_EQ(MaxToShow(policy.get(), SchedulerTaskTime::kEvening, 5 /* quota */),
-            5);
-  EXPECT_EQ(MaxToShow(policy.get(), SchedulerTaskTime::kEvening, 4 /* quota */),
-            4);
-
-  // Test 0 quota.
-  EXPECT_EQ(MaxToShow(policy.get(), SchedulerTaskTime::kMorning, 0 /* quota */),
-            0);
-  EXPECT_EQ(MaxToShow(policy.get(), SchedulerTaskTime::kMorning, 0 /* quota */),
-            0);
-}
-
-}  // namespace
-}  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/internal/notification_entry.cc b/chrome/browser/notifications/scheduler/internal/notification_entry.cc
index 108595b..d33b804f 100644
--- a/chrome/browser/notifications/scheduler/internal/notification_entry.cc
+++ b/chrome/browser/notifications/scheduler/internal/notification_entry.cc
@@ -21,8 +21,7 @@
   return type == other.type && guid == other.guid &&
          create_time == other.create_time &&
          notification_data == other.notification_data &&
-         small_icon_uuid == other.small_icon_uuid &&
-         large_icon_uuid == other.large_icon_uuid &&
+         icons_uuid == other.icons_uuid &&
          schedule_params == other.schedule_params;
 }
 
diff --git a/chrome/browser/notifications/scheduler/internal/notification_entry.h b/chrome/browser/notifications/scheduler/internal/notification_entry.h
index 1b42d7a..26f75701 100644
--- a/chrome/browser/notifications/scheduler/internal/notification_entry.h
+++ b/chrome/browser/notifications/scheduler/internal/notification_entry.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_INTERNAL_NOTIFICATION_ENTRY_H_
 #define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_INTERNAL_NOTIFICATION_ENTRY_H_
 
+#include <map>
 #include <string>
 
 #include "base/time/time.h"
@@ -37,13 +38,9 @@
   // shown.
   NotificationData notification_data;
 
-  // The unique identifier of the small icon on notification, which must be
+  // The map of icons uuid on notification, which must be
   // loaded asynchronously into memory.
-  std::string small_icon_uuid;
-
-  // The unique identifier of the large icon on notification, which must be
-  // loaded asynchronously into memory.
-  std::string large_icon_uuid;
+  std::map<IconType, std::string> icons_uuid;
 
   // Scheduling details.
   ScheduleParams schedule_params;
diff --git a/chrome/browser/notifications/scheduler/internal/proto_conversion.cc b/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
index 4c6aa922..f0afb61a 100644
--- a/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
+++ b/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
@@ -171,6 +171,30 @@
   NOTREACHED();
 }
 
+proto::IconType ToIconType(IconType type) {
+  switch (type) {
+    case IconType::kUnknownType:
+      return proto::IconType::UNKNOWN_ICON_TYPE;
+    case IconType::kSmallIcon:
+      return proto::IconType::SMALL_ICON;
+    case IconType::kLargeIcon:
+      return proto::IconType::LARGE_ICON;
+  }
+  NOTREACHED();
+}
+
+IconType FromIconType(proto::IconType proto_type) {
+  switch (proto_type) {
+    case proto::IconType::UNKNOWN_ICON_TYPE:
+      return IconType::kUnknownType;
+    case proto::IconType::SMALL_ICON:
+      return IconType::kSmallIcon;
+    case proto::IconType::LARGE_ICON:
+      return IconType::kLargeIcon;
+  }
+  NOTREACHED();
+}
+
 proto::ActionButtonType ToActionButtonType(ActionButtonType type) {
   switch (type) {
     case ActionButtonType::kUnknownAction:
@@ -411,9 +435,13 @@
   proto->set_guid(entry->guid);
   proto->set_create_time(TimeToMilliseconds(entry->create_time));
   auto* proto_notification_data = proto->mutable_notification_data();
+  for (const auto& icon_type_uuid_pair : entry->icons_uuid) {
+    auto* proto_icons = proto_notification_data->add_icons_uuid();
+    proto_icons->set_type(ToIconType(icon_type_uuid_pair.first));
+    proto_icons->set_uuid(icon_type_uuid_pair.second);
+  }
   NotificationDataToProto(&entry->notification_data, proto_notification_data);
-  proto_notification_data->set_small_icon_uuid(entry->small_icon_uuid);
-  proto_notification_data->set_large_icon_uuid(entry->large_icon_uuid);
+
   auto* proto_schedule_params = proto->mutable_schedule_params();
   ScheduleParamsToProto(&entry->schedule_params, proto_schedule_params);
 }
@@ -425,10 +453,14 @@
   entry->create_time = MillisecondsToTime(proto->create_time());
   NotificationDataFromProto(proto->mutable_notification_data(),
                             &entry->notification_data);
-  entry->small_icon_uuid = proto->notification_data().small_icon_uuid();
-  entry->large_icon_uuid = proto->notification_data().large_icon_uuid();
   ScheduleParamsFromProto(proto->mutable_schedule_params(),
                           &entry->schedule_params);
+
+  for (int i = 0; i < proto->notification_data().icons_uuid_size(); i++) {
+    const auto& icon_uuid_pair = proto->notification_data().icons_uuid(i);
+    entry->icons_uuid.emplace(FromIconType(icon_uuid_pair.type()),
+                              icon_uuid_pair.uuid());
+  }
 }
 
 }  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc b/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
index da673e5..7183a6e 100644
--- a/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/proto_conversion_unittest.cc
@@ -186,8 +186,8 @@
   // Test notification data.
   entry.notification_data.title = base::UTF8ToUTF16("title");
   entry.notification_data.message = base::UTF8ToUTF16("message");
-  entry.small_icon_uuid = "small_icon_uuid";
-  entry.large_icon_uuid = "large_icon_uuid";
+  entry.icons_uuid.emplace(IconType::kSmallIcon, "small_icon_uuid");
+  entry.icons_uuid.emplace(IconType::kLargeIcon, "large_icon_uuid");
   entry.notification_data.custom_data = {{"url", "https://www.example.com"}};
   TestNotificationEntryConversion(&entry);
 
diff --git a/chrome/browser/notifications/scheduler/public/BUILD.gn b/chrome/browser/notifications/scheduler/public/BUILD.gn
index 6a5175eb..a08c8aa 100644
--- a/chrome/browser/notifications/scheduler/public/BUILD.gn
+++ b/chrome/browser/notifications/scheduler/public/BUILD.gn
@@ -12,6 +12,8 @@
     "display_agent.h",
     "features.cc",
     "features.h",
+    "icon_bundle.cc",
+    "icon_bundle.h",
     "impression_detail.cc",
     "impression_detail.h",
     "notification_background_task_scheduler.h",
diff --git a/chrome/browser/notifications/scheduler/public/icon_bundle.cc b/chrome/browser/notifications/scheduler/public/icon_bundle.cc
new file mode 100644
index 0000000..a99e78a
--- /dev/null
+++ b/chrome/browser/notifications/scheduler/public/icon_bundle.cc
@@ -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.
+
+#include "chrome/browser/notifications/scheduler/public/icon_bundle.h"
+
+namespace notifications {
+
+IconBundle::IconBundle() = default;
+IconBundle::~IconBundle() = default;
+
+}  // namespace notifications
diff --git a/chrome/browser/notifications/scheduler/public/icon_bundle.h b/chrome/browser/notifications/scheduler/public/icon_bundle.h
new file mode 100644
index 0000000..8fbf15e8
--- /dev/null
+++ b/chrome/browser/notifications/scheduler/public/icon_bundle.h
@@ -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.
+
+#ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PUBLIC_ICON_BUNDLE_H_
+#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PUBLIC_ICON_BUNDLE_H_
+
+#include "base/macros.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace notifications {
+
+// A wrapper of various format of icon and andriod id.
+struct IconBundle {
+  IconBundle();
+  ~IconBundle();
+
+  // The icon bitmap.
+  SkBitmap bitmap;
+
+  // TODO(hesen): Handle Android Id.
+};
+
+}  // namespace notifications
+
+#endif  // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PUBLIC_ICON_BUNDLE_H_
diff --git a/chrome/browser/notifications/scheduler/public/notification_data.cc b/chrome/browser/notifications/scheduler/public/notification_data.cc
index 5d573d7..d71479e 100644
--- a/chrome/browser/notifications/scheduler/public/notification_data.cc
+++ b/chrome/browser/notifications/scheduler/public/notification_data.cc
@@ -15,16 +15,14 @@
 
 NotificationData::Button::~Button() = default;
 
-NotificationData::Icon::Icon() = default;
-NotificationData::Icon::~Icon() = default;
-
 NotificationData::NotificationData() = default;
 
 NotificationData::NotificationData(const NotificationData& other) = default;
 
 bool NotificationData::operator==(const NotificationData& other) const {
   return title == other.title && message == other.message &&
-         custom_data == other.custom_data && buttons == other.buttons;
+         custom_data == other.custom_data && buttons == other.buttons &&
+         icons.size() == other.icons.size();
 }
 
 NotificationData::~NotificationData() = default;
diff --git a/chrome/browser/notifications/scheduler/public/notification_data.h b/chrome/browser/notifications/scheduler/public/notification_data.h
index f7cdd73..0b23509 100644
--- a/chrome/browser/notifications/scheduler/public/notification_data.h
+++ b/chrome/browser/notifications/scheduler/public/notification_data.h
@@ -10,8 +10,8 @@
 #include <vector>
 
 #include "base/strings/string16.h"
+#include "chrome/browser/notifications/scheduler/public/icon_bundle.h"
 #include "chrome/browser/notifications/scheduler/public/notification_scheduler_types.h"
-#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace notifications {
 
@@ -38,14 +38,6 @@
     std::string id;
   };
 
-  struct Icon {
-    Icon();
-    ~Icon();
-
-    // The icon bitmap.
-    SkBitmap bitmap;
-  };
-
   using CustomData = std::map<std::string, std::string>;
   NotificationData();
   NotificationData(const NotificationData& other);
@@ -58,11 +50,8 @@
   // The body text of the notification.
   base::string16 message;
 
-  // The small icon of the notification.
-  Icon small_icon;
-
-  // The large icon of the notification.
-  Icon large_icon;
+  // The icons of the notification.
+  std::map<IconType, IconBundle> icons;
 
   // Custom key value pair data associated with each notification. Will be sent
   // back after user interaction.
diff --git a/chrome/browser/notifications/scheduler/public/notification_scheduler_types.h b/chrome/browser/notifications/scheduler/public/notification_scheduler_types.h
index 49be11e..6d54cbb 100644
--- a/chrome/browser/notifications/scheduler/public/notification_scheduler_types.h
+++ b/chrome/browser/notifications/scheduler/public/notification_scheduler_types.h
@@ -143,6 +143,13 @@
   base::Optional<ButtonClickInfo> button_click_info;
 };
 
+// Categorizes type of notification icons.
+enum class IconType {
+  kUnknownType = 0,
+  kSmallIcon = 1,
+  kLargeIcon = 2,
+};
+
 }  // namespace notifications
 
 #endif  // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PUBLIC_NOTIFICATION_SCHEDULER_TYPES_H_
diff --git a/chrome/browser/notifications/scheduler/test/test_utils.cc b/chrome/browser/notifications/scheduler/test/test_utils.cc
index 4e1da96..0c9d3d0 100644
--- a/chrome/browser/notifications/scheduler/test/test_utils.cc
+++ b/chrome/browser/notifications/scheduler/test/test_utils.cc
@@ -7,7 +7,7 @@
 #include <sstream>
 #include <utility>
 
-#include "base/format_macros.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/notifications/scheduler/internal/notification_entry.h"
 #include "chrome/browser/notifications/scheduler/public/notification_data.h"
@@ -105,8 +105,12 @@
            << " : " << static_cast<int>(mapping.second);
   }
 
-  stream << " \n small_icons_id:" << entry->small_icon_uuid << "  ";
-  stream << " \n large_icons_id:" << entry->large_icon_uuid << "  ";
+  if (base::Contains(entry->icons_uuid, IconType::kSmallIcon))
+    stream << " \n small_icons_id:"
+           << entry->icons_uuid.at(IconType::kSmallIcon);
+  if (base::Contains(entry->icons_uuid, IconType::kLargeIcon))
+    stream << " \n large_icons_id:"
+           << entry->icons_uuid.at(IconType::kLargeIcon);
   return stream.str();
 }
 
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index a63478c..68b5c97 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -60,6 +60,7 @@
 #include "components/keep_alive_registry/keep_alive_types.h"
 #include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/prefs/pref_service.h"
+#include "components/sessions/content/content_test_helper.h"
 #include "components/sessions/core/serialized_navigation_entry.h"
 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
 #include "components/ukm/test_ukm_recorder.h"
@@ -1908,9 +1909,8 @@
   sessions::SessionTab tab;
   tab.tab_visual_index = 0;
   tab.current_navigation_index = 1;
-  tab.navigations.push_back(
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-          GetTestURL().spec(), "one"));
+  tab.navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+      GetTestURL().spec(), "one"));
   tab.navigations.back().set_encoded_page_state("");
 
   ASSERT_EQ(1, browser()->tab_strip_model()->count());
@@ -1973,9 +1973,8 @@
     tab1->tab_visual_index = 0;
     tab1->current_navigation_index = 0;
     tab1->pinned = true;
-    tab1->navigations.push_back(
-        sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-            GetTestURL().spec(), "one"));
+    tab1->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+        GetTestURL().spec(), "one"));
     tab1->navigations.back().set_encoded_page_state("");
     window.tabs.push_back(std::move(tab1));
   }
@@ -1985,9 +1984,8 @@
     tab2->tab_visual_index = 1;
     tab2->current_navigation_index = 0;
     tab2->pinned = false;
-    tab2->navigations.push_back(
-        sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-            GetTestURL2().spec(), "two"));
+    tab2->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+        GetTestURL2().spec(), "two"));
     tab2->navigations.back().set_encoded_page_state("");
     window.tabs.push_back(std::move(tab2));
   }
diff --git a/chrome/browser/permissions/permission_manager.cc b/chrome/browser/permissions/permission_manager.cc
index df2a79b4..91bac593 100644
--- a/chrome/browser/permissions/permission_manager.cc
+++ b/chrome/browser/permissions/permission_manager.cc
@@ -448,8 +448,8 @@
 
     auto response_callback =
         std::make_unique<PermissionResponseCallback>(this, request_id, i);
-    auto status = GetPermissionOverrideForDevTools(canonical_requesting_origin,
-                                                   permission);
+    auto status = GetPermissionOverrideForDevTools(
+        url::Origin::Create(canonical_requesting_origin), permission);
     if (status != CONTENT_SETTING_DEFAULT) {
       response_callback->OnPermissionsRequestResponseStatus(
           CONTENT_SETTING_ALLOW);
@@ -614,12 +614,13 @@
 
 bool PermissionManager::IsPermissionOverridableByDevTools(
     content::PermissionType permission,
-    const GURL& origin) {
+    const url::Origin& origin) {
   ContentSettingsType type = PermissionTypeToContentSettingSafe(permission);
   PermissionContextBase* context = GetPermissionContext(type);
 
   return context && !context->IsPermissionKillSwitchOn() &&
-         context->IsPermissionAvailableToOrigins(origin, origin);
+         context->IsPermissionAvailableToOrigins(origin.GetURL(),
+                                                 origin.GetURL());
 }
 
 int PermissionManager::SubscribePermissionStatusChange(
@@ -747,8 +748,8 @@
     const GURL& embedding_origin) {
   GURL canonical_requesting_origin =
       GetCanonicalOrigin(permission, requesting_origin, embedding_origin);
-  auto status =
-      GetPermissionOverrideForDevTools(canonical_requesting_origin, permission);
+  auto status = GetPermissionOverrideForDevTools(
+      url::Origin::Create(canonical_requesting_origin), permission);
   if (status != CONTENT_SETTING_DEFAULT)
     return PermissionResult(status, PermissionStatusSource::UNSPECIFIED);
   PermissionContextBase* context = GetPermissionContext(permission);
@@ -762,7 +763,7 @@
 }
 
 void PermissionManager::SetPermissionOverridesForDevTools(
-    const GURL& origin,
+    const url::Origin& origin,
     const PermissionOverrides& overrides) {
   ContentSettingsTypeOverrides result;
   for (const auto& item : overrides) {
@@ -771,8 +772,7 @@
     if (content_setting != CONTENT_SETTINGS_TYPE_DEFAULT)
       result[content_setting] = PermissionStatusToContentSetting(item.second);
   }
-  devtools_permission_overrides_[url::Origin::Create(origin)] =
-      std::move(result);
+  devtools_permission_overrides_[origin] = std::move(result);
 }
 
 void PermissionManager::ResetPermissionOverridesForDevTools() {
@@ -780,9 +780,9 @@
 }
 
 ContentSetting PermissionManager::GetPermissionOverrideForDevTools(
-    const GURL& origin,
+    const url::Origin& origin,
     ContentSettingsType permission) {
-  auto it = devtools_permission_overrides_.find(url::Origin::Create(origin));
+  auto it = devtools_permission_overrides_.find(origin);
   if (it == devtools_permission_overrides_.end())
     return CONTENT_SETTING_DEFAULT;
 
diff --git a/chrome/browser/permissions/permission_manager.h b/chrome/browser/permissions/permission_manager.h
index 3e2b116..38ee2ee 100644
--- a/chrome/browser/permissions/permission_manager.h
+++ b/chrome/browser/permissions/permission_manager.h
@@ -105,7 +105,7 @@
       content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin) override;
   bool IsPermissionOverridableByDevTools(content::PermissionType permission,
-                                         const GURL& origin) override;
+                                         const url::Origin& origin) override;
   int SubscribePermissionStatusChange(
       content::PermissionType permission,
       content::RenderFrameHost* render_frame_host,
@@ -122,7 +122,7 @@
   // For the given |origin|, overrides permissions that belong to |overrides|.
   // These permissions are in-sync with the PermissionController.
   void SetPermissionOverridesForDevTools(
-      const GURL& origin,
+      const url::Origin& origin,
       const PermissionOverrides& overrides) override;
   void ResetPermissionOverridesForDevTools() override;
 
@@ -166,7 +166,7 @@
       const GURL& embedding_origin);
 
   ContentSetting GetPermissionOverrideForDevTools(
-      const GURL& origin,
+      const url::Origin& origin,
       ContentSettingsType permission);
 
   Profile* profile_;
diff --git a/chrome/browser/permissions/permission_manager_unittest.cc b/chrome/browser/permissions/permission_manager_unittest.cc
index 6a7af6c47..d516e4d 100644
--- a/chrome/browser/permissions/permission_manager_unittest.cc
+++ b/chrome/browser/permissions/permission_manager_unittest.cc
@@ -592,8 +592,10 @@
 }
 
 TEST_F(PermissionManagerTest, InsecureOriginIsNotOverridable) {
-  const GURL kInsecureOrigin("http://example.com/geolocation");
-  const GURL kSecureOrigin("https://example.com/geolocation");
+  const url::Origin kInsecureOrigin =
+      url::Origin::Create(GURL("http://example.com/geolocation"));
+  const url::Origin kSecureOrigin =
+      url::Origin::Create(GURL("https://example.com/geolocation"));
   EXPECT_FALSE(
       GetPermissionControllerDelegate()->IsPermissionOverridableByDevTools(
           PermissionType::GEOLOCATION, kInsecureOrigin));
@@ -608,17 +610,19 @@
   EXPECT_FALSE(
       GetPermissionControllerDelegate()->IsPermissionOverridableByDevTools(
           PermissionType::PROTECTED_MEDIA_IDENTIFIER,
-          GURL("http://localhost")));
+          url::Origin::Create(GURL("http://localhost"))));
 #endif
   EXPECT_TRUE(
       GetPermissionControllerDelegate()->IsPermissionOverridableByDevTools(
-          PermissionType::MIDI_SYSEX, GURL("http://localhost")));
+          PermissionType::MIDI_SYSEX,
+          url::Origin::Create(GURL("http://localhost"))));
 }
 
 TEST_F(PermissionManagerTest, KillSwitchOnIsNotOverridable) {
+  const url::Origin kLocalHost = url::Origin::Create(GURL("http://localhost"));
   EXPECT_TRUE(
       GetPermissionControllerDelegate()->IsPermissionOverridableByDevTools(
-          PermissionType::GEOLOCATION, GURL("http://localhost")));
+          PermissionType::GEOLOCATION, kLocalHost));
 
   // Turn on kill switch for GEOLOCATION.
   base::FieldTrialList field_trial_list(
@@ -636,7 +640,7 @@
 
   EXPECT_FALSE(
       GetPermissionControllerDelegate()->IsPermissionOverridableByDevTools(
-          PermissionType::GEOLOCATION, GURL("http://localhost")));
+          PermissionType::GEOLOCATION, kLocalHost));
 
   // Clean-up.
   variations::testing::ClearAllVariationParams();
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index eea7edd..58b62bf5 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -204,7 +204,7 @@
         net::EmbeddedTestServer::TYPE_HTTPS);
     https_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
     https_server_->RegisterRequestHandler(base::BindRepeating(
-        &BasePreviewsLitePageServerBrowserTest::HandleRedirectRequest,
+        &BasePreviewsLitePageServerBrowserTest::HandleOriginRequest,
         base::Unretained(this)));
     ASSERT_TRUE(https_server_->Start());
 
@@ -237,7 +237,7 @@
         net::EmbeddedTestServer::TYPE_HTTP);
     http_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
     http_server_->RegisterRequestHandler(base::BindRepeating(
-        &BasePreviewsLitePageServerBrowserTest::HandleRedirectRequest,
+        &BasePreviewsLitePageServerBrowserTest::HandleOriginRequest,
         base::Unretained(this)));
     ASSERT_TRUE(http_server_->Start());
 
@@ -307,8 +307,9 @@
         {"control_group", is_control ? "true" : "false"},
         {"preconnect_on_slow_connections", "true"},
         {"preresolve_on_slow_connections", "false"},
+        {"should_probe_origin", "true"},
+        {"origin_probe_timeout_ms", "500"},
     };
-
     base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
         "data-reduction-proxy-pingback-url",
         pingback_server_->GetURL("pingback.com", "/").spec());
@@ -653,6 +654,12 @@
   }
   const GURL& client_redirect_url() const { return client_redirect_url_; }
   const GURL& subframe_url() const { return subframe_url_; }
+
+  int origin_probe_count() const { return origin_probe_count_; }
+  void set_origin_probe_success(bool success) {
+    origin_probe_success_ = success;
+  }
+
   uint64_t got_page_id() const { return got_page_id_; }
   int subresources_requested() const { return subresources_requested_; }
   int previews_server_connections() const {
@@ -679,8 +686,19 @@
       test_hints_component_creator_;
 
  private:
-  std::unique_ptr<net::test_server::HttpResponse> HandleRedirectRequest(
+  std::unique_ptr<net::test_server::HttpResponse> HandleOriginRequest(
       const net::test_server::HttpRequest& request) {
+    if (request.method == net::test_server::METHOD_HEAD) {
+      origin_probe_count_++;
+      if (origin_probe_success_) {
+        std::unique_ptr<net::test_server::BasicHttpResponse> response =
+            std::make_unique<net::test_server::BasicHttpResponse>();
+        response->set_code(net::HTTP_NO_CONTENT);
+        return std::move(response);
+      }
+      return std::make_unique<net::test_server::HungResponse>();
+    }
+
     if (request.GetURL().spec().find("to_https_redirect") !=
         std::string::npos) {
       std::unique_ptr<net::test_server::BasicHttpResponse> response =
@@ -961,6 +979,8 @@
   GURL subframe_url_;
   GURL previews_server_url_;
   GURL slow_http_url_;
+  bool origin_probe_success_ = true;
+  int origin_probe_count_ = 0;
   uint64_t got_page_id_ = 0;
   int subresources_requested_ = 0;
   std::unordered_set<std::string> previews_server_connections_;
@@ -1195,6 +1215,34 @@
 
 IN_PROC_BROWSER_TEST_P(
     PreviewsLitePageServerBrowserTest,
+    DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsOriginProbe_Success)) {
+  // This behavior is not implemented for the nav throttle.
+  if (!GetParam())
+    return;
+
+  set_origin_probe_success(true);
+
+  ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
+  VerifyPreviewLoaded();
+  EXPECT_EQ(1, origin_probe_count());
+}
+
+IN_PROC_BROWSER_TEST_P(
+    PreviewsLitePageServerBrowserTest,
+    DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsOriginProbe_Fail)) {
+  // This behavior is not implemented for the nav throttle.
+  if (!GetParam())
+    return;
+
+  set_origin_probe_success(false);
+
+  ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
+  VerifyPreviewNotLoaded();
+  EXPECT_EQ(1, origin_probe_count());
+}
+
+IN_PROC_BROWSER_TEST_P(
+    PreviewsLitePageServerBrowserTest,
     DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsReloadSoftOptOut)) {
   ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
   VerifyPreviewLoaded();
diff --git a/chrome/browser/previews/previews_lite_page_redirect_url_loader.cc b/chrome/browser/previews/previews_lite_page_redirect_url_loader.cc
index a48e85fce..a5540d02 100644
--- a/chrome/browser/previews/previews_lite_page_redirect_url_loader.cc
+++ b/chrome/browser/previews/previews_lite_page_redirect_url_loader.cc
@@ -11,7 +11,9 @@
 #include "base/memory/ptr_util.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/previews/previews_lite_page_navigation_throttle.h"
+#include "chrome/browser/profiles/profile.h"
 #include "components/previews/core/previews_lite_page_redirect.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/common/previews_state.h"
 #include "net/http/http_status_code.h"
 #include "net/http/http_util.h"
@@ -26,16 +28,101 @@
 }  // namespace
 
 PreviewsLitePageRedirectURLLoader::PreviewsLitePageRedirectURLLoader(
+    content::BrowserContext* browser_context,
     const network::ResourceRequest& tentative_resource_request,
     HandleRequest callback)
     : modified_resource_request_(tentative_resource_request),
       callback_(std::move(callback)),
-      binding_(this) {}
+      binding_(this),
+      origin_probe_finished_successfully_(false),
+      litepage_request_finished_successfully_(false) {
+  pref_service_ = browser_context
+                      ? Profile::FromBrowserContext(browser_context)->GetPrefs()
+                      : nullptr;
+}
 
 PreviewsLitePageRedirectURLLoader::~PreviewsLitePageRedirectURLLoader() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
+void PreviewsLitePageRedirectURLLoader::OnOriginProbeComplete(bool success) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // It is safe to delete the prober during this callback, so do so because it
+  // is an expensive object to keep around.
+  origin_connectivity_prober_.reset();
+
+  if (success) {
+    origin_probe_finished_successfully_ = true;
+    MaybeCallOnLitePageSuccess();
+    return;
+  }
+  OnLitePageFallback();
+}
+
+void PreviewsLitePageRedirectURLLoader::StartOriginProbe(
+    const GURL& original_url,
+    const scoped_refptr<network::SharedURLLoaderFactory>&
+        network_loader_factory) {
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("previews_litepage_origin_prober", R"(
+        semantics {
+          sender: "Previews Litepage Origin Prober"
+          description:
+            "Sends a HEAD request to the origin that the user is navigating to "
+            "in order to establish network connectivity before attempting a "
+            "preview of that site."
+          trigger:
+            "Requested on preview-eligible navigations when Lite mode and "
+            "Previews are enabled and the network is slow."
+          data: "None."
+          destination: WEBSITE
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "Users can control Lite mode on Android via the settings menu. "
+            "Lite mode is not available on iOS, and on desktop only for "
+            "developer testing."
+          policy_exception_justification: "Not implemented."
+        })");
+
+  // This probe is a single chance with a short timeout because it blocks the
+  // navigation.
+  AvailabilityProber::TimeoutPolicy timeout_policy;
+  timeout_policy.base_timeout =
+      previews::params::LitePageRedirectPreviewOriginProbeTimeout();
+
+  AvailabilityProber::RetryPolicy retry_policy;
+  retry_policy.max_retries = 0;
+
+  origin_connectivity_prober_ = std::make_unique<AvailabilityProber>(
+      this, network_loader_factory, pref_service_,
+      AvailabilityProber::ClientName::kLitepagesOriginCheck,
+      original_url.GetOrigin(), AvailabilityProber::HttpMethod::kHead,
+      net::HttpRequestHeaders(), retry_policy, timeout_policy,
+      traffic_annotation, 10 /* max_cache_entries */,
+      base::TimeDelta::FromHours(24) /* revalidate_cache_after */);
+  origin_connectivity_prober_->SetOnCompleteCallback(base::BindRepeating(
+      &PreviewsLitePageRedirectURLLoader::OnOriginProbeComplete,
+      weak_ptr_factory_.GetWeakPtr()));
+  origin_connectivity_prober_->SendNowIfInactive(
+      false /* send_only_in_foreground */);
+}
+
+bool PreviewsLitePageRedirectURLLoader::ShouldSendNextProbe() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return true;
+}
+
+bool PreviewsLitePageRedirectURLLoader::IsResponseSuccess(
+    net::Error net_error,
+    const network::ResourceResponseHead* head,
+    std::unique_ptr<std::string> body) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Any HTTP response is fine, so long as we got it.
+  return net_error == net::OK && head && head->headers;
+}
+
 void PreviewsLitePageRedirectURLLoader::StartRedirectToPreview(
     const net::HttpRequestHeaders& chrome_proxy_headers,
     const scoped_refptr<network::SharedURLLoaderFactory>&
@@ -43,6 +130,7 @@
     int frame_tree_node_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  GURL original_url = modified_resource_request_.url;
   GURL lite_page_url = PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(
       modified_resource_request_.url);
 
@@ -50,6 +138,12 @@
 
   modified_resource_request_.headers.MergeFrom(chrome_proxy_headers);
 
+  if (previews::params::LitePageRedirectShouldProbeOrigin()) {
+    StartOriginProbe(original_url, network_loader_factory);
+  } else {
+    origin_probe_finished_successfully_ = true;
+  }
+
   serving_url_loader_ = std::make_unique<PreviewsLitePageServingURLLoader>(
       base::BindOnce(&PreviewsLitePageRedirectURLLoader::OnResultDetermined,
                      weak_ptr_factory_.GetWeakPtr()));
@@ -60,6 +154,7 @@
 
 void PreviewsLitePageRedirectURLLoader::StartRedirectToOriginalURL(
     const GURL& original_url) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CreateRedirectInformation(original_url);
 
   std::move(callback_).Run(
@@ -70,6 +165,7 @@
 
 void PreviewsLitePageRedirectURLLoader::CreateRedirectInformation(
     const GURL& redirect_url) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   bool insecure_scheme_was_upgraded = false;
   bool copy_fragment = true;
 
@@ -105,11 +201,14 @@
     ServingLoaderResult result,
     base::Optional<net::RedirectInfo> redirect_info,
     scoped_refptr<network::ResourceResponse> response) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!redirect_info || result == ServingLoaderResult::kRedirect);
   DCHECK(!response || result == ServingLoaderResult::kRedirect);
+
   switch (result) {
     case ServingLoaderResult::kSuccess:
-      OnLitePageSuccess();
+      litepage_request_finished_successfully_ = true;
+      MaybeCallOnLitePageSuccess();
       return;
     case ServingLoaderResult::kFallback:
       OnLitePageFallback();
@@ -121,7 +220,24 @@
   NOTREACHED();
 }
 
+void PreviewsLitePageRedirectURLLoader::MaybeCallOnLitePageSuccess() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!callback_)
+    return;
+
+  if (!origin_probe_finished_successfully_ ||
+      !litepage_request_finished_successfully_) {
+    return;
+  }
+
+  OnLitePageSuccess();
+}
+
 void PreviewsLitePageRedirectURLLoader::OnLitePageSuccess() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(origin_probe_finished_successfully_);
+  DCHECK(litepage_request_finished_successfully_);
+
   std::move(callback_).Run(
       std::move(serving_url_loader_),
       base::BindOnce(&PreviewsLitePageRedirectURLLoader::
@@ -132,6 +248,7 @@
 void PreviewsLitePageRedirectURLLoader::OnLitePageRedirect(
     const net::RedirectInfo& redirect_info,
     const network::ResourceResponseHead& response_head) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   redirect_info_ = redirect_info;
 
   response_head_ = response_head;
@@ -144,13 +261,15 @@
 
 void PreviewsLitePageRedirectURLLoader::OnLitePageFallback() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  std::move(callback_).Run(nullptr, {});
+  if (callback_)
+    std::move(callback_).Run(nullptr, {});
 }
 
 void PreviewsLitePageRedirectURLLoader::StartHandlingRedirectToModifiedRequest(
     const network::ResourceRequest& resource_request,
     network::mojom::URLLoaderRequest request,
     network::mojom::URLLoaderClientPtr client) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   response_head_.request_start = base::TimeTicks::Now();
   response_head_.response_start = response_head_.request_start;
 
@@ -172,6 +291,7 @@
     const network::ResourceRequest& /* resource_request */,
     network::mojom::URLLoaderRequest request,
     network::mojom::URLLoaderClientPtr client) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!binding_.is_bound());
   binding_.Bind(std::move(request));
   binding_.set_connection_error_handler(
diff --git a/chrome/browser/previews/previews_lite_page_redirect_url_loader.h b/chrome/browser/previews/previews_lite_page_redirect_url_loader.h
index 1c69b4ba..04417fb 100644
--- a/chrome/browser/previews/previews_lite_page_redirect_url_loader.h
+++ b/chrome/browser/previews/previews_lite_page_redirect_url_loader.h
@@ -10,6 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/sequence_checker.h"
+#include "chrome/browser/availability/availability_prober.h"
 #include "chrome/browser/previews/previews_lite_page_serving_url_loader.h"
 #include "content/public/browser/url_loader_request_interceptor.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -19,6 +20,8 @@
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 
+class PrefService;
+
 namespace previews {
 
 using HandleRequest = base::OnceCallback<void(
@@ -28,10 +31,14 @@
 // A URL loader that attempts to fetch an HTTPS server lite page, and if
 // successful, redirects to the lite page URL, and hands the underlying
 // network URLLoader to a success callback. Currently, it supports serving the
-// Preview and falling back to default behavior.
-class PreviewsLitePageRedirectURLLoader : public network::mojom::URLLoader {
+// Preview and falling back to default behavior. If enabled, the origin server
+// will be probed in parallel with the request to the lite page server and the
+// probe must complete successfully before the success callback is run.
+class PreviewsLitePageRedirectURLLoader : public network::mojom::URLLoader,
+                                          public AvailabilityProber::Delegate {
  public:
   PreviewsLitePageRedirectURLLoader(
+      content::BrowserContext* browser_context,
       const network::ResourceRequest& tentative_resource_request,
       HandleRequest callback);
   ~PreviewsLitePageRedirectURLLoader() override;
@@ -48,6 +55,12 @@
   // Creates a redirect to |original_url|.
   void StartRedirectToOriginalURL(const GURL& original_url);
 
+  // AvailabilityProber::Delegate:
+  bool ShouldSendNextProbe() override;
+  bool IsResponseSuccess(net::Error net_error,
+                         const network::ResourceResponseHead* head,
+                         std::unique_ptr<std::string> body) override;
+
  private:
   // network::mojom::URLLoader:
   void FollowRedirect(const std::vector<std::string>& removed_headers,
@@ -96,6 +109,19 @@
   // Mojo error handling. Deletes |this|.
   void OnConnectionClosed();
 
+  // Checks that both the origin probe and the previews request have completed
+  // before calling |OnLitePageSuccess|.
+  void MaybeCallOnLitePageSuccess();
+
+  // A callback passed to |origin_connectivity_prober_| to notify the completion
+  // of a probe.
+  void OnOriginProbeComplete(bool success);
+
+  // Starts the origin probe given the original page url.
+  void StartOriginProbe(const GURL& original_url,
+                        const scoped_refptr<network::SharedURLLoaderFactory>&
+                            network_loader_factory);
+
   // The underlying URLLoader that speculatively tries to fetch the lite page.
   std::unique_ptr<PreviewsLitePageServingURLLoader> serving_url_loader_;
 
@@ -121,6 +147,22 @@
   // The owning client. Used for serving redirects.
   network::mojom::URLLoaderClientPtr client_;
 
+  // A reference to the profile's prefs. May be null.
+  PrefService* pref_service_;
+
+  // Before a preview can be triggered, we must check that the origin site is
+  // accessible on the network. This prober manages that check and is only set
+  // while determining if a preview can be served.
+  std::unique_ptr<AvailabilityProber> origin_connectivity_prober_;
+
+  // Used to remember if the origin probe completes successfully before the
+  // litepage request.
+  bool origin_probe_finished_successfully_;
+
+  // Used to remember if the lite page request completes successfully before the
+  // origin probe.
+  bool litepage_request_finished_successfully_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<PreviewsLitePageRedirectURLLoader> weak_ptr_factory_{
diff --git a/chrome/browser/previews/previews_lite_page_url_loader_interceptor.cc b/chrome/browser/previews/previews_lite_page_url_loader_interceptor.cc
index b50666a..7809f0cf 100644
--- a/chrome/browser/previews/previews_lite_page_url_loader_interceptor.cc
+++ b/chrome/browser/previews/previews_lite_page_url_loader_interceptor.cc
@@ -140,7 +140,7 @@
   RecordInterceptAttempt(true);
 
   redirect_url_loader_ = std::make_unique<PreviewsLitePageRedirectURLLoader>(
-      tentative_resource_request,
+      browser_context, tentative_resource_request,
       base::BindOnce(
           &PreviewsLitePageURLLoaderInterceptor::HandleRedirectLoader,
           base::Unretained(this), std::move(callback)));
@@ -156,7 +156,7 @@
     const GURL& original_url,
     content::URLLoaderRequestInterceptor::LoaderCallback callback) {
   redirect_url_loader_ = std::make_unique<PreviewsLitePageRedirectURLLoader>(
-      tentative_resource_request,
+      nullptr, tentative_resource_request,
       base::BindOnce(
           &PreviewsLitePageURLLoaderInterceptor::HandleRedirectLoader,
           base::Unretained(this), std::move(callback)));
diff --git a/chrome/browser/previews/previews_lite_page_url_loader_interceptor.h b/chrome/browser/previews/previews_lite_page_url_loader_interceptor.h
index bb5ecfd..4cc8dee3 100644
--- a/chrome/browser/previews/previews_lite_page_url_loader_interceptor.h
+++ b/chrome/browser/previews/previews_lite_page_url_loader_interceptor.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_PREVIEWS_PREVIEWS_LITE_PAGE_URL_LOADER_INTERCEPTOR_H_
 
 #include <stdint.h>
+
 #include <memory>
 #include <set>
 
diff --git a/chrome/browser/previews/previews_lite_page_url_loader_interceptor_unittest.cc b/chrome/browser/previews/previews_lite_page_url_loader_interceptor_unittest.cc
index e5c3d11..6c750a4 100644
--- a/chrome/browser/previews/previews_lite_page_url_loader_interceptor_unittest.cc
+++ b/chrome/browser/previews/previews_lite_page_url_loader_interceptor_unittest.cc
@@ -11,7 +11,9 @@
 #include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/previews/previews_lite_page_navigation_throttle.h"
+#include "components/previews/core/previews_features.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/url_loader_request_interceptor.h"
 #include "content/public/common/previews_state.h"
@@ -26,16 +28,27 @@
 
 // TODO(crbug.com/961073): Fix memory leaks in tests and re-enable on LSAN.
 #ifdef LEAK_SANITIZER
-#define MAYBE_InterceptRequestPreviewsState \
-  DISABLED_InterceptRequestPreviewsState
+#define MAYBE_InterceptRequestPreviewsState_PreviewsOff \
+  DISABLED_InterceptRequestPreviewsState_PreviewsOff
+#define MAYBE_InterceptRequestPreviewsState_ProbeSuccess \
+  DISABLED_InterceptRequestPreviewsState_ProbeSuccess
+#define MAYBE_InterceptRequestPreviewsState_ProbeFail \
+  DISABLED_InterceptRequestPreviewsState_ProbeFail
 #else
-#define MAYBE_InterceptRequestPreviewsState InterceptRequestPreviewsState
+#define MAYBE_InterceptRequestPreviewsState_PreviewsOff \
+  InterceptRequestPreviewsState_PreviewsOff
+#define MAYBE_InterceptRequestPreviewsState_ProbeSuccess \
+  InterceptRequestPreviewsState_ProbeSuccess
+#define MAYBE_InterceptRequestPreviewsState_ProbeFail \
+  InterceptRequestPreviewsState_ProbeFail
 #endif
 
 namespace previews {
 
 namespace {
 
+const GURL kTestUrl("https://google.com/path");
+
 class PreviewsLitePageURLLoaderInterceptorTest : public testing::Test {
  public:
   PreviewsLitePageURLLoaderInterceptorTest()
@@ -50,6 +63,9 @@
   void SetUp() override {
     interceptor_ = std::make_unique<PreviewsLitePageURLLoaderInterceptor>(
         shared_factory_, 1, 2);
+
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kLitePageServerPreviews, {{"should_probe_origin", "true"}});
   }
 
   void SetFakeResponse(const GURL& url,
@@ -61,6 +77,14 @@
         network::URLLoaderCompletionStatus(net_error));
   }
 
+  void SetProbeResponse(const GURL& url,
+                        net::HttpStatusCode code,
+                        int net_error) {
+    test_url_loader_factory_.AddResponse(
+        url, network::CreateResourceResponseHead(code), "data",
+        network::URLLoaderCompletionStatus(net_error));
+  }
+
   void HandlerCallback(
       content::URLLoaderRequestInterceptor::RequestHandler callback) {
     callback_was_empty_ = callback.is_null();
@@ -68,12 +92,6 @@
 
   base::Optional<bool> callback_was_empty() { return callback_was_empty_; }
 
-  void ResetTest() {
-    interceptor_ = std::make_unique<PreviewsLitePageURLLoaderInterceptor>(
-        shared_factory_, 1, 2);
-    callback_was_empty_ = base::nullopt;
-  }
-
   PreviewsLitePageURLLoaderInterceptor& interceptor() { return *interceptor_; }
 
  protected:
@@ -82,25 +100,25 @@
  private:
   base::Optional<bool> callback_was_empty_;
 
-  std::unique_ptr<PreviewsLitePageURLLoaderInterceptor> interceptor_;
+  base::test::ScopedFeatureList scoped_feature_list_;
   network::TestURLLoaderFactory test_url_loader_factory_;
   scoped_refptr<network::SharedURLLoaderFactory> shared_factory_;
+  std::unique_ptr<PreviewsLitePageURLLoaderInterceptor> interceptor_;
 };
 
+// Check that we don't trigger when previews are not allowed.
 TEST_F(PreviewsLitePageURLLoaderInterceptorTest,
-       MAYBE_InterceptRequestPreviewsState) {
+       InterceptRequestPreviewsState_PreviewsOff) {
   base::HistogramTester histogram_tester;
 
   network::ResourceRequest request;
-  request.url = GURL("https://google.com");
+  request.url = kTestUrl;
   request.resource_type = static_cast<int>(content::ResourceType::kMainFrame);
   request.method = "GET";
 
-  SetFakeResponse(
-      PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(request.url),
-      "Fake Body", net::HTTP_OK, net::URLRequestStatus::SUCCESS);
+  SetFakeResponse(request.url, "Fake Body", net::HTTP_OK,
+                  net::URLRequestStatus::SUCCESS);
 
-  // Check that we don't trigger when previews are not allowed.
   request.previews_state = content::PREVIEWS_OFF;
   interceptor().MaybeCreateLoader(
       request, nullptr,
@@ -114,33 +132,74 @@
 
   EXPECT_TRUE(callback_was_empty().has_value());
   EXPECT_TRUE(callback_was_empty().value());
+}
 
-  ResetTest();
-  SetFakeResponse(request.url, "Fake Body", net::HTTP_OK,
-                  net::URLRequestStatus::SUCCESS);
+// Check that we trigger when previews are allowed and the probe is successful.
+TEST_F(PreviewsLitePageURLLoaderInterceptorTest,
+       MAYBE_InterceptRequestPreviewsState_ProbeSuccess) {
+  base::HistogramTester histogram_tester;
 
-  // Check that we trigger when previews are allowed.
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type = static_cast<int>(content::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  SetFakeResponse(
+      PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(request.url),
+      "Fake Body", net::HTTP_OK, net::URLRequestStatus::SUCCESS);
+  SetProbeResponse(request.url.GetOrigin(), net::HTTP_OK,
+                   net::URLRequestStatus::SUCCESS);
+
   request.previews_state = content::LITE_PAGE_REDIRECT_ON;
   interceptor().MaybeCreateLoader(
       request, nullptr,
       base::BindOnce(&PreviewsLitePageURLLoaderInterceptorTest::HandlerCallback,
                      base::Unretained(this)));
 
-  histogram_tester.ExpectBucketCount(
+  histogram_tester.ExpectUniqueSample(
       "Previews.ServerLitePage.URLLoader.Attempted", true, 1);
-  histogram_tester.ExpectTotalCount(
-      "Previews.ServerLitePage.URLLoader.Attempted", 2);
 
   base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(callback_was_empty().has_value());
   EXPECT_FALSE(callback_was_empty().value());
+  LOG(ERROR) << "test end";
+}
+
+TEST_F(PreviewsLitePageURLLoaderInterceptorTest,
+       InterceptRequestPreviewsState_ProbeFail) {
+  base::HistogramTester histogram_tester;
+
+  network::ResourceRequest request;
+  request.url = kTestUrl;
+  request.resource_type = static_cast<int>(content::ResourceType::kMainFrame);
+  request.method = "GET";
+
+  SetFakeResponse(
+      PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(request.url),
+      "Fake Body", net::HTTP_OK, net::URLRequestStatus::SUCCESS);
+  SetProbeResponse(request.url.GetOrigin(), net::HTTP_OK,
+                   net::URLRequestStatus::FAILED);
+
+  request.previews_state = content::LITE_PAGE_REDIRECT_ON;
+  interceptor().MaybeCreateLoader(
+      request, nullptr,
+      base::BindOnce(&PreviewsLitePageURLLoaderInterceptorTest::HandlerCallback,
+                     base::Unretained(this)));
+
+  histogram_tester.ExpectUniqueSample(
+      "Previews.ServerLitePage.URLLoader.Attempted", true, 1);
+
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(callback_was_empty().has_value());
+  EXPECT_TRUE(callback_was_empty().value());
 }
 
 TEST_F(PreviewsLitePageURLLoaderInterceptorTest, InterceptRequestRedirect) {
   base::HistogramTester histogram_tester;
   network::ResourceRequest request;
-  request.url = GURL("https://google.com");
+  request.url = kTestUrl;
   request.resource_type = static_cast<int>(content::ResourceType::kMainFrame);
   request.method = "GET";
   request.previews_state = content::LITE_PAGE_REDIRECT_ON;
@@ -148,6 +207,8 @@
       PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(request.url),
       "Fake Body", net::HTTP_TEMPORARY_REDIRECT,
       net::URLRequestStatus::SUCCESS);
+  SetProbeResponse(request.url.GetOrigin(), net::HTTP_OK,
+                   net::URLRequestStatus::SUCCESS);
 
   interceptor().MaybeCreateLoader(
       request, nullptr,
@@ -165,7 +226,7 @@
        InterceptRequestServerOverloaded) {
   base::HistogramTester histogram_tester;
   network::ResourceRequest request;
-  request.url = GURL("https://google.com");
+  request.url = kTestUrl;
   request.resource_type = static_cast<int>(content::ResourceType::kMainFrame);
   request.method = "GET";
   request.previews_state = content::LITE_PAGE_REDIRECT_ON;
@@ -173,6 +234,8 @@
       PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(request.url),
       "Fake Body", net::HTTP_SERVICE_UNAVAILABLE,
       net::URLRequestStatus::SUCCESS);
+  SetProbeResponse(request.url.GetOrigin(), net::HTTP_OK,
+                   net::URLRequestStatus::SUCCESS);
 
   interceptor().MaybeCreateLoader(
       request, nullptr,
@@ -191,13 +254,15 @@
        InterceptRequestServerNotHandling) {
   base::HistogramTester histogram_tester;
   network::ResourceRequest request;
-  request.url = GURL("https://google.com");
+  request.url = kTestUrl;
   request.resource_type = static_cast<int>(content::ResourceType::kMainFrame);
   request.method = "GET";
   request.previews_state = content::LITE_PAGE_REDIRECT_ON;
   SetFakeResponse(
       PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(request.url),
       "Fake Body", net::HTTP_FORBIDDEN, net::URLRequestStatus::SUCCESS);
+  SetProbeResponse(request.url.GetOrigin(), net::HTTP_OK,
+                   net::URLRequestStatus::SUCCESS);
 
   interceptor().MaybeCreateLoader(
       request, nullptr,
@@ -215,13 +280,15 @@
 TEST_F(PreviewsLitePageURLLoaderInterceptorTest, NetStackError) {
   base::HistogramTester histogram_tester;
   network::ResourceRequest request;
-  request.url = GURL("https://google.com");
+  request.url = kTestUrl;
   request.resource_type = static_cast<int>(content::ResourceType::kMainFrame);
   request.method = "GET";
   request.previews_state = content::LITE_PAGE_REDIRECT_ON;
   SetFakeResponse(
       PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(request.url),
       "Fake Body", net::HTTP_OK, net::URLRequestStatus::FAILED);
+  SetProbeResponse(request.url.GetOrigin(), net::HTTP_OK,
+                   net::URLRequestStatus::SUCCESS);
 
   interceptor().MaybeCreateLoader(
       request, nullptr,
diff --git a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
index c8fd70f..b2cc828 100644
--- a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
@@ -338,7 +338,13 @@
   DISALLOW_COPY_AND_ASSIGN(TabManagerTestWithTwoTabs);
 };
 
-IN_PROC_BROWSER_TEST_F(TabManagerTest, TabManagerBasics) {
+// Flaky on Linux/ChromeOS only. http://crbug.com/997719
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#define MAYBE_TabManagerBasics DISABLED_TabManagerBasics
+#else
+#define MAYBE_TabManagerBasics TabManagerBasics
+#endif
+IN_PROC_BROWSER_TEST_F(TabManagerTest, MAYBE_TabManagerBasics) {
   using content::WindowedNotificationObserver;
 
   // Get three tabs open.
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
index df3ae5c..bf2d058 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
@@ -1976,7 +1976,8 @@
   });
 });
 
-TEST_F('ChromeVoxBackgroundTest', 'PopUpButtonSetSize', function() {
+// See https://crbug.com/997688
+TEST_F('ChromeVoxBackgroundTest', 'DISABLED_PopUpButtonSetSize', function() {
   var mockFeedback = this.createMockFeedback();
   this.runWithLoadedTree(function() {/*!
     <div>
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
index 01c26c1..55d6fdd 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
@@ -12,7 +12,7 @@
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_choose_mobile.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_ip_config.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_nameservers.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_property_list.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_property_list_mojo.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_proxy.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_shared_css.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_siminfo.html">
@@ -159,12 +159,12 @@
     </template>
 
     <!-- Other properties to show if present. -->
-    <template is="dom-if" if="[[hasInfoFields_(networkProperties)]]">
+    <template is="dom-if" if="[[hasInfoFields_(managedProperties_)]]">
       <div class="section single-column indented">
-        <network-property-list
-            fields="[[getInfoFields_(networkProperties)]]"
-            property-dict="[[networkProperties]]">
-        </network-property-list>
+        <network-property-list-mojo
+            fields="[[getInfoFields_(managedProperties_)]]"
+            property-dict="[[managedProperties_]]">
+        </network-property-list-mojo>
       </div>
     </template>
   </template>
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
index 6d5adb5..bc3ba9e7 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
@@ -25,13 +25,7 @@
     /** The network GUID to display details for. */
     guid: String,
 
-    /**
-     * The current properties for the network matching |guid|.
-     * @type {!CrOnc.NetworkProperties|undefined}
-     */
-    networkProperties: Object,
-
-    /** @private {!OncMojo.ManagedProperties|undefined} */
+    /** @private {!chromeos.networkConfig.mojom.ManagedProperties|undefined} */
     managedProperties_: Object,
 
     /** @private {?OncMojo.DeviceStateProperties} */
@@ -67,7 +61,7 @@
    * prevents setProperties from being called when setting default properties.
    * @private {boolean}
    */
-  networkPropertiesReceived_: false,
+  propertiesReceived_: false,
 
   /**
    * This UI will use both the networkingPrivate extension API and the
@@ -105,14 +99,8 @@
       this.close_();
     }
 
-    // Set basic networkProperties until they are loaded.
-    this.networkPropertiesReceived_ = false;
-    this.networkProperties = {
-      GUID: this.guid,
-      Type: type,
-      ConnectionState: CrOnc.ConnectionState.NOT_CONNECTED,
-      Name: {Active: name},
-    };
+    // Set default managedProperties_ until they are loaded.
+    this.propertiesReceived_ = false;
     this.managedProperties_ = OncMojo.getDefaultManagedProperties(
         OncMojo.getNetworkTypeFromString(type), this.guid, name);
     this.getNetworkDetails_();
@@ -172,7 +160,7 @@
    * @param {!chromeos.networkConfig.mojom.NetworkStateProperties} network
    */
   onNetworkStateChanged: function(network) {
-    if (!this.guid || !this.networkProperties) {
+    if (!this.guid || !this.managedProperties_) {
       return;
     }
     if (network.guid == this.guid) {
@@ -188,41 +176,9 @@
     }
   },
 
-  /**
-   * Calls networkingPrivate.getProperties for this.guid.
-   * @private
-   */
+  /** @private */
   getNetworkDetails_: function() {
     assert(this.guid);
-    this.networkingPrivate.getManagedProperties(
-        this.guid, this.getPropertiesCallback_.bind(this));
-  },
-
-  /**
-   * networkingPrivate.getProperties callback.
-   * @param {!CrOnc.NetworkProperties} properties The network properties.
-   * @private
-   */
-  getPropertiesCallback_: function(properties) {
-    if (chrome.runtime.lastError) {
-      const message = chrome.runtime.lastError.message;
-      if (message == 'Error.InvalidNetworkGuid') {
-        console.error('Details page: GUID no longer exists: ' + this.guid);
-      } else {
-        console.error(
-            'Unexpected networkingPrivate.getManagedProperties error: ' +
-            message + ' For: ' + this.guid);
-      }
-      this.close_();
-      return;
-    }
-    if (!properties) {
-      console.error('No properties for: ' + this.guid);
-      this.close_();
-      return;
-    }
-
-    // Get the managed properties and then update networkProperties_, etc.
     this.networkConfig_.getManagedProperties(this.guid).then(response => {
       if (!response.result) {
         // Edge case, may occur when disabling. Close this.
@@ -230,8 +186,7 @@
         return;
       }
       this.managedProperties_ = response.result;
-      this.networkProperties_ = properties;
-      this.networkPropertiesReceived_ = true;
+      this.propertiesReceived_ = true;
     });
   },
 
@@ -258,7 +213,7 @@
    * @private
    */
   setMojoNetworkProperties_: function(config) {
-    if (!this.networkPropertiesReceived_ || !this.guid) {
+    if (!this.propertiesReceived_ || !this.guid) {
       return;
     }
     this.networkConfig_.setProperties(this.guid, config).then(response => {
@@ -304,7 +259,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -313,7 +268,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -323,7 +278,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -332,7 +287,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -342,7 +297,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -352,7 +307,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {string}
    * @private
    */
@@ -364,7 +319,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -374,7 +329,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -386,7 +341,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -436,14 +391,16 @@
 
   /** @private */
   onConnectDisconnectClick_: function() {
-    assert(this.networkProperties && this.managedProperties_);
+    if (!this.managedProperties_) {
+      return;
+    }
     if (!this.showConnect_(this.managedProperties_)) {
       this.networkingPrivate.startDisconnect(this.guid);
       return;
     }
 
-    const properties = this.networkProperties;
-    this.networkingPrivate.startConnect(properties.GUID, function() {
+    const guid = this.managedProperties_.guid;
+    this.networkingPrivate.startConnect(guid, function() {
       if (chrome.runtime.lastError) {
         const message = chrome.runtime.lastError.message;
         if (message == 'connecting' || message == 'connect-canceled' ||
@@ -452,7 +409,7 @@
         }
         console.error(
             'Unexpected networkingPrivate.startConnect error: ' + message +
-            ' For: ' + properties.GUID);
+            ' For: ' + guid);
       }
     });
   },
@@ -462,7 +419,7 @@
    * @private
    */
   onApnChange_: function(event) {
-    if (!this.networkPropertiesReceived_) {
+    if (!this.propertiesReceived_) {
       return;
     }
     const apn = event.detail;
@@ -495,6 +452,9 @@
    * @private
    */
   onProxyChange_: function(event) {
+    if (!this.propertiesReceived_) {
+      return;
+    }
     this.setMojoNetworkProperties_({proxySettings: event.detail});
   },
 
@@ -505,7 +465,7 @@
    */
   hasVisibleFields_: function(fields) {
     return fields.some((field) => {
-      const value = this.get(field, this.networkProperties);
+      const value = this.get(field, this.managedProperties_);
       return value !== undefined && value !== '';
     });
   },
@@ -524,18 +484,18 @@
    */
   getInfoFields_: function() {
     /** @type {!Array<string>} */ const fields = [];
-    const type = this.networkProperties.Type;
-    if (type == CrOnc.Type.CELLULAR && this.networkProperties.Cellular) {
+    const type = this.managedProperties_.type;
+    if (type == mojom.NetworkType.kCellular) {
       fields.push(
-          'Cellular.HomeProvider.Name', 'Cellular.ServingOperator.Name',
-          'Cellular.ActivationState', 'Cellular.RoamingState',
-          'RestrictedConnectivity', 'Cellular.MEID', 'Cellular.ESN',
-          'Cellular.ICCID', 'Cellular.IMEI', 'Cellular.IMSI', 'Cellular.MDN',
-          'Cellular.MIN');
-    } else if (type == CrOnc.Type.WI_FI) {
-      fields.push('RestrictedConnectivity');
+          'cellular.homeProvider.name', 'cellular.servingOperator.name',
+          'cellular.activationState', 'cellular.roamingState',
+          'restrictedConnectivity', 'cellular.meid', 'cellular.esn',
+          'cellular.iccid', 'cellular.imei', 'cellular.imsi', 'cellular.mdn',
+          'cellular.min');
+    } else if (type == mojom.NetworkType.kWiFi) {
+      fields.push('restrictedConnectivity');
     }
-    fields.push('MacAddress');
+    fields.push('macAddress');
     return fields;
   },
 });
diff --git a/chrome/browser/resources/chromeos/switch_access/focus_ring_manager.js b/chrome/browser/resources/chromeos/switch_access/focus_ring_manager.js
new file mode 100644
index 0000000..f951f35
--- /dev/null
+++ b/chrome/browser/resources/chromeos/switch_access/focus_ring_manager.js
@@ -0,0 +1,156 @@
+// 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.
+
+/**
+ * Class to handle focus rings.
+ */
+class FocusRingManager {
+  /**
+   * @param {!BackButtonManager} backButtonManager
+   */
+  constructor(backButtonManager) {
+    /**
+     * A map of all the focus rings.
+     * @private {!Map<SAConstants.Focus.ID,
+     *     chrome.accessibilityPrivate.FocusRingInfo>}
+     */
+    this.rings_;
+
+    /**
+     * Regex pattern to verify valid colors. Checks that the first character
+     * is '#', followed by between 3 and 8 valid hex characters, and no other
+     * characters (ignoring case).
+     */
+    this.colorPattern_ = /^#[0-9A-F]{3,8}$/i;
+
+    /**
+     * Keeps track of the scope node currently being focused.
+     * @private {chrome.automation.AutomationNode}
+     */
+    this.currentScope_;
+
+    /**
+     * The back button manager.
+     */
+    this.backButtonManager_ = backButtonManager;
+  }
+
+  /** Finishes setup of focus rings once the preferences are loaded. */
+  onPrefsReady() {
+    // Currently all focus rings share the same color.
+    const color = this.switchAccess_.getStringPreference(
+        SAConstants.Preference.PRIMARY_FOCUS_COLOR);
+
+    // Create each focus ring.
+    this.rings_[SAConstants.Focus.ID.PRIMARY] = {
+      id: SAConstants.Focus.ID.PRIMARY,
+      rects: [],
+      type: chrome.accessibilityPrivate.FocusType.SOLID,
+      color: color,
+      secondaryColor: SAConstants.Focus.SECONDARY_COLOR
+    };
+    this.rings_[SAConstants.Focus.ID.SCOPE] = {
+      id: SAConstants.Focus.ID.SCOPE,
+      rects: [],
+      type: chrome.accessibilityPrivate.FocusType.DASHED,
+      color: color,
+      secondaryColor: SAConstants.Focus.SECONDARY_COLOR
+    };
+    this.rings_[SAConstants.Focus.ID.TEXT] = {
+      id: SAConstants.Focus.ID.TEXT,
+      rects: [],
+      type: chrome.accessibilityPrivate.FocusType.DASHED,
+      color: color,
+      secondaryColor: SAConstants.Focus.SECONDARY_COLOR
+    };
+  }
+
+  /**
+   * Sets the focus ring color.
+   * @param {!string} color
+   */
+  setColor(color) {
+    if (this.colorPattern_.test(color) !== true) {
+      new Error(
+          'Problem setting focus ring color: color is not a valid' +
+          'CSS color string.');
+      return;
+    }
+    this.rings_.forEach((ring) => ring.color = color);
+  }
+
+  /**
+   * Sets the primary and scope focus rings to be around the given nodes.
+   * Saves the scope for future comparison.
+   * @param {!chrome.automation.AutomationNode} primary
+   * @param {!chrome.automation.AutomationNode} scope
+   */
+  setFocusNodes(primary, scope) {
+    const focusRect = primary.location;
+
+    // If the scope element has not changed, we want to use the previously
+    // calculated rect as the current scope rect.
+    let scopeRect = scope.location;
+    const currentScopeRects = this.rings_[SAConstants.Focus.ID.SCOPE].rects;
+    if (currentScopeRects.length && scope === this.currentScope_)
+      scopeRect = currentScopeRects[0];
+    this.currentScope_ = scope;
+
+    if (primary === this.backButtonManager_.buttonNode()) {
+      this.backButtonManager_.show(scopeRect);
+
+      this.rings_[SAConstants.Focus.ID.PRIMARY].rects = [];
+      this.rings_[SAConstants.Focus.ID.SCOPE].rects = [scopeRect];
+      this.updateFocusRings_();
+      return;
+    }
+    this.backButtonManager_.hide();
+
+    // If the current element is not the back button, the scope rect should
+    // expand to contain the focus rect.
+    scopeRect = RectHelper.expandToFitWithPadding(
+        SAConstants.Focus.SCOPE_BUFFER, scopeRect, focusRect);
+
+    this.rings_[SAConstants.Focus.ID.PRIMARY].rects = [focusRect];
+    this.rings_[SAConstants.Focus.ID.SCOPE].rects = [scopeRect];
+    this.updateFocusRings_();
+  }
+
+  /**
+   * Clears the focus ring with the given ID.
+   * @param {!SAConstants.Focus.ID} id
+   */
+  clearRing(id) {
+    this.rings_[id].rects = [];
+    this.updateFocusRings_();
+  }
+
+  /**
+   * Clears all focus rings.
+   */
+  clearAll() {
+    this.rings_.forEach((ring) => ring.rects = []);
+    updateFocusRings_();
+  }
+
+  /**
+   * Sets the indicated focus ring to highlight the given rects.
+   * @param {!SAConstants.Focus.ID} id
+   * @param {!Array<chrome.accessibilityPrivate.ScreenRect>} rects
+   */
+  setRing(id, rects) {
+    this.rings_[id].rects = rects;
+    this.updateFocusRings_();
+  }
+
+  /**
+   * Updates all focus rings to reflect new location, color, style, or other
+   * changes.
+   */
+  updateFocusRings_() {
+    let focusRings = [];
+    this.rings_.forEach((ring) => focusRings.push(ring));
+    chrome.accessibilityPrivate.setFocusRings(focusRings);
+  }
+}
diff --git a/chrome/browser/resources/chromeos/switch_access/menu_manager.js b/chrome/browser/resources/chromeos/switch_access/menu_manager.js
index ed110b2..5fab495 100644
--- a/chrome/browser/resources/chromeos/switch_access/menu_manager.js
+++ b/chrome/browser/resources/chromeos/switch_access/menu_manager.js
@@ -94,6 +94,14 @@
      */
     this.onMenuPanelChildrenChanged_ = this.highlightFirstAction_.bind(this);
 
+    /**
+     * A stack to keep track of all menus that have been opened before
+     * the current menu (so the top of the stack will be the parent
+     * menu of the current menu).
+     * @private {!Array<SAConstants.MenuId>}
+     */
+    this.menuStack_ = [];
+
     this.init_();
   }
 
@@ -159,10 +167,12 @@
    * @param {!chrome.automation.AutomationNode} navNode The currently
    *     highlighted node, for which the menu is being opened.
    * @param {!SAConstants.MenuId} menuId Indicates the menu being opened.
+   * @param {boolean=} isSubmenu Whether or not the menu being opened is a
+   *     submenu of the current menu.
    * @return {boolean} Whether or not the menu was successfully opened.
    * @private
    */
-  openMenu_(navNode, menuId) {
+  openMenu_(navNode, menuId, isSubmenu = false) {
     // Action currently highlighted in the menu (null if the menu was closed
     // before this function was called).
     const actionNode = this.node_;
@@ -173,6 +183,11 @@
     if (!shouldReloadMenu) {
       // Close the current menu before opening a new one.
       this.closeCurrentMenu_();
+
+      if (currentMenuId && isSubmenu) {
+        // Opening a submenu, so push the parent menu onto the stack.
+        this.menuStack_.push(currentMenuId);
+      }
     }
 
     const actions = this.getMenuActions_(navNode, menuId);
@@ -356,8 +371,11 @@
 
   /**
    * Perform the action indicated by the current button (or no action if the
-   * entire menu is selected). Then exit the menu and return to traditional
-   * navigation.
+   * entire menu is selected). If the back button is selected and the current
+   * menu is a submenu (i.e. not the main menu), then the current menu will be
+   * closed and the parent menu that opened the current menu will be re-opened.
+   * If the current menu is the main menu, then exit the menu panel entirely
+   * and return to traditional navigation.
    * @return {boolean} Whether this function had any effect.
    */
   selectCurrentNode() {
@@ -372,7 +390,16 @@
       this.node_.doDefault();
     } else {
       // The back button was selected.
-      this.exit();
+
+      // Id of the menu that opened the current menu (null if the current
+      // menu is the main menu and not a submenu).
+      const parentMenuId = this.menuStack_.pop();
+      if (parentMenuId && this.menuOriginNode_) {
+        // Re-open the parent menu.
+        this.openMenu_(this.menuOriginNode_, parentMenuId);
+      } else {
+        this.exit();
+      }
     }
     return true;
   }
diff --git a/chrome/browser/resources/local_ntp/customize.js b/chrome/browser/resources/local_ntp/customize.js
index e4459d32..b08315b 100644
--- a/chrome/browser/resources/local_ntp/customize.js
+++ b/chrome/browser/resources/local_ntp/customize.js
@@ -733,7 +733,6 @@
   } else if (customize.arrowKeys.includes(event.keyCode)) {
     // Handle arrow key navigation.
     event.preventDefault();
-    event.stopPropagation();
 
     let target = null;
     if (event.keyCode === customize.KEYCODES.LEFT) {
@@ -1918,7 +1917,8 @@
 
   // On any arrow key event in the tiles area, focus the first tile.
   $(customize.IDS.TILES).onkeydown = function(event) {
-    if (customize.arrowKeys.includes(event.keyCode)) {
+    if (document.activeElement === $(customize.IDS.TILES) &&
+        customize.arrowKeys.includes(event.keyCode)) {
       event.preventDefault();
       if ($(customize.IDS.MENU)
               .classList.contains(customize.CLASSES.COLLECTION_DIALOG)) {
@@ -1930,14 +1930,16 @@
   };
 
   $(customize.IDS.BACKGROUNDS_MENU).onkeydown = function(event) {
-    if (customize.arrowKeys.includes(event.keyCode)) {
+    if (document.activeElement === $(customize.IDS.BACKGROUNDS_MENU) &&
+        customize.arrowKeys.includes(event.keyCode)) {
       event.preventDefault();
       $(customize.IDS.BACKGROUNDS_UPLOAD_ICON).focus();
     }
   };
 
   $(customize.IDS.BACKGROUNDS_IMAGE_MENU).onkeydown = function(event) {
-    if (customize.arrowKeys.includes(event.keyCode)) {
+    if (document.activeElement === $(customize.IDS.BACKGROUNDS_IMAGE_MENU) &&
+        customize.arrowKeys.includes(event.keyCode)) {
       event.preventDefault();
       document.querySelector('[id$="img_tile_0"]').focus();
     }
@@ -2270,7 +2272,8 @@
 
   // On arrow keys focus the first element.
   $(customize.IDS.COLORS_MENU).onkeydown = function(event) {
-    if (customize.arrowKeys.includes(event.keyCode)) {
+    if (document.activeElement === $(customize.IDS.COLORS_MENU) &&
+        customize.arrowKeys.includes(event.keyCode)) {
       if (configData.chromeColorsCustomColorPicker) {
         $(customize.IDS.COLOR_PICKER_TILE).focus();
       } else {
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index 31250ee..1a26722 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -891,6 +891,7 @@
   margin-inline-start: 40px;
   position: relative;
   width: 568px;
+  z-index: 1;
 }
 
 .menu-panel {
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index 6b98da33..0101cad 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -6,13 +6,7 @@
  * @fileoverview The local InstantExtended NTP.
  */
 
-/**
- * Whether the most visited tiles have finished loading, i.e. we've received the
- * 'loaded' postMessage from the iframe. Used by tests to detect that loading
- * has completed.
- * @type {boolean}
- */
-let tilesAreLoaded = false;
+// Global local statics (visible for testing).
 
 /**
  * Whether the Most Visited and edit custom link iframes should be created while
@@ -23,51 +17,21 @@
 let iframesAndVoiceSearchDisabledForTesting = false;
 
 /**
+ * Whether the most visited tiles have finished loading, i.e. we've received the
+ * 'loaded' postMessage from the iframe. Used by tests to detect that loading
+ * has completed.
+ * @type {boolean}
+ */
+let tilesAreLoaded = false;
+
+/**
  * Controls rendering the new tab page for InstantExtended.
  * @return {Object} A limited interface for testing the local NTP.
  */
 function LocalNTP() {
 'use strict';
 
-/**
- * Called by tests to disable the creation of Most Visited and edit custom link
- * iframes.
- */
-function disableIframesAndVoiceSearchForTesting() {
-  iframesAndVoiceSearchDisabledForTesting = true;
-}
-
-/**
- * Specifications for an NTP design (not comprehensive).
- *
- * backgroundColor: The 4-component color of default background,
- * darkBackgroundColor: The 4-component color of default dark background,
- * iconBackgroundColor: The 4-component color of default dark icon background,
- * iconDarkBackgroundColor: The 4-component color of default icon background,
- * numTitleLines: Number of lines to display in titles.
- * titleColor: The 4-component color of title text.
- * titleColorAgainstDark: The 4-component color of title text against a dark
- *   theme.
- *
- * @type {{
- *   backgroundColor: !Array<number>,
- *   darkBackgroundColor: !Array<number>,
- *   iconBackgroundColor: !Array<number>,
- *   iconDarkBackgroundColor: !Array<number>,
- *   numTitleLines: number,
- *   titleColor: !Array<number>,
- *   titleColorAgainstDark: !Array<number>,
- * }}
- */
-const NTP_DESIGN = {
-  backgroundColor: [255, 255, 255, 255],
-  darkBackgroundColor: [50, 54, 57, 255],
-  iconBackgroundColor: [241, 243, 244, 255],  /** GG100 */
-  iconDarkBackgroundColor: [32, 33, 36, 255], /** GG900 */
-  numTitleLines: 1,
-  titleColor: [60, 64, 67, 255],               /** GG800 */
-  titleColorAgainstDark: [248, 249, 250, 255], /** GG050 */
-};
+// Constants.
 
 /**
  * Enum for classnames.
@@ -107,6 +71,21 @@
 };
 
 /**
+ * Background color for Chrome dark mode. Used to determine if it is possible to
+ * display a Google Doodle, or if the notifier should be used instead.
+ * @type {string}
+ * @const
+ */
+const DARK_MODE_BACKGROUND_COLOR = 'rgba(50,54,57,1)';
+
+/**
+ * The period of time (ms) before transitions can be applied to a toast
+ * notification after modifying the "display" property.
+ * @type {number}
+ */
+const DISPLAY_TIMEOUT = 20;
+
+/**
  * Enum for HTML element ids.
  * @enum {string}
  * @const
@@ -165,6 +144,14 @@
 };
 
 /**
+ * The maximum number of tiles to show in the Most Visited section if custom
+ * links is enabled.
+ * @type {number}
+ * @const
+ */
+const MAX_NUM_TILES_CUSTOM_LINKS = 10;
+
+/**
  * The maximum number of tiles to show in the Most Visited section.
  * @type {number}
  * @const
@@ -172,12 +159,42 @@
 const MAX_NUM_TILES_MOST_VISITED = 8;
 
 /**
- * The maximum number of tiles to show in the Most Visited section if custom
- * links is enabled.
+ * The period of time (ms) before the Most Visited notification is hidden.
  * @type {number}
- * @const
  */
-const MAX_NUM_TILES_CUSTOM_LINKS = 10;
+const NOTIFICATION_TIMEOUT = 10000;
+
+/**
+ * Specifications for an NTP design (not comprehensive).
+ *
+ * backgroundColor: The 4-component color of default background,
+ * darkBackgroundColor: The 4-component color of default dark background,
+ * iconBackgroundColor: The 4-component color of default dark icon background,
+ * iconDarkBackgroundColor: The 4-component color of default icon background,
+ * numTitleLines: Number of lines to display in titles.
+ * titleColor: The 4-component color of title text.
+ * titleColorAgainstDark: The 4-component color of title text against a dark
+ *   theme.
+ *
+ * @type {{
+ *   backgroundColor: !Array<number>,
+ *   darkBackgroundColor: !Array<number>,
+ *   iconBackgroundColor: !Array<number>,
+ *   iconDarkBackgroundColor: !Array<number>,
+ *   numTitleLines: number,
+ *   titleColor: !Array<number>,
+ *   titleColorAgainstDark: !Array<number>,
+ * }}
+ */
+const NTP_DESIGN = {
+  backgroundColor: [255, 255, 255, 255],
+  darkBackgroundColor: [50, 54, 57, 255],
+  iconBackgroundColor: [241, 243, 244, 255],  /** GG100 */
+  iconDarkBackgroundColor: [32, 33, 36, 255], /** GG900 */
+  numTitleLines: 1,
+  titleColor: [60, 64, 67, 255],               /** GG800 */
+  titleColorAgainstDark: [248, 249, 250, 255], /** GG050 */
+};
 
 /**
  * Background colors considered "white". Used to determine if it is possible to
@@ -187,40 +204,7 @@
  */
 const WHITE_BACKGROUND_COLORS = ['rgba(255,255,255,1)', 'rgba(0,0,0,0)'];
 
-/**
- * Background color for Chrome dark mode. Used to determine if it is possible to
- * display a Google Doodle, or if the notifier should be used instead.
- * @type {string}
- * @const
- */
-const DARK_MODE_BACKGROUND_COLOR = 'rgba(50,54,57,1)';
-
-/**
- * The period of time (ms) before the Most Visited notification is hidden.
- * @type {number}
- */
-const NOTIFICATION_TIMEOUT = 10000;
-
-/**
- * The period of time (ms) before transitions can be applied to a toast
- * notification after modifying the "display" property.
- * @type {number}
- */
-const DISPLAY_TIMEOUT = 20;
-
-/**
- * The last blacklisted tile rid if any, which by definition should not be
- * filler.
- * @type {?number}
- */
-let lastBlacklistedTile = null;
-
-/**
- * The timeout function for automatically hiding the pop-up notification. Only
- * set if a notification is visible.
- * @type {?Object}
- */
-let delayedHideNotification = null;
+// Local statics.
 
 /**
  * The currently visible notification element. Null if no notification is
@@ -230,10 +214,11 @@
 let currNotification = null;
 
 /**
- * The browser embeddedSearch.newTabPage object.
- * @type {Object}
+ * The timeout function for automatically hiding the pop-up notification. Only
+ * set if a notification is visible.
+ * @type {?Object}
  */
-let ntpApiHandle;
+let delayedHideNotification = null;
 
 /**
  * True if dark mode is enabled.
@@ -242,6 +227,31 @@
 let isDarkModeEnabled = false;
 
 /**
+ * The last blacklisted tile rid if any, which by definition should not be
+ * filler.
+ * @type {?number}
+ */
+let lastBlacklistedTile = null;
+
+/**
+ * The browser embeddedSearch.newTabPage object.
+ * @type {Object}
+ */
+let ntpApiHandle;
+
+// Helper methods.
+
+/**
+ * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
+ * @param {Array<number>} color Array of rgba color components.
+ * @return {string} CSS color in RGBA format.
+ */
+function convertToRGBAColor(color) {
+  return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
+      color[3] / 255 + ')';
+}
+
+/**
  * Returns a timeout that can be executed early. Calls back true if this was
  * an early execution, false otherwise.
  * @param {!Function} timeout The timeout function. Requires a boolean param.
@@ -263,422 +273,174 @@
   };
 }
 
-/**
- * Called by tests to override the executable timeout with a test timeout.
- * @param {!Function} timeout The timeout function. Requires a boolean param.
- */
-function overrideExecutableTimeoutForTesting(timeout) {
-  createExecutableTimeout = timeout;
-}
+/** Create the Most Visited and edit custom links iframes. */
+function createIframes() {
+  // Collect arguments for the most visited iframe.
+  const args = [];
 
-/**
- * Returns theme background info, first checking for history.state.notheme. If
- * the page has notheme set, returns a fallback light-colored theme (or dark-
- * colored theme if dark mode is enabled). This is used when the doodle is
- * displayed after clicking the notifier.
- * @return {?ThemeBackgroundInfo}
- */
-function getThemeBackgroundInfo() {
-  if (history.state && history.state.notheme) {
-    return {
-      alternateLogo: false,
-      backgroundColorRgba:
-          (isDarkModeEnabled ? NTP_DESIGN.darkBackgroundColor :
-                               NTP_DESIGN.backgroundColor),
-      customBackgroundConfigured: false,
-      iconBackgroundColor:
-          (isDarkModeEnabled ? NTP_DESIGN.iconDarkBackgroundColor :
-                               NTP_DESIGN.iconBackgroundColor),
-      isNtpBackgroundDark: isDarkModeEnabled,
-      textColorLightRgba: [102, 102, 102, 255],
-      textColorRgba:
-          (isDarkModeEnabled ? NTP_DESIGN.titleColorAgainstDark :
-                               NTP_DESIGN.titleColor),
-      useTitleContainer: false,
-      useWhiteAddIcon: isDarkModeEnabled,
-      usingDefaultTheme: true,
-    };
+  const searchboxApiHandle = window.chrome.embeddedSearch.searchBox;
+
+  if (searchboxApiHandle.rtl) {
+    args.push('rtl=1');
+  }
+  if (NTP_DESIGN.numTitleLines > 1) {
+    args.push('ntl=' + NTP_DESIGN.numTitleLines);
   }
 
-  const info = window.chrome.embeddedSearch.newTabPage.themeBackgroundInfo;
-  const preview = $(customize.IDS.CUSTOM_BG_PREVIEW);
-  if (preview.dataset.hasPreview === 'true') {
-    info.isNtpBackgroundDark = preview.dataset.hasImage === 'true';
-    info.customBackgroundConfigured = preview.dataset.hasImage === 'true';
-    info.alternateLogo = preview.dataset.hasImage === 'true';
-    // backgroundImage is in the form: url("actual url"). Remove everything
-    // except the actual url.
-    info.imageUrl = preview.style.backgroundImage.slice(5, -2);
-  }
-  return info;
-}
-
-/**
- * Determine whether dark chips should be used if dark mode is enabled. This is
- * is the case when dark mode is enabled and a background image (from a custom
- * background or user theme) is not set.
- *
- * @param {!Object} info Theme background information.
- * @return {boolean} Whether the chips should be dark.
- */
-function getUseDarkChips(info) {
-  return window.matchMedia('(prefers-color-scheme: dark)').matches &&
-      !info.imageUrl;
-}
-
-/**
- * Updates the NTP based on the current theme.
- */
-function renderTheme() {
-  const info = getThemeBackgroundInfo();
-  if (!info) {
-    return;
-  }
-
-  $(IDS.NTP_CONTENTS).classList.toggle(CLASSES.DARK, info.isNtpBackgroundDark);
-
-  // Update dark mode styling.
-  isDarkModeEnabled = window.matchMedia('(prefers-color-scheme: dark)').matches;
-  document.body.classList.toggle('light-chip', !getUseDarkChips(info));
-
-  const background = [
-    convertToRGBAColor(info.backgroundColorRgba), info.imageUrl,
-    info.imageTiling, info.imageHorizontalAlignment, info.imageVerticalAlignment
-  ].join(' ').trim();
-
-  // If a custom background has been selected the image will be applied to the
-  // custom-background element instead of the body.
-  if (!info.customBackgroundConfigured) {
-    document.body.style.background = background;
-  }
-
-  // Dark mode uses a white Google logo.
-  const useWhiteLogo =
-      info.alternateLogo || (info.usingDefaultTheme && isDarkModeEnabled);
-  document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, useWhiteLogo);
-  const isNonWhiteBackground = !WHITE_BACKGROUND_COLORS.includes(background);
-  document.body.classList.toggle(CLASSES.NON_WHITE_BG, isNonWhiteBackground);
-
-  if (info.logoColor) {
-    document.body.style.setProperty(
-        '--logo-color', convertToRGBAColor(info.logoColor));
-  }
-
-  // The doodle notifier should be shown for non-default backgrounds. This
-  // includes non-white backgrounds, excluding dark mode gray if dark mode is
-  // enabled.
-  const isDefaultBackground = WHITE_BACKGROUND_COLORS.includes(background) ||
-      (isDarkModeEnabled && background === DARK_MODE_BACKGROUND_COLOR);
-  document.body.classList.toggle(CLASSES.USE_NOTIFIER, !isDefaultBackground);
-
-  updateThemeAttribution(info.attributionUrl, info.imageHorizontalAlignment);
-  setCustomThemeStyle(info);
-
-  if (info.customBackgroundConfigured) {
-    // Do anything only if the custom background changed.
-    const imageUrl = assert(info.imageUrl);
-    if (!$(IDS.CUSTOM_BG).style.backgroundImage.includes(imageUrl)) {
-      const imageWithOverlay = [
-        customize.CUSTOM_BACKGROUND_OVERLAY, 'url(' + imageUrl + ')'
-      ].join(',').trim();
-      // If the theme update is because of uploading a local image then we
-      // should close the customization menu. Closing the menu before the image
-      // is selected doesn't look good.
-      const localImageFileName = 'background.jpg';
-      if (!configData.richerPicker &&
-          imageWithOverlay.includes(localImageFileName)) {
-        customize.closeCustomizationDialog();
-      }
-      // |image| and |imageWithOverlay| use the same url as their source.
-      // Waiting to display the custom background until |image| is fully
-      // loaded ensures that |imageWithOverlay| is also loaded.
-      $(IDS.CUSTOM_BG).style.backgroundImage = imageWithOverlay;
-      const image = new Image();
-      image.onload = function() {
-        $(IDS.CUSTOM_BG).style.opacity = '1';
-      };
-      image.src = imageUrl;
-
-      customize.clearAttribution();
-      customize.setAttribution(
-          '' + info.attribution1, '' + info.attribution2,
-          '' + info.attributionActionUrl);
-    }
-  } else {
-    $(IDS.CUSTOM_BG).style.opacity = '0';
-    $(IDS.CUSTOM_BG).style.backgroundImage = '';
-    customize.clearAttribution();
-  }
-
-  $(customize.IDS.RESTORE_DEFAULT)
-      .classList.toggle(
-          customize.CLASSES.OPTION_DISABLED, !info.customBackgroundConfigured);
-  $(customize.IDS.RESTORE_DEFAULT).tabIndex =
-      (info.customBackgroundConfigured ? 0 : -1);
-
-  $(customize.IDS.EDIT_BG)
-      .classList.toggle(
-          CLASSES.ENTRY_POINT_ENHANCED, !info.customBackgroundConfigured);
+  args.push(
+      'title=' +
+      encodeURIComponent(configData.translatedStrings.mostVisitedTitle));
+  args.push(
+      'removeTooltip=' +
+      encodeURIComponent(configData.translatedStrings.removeThumbnailTooltip));
 
   if (configData.isGooglePage) {
-    customize.onThemeChange();
+    args.push('enableCustomLinks=1');
+    if (configData.enableShortcutsGrid) {
+      args.push('enableGrid=1');
+    }
+    args.push(
+        'addLink=' +
+        encodeURIComponent(configData.translatedStrings.addLinkTitle));
+    args.push(
+        'addLinkTooltip=' +
+        encodeURIComponent(configData.translatedStrings.addLinkTooltip));
+    args.push(
+        'editLinkTooltip=' +
+        encodeURIComponent(configData.translatedStrings.editLinkTooltip));
   }
+
+  // Create the most visited iframe.
+  const iframe = document.createElement('iframe');
+  iframe.id = IDS.TILES_IFRAME;
+  iframe.name = IDS.TILES_IFRAME;
+  iframe.title = configData.translatedStrings.mostVisitedTitle;
+  iframe.src = 'chrome-search://most-visited/single.html?' + args.join('&');
+  $(IDS.TILES).appendChild(iframe);
+
+  iframe.onload = function() {
+    sendThemeInfoToMostVisitedIframe();
+    reloadTiles();
+  };
+
+  if (configData.isGooglePage) {
+    // Collect arguments for the edit custom link iframe.
+    const clArgs = [];
+
+    if (searchboxApiHandle.rtl) {
+      clArgs.push('rtl=1');
+    }
+
+    clArgs.push(
+        'addTitle=' +
+        encodeURIComponent(configData.translatedStrings.addLinkTitle));
+    clArgs.push(
+        'editTitle=' +
+        encodeURIComponent(configData.translatedStrings.editLinkTitle));
+    clArgs.push(
+        'nameField=' +
+        encodeURIComponent(configData.translatedStrings.nameField));
+    clArgs.push(
+        'urlField=' +
+        encodeURIComponent(configData.translatedStrings.urlField));
+    clArgs.push(
+        'linkRemove=' +
+        encodeURIComponent(configData.translatedStrings.linkRemove));
+    clArgs.push(
+        'linkCancel=' +
+        encodeURIComponent(configData.translatedStrings.linkCancel));
+    clArgs.push(
+        'linkDone=' +
+        encodeURIComponent(configData.translatedStrings.linkDone));
+    clArgs.push(
+        'invalidUrl=' +
+        encodeURIComponent(configData.translatedStrings.invalidUrl));
+
+    // Create the edit custom link iframe.
+    const clIframe = document.createElement('iframe');
+    clIframe.id = IDS.CUSTOM_LINKS_EDIT_IFRAME;
+    clIframe.name = IDS.CUSTOM_LINKS_EDIT_IFRAME;
+    clIframe.title = configData.translatedStrings.editLinkTitle;
+    clIframe.src = 'chrome-search://most-visited/edit.html?' + clArgs.join('&');
+    const clIframeDialog = document.createElement('dialog');
+    clIframeDialog.id = IDS.CUSTOM_LINKS_EDIT_IFRAME_DIALOG;
+    clIframeDialog.classList.add(CLASSES.CUSTOMIZE_DIALOG);
+    clIframeDialog.appendChild(clIframe);
+    document.body.appendChild(clIframeDialog);
+  }
+
+  window.addEventListener('message', handlePostMessage);
 }
 
 /**
- * Sends the current theme info to the most visited iframe.
+ * Return true if custom links are enabled.
+ * @return {boolean}
  */
-function sendThemeInfoToMostVisitedIframe() {
-  const info = getThemeBackgroundInfo();
-  if (!info) {
+function customLinksEnabled() {
+  return configData.isGooglePage &&
+      !chrome.embeddedSearch.newTabPage.isUsingMostVisited;
+}
+
+/**
+ * Called by tests to disable the creation of Most Visited and edit custom link
+ * iframes.
+ */
+function disableIframesAndVoiceSearchForTesting() {
+  iframesAndVoiceSearchDisabledForTesting = true;
+}
+
+/**
+ * Animates the pop-up notification to float down, and clears the timeout to
+ * hide the notification.
+ * @param {?Element} notification The notification element.
+ * @param {?Element} notificationContainer The notification container element.
+ * @param {boolean} showPromo Do show the promo if present.
+ */
+function floatDownNotification(notification, notificationContainer, showPromo) {
+  if (!notification || !notificationContainer) {
     return;
   }
 
-  const message = {cmd: 'updateTheme'};
-  message.isThemeDark = info.isNtpBackgroundDark;
-  message.customBackground = info.customBackgroundConfigured;
-  message.useTitleContainer = info.useTitleContainer;
-  message.iconBackgroundColor = convertToRGBAColor(info.iconBackgroundColor);
-  message.useWhiteAddIcon = info.useWhiteAddIcon;
-
-  let titleColor = NTP_DESIGN.titleColor;
-  if (!info.usingDefaultTheme && info.textColorRgba) {
-    titleColor = info.textColorRgba;
-  } else if (info.isNtpBackgroundDark) {
-    titleColor = NTP_DESIGN.titleColorAgainstDark;
-  }
-  message.tileTitleColor = convertToRGBAColor(titleColor);
-
-  const iframe = $(IDS.TILES_IFRAME);
-  if (iframe) {
-    iframe.contentWindow.postMessage(message, '*');
-  }
-}
-
-/**
- * Updates the OneGoogleBar (if it is loaded) based on the current theme.
- * TODO(crbug.com/918582): Add support for OGB dark mode.
- */
-function renderOneGoogleBarTheme() {
-  if (!window.gbar) {
-    return;
-  }
-  try {
-    const oneGoogleBarApi = window.gbar.a;
-    const oneGoogleBarPromise = oneGoogleBarApi.bf();
-    oneGoogleBarPromise.then(function(oneGoogleBar) {
-      const setForegroundStyle = oneGoogleBar.pc.bind(oneGoogleBar);
-      const themeInfo = getThemeBackgroundInfo();
-      setForegroundStyle(themeInfo && themeInfo.isNtpBackgroundDark ? 1 : 0);
-    });
-  } catch (err) {
-    console.log('Failed setting OneGoogleBar theme:\n' + err);
-  }
-}
-
-/**
- * Callback for embeddedSearch.newTabPage.onthemechange.
- */
-function onThemeChange() {
-  renderTheme();
-  renderOneGoogleBarTheme();
-  sendThemeInfoToMostVisitedIframe();
-}
-
-/**
- * Updates the NTP style according to theme.
- * @param {Object} themeInfo The information about the theme.
- */
-function setCustomThemeStyle(themeInfo) {
-  let textColor = '';
-  let textColorLight = '';
-  let mvxFilter = '';
-  if (!themeInfo.usingDefaultTheme) {
-    textColor = convertToRGBAColor(themeInfo.textColorRgba);
-    textColorLight = convertToRGBAColor(themeInfo.textColorLightRgba);
-    mvxFilter = 'drop-shadow(0 0 0 ' + textColor + ')';
-  }
-
-  $(IDS.NTP_CONTENTS)
-      .classList.toggle(CLASSES.DEFAULT_THEME, themeInfo.usingDefaultTheme);
-
-  document.body.style.setProperty('--text-color', textColor);
-  document.body.style.setProperty('--text-color-light', textColorLight);
-}
-
-/**
- * Renders the attribution if the URL is present, otherwise hides it.
- * @param {string|undefined} url The URL of the attribution image, if any.
- * @param {string|undefined} themeBackgroundAlignment The alignment of the theme
- *     background image. This is used to compute the attribution's alignment.
- */
-function updateThemeAttribution(url, themeBackgroundAlignment) {
-  if (!url) {
-    setAttributionVisibility_(false);
+  if (!notificationContainer.classList.contains(CLASSES.FLOAT_UP)) {
     return;
   }
 
-  const attribution = $(IDS.ATTRIBUTION);
-  let attributionImage = attribution.querySelector('img');
-  if (!attributionImage) {
-    attributionImage = new Image();
-    attribution.appendChild(attributionImage);
-  }
-  attributionImage.style.content = url;
-
-  // To avoid conflicts, place the attribution on the left for themes that
-  // right align their background images.
-  attribution.classList.toggle(
-      CLASSES.LEFT_ALIGN_ATTRIBUTION, themeBackgroundAlignment == 'right');
-  setAttributionVisibility_(true);
-}
-
-/**
- * Sets the visibility of the theme attribution.
- * @param {boolean} show True to show the attribution.
- */
-function setAttributionVisibility_(show) {
-  $(IDS.ATTRIBUTION).style.display = show ? '' : 'none';
-}
-
-/**
- * Converts an Array of color components into RGBA format "rgba(R,G,B,A)".
- * @param {Array<number>} color Array of rgba color components.
- * @return {string} CSS color in RGBA format.
- */
-function convertToRGBAColor(color) {
-  return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
-      color[3] / 255 + ')';
-}
-
-/**
- * Callback for embeddedSearch.newTabPage.onmostvisitedchange. Called when the
- * NTP tiles are updated.
- */
-function onMostVisitedChange() {
-  reloadTiles();
-}
-
-/**
- * Fetches new data (RIDs) from the embeddedSearch.newTabPage API and passes
- * them to the iframe.
- */
-function reloadTiles() {
-  // Don't attempt to load tiles if the MV data isn't available yet - this can
-  // happen occasionally, see https://crbug.com/794942. In that case, we should
-  // get an onMostVisitedChange call once they are available.
-  // Note that MV data being available is different from having > 0 tiles. There
-  // can legitimately be 0 tiles, e.g. if the user blacklisted them all.
-  if (!ntpApiHandle.mostVisitedAvailable) {
-    return;
+  // Clear the timeout to hide the notification.
+  if (delayedHideNotification) {
+    delayedHideNotification.clear();
+    delayedHideNotification = null;
+    currNotification = null;
   }
 
-  const pages = ntpApiHandle.mostVisited;
-  const cmds = [];
-  const maxNumTiles = customLinksEnabled() ? MAX_NUM_TILES_CUSTOM_LINKS :
-                                             MAX_NUM_TILES_MOST_VISITED;
-  for (let i = 0; i < Math.min(maxNumTiles, pages.length); ++i) {
-    cmds.push({cmd: 'tile', rid: pages[i].rid});
+  if (showPromo) {
+    // Show middle-slot promo if one is present.
+    const promo = $(IDS.PROMO);
+    if (promo) {
+      promo.classList.remove(CLASSES.HIDE_NOTIFICATION);
+      // Timeout is required for the "float" transition to work. Modifying the
+      // "display" property prevents transitions from activating for a brief
+      // period of time.
+      window.setTimeout(() => {
+        promo.classList.remove(CLASSES.FLOAT_DOWN);
+      }, DISPLAY_TIMEOUT);
+    }
   }
-  cmds.push({cmd: 'show'});
 
-  $(IDS.MOST_VISITED).hidden =
-      !chrome.embeddedSearch.newTabPage.areShortcutsVisible;
-
-  const iframe = $(IDS.TILES_IFRAME);
-  if (iframe) {
-    iframe.contentWindow.postMessage(cmds, '*');
-  }
-}
-
-/**
- * Callback for embeddedSearch.newTabPage.onaddcustomlinkdone. Called when the
- * custom link was successfully added. Shows the "Shortcut added" notification.
- * @param {boolean} success True if the link was successfully added.
- */
-function onAddCustomLinkDone(success) {
-  if (success) {
-    showNotification(configData.translatedStrings.linkAddedMsg);
-  } else {
-    showErrorNotification(
-        configData.translatedStrings.linkCantCreate, null, null);
-  }
-  ntpApiHandle.logEvent(LOG_TYPE.NTP_CUSTOMIZE_SHORTCUT_DONE);
-}
-
-/**
- * Callback for embeddedSearch.newTabPage.onupdatecustomlinkdone. Called when
- * the custom link was successfully updated. Shows the "Shortcut edited"
- * notification.
- * @param {boolean} success True if the link was successfully updated.
- */
-function onUpdateCustomLinkDone(success) {
-  if (success) {
-    showNotification(configData.translatedStrings.linkEditedMsg);
-  } else {
-    showErrorNotification(
-        configData.translatedStrings.linkCantEdit, null, null);
-  }
-}
-
-/**
- * Callback for embeddedSearch.newTabPage.ondeletecustomlinkdone. Called when
- * the custom link was successfully deleted. Shows the "Shortcut deleted"
- * notification.
- * @param {boolean} success True if the link was successfully deleted.
- */
-function onDeleteCustomLinkDone(success) {
-  if (success) {
-    showNotification(configData.translatedStrings.linkRemovedMsg);
-  } else {
-    showErrorNotification(
-        configData.translatedStrings.linkCantRemove, null, null);
-  }
-}
-
-/**
- * Shows the Most Visited pop-up notification and triggers a delay to hide it.
- * The message will be set to |msg|.
- * @param {string} msg The notification message.
- */
-function showNotification(msg) {
-  $(IDS.NOTIFICATION_MESSAGE).textContent = msg;
-  $(IDS.RESTORE_ALL_LINK).textContent = customLinksEnabled() ?
-      configData.translatedStrings.restoreDefaultLinks :
-      configData.translatedStrings.restoreThumbnailsShort;
-  floatUpNotification($(IDS.NOTIFICATION), $(IDS.NOTIFICATION_CONTAINER));
-  $(IDS.UNDO_LINK).focus();
-}
-
-/**
- * Hides the Most Visited pop-up notification.
- */
-function hideNotification() {
-  floatDownNotification(
-      $(IDS.NOTIFICATION), $(IDS.NOTIFICATION_CONTAINER), /*showPromo=*/ true);
-}
-
-/**
- * Shows the error pop-up notification and triggers a delay to hide it. The
- * message will be set to |msg|. If |linkName| and |linkOnClick| are present,
- * shows an error link with text set to |linkName| and onclick handled by
- * |linkOnClick|.
- * @param {string} msg The notification message.
- * @param {?string} linkName The error link text.
- * @param {?Function} linkOnClick The error link onclick handler.
- */
-function showErrorNotification(msg, linkName, linkOnClick) {
-  const notification = $(IDS.ERROR_NOTIFICATION);
-  $(IDS.ERROR_NOTIFICATION_MSG).textContent = msg;
-  if (linkName && linkOnClick) {
-    const notificationLink = $(IDS.ERROR_NOTIFICATION_LINK);
-    notificationLink.textContent = linkName;
-    notificationLink.onclick = linkOnClick;
-    notification.classList.add(CLASSES.HAS_LINK);
-  } else {
-    notification.classList.remove(CLASSES.HAS_LINK);
-  }
-  floatUpNotification(notification, $(IDS.ERROR_NOTIFICATION_CONTAINER));
+  // Reset notification visibility once the animation is complete.
+  notificationContainer.addEventListener('transitionend', (event) => {
+    // Blur the hidden items.
+    $(IDS.UNDO_LINK).blur();
+    $(IDS.RESTORE_ALL_LINK).blur();
+    if (notification.classList.contains(CLASSES.HAS_LINK)) {
+      notification.classList.remove(CLASSES.HAS_LINK);
+      $(IDS.ERROR_NOTIFICATION_LINK).blur();
+    }
+    // Hide the notification
+    if (!notification.classList.contains(CLASSES.FLOAT_UP)) {
+      notification.classList.add(CLASSES.HIDE_NOTIFICATION);
+    }
+  }, {once: true});
+  notificationContainer.classList.remove(CLASSES.FLOAT_UP);
 }
 
 /**
@@ -737,168 +499,58 @@
 }
 
 /**
- * Animates the pop-up notification to float down, and clears the timeout to
- * hide the notification.
- * @param {?Element} notification The notification element.
- * @param {?Element} notificationContainer The notification container element.
- * @param {boolean} showPromo Do show the promo if present.
+ * Returns theme background info, first checking for history.state.notheme. If
+ * the page has notheme set, returns a fallback light-colored theme (or dark-
+ * colored theme if dark mode is enabled). This is used when the doodle is
+ * displayed after clicking the notifier.
+ * @return {?ThemeBackgroundInfo}
  */
-function floatDownNotification(notification, notificationContainer, showPromo) {
-  if (!notification || !notificationContainer) {
-    return;
+function getThemeBackgroundInfo() {
+  if (history.state && history.state.notheme) {
+    return {
+      alternateLogo: false,
+      backgroundColorRgba:
+          (isDarkModeEnabled ? NTP_DESIGN.darkBackgroundColor :
+                               NTP_DESIGN.backgroundColor),
+      customBackgroundConfigured: false,
+      iconBackgroundColor:
+          (isDarkModeEnabled ? NTP_DESIGN.iconDarkBackgroundColor :
+                               NTP_DESIGN.iconBackgroundColor),
+      isNtpBackgroundDark: isDarkModeEnabled,
+      textColorLightRgba: [102, 102, 102, 255],
+      textColorRgba:
+          (isDarkModeEnabled ? NTP_DESIGN.titleColorAgainstDark :
+                               NTP_DESIGN.titleColor),
+      useTitleContainer: false,
+      useWhiteAddIcon: isDarkModeEnabled,
+      usingDefaultTheme: true,
+    };
   }
 
-  if (!notificationContainer.classList.contains(CLASSES.FLOAT_UP)) {
-    return;
+  const info = window.chrome.embeddedSearch.newTabPage.themeBackgroundInfo;
+  const preview = $(customize.IDS.CUSTOM_BG_PREVIEW);
+  if (preview.dataset.hasPreview === 'true') {
+    info.isNtpBackgroundDark = preview.dataset.hasImage === 'true';
+    info.customBackgroundConfigured = preview.dataset.hasImage === 'true';
+    info.alternateLogo = preview.dataset.hasImage === 'true';
+    // backgroundImage is in the form: url("actual url"). Remove everything
+    // except the actual url.
+    info.imageUrl = preview.style.backgroundImage.slice(5, -2);
   }
-
-  // Clear the timeout to hide the notification.
-  if (delayedHideNotification) {
-    delayedHideNotification.clear();
-    delayedHideNotification = null;
-    currNotification = null;
-  }
-
-  if (showPromo) {
-    // Show middle-slot promo if one is present.
-    const promo = $(IDS.PROMO);
-    if (promo) {
-      promo.classList.remove(CLASSES.HIDE_NOTIFICATION);
-      // Timeout is required for the "float" transition to work. Modifying the
-      // "display" property prevents transitions from activating for a brief
-      // period of time.
-      window.setTimeout(() => {
-        promo.classList.remove(CLASSES.FLOAT_DOWN);
-      }, DISPLAY_TIMEOUT);
-    }
-  }
-
-  // Reset notification visibility once the animation is complete.
-  notificationContainer.addEventListener('transitionend', (event) => {
-    // Blur the hidden items.
-    $(IDS.UNDO_LINK).blur();
-    $(IDS.RESTORE_ALL_LINK).blur();
-    if (notification.classList.contains(CLASSES.HAS_LINK)) {
-      notification.classList.remove(CLASSES.HAS_LINK);
-      $(IDS.ERROR_NOTIFICATION_LINK).blur();
-    }
-    // Hide the notification
-    if (!notification.classList.contains(CLASSES.FLOAT_UP)) {
-      notification.classList.add(CLASSES.HIDE_NOTIFICATION);
-    }
-  }, {once: true});
-  notificationContainer.classList.remove(CLASSES.FLOAT_UP);
+  return info;
 }
 
 /**
- * Return true if custom links are enabled.
- * @return {boolean}
+ * Determine whether dark chips should be used if dark mode is enabled. This is
+ * is the case when dark mode is enabled and a background image (from a custom
+ * background or user theme) is not set.
+ *
+ * @param {!Object} info Theme background information.
+ * @return {boolean} Whether the chips should be dark.
  */
-function customLinksEnabled() {
-  return configData.isGooglePage &&
-      !chrome.embeddedSearch.newTabPage.isUsingMostVisited;
-}
-
-/**
- * Handles a click on the notification undo link by hiding the notification and
- * informing Chrome.
- */
-function onUndo() {
-  hideNotification();
-  // Focus on the omnibox after the notification is hidden.
-  window.chrome.embeddedSearch.searchBox.startCapturingKeyStrokes();
-  if (customLinksEnabled()) {
-    ntpApiHandle.undoCustomLinkAction();
-  } else if (lastBlacklistedTile != null) {
-    ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile);
-  }
-}
-
-/**
- * Handles a click on the restore all notification link by hiding the
- * notification and informing Chrome.
- */
-function onRestoreAll() {
-  hideNotification();
-  // Focus on the omnibox after the notification is hidden.
-  window.chrome.embeddedSearch.searchBox.startCapturingKeyStrokes();
-  if (customLinksEnabled()) {
-    ntpApiHandle.resetCustomLinks();
-  } else {
-    ntpApiHandle.undoAllMostVisitedDeletions();
-  }
-}
-
-/**
- * Callback for embeddedSearch.newTabPage.oninputstart. Handles new input by
- * disposing the NTP, according to where the input was entered.
- */
-function onInputStart() {
-  if (isFakeboxFocused()) {
-    setFakeboxFocus(false);
-    setFakeboxDragFocus(false);
-    setFakeboxVisibility(false);
-  }
-}
-
-/**
- * Callback for embeddedSearch.newTabPage.oninputcancel. Restores the NTP
- * (re-enables the fakebox and unhides the logo.)
- */
-function onInputCancel() {
-  setFakeboxVisibility(true);
-}
-
-/**
- * @param {boolean} focus True to focus the fakebox.
- */
-function setFakeboxFocus(focus) {
-  document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
-}
-
-/**
- * @param {boolean} focus True to show a dragging focus on the fakebox.
- */
-function setFakeboxDragFocus(focus) {
-  document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
-}
-
-/**
- * @return {boolean} True if the fakebox has focus.
- */
-function isFakeboxFocused() {
-  return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
-      document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
-}
-
-/**
- * @param {!Event} event The click event.
- * @return {boolean} True if the click occurred in an enabled fakebox.
- */
-function isFakeboxClick(event) {
-  return $(IDS.FAKEBOX).contains(/** @type HTMLElement */ (event.target)) &&
-      !$(IDS.FAKEBOX_MICROPHONE)
-           .contains(/** @type HTMLElement */ (event.target));
-}
-
-/**
- * @param {boolean} show True to show the fakebox and logo.
- */
-function setFakeboxVisibility(show) {
-  document.body.classList.toggle(CLASSES.HIDE_FAKEBOX, !show);
-}
-
-/**
- * @param {!Element} element
- * @param {!Array<string>} keys
- * @param {!function(Event)} handler
- */
-function registerKeyHandler(element, keys, handler) {
-  element.addEventListener('keydown', e => {
-    if (keys.includes(e.key)) {
-      handler(e);
-    }
-  });
+function getUseDarkChips(info) {
+  return window.matchMedia('(prefers-color-scheme: dark)').matches &&
+      !info.imageUrl;
 }
 
 /**
@@ -959,43 +611,10 @@
   }
 }
 
-/**
- * Request data for search suggestions, promo, and the OGB. Insert it into
- * the page once it's available. For search suggestions this should be almost
- * immediately as cached data is always used. Promos and the OGB may need
- * to wait for the asynchronous network request to complete.
- */
-function requestAndInsertGoogleResources() {
-  if (!$('search-suggestions-loader')) {
-    const ssScript = document.createElement('script');
-    ssScript.id = 'search-suggestions-loader';
-    ssScript.src = 'chrome-search://local-ntp/search-suggestions.js';
-    ssScript.async = false;
-    document.body.appendChild(ssScript);
-    ssScript.onload = function() {
-      injectSearchSuggestions(searchSuggestions);
-    };
-  }
-  if (!$('one-google-loader')) {
-    // Load the OneGoogleBar script. It'll create a global variable |og| which
-    // is a JSON object corresponding to the native OneGoogleBarData type.
-    const ogScript = document.createElement('script');
-    ogScript.id = 'one-google-loader';
-    ogScript.src = 'chrome-search://local-ntp/one-google.js';
-    document.body.appendChild(ogScript);
-    ogScript.onload = function() {
-      injectOneGoogleBar(og);
-    };
-  }
-  if (!$('promo-loader')) {
-    const promoScript = document.createElement('script');
-    promoScript.id = 'promo-loader';
-    promoScript.src = 'chrome-search://local-ntp/promo.js';
-    document.body.appendChild(promoScript);
-    promoScript.onload = function() {
-      injectPromo(promo);
-    };
-  }
+/** Hides the Most Visited pop-up notification. */
+function hideNotification() {
+  floatDownNotification(
+      $(IDS.NOTIFICATION), $(IDS.NOTIFICATION_CONTAINER), /*showPromo=*/ true);
 }
 
 /**
@@ -1143,111 +762,42 @@
 }
 
 /**
- * Create the Most Visited and edit custom links iframes.
+ * Injects the One Google Bar into the page. Called asynchronously, so that it
+ * doesn't block the main page load.
  */
-function createIframes() {
-  // Collect arguments for the most visited iframe.
-  const args = [];
-
-  const searchboxApiHandle = window.chrome.embeddedSearch.searchBox;
-
-  if (searchboxApiHandle.rtl) {
-    args.push('rtl=1');
-  }
-  if (NTP_DESIGN.numTitleLines > 1) {
-    args.push('ntl=' + NTP_DESIGN.numTitleLines);
+function injectOneGoogleBar(ogb) {
+  if (ogb.barHtml === '') {
+    return;
   }
 
-  args.push(
-      'title=' +
-      encodeURIComponent(configData.translatedStrings.mostVisitedTitle));
-  args.push(
-      'removeTooltip=' +
-      encodeURIComponent(configData.translatedStrings.removeThumbnailTooltip));
+  const inHeadStyle = document.createElement('style');
+  inHeadStyle.type = 'text/css';
+  inHeadStyle.appendChild(document.createTextNode(ogb.inHeadStyle));
+  document.head.appendChild(inHeadStyle);
 
-  if (configData.isGooglePage) {
-    args.push('enableCustomLinks=1');
-    if (configData.enableShortcutsGrid) {
-      args.push('enableGrid=1');
-    }
-    args.push(
-        'addLink=' +
-        encodeURIComponent(configData.translatedStrings.addLinkTitle));
-    args.push(
-        'addLinkTooltip=' +
-        encodeURIComponent(configData.translatedStrings.addLinkTooltip));
-    args.push(
-        'editLinkTooltip=' +
-        encodeURIComponent(configData.translatedStrings.editLinkTooltip));
-  }
+  const inHeadScript = document.createElement('script');
+  inHeadScript.type = 'text/javascript';
+  inHeadScript.appendChild(document.createTextNode(ogb.inHeadScript));
+  document.head.appendChild(inHeadScript);
 
-  // Create the most visited iframe.
-  const iframe = document.createElement('iframe');
-  iframe.id = IDS.TILES_IFRAME;
-  iframe.name = IDS.TILES_IFRAME;
-  iframe.title = configData.translatedStrings.mostVisitedTitle;
-  iframe.src = 'chrome-search://most-visited/single.html?' + args.join('&');
-  $(IDS.TILES).appendChild(iframe);
+  renderOneGoogleBarTheme();
 
-  iframe.onload = function() {
-    sendThemeInfoToMostVisitedIframe();
-    reloadTiles();
-  };
+  const ogElem = $('one-google');
+  ogElem.innerHTML = ogb.barHtml;
 
-  if (configData.isGooglePage) {
-    // Collect arguments for the edit custom link iframe.
-    const clArgs = [];
+  const afterBarScript = document.createElement('script');
+  afterBarScript.type = 'text/javascript';
+  afterBarScript.appendChild(document.createTextNode(ogb.afterBarScript));
+  ogElem.parentNode.insertBefore(afterBarScript, ogElem.nextSibling);
 
-    if (searchboxApiHandle.rtl) {
-      clArgs.push('rtl=1');
-    }
+  $('one-google-end-of-body').innerHTML = ogb.endOfBodyHtml;
 
-    clArgs.push(
-        'addTitle=' +
-        encodeURIComponent(configData.translatedStrings.addLinkTitle));
-    clArgs.push(
-        'editTitle=' +
-        encodeURIComponent(configData.translatedStrings.editLinkTitle));
-    clArgs.push(
-        'nameField=' +
-        encodeURIComponent(configData.translatedStrings.nameField));
-    clArgs.push(
-        'urlField=' +
-        encodeURIComponent(configData.translatedStrings.urlField));
-    clArgs.push(
-        'linkRemove=' +
-        encodeURIComponent(configData.translatedStrings.linkRemove));
-    clArgs.push(
-        'linkCancel=' +
-        encodeURIComponent(configData.translatedStrings.linkCancel));
-    clArgs.push(
-        'linkDone=' +
-        encodeURIComponent(configData.translatedStrings.linkDone));
-    clArgs.push(
-        'invalidUrl=' +
-        encodeURIComponent(configData.translatedStrings.invalidUrl));
+  const endOfBodyScript = document.createElement('script');
+  endOfBodyScript.type = 'text/javascript';
+  endOfBodyScript.appendChild(document.createTextNode(ogb.endOfBodyScript));
+  document.body.appendChild(endOfBodyScript);
 
-    // Create the edit custom link iframe.
-    const clIframe = document.createElement('iframe');
-    clIframe.id = IDS.CUSTOM_LINKS_EDIT_IFRAME;
-    clIframe.name = IDS.CUSTOM_LINKS_EDIT_IFRAME;
-    clIframe.title = configData.translatedStrings.editLinkTitle;
-    clIframe.src = 'chrome-search://most-visited/edit.html?' + clArgs.join('&');
-    const clIframeDialog = document.createElement('dialog');
-    clIframeDialog.id = IDS.CUSTOM_LINKS_EDIT_IFRAME_DIALOG;
-    clIframeDialog.classList.add(CLASSES.CUSTOMIZE_DIALOG);
-    clIframeDialog.appendChild(clIframe);
-    document.body.appendChild(clIframeDialog);
-  }
-
-  window.addEventListener('message', handlePostMessage);
-}
-
-/**
- * Binds event listeners.
- */
-function listen() {
-  document.addEventListener('DOMContentLoaded', init);
+  ntpApiHandle.logEvent(LOG_TYPE.NTP_ONE_GOOGLE_BAR_SHOWN);
 }
 
 /**
@@ -1306,42 +856,488 @@
 }
 
 /**
- * Injects the One Google Bar into the page. Called asynchronously, so that it
- * doesn't block the main page load.
+ * @param {!Event} event The click event.
+ * @return {boolean} True if the click occurred in an enabled fakebox.
  */
-function injectOneGoogleBar(ogb) {
-  if (ogb.barHtml === '') {
+function isFakeboxClick(event) {
+  return $(IDS.FAKEBOX).contains(/** @type HTMLElement */ (event.target)) &&
+      !$(IDS.FAKEBOX_MICROPHONE)
+           .contains(/** @type HTMLElement */ (event.target));
+}
+
+/** @return {boolean} True if the fakebox has focus. */
+function isFakeboxFocused() {
+  return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS) ||
+      document.body.classList.contains(CLASSES.FAKEBOX_DRAG_FOCUS);
+}
+
+/** Binds event listeners. */
+function listen() {
+  document.addEventListener('DOMContentLoaded', init);
+}
+
+/**
+ * Callback for embeddedSearch.newTabPage.onaddcustomlinkdone. Called when the
+ * custom link was successfully added. Shows the "Shortcut added" notification.
+ * @param {boolean} success True if the link was successfully added.
+ */
+function onAddCustomLinkDone(success) {
+  if (success) {
+    showNotification(configData.translatedStrings.linkAddedMsg);
+  } else {
+    showErrorNotification(
+        configData.translatedStrings.linkCantCreate, null, null);
+  }
+  ntpApiHandle.logEvent(LOG_TYPE.NTP_CUSTOMIZE_SHORTCUT_DONE);
+}
+
+/**
+ * Callback for embeddedSearch.newTabPage.ondeletecustomlinkdone. Called when
+ * the custom link was successfully deleted. Shows the "Shortcut deleted"
+ * notification.
+ * @param {boolean} success True if the link was successfully deleted.
+ */
+function onDeleteCustomLinkDone(success) {
+  if (success) {
+    showNotification(configData.translatedStrings.linkRemovedMsg);
+  } else {
+    showErrorNotification(
+        configData.translatedStrings.linkCantRemove, null, null);
+  }
+}
+
+/**
+ * Callback for embeddedSearch.newTabPage.oninputcancel. Restores the NTP
+ * (re-enables the fakebox and unhides the logo.)
+ */
+function onInputCancel() {
+  setFakeboxVisibility(true);
+}
+
+/**
+ * Callback for embeddedSearch.newTabPage.oninputstart. Handles new input by
+ * disposing the NTP, according to where the input was entered.
+ */
+function onInputStart() {
+  if (isFakeboxFocused()) {
+    setFakeboxFocus(false);
+    setFakeboxDragFocus(false);
+    setFakeboxVisibility(false);
+  }
+}
+
+/**
+ * Callback for embeddedSearch.newTabPage.onmostvisitedchange. Called when the
+ * NTP tiles are updated.
+ */
+function onMostVisitedChange() {
+  reloadTiles();
+}
+
+/**
+ * Handles a click on the restore all notification link by hiding the
+ * notification and informing Chrome.
+ */
+function onRestoreAll() {
+  hideNotification();
+  // Focus on the omnibox after the notification is hidden.
+  window.chrome.embeddedSearch.searchBox.startCapturingKeyStrokes();
+  if (customLinksEnabled()) {
+    ntpApiHandle.resetCustomLinks();
+  } else {
+    ntpApiHandle.undoAllMostVisitedDeletions();
+  }
+}
+
+/**
+ * Callback for embeddedSearch.newTabPage.onthemechange.
+ */
+function onThemeChange() {
+  renderTheme();
+  renderOneGoogleBarTheme();
+  sendThemeInfoToMostVisitedIframe();
+}
+
+/**
+ * Handles a click on the notification undo link by hiding the notification and
+ * informing Chrome.
+ */
+function onUndo() {
+  hideNotification();
+  // Focus on the omnibox after the notification is hidden.
+  window.chrome.embeddedSearch.searchBox.startCapturingKeyStrokes();
+  if (customLinksEnabled()) {
+    ntpApiHandle.undoCustomLinkAction();
+  } else if (lastBlacklistedTile != null) {
+    ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile);
+  }
+}
+
+/**
+ * Callback for embeddedSearch.newTabPage.onupdatecustomlinkdone. Called when
+ * the custom link was successfully updated. Shows the "Shortcut edited"
+ * notification.
+ * @param {boolean} success True if the link was successfully updated.
+ */
+function onUpdateCustomLinkDone(success) {
+  if (success) {
+    showNotification(configData.translatedStrings.linkEditedMsg);
+  } else {
+    showErrorNotification(
+        configData.translatedStrings.linkCantEdit, null, null);
+  }
+}
+
+/**
+ * Called by tests to override the executable timeout with a test timeout.
+ * @param {!Function} timeout The timeout function. Requires a boolean param.
+ */
+function overrideExecutableTimeoutForTesting(timeout) {
+  createExecutableTimeout = timeout;
+}
+
+/**
+ * @param {!Element} element
+ * @param {!Array<string>} keys
+ * @param {!function(Event)} handler
+ */
+function registerKeyHandler(element, keys, handler) {
+  element.addEventListener('keydown', e => {
+    if (keys.includes(e.key)) {
+      handler(e);
+    }
+  });
+}
+
+/**
+ * Fetches new data (RIDs) from the embeddedSearch.newTabPage API and passes
+ * them to the iframe.
+ */
+function reloadTiles() {
+  // Don't attempt to load tiles if the MV data isn't available yet - this can
+  // happen occasionally, see https://crbug.com/794942. In that case, we should
+  // get an onMostVisitedChange call once they are available.
+  // Note that MV data being available is different from having > 0 tiles. There
+  // can legitimately be 0 tiles, e.g. if the user blacklisted them all.
+  if (!ntpApiHandle.mostVisitedAvailable) {
     return;
   }
 
-  const inHeadStyle = document.createElement('style');
-  inHeadStyle.type = 'text/css';
-  inHeadStyle.appendChild(document.createTextNode(ogb.inHeadStyle));
-  document.head.appendChild(inHeadStyle);
+  const pages = ntpApiHandle.mostVisited;
+  const cmds = [];
+  const maxNumTiles = customLinksEnabled() ? MAX_NUM_TILES_CUSTOM_LINKS :
+                                             MAX_NUM_TILES_MOST_VISITED;
+  for (let i = 0; i < Math.min(maxNumTiles, pages.length); ++i) {
+    cmds.push({cmd: 'tile', rid: pages[i].rid});
+  }
+  cmds.push({cmd: 'show'});
 
-  const inHeadScript = document.createElement('script');
-  inHeadScript.type = 'text/javascript';
-  inHeadScript.appendChild(document.createTextNode(ogb.inHeadScript));
-  document.head.appendChild(inHeadScript);
+  $(IDS.MOST_VISITED).hidden =
+      !chrome.embeddedSearch.newTabPage.areShortcutsVisible;
 
-  renderOneGoogleBarTheme();
+  const iframe = $(IDS.TILES_IFRAME);
+  if (iframe) {
+    iframe.contentWindow.postMessage(cmds, '*');
+  }
+}
 
-  const ogElem = $('one-google');
-  ogElem.innerHTML = ogb.barHtml;
+/**
+ * Updates the OneGoogleBar (if it is loaded) based on the current theme.
+ * TODO(crbug.com/918582): Add support for OGB dark mode.
+ */
+function renderOneGoogleBarTheme() {
+  if (!window.gbar) {
+    return;
+  }
+  try {
+    const oneGoogleBarApi = window.gbar.a;
+    const oneGoogleBarPromise = oneGoogleBarApi.bf();
+    oneGoogleBarPromise.then(function(oneGoogleBar) {
+      const setForegroundStyle = oneGoogleBar.pc.bind(oneGoogleBar);
+      const themeInfo = getThemeBackgroundInfo();
+      setForegroundStyle(themeInfo && themeInfo.isNtpBackgroundDark ? 1 : 0);
+    });
+  } catch (err) {
+    console.log('Failed setting OneGoogleBar theme:\n' + err);
+  }
+}
 
-  const afterBarScript = document.createElement('script');
-  afterBarScript.type = 'text/javascript';
-  afterBarScript.appendChild(document.createTextNode(ogb.afterBarScript));
-  ogElem.parentNode.insertBefore(afterBarScript, ogElem.nextSibling);
+/** Updates the NTP based on the current theme. */
+function renderTheme() {
+  const info = getThemeBackgroundInfo();
+  if (!info) {
+    return;
+  }
 
-  $('one-google-end-of-body').innerHTML = ogb.endOfBodyHtml;
+  $(IDS.NTP_CONTENTS).classList.toggle(CLASSES.DARK, info.isNtpBackgroundDark);
 
-  const endOfBodyScript = document.createElement('script');
-  endOfBodyScript.type = 'text/javascript';
-  endOfBodyScript.appendChild(document.createTextNode(ogb.endOfBodyScript));
-  document.body.appendChild(endOfBodyScript);
+  // Update dark mode styling.
+  isDarkModeEnabled = window.matchMedia('(prefers-color-scheme: dark)').matches;
+  document.body.classList.toggle('light-chip', !getUseDarkChips(info));
 
-  ntpApiHandle.logEvent(LOG_TYPE.NTP_ONE_GOOGLE_BAR_SHOWN);
+  const background = [
+    convertToRGBAColor(info.backgroundColorRgba), info.imageUrl,
+    info.imageTiling, info.imageHorizontalAlignment, info.imageVerticalAlignment
+  ].join(' ').trim();
+
+  // If a custom background has been selected the image will be applied to the
+  // custom-background element instead of the body.
+  if (!info.customBackgroundConfigured) {
+    document.body.style.background = background;
+  }
+
+  // Dark mode uses a white Google logo.
+  const useWhiteLogo =
+      info.alternateLogo || (info.usingDefaultTheme && isDarkModeEnabled);
+  document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, useWhiteLogo);
+  const isNonWhiteBackground = !WHITE_BACKGROUND_COLORS.includes(background);
+  document.body.classList.toggle(CLASSES.NON_WHITE_BG, isNonWhiteBackground);
+
+  if (info.logoColor) {
+    document.body.style.setProperty(
+        '--logo-color', convertToRGBAColor(info.logoColor));
+  }
+
+  // The doodle notifier should be shown for non-default backgrounds. This
+  // includes non-white backgrounds, excluding dark mode gray if dark mode is
+  // enabled.
+  const isDefaultBackground = WHITE_BACKGROUND_COLORS.includes(background) ||
+      (isDarkModeEnabled && background === DARK_MODE_BACKGROUND_COLOR);
+  document.body.classList.toggle(CLASSES.USE_NOTIFIER, !isDefaultBackground);
+
+  updateThemeAttribution(info.attributionUrl, info.imageHorizontalAlignment);
+  setCustomThemeStyle(info);
+
+  if (info.customBackgroundConfigured) {
+    // Do anything only if the custom background changed.
+    const imageUrl = assert(info.imageUrl);
+    if (!$(IDS.CUSTOM_BG).style.backgroundImage.includes(imageUrl)) {
+      const imageWithOverlay = [
+        customize.CUSTOM_BACKGROUND_OVERLAY, 'url(' + imageUrl + ')'
+      ].join(',').trim();
+      // If the theme update is because of uploading a local image then we
+      // should close the customization menu. Closing the menu before the image
+      // is selected doesn't look good.
+      const localImageFileName = 'background.jpg';
+      if (!configData.richerPicker &&
+          imageWithOverlay.includes(localImageFileName)) {
+        customize.closeCustomizationDialog();
+      }
+      // |image| and |imageWithOverlay| use the same url as their source.
+      // Waiting to display the custom background until |image| is fully
+      // loaded ensures that |imageWithOverlay| is also loaded.
+      $(IDS.CUSTOM_BG).style.backgroundImage = imageWithOverlay;
+      const image = new Image();
+      image.onload = function() {
+        $(IDS.CUSTOM_BG).style.opacity = '1';
+      };
+      image.src = imageUrl;
+
+      customize.clearAttribution();
+      customize.setAttribution(
+          '' + info.attribution1, '' + info.attribution2,
+          '' + info.attributionActionUrl);
+    }
+  } else {
+    $(IDS.CUSTOM_BG).style.opacity = '0';
+    $(IDS.CUSTOM_BG).style.backgroundImage = '';
+    customize.clearAttribution();
+  }
+
+  $(customize.IDS.RESTORE_DEFAULT)
+      .classList.toggle(
+          customize.CLASSES.OPTION_DISABLED, !info.customBackgroundConfigured);
+  $(customize.IDS.RESTORE_DEFAULT).tabIndex =
+      (info.customBackgroundConfigured ? 0 : -1);
+
+  $(customize.IDS.EDIT_BG)
+      .classList.toggle(
+          CLASSES.ENTRY_POINT_ENHANCED, !info.customBackgroundConfigured);
+
+  if (configData.isGooglePage) {
+    customize.onThemeChange();
+  }
+}
+
+/**
+ * Request data for search suggestions, promo, and the OGB. Insert it into
+ * the page once it's available. For search suggestions this should be almost
+ * immediately as cached data is always used. Promos and the OGB may need
+ * to wait for the asynchronous network request to complete.
+ */
+function requestAndInsertGoogleResources() {
+  if (!$('search-suggestions-loader')) {
+    const ssScript = document.createElement('script');
+    ssScript.id = 'search-suggestions-loader';
+    ssScript.src = 'chrome-search://local-ntp/search-suggestions.js';
+    ssScript.async = false;
+    document.body.appendChild(ssScript);
+    ssScript.onload = function() {
+      injectSearchSuggestions(searchSuggestions);
+    };
+  }
+  if (!$('one-google-loader')) {
+    // Load the OneGoogleBar script. It'll create a global variable |og| which
+    // is a JSON object corresponding to the native OneGoogleBarData type.
+    const ogScript = document.createElement('script');
+    ogScript.id = 'one-google-loader';
+    ogScript.src = 'chrome-search://local-ntp/one-google.js';
+    document.body.appendChild(ogScript);
+    ogScript.onload = function() {
+      injectOneGoogleBar(og);
+    };
+  }
+  if (!$('promo-loader')) {
+    const promoScript = document.createElement('script');
+    promoScript.id = 'promo-loader';
+    promoScript.src = 'chrome-search://local-ntp/promo.js';
+    document.body.appendChild(promoScript);
+    promoScript.onload = function() {
+      injectPromo(promo);
+    };
+  }
+}
+
+/** Sends the current theme info to the most visited iframe. */
+function sendThemeInfoToMostVisitedIframe() {
+  const info = getThemeBackgroundInfo();
+  if (!info) {
+    return;
+  }
+
+  const message = {cmd: 'updateTheme'};
+  message.isThemeDark = info.isNtpBackgroundDark;
+  message.customBackground = info.customBackgroundConfigured;
+  message.useTitleContainer = info.useTitleContainer;
+  message.iconBackgroundColor = convertToRGBAColor(info.iconBackgroundColor);
+  message.useWhiteAddIcon = info.useWhiteAddIcon;
+
+  let titleColor = NTP_DESIGN.titleColor;
+  if (!info.usingDefaultTheme && info.textColorRgba) {
+    titleColor = info.textColorRgba;
+  } else if (info.isNtpBackgroundDark) {
+    titleColor = NTP_DESIGN.titleColorAgainstDark;
+  }
+  message.tileTitleColor = convertToRGBAColor(titleColor);
+
+  const iframe = $(IDS.TILES_IFRAME);
+  if (iframe) {
+    iframe.contentWindow.postMessage(message, '*');
+  }
+}
+
+/**
+ * Sets the visibility of the theme attribution.
+ * @param {boolean} show True to show the attribution.
+ */
+function setAttributionVisibility(show) {
+  $(IDS.ATTRIBUTION).style.display = show ? '' : 'none';
+}
+
+/**
+ * Updates the NTP style according to theme.
+ * @param {Object} themeInfo The information about the theme.
+ */
+function setCustomThemeStyle(themeInfo) {
+  let textColor = '';
+  let textColorLight = '';
+  let mvxFilter = '';
+  if (!themeInfo.usingDefaultTheme) {
+    textColor = convertToRGBAColor(themeInfo.textColorRgba);
+    textColorLight = convertToRGBAColor(themeInfo.textColorLightRgba);
+    mvxFilter = 'drop-shadow(0 0 0 ' + textColor + ')';
+  }
+
+  $(IDS.NTP_CONTENTS)
+      .classList.toggle(CLASSES.DEFAULT_THEME, themeInfo.usingDefaultTheme);
+
+  document.body.style.setProperty('--text-color', textColor);
+  document.body.style.setProperty('--text-color-light', textColorLight);
+}
+
+/**
+ * @param {boolean} focus True to show a dragging focus on the fakebox.
+ */
+function setFakeboxDragFocus(focus) {
+  document.body.classList.toggle(CLASSES.FAKEBOX_DRAG_FOCUS, focus);
+}
+
+/**
+ * @param {boolean} focus True to focus the fakebox.
+ */
+function setFakeboxFocus(focus) {
+  document.body.classList.toggle(CLASSES.FAKEBOX_FOCUS, focus);
+}
+
+/**
+ * @param {boolean} show True to show the fakebox and logo.
+ */
+function setFakeboxVisibility(show) {
+  document.body.classList.toggle(CLASSES.HIDE_FAKEBOX, !show);
+}
+
+/**
+ * Shows the error pop-up notification and triggers a delay to hide it. The
+ * message will be set to |msg|. If |linkName| and |linkOnClick| are present,
+ * shows an error link with text set to |linkName| and onclick handled by
+ * |linkOnClick|.
+ * @param {string} msg The notification message.
+ * @param {?string} linkName The error link text.
+ * @param {?Function} linkOnClick The error link onclick handler.
+ */
+function showErrorNotification(msg, linkName, linkOnClick) {
+  const notification = $(IDS.ERROR_NOTIFICATION);
+  $(IDS.ERROR_NOTIFICATION_MSG).textContent = msg;
+  if (linkName && linkOnClick) {
+    const notificationLink = $(IDS.ERROR_NOTIFICATION_LINK);
+    notificationLink.textContent = linkName;
+    notificationLink.onclick = linkOnClick;
+    notification.classList.add(CLASSES.HAS_LINK);
+  } else {
+    notification.classList.remove(CLASSES.HAS_LINK);
+  }
+  floatUpNotification(notification, $(IDS.ERROR_NOTIFICATION_CONTAINER));
+}
+
+/**
+ * Shows the Most Visited pop-up notification and triggers a delay to hide it.
+ * The message will be set to |msg|.
+ * @param {string} msg The notification message.
+ */
+function showNotification(msg) {
+  $(IDS.NOTIFICATION_MESSAGE).textContent = msg;
+  $(IDS.RESTORE_ALL_LINK).textContent = customLinksEnabled() ?
+      configData.translatedStrings.restoreDefaultLinks :
+      configData.translatedStrings.restoreThumbnailsShort;
+  floatUpNotification($(IDS.NOTIFICATION), $(IDS.NOTIFICATION_CONTAINER));
+  $(IDS.UNDO_LINK).focus();
+}
+
+/**
+ * Renders the attribution if the URL is present, otherwise hides it.
+ * @param {string|undefined} url The URL of the attribution image, if any.
+ * @param {string|undefined} themeBackgroundAlignment The alignment of the theme
+ *     background image. This is used to compute the attribution's alignment.
+ */
+function updateThemeAttribution(url, themeBackgroundAlignment) {
+  if (!url) {
+    setAttributionVisibility(false);
+    return;
+  }
+
+  const attribution = $(IDS.ATTRIBUTION);
+  let attributionImage = attribution.querySelector('img');
+  if (!attributionImage) {
+    attributionImage = new Image();
+    attribution.appendChild(attributionImage);
+  }
+  attributionImage.style.content = url;
+
+  // To avoid conflicts, place the attribution on the left for themes that
+  // right align their background images.
+  attribution.classList.toggle(
+      CLASSES.LEFT_ALIGN_ATTRIBUTION, themeBackgroundAlignment == 'right');
+  setAttributionVisibility(true);
 }
 
 return {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
index bf15f4f..4ce2b837 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.html
@@ -19,6 +19,10 @@
         --menu-link-color: var(--google-blue-600);
         --menu-text-color: var(--google-grey-refresh-700);
         --menu-icon-color: var(--google-grey-refresh-700);
+        /* The tap target extends slightly above each visible menu item. */
+        --tap-target-padding: 3px;
+        /* Width of the keyboard focus border. */
+        --focus-border-width: 1px;
         box-sizing: border-box;
         display: block;
         padding-bottom: 2px;
@@ -36,21 +40,25 @@
       }
 
       /* The <a> is the entire tap target, including the padding around the
-         visible icon and text. */
+       * visible icon and text. */
       a {
         background: transparent;
         display: block;
-        padding: 3px 0;
+        padding: var(--tap-target-padding) 0;
       }
 
       /* The "item" draws the icon, text, and rounded background. */
       a > .item {
         align-items: center;
+        border-block-end-width: var(--focus-border-width);
+        border-block-start-width: var(--focus-border-width);
         /* Always apply border so item doesn't shift when focused. */
         border-color: transparent;
+        border-inline-end-width: var(--focus-border-width);
+        /* No border on window edge (left in LTR, right in RTL). */
+        border-inline-start-width: 0;
         border-radius: 0 20px 20px 0;
         border-style: solid;
-        border-width: 1px 1px 1px 0;
         color: var(--menu-text-color);
         display: flex;
         font-weight: 500;
@@ -60,6 +68,11 @@
         pointer-events: none;
       }
 
+      :host-context([dir=rtl]) a > .item {
+        /* Chrome doesn't support border-start-end-radius, so override. */
+        border-radius: 20px 0 0 20px;
+      }
+
       a.iron-selected > .item {
         background-color: var(--google-blue-50);
         color: var(--menu-link-color);
@@ -131,6 +144,13 @@
           border-bottom: var(--cr-separator-line);  /* override */
         }
       }
+
+      #aboutItem {
+        /* Reserve space so the last menu item isn't too close to the window
+         * bottom edge, 48px under the text baseline. */
+        margin-bottom: calc(
+            48px - calc(var(--tap-target-padding) + var(--focus-border-width)));
+      }
     </style>
     <iron-selector id="topMenu"
         attr-for-selected="href" on-iron-activate="onSelectorActivate_"
@@ -259,7 +279,7 @@
         </iron-selector>
       </iron-collapse>
       <div id="menuSeparator"></div>
-      <a id="about-menu" href="/help">
+      <a id="aboutItem" href="/help">
         <div class="item">
           $i18n{aboutOsPageTitle}
         </div>
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.html b/chrome/browser/resources/settings/internet_page/internet_detail_page.html
index 10ae002..ac938ae 100644
--- a/chrome/browser/resources/settings/internet_page/internet_detail_page.html
+++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.html
@@ -4,7 +4,7 @@
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_choose_mobile.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_ip_config.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_nameservers.html">
-<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_property_list.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/network/network_property_list_mojo.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/network/network_siminfo.html">
 <link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_icon.html">
 <link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_listener_behavior.html">
@@ -120,7 +120,7 @@
           hidden$="[[!showConnect_(managedProperties_, globalPolicy,
               managedNetworkAvailable)]]"
           disabled="[[!enableConnect_(managedProperties_, defaultNetwork,
-              networkPropertiesReceived_, outOfRange_, globalPolicy,
+              propertiesReceived_, outOfRange_, globalPolicy,
               managedNetworkAvailable)]]"
           label="$i18n{networkButtonConnect}"
           pref="[[getFakeVpnConfigPrefForEnforcement_(managedProperties_,
@@ -163,8 +163,8 @@
     <template is="dom-if" if="[[!isSecondaryUser_]]">
       <!-- Prefer this network. -->
       <template is="dom-if"
-          if="[[showPreferNetwork_(networkProperties_, managedProperties_,
-              globalPolicy, managedNetworkAvailable)]]">
+          if="[[showPreferNetwork_(managedProperties_, globalPolicy,
+              managedNetworkAvailable)]]">
         <div class="settings-box"  on-click="onPreferNetworkRowClicked_"
             actionable$="[[!isNetworkPolicyEnforced(
                 managedProperties_.priority)]]">
@@ -232,19 +232,18 @@
         <div class="secondary">[[ipAddress_]]</div>
       </div>
       <!-- Properties to always show if present. -->
-      <template is="dom-if" if="[[hasInfoFields_(networkProperties_)]]">
+      <template is="dom-if" if="[[hasInfoFields_(managedProperties_)]]">
         <div class="settings-box single-column stretch">
-          <network-property-list
-              fields="[[getInfoFields_(networkProperties_)]]"
-              edit-field-types="[[getInfoEditFieldTypes_(networkProperties_)]]"
-              property-dict="[[networkProperties_]]"
+          <network-property-list-mojo
+              fields="[[getInfoFields_(managedProperties_)]]"
+              edit-field-types="[[getInfoEditFieldTypes_(managedProperties_)]]"
+              property-dict="[[managedProperties_]]"
               on-property-change="onNetworkPropertyChange_">
-          </network-property-list>
+          </network-property-list-mojo>
         </div>
       </template>
 
-      <template is="dom-if"
-          if="[[showAdvanced_(networkProperties_, managedProperties_)]]">
+      <template is="dom-if" if="[[showAdvanced_(managedProperties_)]]">
         <!-- Advanced toggle. -->
         <cr-expand-button
             alt="$i18n{networkSectionAdvancedA11yLabel}"
@@ -256,25 +255,25 @@
         <!-- Advanced section -->
         <iron-collapse opened="[[advancedExpanded_]]">
           <div class="settings-box single-column stretch indented first"
-              hidden$="[[!hasAdvancedOrDeviceFields_(networkProperties_)]]">
+              hidden$="[[!hasAdvancedOrDeviceFields_(managedProperties_)]]">
             <!-- Advanced properties -->
-            <network-property-list
-                hidden$="[[!hasAdvancedFields_(networkProperties_)]]"
-                fields="[[getAdvancedFields_(networkProperties_)]]"
-                property-dict="[[networkProperties_]]">
-            </network-property-list>
+            <network-property-list-mojo
+                hidden$="[[!hasAdvancedFields_(managedProperties_)]]"
+                fields="[[getAdvancedFields_(managedProperties_)]]"
+                property-dict="[[managedProperties_]]">
+            </network-property-list-mojo>
             <!-- Device properties -->
-            <network-property-list
-                hidden$="[[!hasDeviceFields_(networkProperties_)]]"
-                fields="[[getDeviceFields_(networkProperties_)]]"
-                property-dict="[[networkProperties_]]">
-            </network-property-list>
+            <network-property-list-mojo
+                hidden$="[[!hasDeviceFields_(managedProperties_)]]"
+                fields="[[getDeviceFields_(managedProperties_)]]"
+                property-dict="[[managedProperties_]]">
+            </network-property-list-mojo>
           </div>
         </iron-collapse>
       </template>
 
-      <template is="dom-if" if="[[hasNetworkSection_(networkProperties_,
-          managedProperties_, globalPolicy, managedNetworkAvailable)]]">
+      <template is="dom-if" if="[[hasNetworkSection_(managedProperties_,
+          globalPolicy, managedNetworkAvailable)]]">
         <!-- Network toggle -->
         <cr-expand-button
             alt="$i18n{networkSectionNetworkExpandA11yLabel}"
@@ -322,8 +321,8 @@
         </iron-collapse>
       </template>
 
-      <template is="dom-if" if="[[hasProxySection_(networkProperties_,
-          managedProperties_, globalPolicy, managedNetworkAvailable)]]">
+      <template is="dom-if" if="[[hasProxySection_(managedProperties_,
+          globalPolicy, managedNetworkAvailable)]]">
         <!-- Proxy toggle -->
         <cr-expand-button
             alt="$i18n{networkSectionProxyExpandA11yLabel}"
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
index d27af7b..aefc637 100644
--- a/chrome/browser/resources/settings/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
@@ -32,17 +32,7 @@
       notify: true,
     },
 
-    /**
-     * The current properties for the network matching |guid|. Note: This may
-     * become set to |undefined| after it is initially set if the network is no
-     * longer visible, so always test that it is set before accessing it.
-     * @private {!CrOnc.NetworkProperties|undefined}
-     */
-    networkProperties_: {
-      type: Object,
-    },
-
-    /** @private {!OncMojo.ManagedProperties|undefined} */
+    /** @private {!chromeos.networkConfig.mojom.ManagedProperties|undefined} */
     managedProperties_: {
       type: Object,
       observer: 'managedPropertiesChanged_',
@@ -205,12 +195,12 @@
    * prevents setProperties from being called when setting default properties.
    * @private {boolean}
    */
-  networkPropertiesReceived_: false,
+  propertiesReceived_: false,
 
   /**
    * Set in currentRouteChanged() if the showConfigure URL query
    * parameter is set to true. The dialog cannot be shown until the
-   * network properties have been fetched in networkPropertiesChanged_().
+   * network properties have been fetched in managedPropertiesChanged_().
    * @private {boolean}
    */
   shouldShowConfigureWhenNetworkLoaded_: false,
@@ -253,27 +243,20 @@
 
     this.shouldShowConfigureWhenNetworkLoaded_ =
         queryParams.get('showConfigure') == 'true';
-    const type = /** @type {!chrome.networkingPrivate.NetworkType} */ (
-                     queryParams.get('type')) ||
-        CrOnc.Type.WI_FI;
+    const type = queryParams.get('type') || 'WiFi';
     const name = queryParams.get('name') || type;
     this.init(guid, type, name);
   },
 
   /**
    * @param {string} guid
-   * @param {!chrome.networkingPrivate.NetworkType} type
+   * @param {string} type
    * @param {string} name
    */
   init: function(guid, type, name) {
     this.guid = guid;
-    // Set basic networkProperties until they are loaded.
-    this.networkPropertiesReceived_ = false;
-    this.networkProperties_ = {
-      GUID: this.guid,
-      Type: type,
-      Name: {Active: name},
-    };
+    // Set default properties until they are loaded.
+    this.propertiesReceived_ = false;
     this.managedProperties_ = OncMojo.getDefaultManagedProperties(
         OncMojo.getNetworkTypeFromString(type), this.guid, name);
     this.didSetFocus_ = false;
@@ -294,9 +277,8 @@
       // Clear network properties before navigating away to ensure that a future
       // navigation back to the details page does not show a flicker of
       // incorrect text. See https://crbug.com/905986.
-      this.networkProperties_ = undefined;
       this.managedProperties_ = undefined;
-      this.networkPropertiesReceived_ = false;
+      this.propertiesReceived_ = false;
 
       settings.navigateToPreviousRoute();
     });
@@ -323,7 +305,7 @@
    * @param {!chromeos.networkConfig.mojom.NetworkStateProperties} network
    */
   onNetworkStateChanged: function(network) {
-    if (!this.guid || !this.networkProperties_) {
+    if (!this.guid || !this.managedProperties_) {
       return;
     }
     if (network.guid == this.guid) {
@@ -360,8 +342,7 @@
     }
 
     // Set the IPAddress property to the IPv4 Address.
-    const ipv4 =
-        OncMojo.getIPConfigForType(this.managedProperties_, CrOnc.IPType.IPV4);
+    const ipv4 = OncMojo.getIPConfigForType(this.managedProperties_, 'IPv4');
     this.ipAddress_ = (ipv4 && ipv4.ipAddress) || '';
 
     // Update the detail page title.
@@ -404,7 +385,7 @@
 
   /** @private */
   autoConnectPrefChanged_: function() {
-    if (!this.networkProperties_ || !this.guid) {
+    if (!this.propertiesReceived_) {
       return;
     }
     const config = {};
@@ -456,7 +437,7 @@
 
   /** @private */
   preferNetworkChanged_: function() {
-    if (!this.networkProperties_ || !this.guid) {
+    if (!this.propertiesReceived_) {
       return;
     }
     const config = {};
@@ -467,12 +448,13 @@
   /** @private */
   checkNetworkExists_: function() {
     const filter = {
-      networkType: CrOnc.Type.ALL,
-      visible: true,
-      configured: false
+      filter: mojom.FilterType.kVisible,
+      networkType: mojom.NetworkType.kAll,
+      limit: mojom.kNoLimit,
     };
-    this.networkingPrivate.getNetworks(filter, networks => {
-      if (networks.find(network => network.GUID == this.guid)) {
+    this.networkConfig_.getNetworkState(this.guid).then(response => {
+      if (response.result) {
+        // Don't update the state, a change event will trigger the update.
         return;
       }
       this.outOfRange_ = true;
@@ -485,10 +467,7 @@
     });
   },
 
-  /**
-   * Calls networkingPrivate.getProperties for this.guid.
-   * @private
-   */
+  /** @private */
   getNetworkDetails_: function() {
     assert(this.guid);
     if (this.isSecondaryUser_) {
@@ -496,62 +475,40 @@
         this.getStateCallback_(response.result);
       });
     } else {
-      this.networkingPrivate.getManagedProperties(
-          this.guid, this.getPropertiesCallback_.bind(this));
+      this.networkConfig_.getManagedProperties(this.guid).then(response => {
+        this.getPropertiesCallback_(response.result);
+      });
     }
   },
 
   /**
-   * networkingPrivate.getProperties callback.
-   * @param {!CrOnc.NetworkProperties} properties The network properties.
+   * @param {?mojom.ManagedProperties} properties
    * @private
    */
   getPropertiesCallback_: function(properties) {
-    if (chrome.runtime.lastError) {
-      const message = chrome.runtime.lastError.message;
-      if (message == 'Error.InvalidNetworkGuid') {
-        console.error('Details page: GUID no longer exists: ' + this.guid);
-      } else {
-        console.error(
-            'Unexpected networkingPrivate.getManagedProperties error: ' +
-            message + ' For: ' + this.guid);
-      }
-      this.close();
-      return;
-    }
-
     // Details page was closed while request was in progress, ignore the result.
     if (!this.guid) {
       return;
     }
 
     if (!properties) {
-      // Edge case, may occur when disabling. Close this.
+      console.error('Details page: GUID no longer exists: ' + this.guid);
       this.close();
       return;
     }
 
-    // Get the managed properties and then update networkProperties_, etc.
-    this.networkConfig_.getManagedProperties(this.guid).then(response => {
-      if (!response.result) {
-        // Edge case, may occur when disabling. Close this.
-        this.close();
-        return;
-      }
-      this.managedProperties_ = response.result;
-      // Detail page should not be shown when Arc VPN is not connected.
-      if (this.isArcVpn_(this.managedProperties_) &&
-          !this.isConnectedState_(this.managedProperties_)) {
-        this.guid = '';
-        this.close();
-      }
-      this.networkProperties_ = properties;
-      this.networkPropertiesReceived_ = true;
-      this.outOfRange_ = false;
-      if (!this.deviceState_) {
-        this.getDeviceState_();
-      }
-    });
+    this.managedProperties_ = properties;
+    // Detail page should not be shown when Arc VPN is not connected.
+    if (this.isArcVpn_(this.managedProperties_) &&
+        !this.isConnectedState_(this.managedProperties_)) {
+      this.guid = '';
+      this.close();
+    }
+    this.propertiesReceived_ = true;
+    this.outOfRange_ = false;
+    if (!this.deviceState_) {
+      this.getDeviceState_();
+    }
   },
 
   /**
@@ -564,38 +521,12 @@
       this.close();
       return;
     }
-    const type = /** @type {CrOnc.Type} */ (
-        OncMojo.getNetworkTypeString(networkState.type));
-
-    let connectionState;
-    switch (networkState.connectionState) {
-      case mojom.ConnectionStateType.kOnline:
-      case mojom.ConnectionStateType.kConnected:
-      case mojom.ConnectionStateType.kPortal:
-        connectionState = CrOnc.ConnectionState.CONNECTED;
-        break;
-      case mojom.ConnectionStateType.kConnecting:
-        connectionState = CrOnc.ConnectionState.CONNECTING;
-        break;
-      case mojom.ConnectionStateType.kNotConnected:
-        connectionState = CrOnc.ConnectionState.NOT_CONNECTED;
-        break;
-    }
-
-    this.networkProperties_ = {
-      GUID: networkState.guid,
-      Name: {Active: networkState.name},
-      Type: type,
-      Connectable: networkState.connectable,
-      ConnectionState: connectionState,
-    };
     this.managedProperties_ = OncMojo.getDefaultManagedProperties(
-        OncMojo.getNetworkTypeFromString(type), networkState.guid,
-        networkState.name);
+        networkState.type, networkState.guid, networkState.name);
     this.managedProperties_.connectable = networkState.connectable;
     this.managedProperties_.connectionState = networkState.connectionState;
 
-    this.networkPropertiesReceived_ = true;
+    this.propertiesReceived_ = true;
     this.outOfRange_ = false;
   },
 
@@ -611,29 +542,11 @@
   },
 
   /**
-   * @param {!chrome.networkingPrivate.NetworkConfigProperties} onc The ONC
-   *     network properties.
-   * @private
-   */
-  setNetworkProperties_: function(onc) {
-    if (!this.networkPropertiesReceived_ || !this.guid) {
-      return;
-    }
-    this.networkingPrivate.setProperties(this.guid, onc, () => {
-      if (chrome.runtime.lastError) {
-        // An error typically indicates invalid input; request the properties
-        // to update any invalid fields.
-        this.getNetworkDetails_();
-      }
-    });
-  },
-
-  /**
    * @param {!mojom.ConfigProperties} config
    * @private
    */
   setMojoNetworkProperties_: function(config) {
-    if (!this.networkPropertiesReceived_ || !this.guid) {
+    if (!this.propertiesReceived_ || !this.guid) {
       return;
     }
     this.networkConfig_.setProperties(this.guid, config).then(response => {
@@ -647,18 +560,6 @@
   },
 
   /**
-   * @return {!chrome.networkingPrivate.NetworkConfigProperties} An ONC
-   *     dictionary with just the Type property set. Used for passing properties
-   *     to setNetworkProperties_.
-   * @private
-   */
-  getEmptyNetworkProperties_: function() {
-    const type = this.networkProperties_ ? this.networkProperties_.Type :
-                                           CrOnc.Type.WI_FI;
-    return {Type: type};
-  },
-
-  /**
    * @param {!mojom.ManagedProperties} managedProperties
    * @param {boolean} outOfRange
    * @return {string} The text to display for the network connection state.
@@ -703,14 +604,13 @@
       return this.i18n('networkAllowDataRoamingDisabled');
     }
 
-    return managedProperties.cellular.roamingState ==
-            CrOnc.RoamingState.ROAMING ?
+    return managedProperties.cellular.roamingState == 'Roaming' ?
         this.i18n('networkAllowDataRoamingEnabledRoaming') :
         this.i18n('networkAllowDataRoamingEnabledHome');
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties|undefined} managedProperties
+   * @param {!mojom.ManagedProperties|undefined} managedProperties
    * @return {boolean} True if the network is connected.
    * @private
    */
@@ -720,7 +620,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -730,7 +630,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -740,7 +640,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -750,7 +650,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -760,7 +660,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
    * @return {boolean}
@@ -782,7 +682,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
    * @return {boolean}
@@ -813,7 +713,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -827,7 +727,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -847,7 +747,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -864,7 +764,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
    * @return {boolean}
@@ -901,7 +801,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.settingsPrivate.PrefObject} vpnConfigAllowed
    * @return {boolean}
    * @private
@@ -915,7 +815,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.settingsPrivate.PrefObject} vpnConfigAllowed
    * @return {boolean}
    * @private
@@ -934,7 +834,7 @@
 
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    */
   hasRecommendedFields_: function(managedProperties) {
@@ -958,7 +858,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -981,8 +881,7 @@
     // Only show for connected networks or LTE networks with a valid MDN.
     if (!this.isConnectedState_(managedProperties)) {
       const technology = managedProperties.cellular.networkTechnology;
-      if (technology != CrOnc.NetworkTechnology.LTE &&
-          technology != CrOnc.NetworkTechnology.LTE_ADVANCED) {
+      if (technology != 'LTE' && technology != 'LTEAdvanced') {
         return false;
       }
       if (!managedProperties.cellular.mdn) {
@@ -994,9 +893,9 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {?OncMojo.NetworkStateProperties} defaultNetwork
-   * @param {boolean} networkPropertiesReceived
+   * @param {boolean} propertiesReceived
    * @param {boolean} outOfRange
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
@@ -1004,13 +903,13 @@
    * @private
    */
   enableConnect_: function(
-      managedProperties, defaultNetwork, networkPropertiesReceived, outOfRange,
+      managedProperties, defaultNetwork, propertiesReceived, outOfRange,
       globalPolicy, managedNetworkAvailable) {
     if (!this.showConnect_(
             managedProperties, globalPolicy, managedNetworkAvailable)) {
       return false;
     }
-    if (!networkPropertiesReceived || outOfRange) {
+    if (!propertiesReceived || outOfRange) {
       return false;
     }
     if (managedProperties.type == mojom.NetworkType.kVPN && !defaultNetwork) {
@@ -1157,17 +1056,17 @@
    * Event triggered for elements associated with network properties.
    * @param {!CustomEvent<!{
    *     field: string,
-   *     value: !CrOnc.NetworkPropertyType
+   *     value: (string|number|boolean|!Array<string>)
    * }>} e
    * @private
    */
   onNetworkPropertyChange_: function(e) {
-    if (!this.networkProperties_) {
+    if (!this.propertiesReceived_) {
       return;
     }
     const field = e.detail.field;
     const value = e.detail.value;
-    const onc = this.getEmptyNetworkProperties_();
+    const config = {};
     const valueType = typeof value;
     if (valueType != 'string' && valueType != 'number' &&
         valueType != 'boolean' && !Array.isArray(value)) {
@@ -1176,14 +1075,13 @@
           ' Value: ' + JSON.stringify(value));
       return;
     }
-    CrOnc.setProperty(onc, field, value);
+    OncMojo.setConfigProperty(config, field, value);
     // Ensure any required configuration properties are also set.
-    if (field.match(/^VPN/)) {
-      const vpnType = CrOnc.getActiveValue(this.networkProperties_.VPN.Type);
-      assert(vpnType);
-      CrOnc.setProperty(onc, 'VPN.Type', vpnType);
+    if (this.managedProperties_.vpn && config.vpn &&
+        config.vpn.type === undefined) {
+      config.vpn.type = this.managedProperties_.vpn.type;
     }
-    this.setNetworkProperties_(onc);
+    this.setMojoNetworkProperties_(config);
   },
 
   /**
@@ -1191,7 +1089,7 @@
    * @private
    */
   onApnChange_: function(event) {
-    if (!this.networkPropertiesReceived_) {
+    if (!this.propertiesReceived_) {
       return;
     }
     const apn = event.detail;
@@ -1225,11 +1123,14 @@
    * @private
    */
   onProxyChange_: function(event) {
+    if (!this.propertiesReceived_) {
+      return;
+    }
     this.setMojoNetworkProperties_({proxySettings: event.detail});
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
    * @return {boolean} True if the shared message should be shown.
@@ -1245,7 +1146,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
    * @return {boolean} True if the AutoConnect checkbox should be shown.
@@ -1262,7 +1163,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean} Whether the toggle for the Always-on VPN feature is
    * displayed.
    * @private
@@ -1286,17 +1187,15 @@
   },
 
   /**
-   * @param {!CrOnc.NetworkProperties} networkProperties
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
    * @return {boolean} True if the prefer network checkbox should be shown.
    * @private
    */
   showPreferNetwork_: function(
-      networkProperties, managedProperties, globalPolicy,
-      managedNetworkAvailable) {
-    if (!networkProperties || !managedProperties) {
+      managedProperties, globalPolicy, managedNetworkAvailable) {
+    if (!managedProperties) {
       return false;
     }
 
@@ -1336,7 +1235,7 @@
    */
   hasVisibleFields_: function(fields) {
     for (let i = 0; i < fields.length; ++i) {
-      const value = this.get(fields[i], this.networkProperties_);
+      const value = this.get(fields[i], this.managedProperties_);
       if (value !== undefined && value !== '') {
         return true;
       }
@@ -1358,63 +1257,66 @@
    * @private
    */
   getInfoFields_: function() {
-    if (!this.networkProperties_) {
+    if (!this.managedProperties_) {
       return [];
     }
 
     /** @type {!Array<string>} */ const fields = [];
-    const type = this.networkProperties_.Type;
-    if (type == CrOnc.Type.CELLULAR && !!this.networkProperties_.Cellular) {
+    const type = this.managedProperties_.type;
+    if (type == mojom.NetworkType.kCellular) {
       fields.push(
-          'Cellular.ActivationState', 'RestrictedConnectivity',
-          'Cellular.ServingOperator.Name');
-    } else if (type == CrOnc.Type.TETHER && !!this.networkProperties_.Tether) {
+          'cellular.activationState', 'restrictedConnectivity',
+          'cellular.servingOperator.name');
+    } else if (type == mojom.NetworkType.kTether) {
       fields.push(
-          'Tether.BatteryPercentage', 'Tether.SignalStrength',
-          'Tether.Carrier');
-    } else if (type == CrOnc.Type.VPN && !!this.networkProperties_.VPN) {
-      const vpnType = CrOnc.getActiveValue(this.networkProperties_.VPN.Type);
+          'tether.batteryPercentage', 'tether.signalStrength',
+          'tether.carrier');
+    } else if (type == mojom.NetworkType.kVPN) {
+      const vpnType = this.managedProperties_.vpn.type;
       switch (vpnType) {
-        case CrOnc.VPNType.THIRD_PARTY_VPN:
-          fields.push('VPN.ThirdPartyVPN.ProviderName');
+        case mojom.VPNType.kThirdPartyVPN:
+          fields.push('vpn.providerName');
           break;
-        case CrOnc.VPNType.ARCVPN:
-          fields.push('VPN.Type');
+        case mojom.VPNType.kArcVPN:
+          fields.push('vpn.type');
           break;
-        case CrOnc.VPNType.OPEN_VPN:
+        case mojom.VPNType.kOpenVPN:
           fields.push(
-              'VPN.Type', 'VPN.Host', 'VPN.OpenVPN.Username',
-              'VPN.OpenVPN.ExtraHosts');
+              'vpn.type', 'vpn.host', 'vpn.openVpn.username',
+              'vpn.openVpn.extraHosts');
           break;
-        case CrOnc.VPNType.L2TP_IPSEC:
-          fields.push('VPN.Type', 'VPN.Host', 'VPN.L2TP.Username');
+        case mojom.VPNType.kL2TPIPsec:
+          fields.push('vpn.type', 'vpn.host', 'vpn.l2tp.username');
           break;
       }
-    } else if (type == CrOnc.Type.WI_FI) {
-      fields.push('RestrictedConnectivity');
+    } else if (type == mojom.NetworkType.kWiFi) {
+      fields.push('restrictedConnectivity');
     }
     return fields;
   },
 
   /**
+   * Provides the list of editable fields to <network-property-list>.
+   * NOTE: Entries added to this list must be reflected in ConfigProperties in
+   * chromeos.network_config.mojom and handled in the service implementation.
    * @return {!Object} A dictionary of editable fields in the info section.
    * @private
    */
   getInfoEditFieldTypes_: function() {
-    if (!this.networkProperties_) {
+    if (!this.managedProperties_) {
       return [];
     }
 
     /** @dict */ const editFields = {};
-    const type = this.networkProperties_.Type;
-    if (type == CrOnc.Type.VPN && !!this.networkProperties_.VPN) {
-      const vpnType = CrOnc.getActiveValue(this.networkProperties_.VPN.Type);
-      if (vpnType != CrOnc.VPNType.THIRD_PARTY_VPN) {
-        editFields['VPN.Host'] = 'String';
+    const type = this.managedProperties_.type;
+    if (type == mojom.NetworkType.kVPN) {
+      const vpnType = this.managedProperties_.vpn.type;
+      if (vpnType != mojom.VPNType.kThirdPartyVPN) {
+        editFields['vpn.host'] = 'String';
       }
-      if (vpnType == CrOnc.VPNType.OPEN_VPN) {
-        editFields['VPN.OpenVPN.Username'] = 'String';
-        editFields['VPN.OpenVPN.ExtraHosts'] = 'StringArray';
+      if (vpnType == mojom.VPNType.kOpenVPN) {
+        editFields['vpn.openVpn.username'] = 'String';
+        editFields['vpn.openVpn.extraHosts'] = 'StringArray';
       }
     }
     return editFields;
@@ -1425,24 +1327,24 @@
    * @private
    */
   getAdvancedFields_: function() {
-    if (!this.networkProperties_) {
+    if (!this.managedProperties_) {
       return [];
     }
 
     /** @type {!Array<string>} */ const fields = [];
-    const type = this.networkProperties_.Type;
-    if (type != CrOnc.Type.TETHER) {
-      fields.push('MacAddress');
+    const type = this.managedProperties_.type;
+    if (type != mojom.NetworkType.kTether) {
+      fields.push('macAddress');
     }
-    if (type == CrOnc.Type.CELLULAR && !!this.networkProperties_.Cellular) {
+    if (type == mojom.NetworkType.kCellular) {
       fields.push(
-          'Cellular.Family', 'Cellular.NetworkTechnology',
-          'Cellular.ServingOperator.Code');
-    } else if (type == CrOnc.Type.WI_FI) {
+          'cellular.family', 'cellular.networkTechnology',
+          'cellular.servingOperator.code');
+    } else if (type == mojom.NetworkType.kWiFi) {
       fields.push(
-          'WiFi.SSID', 'WiFi.BSSID', 'WiFi.SignalStrength', 'WiFi.Security',
-          'WiFi.EAP.Outer', 'WiFi.EAP.Inner', 'WiFi.EAP.SubjectMatch',
-          'WiFi.EAP.Identity', 'WiFi.EAP.AnonymousIdentity', 'WiFi.Frequency');
+          'wifi.ssid', 'wifi.bssid', 'wifi.signalStrength', 'wifi.security',
+          'wifi.eap.outer', 'wifi.eap.inner', 'wifi.eap.subjectMatch',
+          'wifi.eap.identity', 'wifi.eap.anonymousIdentity', 'wifi.frequency');
     }
     return fields;
   },
@@ -1452,36 +1354,33 @@
    * @private
    */
   getDeviceFields_: function() {
-    if (!this.networkProperties_ ||
-        this.networkProperties_.Type !== CrOnc.Type.CELLULAR) {
+    if (!this.managedProperties_ ||
+        this.managedProperties_.type !== mojom.NetworkType.kCellular) {
       return [];
     }
 
     return [
-      'Cellular.HomeProvider.Name', 'Cellular.HomeProvider.Country',
-      'Cellular.HomeProvider.Code', 'Cellular.Manufacturer', 'Cellular.ModelID',
-      'Cellular.FirmwareRevision', 'Cellular.HardwareRevision', 'Cellular.ESN',
-      'Cellular.ICCID', 'Cellular.IMEI', 'Cellular.IMSI', 'Cellular.MDN',
-      'Cellular.MEID', 'Cellular.MIN'
+      'cellular.homeProvider.name', 'cellular.homeProvider.country',
+      'cellular.homeProvider.code', 'cellular.manufacturer', 'cellular.modelId',
+      'cellular.firmwareRevision', 'cellular.hardwareRevision', 'cellular.esn',
+      'cellular.iccid', 'cellular.imei', 'cellular.imsi', 'cellular.mdn',
+      'cellular.meid', 'cellular.min'
     ];
   },
 
   /**
-   * @param {!CrOnc.NetworkProperties} networkProperties
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
-  showAdvanced_: function(networkProperties, managedProperties) {
+  showAdvanced_: function(managedProperties) {
     if (!managedProperties ||
         managedProperties.type == mojom.NetworkType.kTether) {
       // These settings apply to the underlying WiFi network, not the Tether
       // network.
       return false;
     }
-    return this.hasAdvancedFields_() || this.hasDeviceFields_() ||
-        (managedProperties.type != mojom.NetworkType.kVPN &&
-         this.isRememberedOrConnected_(managedProperties));
+    return this.hasAdvancedFields_() || this.hasDeviceFields_();
   },
 
   /**
@@ -1509,17 +1408,15 @@
   },
 
   /**
-   * @param {!CrOnc.NetworkProperties} networkProperties
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
    * @return {boolean}
    * @private
    */
   hasNetworkSection_: function(
-      networkProperties, managedProperties, globalPolicy,
-      managedNetworkAvailable) {
-    if (!networkProperties || !managedProperties ||
+      managedProperties, globalPolicy, managedNetworkAvailable) {
+    if (!managedProperties ||
         managedProperties.type == mojom.NetworkType.kTether) {
       // These settings apply to the underlying WiFi network, not the Tether
       // network.
@@ -1536,17 +1433,15 @@
   },
 
   /**
-   * @param {!CrOnc.NetworkProperties} networkProperties
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @param {!chrome.networkingPrivate.GlobalPolicy} globalPolicy
    * @param {boolean} managedNetworkAvailable
    * @return {boolean}
    * @private
    */
   hasProxySection_: function(
-      networkProperties, managedProperties, globalPolicy,
-      managedNetworkAvailable) {
-    if (!networkProperties || !managedProperties ||
+      managedProperties, globalPolicy, managedNetworkAvailable) {
+    if (!managedProperties ||
         managedProperties.type == mojom.NetworkType.kTether) {
       // Proxy settings apply to the underlying WiFi network, not the Tether
       // network.
@@ -1560,7 +1455,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -1571,7 +1466,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -1582,7 +1477,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
@@ -1593,7 +1488,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties|undefined} managedProperties
+   * @param {!mojom.ManagedProperties|undefined} managedProperties
    * @return {boolean}
    * @private
    */
@@ -1604,7 +1499,7 @@
   },
 
   /**
-   * @param {!OncMojo.ManagedProperties|undefined} managedProperties
+   * @param {!mojom.ManagedProperties|undefined} managedProperties
    * @return {boolean}
    * @private
    */
@@ -1616,7 +1511,7 @@
 
   /**
    * @param {string} ipAddress
-   * @param {!OncMojo.ManagedProperties} managedProperties
+   * @param {!mojom.ManagedProperties} managedProperties
    * @return {boolean}
    * @private
    */
diff --git a/chrome/browser/resources/settings/internet_page/network_summary_item.js b/chrome/browser/resources/settings/internet_page/network_summary_item.js
index 228df36d..8f37d8dc 100644
--- a/chrome/browser/resources/settings/internet_page/network_summary_item.js
+++ b/chrome/browser/resources/settings/internet_page/network_summary_item.js
@@ -184,8 +184,7 @@
       return true;
     }
     const simLockType = deviceState.simLockStatus.lockType;
-    return simLockType == CrOnc.LockType.PIN ||
-        simLockType == CrOnc.LockType.PUK;
+    return simLockType == 'sim-pin' || simLockType == 'sim-puk';
   },
 
   /**
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 58525140..60f493f 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -480,10 +480,7 @@
           r.PLUGIN_VM.createChild('/pluginVm/sharedPaths');
     }
 
-    if (loadTimeData.valueExists('assistantEnabled') &&
-        loadTimeData.getBoolean('assistantEnabled')) {
-      r.GOOGLE_ASSISTANT = r.SEARCH.createChild('/googleAssistant');
-    }
+    r.GOOGLE_ASSISTANT = r.SEARCH.createChild('/googleAssistant');
 
     // This if/else accounts for sections that were added or refactored in
     // the settings split (crbug.com/950007) and some routes that were created
diff --git a/chrome/browser/resources/tab_strip/tab.html b/chrome/browser/resources/tab_strip/tab.html
index 1c46717..3964f17b 100644
--- a/chrome/browser/resources/tab_strip/tab.html
+++ b/chrome/browser/resources/tab_strip/tab.html
@@ -21,6 +21,7 @@
     box-sizing: border-box;
     display: flex;
     height: 40px;
+    justify-content: center;
     margin: 0;
     padding-inline-end: 4px;
     padding-inline-start: 12px;
@@ -69,6 +70,18 @@
     background: var(--tabstrip-card-background-color);
     flex: 1;
   }
+
+  /* Pinned tab styles */
+  :host([pinned]) #title {
+    border-block-end: 0;
+    height: 100%;
+  }
+
+  :host([pinned]) #titleText,
+  :host([pinned]) #close,
+  :host([pinned]) #thumbnail {
+    display: none;
+  }
 </style>
 
 <header id="title">
diff --git a/chrome/browser/resources/tab_strip/tab.js b/chrome/browser/resources/tab_strip/tab.js
index 25dac465..769d91ed 100644
--- a/chrome/browser/resources/tab_strip/tab.js
+++ b/chrome/browser/resources/tab_strip/tab.js
@@ -45,6 +45,7 @@
   /** @param {!Tab} tab */
   set tab(tab) {
     this.toggleAttribute('active', tab.active);
+    this.toggleAttribute('pinned', tab.pinned);
 
     if (!this.tab_ || this.tab_.title !== tab.title) {
       this.titleTextEl_.textContent = tab.title;
diff --git a/chrome/browser/resources/tab_strip/tab_list.html b/chrome/browser/resources/tab_strip/tab_list.html
index ef5e60d..3f8c83fb 100644
--- a/chrome/browser/resources/tab_strip/tab_list.html
+++ b/chrome/browser/resources/tab_strip/tab_list.html
@@ -5,6 +5,14 @@
     width: fit-content;
   }
 
+  #pinnedTabsContainer {
+    display: grid;
+    grid-auto-columns: 50px;
+    grid-auto-flow: column;
+    grid-gap: 10px;
+    grid-template-rows: repeat(4, 50px);
+  }
+
   #tabsContainer {
     display: grid;
     grid-auto-columns: 280px;
@@ -12,6 +20,11 @@
     grid-gap: 16px;
     grid-template-rows: 230px;
   }
+
+  #pinnedTabsContainer:not(:empty) + #tabsContainer {
+    margin-inline-start: 16px;
+  }
 </style>
 
+<div id="pinnedTabsContainer"></div>
 <div id="tabsContainer"></div>
diff --git a/chrome/browser/resources/tab_strip/tab_list.js b/chrome/browser/resources/tab_strip/tab_list.js
index 1a2c0c9..2cad026 100644
--- a/chrome/browser/resources/tab_strip/tab_list.js
+++ b/chrome/browser/resources/tab_strip/tab_list.js
@@ -16,6 +16,11 @@
   constructor() {
     super();
 
+    /** @private {!Element} */
+    this.pinnedTabsContainerElement_ =
+        /** @type {!Element} */ (
+            this.shadowRoot.querySelector('#pinnedTabsContainer'));
+
     /** @private {!TabsApiProxy} */
     this.tabsApi_ = TabsApiProxy.getInstance();
 
@@ -35,8 +40,6 @@
     this.tabsApi_.getCurrentWindow().then((currentWindow) => {
       this.windowId_ = currentWindow.id;
 
-      const fragment = document.createDocumentFragment();
-
       // TODO(johntlee): currentWindow.tabs is guaranteed to be defined because
       // `populate: true` is passed in as part of the arguments to the API.
       // Once the closure compiler is able to type `assert` to return a truthy
@@ -45,11 +48,10 @@
       if (currentWindow.tabs) {
         for (const tab of currentWindow.tabs) {
           if (tab) {
-            this.onTabCreated_(tab, fragment);
+            this.onTabCreated_(tab);
           }
         }
       }
-      this.tabsContainerElement_.appendChild(fragment);
 
       this.tabsApiHandler_.onActivated.addListener(
           this.onTabActivated_.bind(this));
@@ -76,19 +78,31 @@
    * @private
    */
   findTabElement_(tabId) {
-    return /** @type {?TabElement} */ (this.tabsContainerElement_.querySelector(
-        `tabstrip-tab[data-tab-id="${tabId}"]`));
+    return /** @type {?TabElement} */ (
+        this.shadowRoot.querySelector(`tabstrip-tab[data-tab-id="${tabId}"]`));
   }
 
   /**
    * @param {!TabElement} tabElement
    * @param {number} index
-   * @param {!Node=} opt_parent
    * @private
    */
-  insertTabAt_(tabElement, index, opt_parent) {
-    (opt_parent || this.tabsContainerElement_)
-        .insertBefore(tabElement, this.tabsContainerElement_.children[index]);
+  insertTabOrMoveTo_(tabElement, index) {
+    // Remove the tabElement if it already exists in the DOM
+    tabElement.remove();
+
+    if (tabElement.tab && tabElement.tab.pinned) {
+      this.pinnedTabsContainerElement_.insertBefore(
+          tabElement, this.pinnedTabsContainerElement_.childNodes[index]);
+      return;
+    }
+
+    // Pinned tabs are in their own container, so the index of non-pinned
+    // tabs need to be offset by the number of pinned tabs
+    const offsetIndex =
+        index - this.pinnedTabsContainerElement_.childElementCount;
+    this.tabsContainerElement_.insertBefore(
+        tabElement, this.tabsContainerElement_.childNodes[offsetIndex]);
   }
 
   /**
@@ -101,7 +115,7 @@
     }
 
     const previouslyActiveTab =
-        this.tabsContainerElement_.querySelector('tabstrip-tab[active]');
+        this.shadowRoot.querySelector('tabstrip-tab[active]');
     if (previouslyActiveTab) {
       previouslyActiveTab.tab = /** @type {!Tab} */ (
           Object.assign({}, previouslyActiveTab.tab, {active: false}));
@@ -114,15 +128,14 @@
 
   /**
    * @param {!Tab} tab
-   * @param {!Node=} opt_parent
    * @private
    */
-  onTabCreated_(tab, opt_parent) {
+  onTabCreated_(tab) {
     if (tab.windowId !== this.windowId_) {
       return;
     }
 
-    this.insertTabAt_(this.createTabElement_(tab), tab.index, opt_parent);
+    this.insertTabOrMoveTo_(this.createTabElement_(tab), tab.index);
   }
 
   /**
@@ -155,6 +168,12 @@
     const tabElement = this.findTabElement_(tabId);
     if (tabElement) {
       tabElement.tab = tab;
+
+      if (changeInfo.pinned !== undefined) {
+        // If the tab is being pinned or unpinned, we need to move it to its new
+        // location
+        this.insertTabOrMoveTo_(tabElement, tab.index);
+      }
     }
   }
 }
diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc
index f059cb2..348e342 100644
--- a/chrome/browser/sessions/session_restore_browsertest.cc
+++ b/chrome/browser/sessions/session_restore_browsertest.cc
@@ -52,6 +52,7 @@
 #include "components/keep_alive_registry/keep_alive_types.h"
 #include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/sessions/content/content_live_tab.h"
+#include "components/sessions/content/content_test_helper.h"
 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
 #include "components/sessions/core/session_types.h"
 #include "components/sessions/core/tab_restore_service.h"
@@ -81,6 +82,7 @@
 #include "ui/aura/window.h"
 #endif
 
+using sessions::ContentTestHelper;
 using sessions::SerializedNavigationEntry;
 using sessions::SerializedNavigationEntryTestHelper;
 
@@ -717,11 +719,9 @@
   tab.current_navigation_index = 1;
   tab.pinned = false;
   tab.navigations.push_back(
-      SerializedNavigationEntryTestHelper::CreateNavigation(url1.spec(),
-                                                            "one"));
+      ContentTestHelper::CreateNavigation(url1.spec(), "one"));
   tab.navigations.push_back(
-      SerializedNavigationEntryTestHelper::CreateNavigation(url2.spec(),
-                                                            "two"));
+      ContentTestHelper::CreateNavigation(url2.spec(), "two"));
 
   for (size_t i = 0; i < tab.navigations.size(); ++i) {
     ASSERT_FALSE(tab.navigations[i].timestamp().is_null());
@@ -798,9 +798,9 @@
   GURL url1("http://google.com");
   GURL url2("http://google2.com");
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(url1.spec(), "one");
+      ContentTestHelper::CreateNavigation(url1.spec(), "one");
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(url2.spec(), "two");
+      ContentTestHelper::CreateNavigation(url2.spec(), "two");
   SerializedNavigationEntryTestHelper::SetIsOverridingUserAgent(true, &nav2);
 
   // Set up the restore data -- one window with two tabs.
@@ -812,8 +812,7 @@
     tab1->current_navigation_index = 0;
     tab1->pinned = true;
     tab1->navigations.push_back(
-        sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-            url1.spec(), "one"));
+        ContentTestHelper::CreateNavigation(url1.spec(), "one"));
     window.tabs.push_back(std::move(tab1));
   }
 
@@ -823,8 +822,7 @@
     tab2->current_navigation_index = 0;
     tab2->pinned = false;
     tab2->navigations.push_back(
-        sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-            url2.spec(), "two"));
+        ContentTestHelper::CreateNavigation(url2.spec(), "two"));
     window.tabs.push_back(std::move(tab2));
   }
 
diff --git a/chrome/browser/sessions/session_service_unittest.cc b/chrome/browser/sessions/session_service_unittest.cc
index d061ba1..855909b9 100644
--- a/chrome/browser/sessions/session_service_unittest.cc
+++ b/chrome/browser/sessions/session_service_unittest.cc
@@ -35,6 +35,7 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "components/sessions/content/content_test_helper.h"
 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
 #include "components/sessions/core/session_command.h"
 #include "components/sessions/core/session_types.h"
@@ -46,6 +47,7 @@
 #include "third_party/skia/include/core/SkColor.h"
 
 using content::NavigationEntry;
+using sessions::ContentTestHelper;
 using sessions::SerializedNavigationEntry;
 using sessions::SerializedNavigationEntryTestHelper;
 
@@ -121,8 +123,7 @@
   bool CreateAndWriteSessionWithOneTab(bool pinned_state, bool write_always) {
     SessionID tab_id = SessionID::NewUnique();
     SerializedNavigationEntry nav1 =
-        SerializedNavigationEntryTestHelper::CreateNavigation(
-            "http://google.com", "abc");
+        ContentTestHelper::CreateNavigation("http://google.com", "abc");
 
     helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
     UpdateNavigation(window_id, tab_id, nav1, true);
@@ -152,10 +153,8 @@
       const SessionID& tab2_id,
       SerializedNavigationEntry* nav1,
       SerializedNavigationEntry* nav2) {
-    *nav1 = SerializedNavigationEntryTestHelper::CreateNavigation(
-        "http://google.com", "abc");
-    *nav2 = SerializedNavigationEntryTestHelper::CreateNavigation(
-        "http://google2.com", "abcd");
+    *nav1 = ContentTestHelper::CreateNavigation("http://google.com", "abc");
+    *nav2 = ContentTestHelper::CreateNavigation("http://google2.com", "abcd");
 
     helper_.PrepareTabInWindow(window_id, tab1_id, 0, true);
     UpdateNavigation(window_id, tab1_id, *nav1, true);
@@ -189,8 +188,7 @@
   ASSERT_NE(window_id, tab_id);
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntryTestHelper::SetOriginalRequestURL(
       GURL("http://original.request.com"), &nav1);
 
@@ -220,8 +218,7 @@
   ASSERT_NE(window_id, tab_id);
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntryTestHelper::SetHasPostData(true, &nav1);
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
@@ -239,11 +236,9 @@
   ASSERT_NE(tab_id, tab2_id);
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google2.com", "abcd");
+      ContentTestHelper::CreateNavigation("http://google2.com", "abcd");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
@@ -270,11 +265,9 @@
   SessionID tab_id = SessionID::NewUnique();
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google2.com", "abcd");
+      ContentTestHelper::CreateNavigation("http://google2.com", "abcd");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   for (int i = 0; i < 6; ++i) {
@@ -362,8 +355,7 @@
   SessionID tab2_id = SessionID::NewUnique();
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
 
   helper_.PrepareTabInWindow(window_id, tab1_id, 0, true);
   UpdateNavigation(window_id, tab1_id, nav1, true);
@@ -394,11 +386,9 @@
   ASSERT_NE(tab_id, tab2_id);
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google2.com", "abcd");
+      ContentTestHelper::CreateNavigation("http://google2.com", "abcd");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
@@ -468,11 +458,9 @@
                              ui::SHOW_STATE_NORMAL);
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google2.com", "abcd");
+      ContentTestHelper::CreateNavigation("http://google2.com", "abcd");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
@@ -511,11 +499,9 @@
   service()->SetWindowWorkspace(window2_id, window_workspace);
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google2.com", "abcd");
+      ContentTestHelper::CreateNavigation("http://google2.com", "abcd");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
@@ -563,11 +549,9 @@
   service()->SetWindowAppName(window2_id, "TestApp");
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google2.com", "abcd");
+      ContentTestHelper::CreateNavigation("http://google2.com", "abcd");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
@@ -628,9 +612,8 @@
 
   // Add 5 navigations, with the 4th selected.
   for (int i = 0; i < 5; ++i) {
-    SerializedNavigationEntry nav =
-        SerializedNavigationEntryTestHelper::CreateNavigation(
-            base_url + base::NumberToString(i), "a");
+    SerializedNavigationEntry nav = ContentTestHelper::CreateNavigation(
+        base_url + base::NumberToString(i), "a");
     nav.set_index(i);
     UpdateNavigation(window_id, tab_id, nav, (i == 3));
   }
@@ -680,9 +663,8 @@
 
   // Add 5 navigations, with the 4th selected.
   for (int i = 0; i < 5; ++i) {
-    SerializedNavigationEntry nav =
-        SerializedNavigationEntryTestHelper::CreateNavigation(
-            base_url + base::NumberToString(i), "a");
+    SerializedNavigationEntry nav = ContentTestHelper::CreateNavigation(
+        base_url + base::NumberToString(i), "a");
     nav.set_index(i);
     UpdateNavigation(window_id, tab_id, nav, (i == 3));
   }
@@ -802,9 +784,8 @@
 
   // Add 5 navigations, with the 4th selected.
   for (int i = 0; i < 5; ++i) {
-    SerializedNavigationEntry nav =
-        SerializedNavigationEntryTestHelper::CreateNavigation(
-            base_url + base::NumberToString(i), "a");
+    SerializedNavigationEntry nav = ContentTestHelper::CreateNavigation(
+        base_url + base::NumberToString(i), "a");
     nav.set_index(i);
     UpdateNavigation(window_id, tab_id, nav, (i == 3));
   }
@@ -850,8 +831,7 @@
   std::string app_id("foo");
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
@@ -873,8 +853,7 @@
       "Safari/535.19";
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntryTestHelper::SetIsOverridingUserAgent(true, &nav1);
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
@@ -897,8 +876,7 @@
   ASSERT_NE(window_id, tab_id);
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
@@ -915,8 +893,7 @@
   SessionID tab_id = SessionID::NewUnique();
   ASSERT_NE(window_id, tab_id);
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
   service()->SetWindowBounds(window_id,
@@ -940,17 +917,16 @@
   // Create a TabNavigation containing page_state and representing a POST
   // request.
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "title");
+      ContentTestHelper::CreateNavigation("http://google.com", "title");
   SerializedNavigationEntryTestHelper::SetEncodedPageState(
       page_state.ToEncodedData(), &nav1);
   SerializedNavigationEntryTestHelper::SetHasPostData(true, &nav1);
+  nav1.set_index(0);
 
   // Create a TabNavigation containing page_state and representing a normal
   // request.
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com/nopost", "title");
+      ContentTestHelper::CreateNavigation("http://google.com/nopost", "title");
   SerializedNavigationEntryTestHelper::SetEncodedPageState(
       page_state.ToEncodedData(), &nav2);
   nav2.set_index(1);
@@ -966,8 +942,14 @@
 
   // Expected: the page state of both navigations was saved and restored.
   ASSERT_EQ(2u, windows[0]->tabs[0]->navigations.size());
-  helper_.AssertNavigationEquals(nav1, windows[0]->tabs[0]->navigations[0]);
-  helper_.AssertNavigationEquals(nav2, windows[0]->tabs[0]->navigations[1]);
+  {
+    SCOPED_TRACE("Comparing |nav1| and |navigations[0]|");
+    helper_.AssertNavigationEquals(nav1, windows[0]->tabs[0]->navigations[0]);
+  }
+  {
+    SCOPED_TRACE("Comparing |nav2| and |navigations[1]|");
+    helper_.AssertNavigationEquals(nav2, windows[0]->tabs[0]->navigations[1]);
+  }
 }
 
 TEST_F(SessionServiceTest, RemovePostDataWithPasswords) {
@@ -981,8 +963,7 @@
   // Create a TabNavigation containing page_state and representing a POST
   // request with passwords.
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "title");
+      ContentTestHelper::CreateNavigation("http://google.com", "title");
   SerializedNavigationEntryTestHelper::SetEncodedPageState(
       page_state.ToEncodedData(), &nav1);
   SerializedNavigationEntryTestHelper::SetHasPostData(true, &nav1);
@@ -1008,9 +989,8 @@
 
   // Add 5 navigations, some with the same index
   for (int i = 0; i < 5; ++i) {
-    SerializedNavigationEntry nav =
-        SerializedNavigationEntryTestHelper::CreateNavigation(
-            base_url + base::NumberToString(i), "a");
+    SerializedNavigationEntry nav = ContentTestHelper::CreateNavigation(
+        base_url + base::NumberToString(i), "a");
     nav.set_index(i / 2);
     UpdateNavigation(window_id, tab_id, nav, true);
   }
@@ -1038,9 +1018,8 @@
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
 
   for (int i = 0; i < 5; ++i) {
-    SerializedNavigationEntry nav =
-        SerializedNavigationEntryTestHelper::CreateNavigation(
-            base_url + base::NumberToString(i), "a");
+    SerializedNavigationEntry nav = ContentTestHelper::CreateNavigation(
+        base_url + base::NumberToString(i), "a");
     nav.set_index(i);
     UpdateNavigation(window_id, tab_id, nav, true);
   }
@@ -1058,9 +1037,8 @@
   EXPECT_EQ(0, available_range.second);
 
   // Add another navigation to replace the last one.
-  SerializedNavigationEntry nav =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          base_url + base::NumberToString(5), "a");
+  SerializedNavigationEntry nav = ContentTestHelper::CreateNavigation(
+      base_url + base::NumberToString(5), "a");
   nav.set_index(4);
   UpdateNavigation(window_id, tab_id, nav, true);
 
@@ -1126,14 +1104,11 @@
   SessionID tab_id = SessionID::NewUnique();
 
   SerializedNavigationEntry nav1 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://google.com", "abc");
+      ContentTestHelper::CreateNavigation("http://google.com", "abc");
   SerializedNavigationEntry nav2 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          chrome::kChromeUIQuitURL, "quit");
-  SerializedNavigationEntry nav3 =
-      SerializedNavigationEntryTestHelper::CreateNavigation(
-          chrome::kChromeUIRestartURL, "restart");
+      ContentTestHelper::CreateNavigation(chrome::kChromeUIQuitURL, "quit");
+  SerializedNavigationEntry nav3 = ContentTestHelper::CreateNavigation(
+      chrome::kChromeUIRestartURL, "restart");
 
   helper_.PrepareTabInWindow(window_id, tab_id, 0, true);
   UpdateNavigation(window_id, tab_id, nav1, true);
diff --git a/chrome/browser/sessions/tab_restore_service_unittest.cc b/chrome/browser/sessions/tab_restore_service_unittest.cc
index 4cfb76e8..47daa579 100644
--- a/chrome/browser/sessions/tab_restore_service_unittest.cc
+++ b/chrome/browser/sessions/tab_restore_service_unittest.cc
@@ -30,6 +30,7 @@
 #include "components/history/core/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/sessions/content/content_live_tab.h"
+#include "components/sessions/content/content_test_helper.h"
 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
 #include "components/sessions/core/session_types.h"
 #include "components/sessions/core/tab_restore_service_observer.h"
@@ -50,6 +51,7 @@
 
 using content::NavigationEntry;
 using content::WebContentsTester;
+using sessions::ContentTestHelper;
 using sessions::SerializedNavigationEntry;
 using sessions::SerializedNavigationEntryTestHelper;
 
@@ -156,8 +158,7 @@
       session_service->SetPinnedState(window_id(), tab_id(), true);
     session_service->UpdateTabNavigation(
         window_id(), tab_id(),
-        SerializedNavigationEntryTestHelper::CreateNavigation(url1_.spec(),
-                                                              "title"));
+        ContentTestHelper::CreateNavigation(url1_.spec(), "title"));
   }
 
   // Creates a SessionService and assigns it to the Profile. The SessionService
@@ -869,10 +870,9 @@
 
   const size_t max_entries = kMaxEntries;
   for (size_t i = 0; i < max_entries + 5; i++) {
-    SerializedNavigationEntry navigation =
-        SerializedNavigationEntryTestHelper::CreateNavigation(
-            base::StringPrintf("http://%d", static_cast<int>(i)),
-            base::NumberToString(i));
+    SerializedNavigationEntry navigation = ContentTestHelper::CreateNavigation(
+        base::StringPrintf("http://%d", static_cast<int>(i)),
+        base::NumberToString(i));
 
     auto tab = std::make_unique<Tab>();
     tab->navigations.push_back(navigation);
@@ -892,8 +892,7 @@
   // Prune older first.
   const char kRecentUrl[] = "http://recent";
   SerializedNavigationEntry navigation =
-      SerializedNavigationEntryTestHelper::CreateNavigation(kRecentUrl,
-                                                            "Most recent");
+      ContentTestHelper::CreateNavigation(kRecentUrl, "Most recent");
   auto tab = std::make_unique<Tab>();
   tab->navigations.push_back(navigation);
   tab->current_navigation_index = 0;
@@ -906,8 +905,8 @@
                                   .virtual_url());
 
   // Ignore NTPs.
-  navigation = SerializedNavigationEntryTestHelper::CreateNavigation(
-      chrome::kChromeUINewTabURL, "New tab");
+  navigation = ContentTestHelper::CreateNavigation(chrome::kChromeUINewTabURL,
+                                                   "New tab");
 
   tab = std::make_unique<Tab>();
   tab->navigations.push_back(navigation);
diff --git a/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc b/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
index 977ef539..ce1b9ef 100644
--- a/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
@@ -688,8 +688,14 @@
 
 // If the server sends the same cards and addresses with changed data, they
 // should change on the client.
+// Flaky on Mac/Linux/ChromeOS only. http://crbug.com/997825
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+#define MAYBE_ChangedEntityGetsUpdated DISABLED_ChangedEntityGetsUpdated
+#else
+#define MAYBE_ChangedEntityGetsUpdated ChangedEntityGetsUpdated
+#endif
 IN_PROC_BROWSER_TEST_P(SingleClientWalletSyncTestWithDefaultFeatures,
-                       ChangedEntityGetsUpdated) {
+                       MAYBE_ChangedEntityGetsUpdated) {
   GetFakeServer()->SetWalletData(
       {CreateSyncWalletCard(/*name=*/"card-1", /*last_four=*/"0002",
                             kDefaultBillingAddressID),
diff --git a/chrome/browser/sync/test/integration/two_client_autofill_sync_test.cc b/chrome/browser/sync/test/integration/two_client_autofill_sync_test.cc
index 572ad32..dca21d94 100644
--- a/chrome/browser/sync/test/integration/two_client_autofill_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_autofill_sync_test.cc
@@ -7,6 +7,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "chrome/browser/sync/test/integration/autofill_helper.h"
 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
@@ -115,8 +116,14 @@
                                LOCAL_DELETION, 2);
 }
 
+// Flaky on Linux/Win/ChromeOS only. http://crbug.com/997629
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS)
+#define MAYBE_SyncHistogramsInitialSync DISABLED_SyncHistogramsInitialSync
+#else
+#define MAYBE_SyncHistogramsInitialSync SyncHistogramsInitialSync
+#endif
 IN_PROC_BROWSER_TEST_F(TwoClientAutofillProfileSyncTest,
-                       SyncHistogramsInitialSync) {
+                       MAYBE_SyncHistogramsInitialSync) {
   ASSERT_TRUE(SetupClients());
 
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
diff --git a/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc b/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
index 79bcae7..18781af 100644
--- a/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
+++ b/chrome/browser/ui/app_list/search/tests/app_search_provider_unittest.cc
@@ -42,6 +42,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/arc/test/fake_app_instance.h"
 #include "components/crx_file/id_util.h"
+#include "components/sessions/content/content_test_helper.h"
 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
 #include "components/sessions/core/session_id.h"
 #include "components/sync/model/string_ordinal.h"
@@ -435,9 +436,8 @@
     session_tracker()->PutTabInWindow(kForeignSessionTag1, kWindowId1, kTabId1);
     session_tracker()
         ->GetTab(kForeignSessionTag1, kTabId1)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "http://url1", "title1"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "http://url1", "title1"));
     session_tracker()->GetTab(kForeignSessionTag1, kTabId1)->timestamp =
         kTimestamp1;
     session_tracker()->GetSession(kForeignSessionTag1)->modified_time =
@@ -449,9 +449,8 @@
     session_tracker()->PutTabInWindow(kForeignSessionTag2, kWindowId2, kTabId2);
     session_tracker()
         ->GetTab(kForeignSessionTag2, kTabId2)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "http://url2", "title2"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "http://url2", "title2"));
     session_tracker()->GetTab(kForeignSessionTag2, kTabId2)->timestamp =
         kTimestamp2;
     session_tracker()->GetSession(kForeignSessionTag2)->modified_time =
@@ -463,9 +462,8 @@
     session_tracker()->PutTabInWindow(kForeignSessionTag3, kWindowId3, kTabId3);
     session_tracker()
         ->GetTab(kForeignSessionTag3, kTabId3)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "http://url3", "title3"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "http://url3", "title3"));
     session_tracker()->GetTab(kForeignSessionTag3, kTabId3)->timestamp =
         kTimestamp3;
     session_tracker()->GetSession(kForeignSessionTag3)->modified_time =
@@ -488,9 +486,8 @@
     session_tracker()->PutTabInWindow(kLocalSessionTag, kWindowId1, kTabId1);
     session_tracker()
         ->GetTab(kLocalSessionTag, kTabId1)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "http://url1", "title1"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "http://url1", "title1"));
     session_tracker()->GetTab(kLocalSessionTag, kTabId1)->timestamp =
         kTimestamp1;
     session_tracker()->GetSession(kLocalSessionTag)->modified_time =
@@ -514,9 +511,8 @@
     session_tracker()->PutTabInWindow(kForeignSessionTag1, kWindowId1, kTabId1);
     session_tracker()
         ->GetTab(kForeignSessionTag1, kTabId1)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "http://url1", "title1"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "http://url1", "title1"));
     session_tracker()->GetTab(kForeignSessionTag1, kTabId1)->timestamp =
         kTimestamp1;
     session_tracker()->GetSession(kForeignSessionTag1)->modified_time =
@@ -540,9 +536,8 @@
     session_tracker()->PutTabInWindow(kForeignSessionTag1, kWindowId1, kTabId1);
     session_tracker()
         ->GetTab(kForeignSessionTag1, kTabId1)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "http://url1", "title1"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "http://url1", "title1"));
     session_tracker()->GetTab(kForeignSessionTag1, kTabId1)->timestamp =
         kTimestamp1;
     session_tracker()->GetSession(kForeignSessionTag1)->modified_time =
@@ -566,9 +561,8 @@
     session_tracker()->PutTabInWindow(kForeignSessionTag1, kWindowId1, kTabId1);
     session_tracker()
         ->GetTab(kForeignSessionTag1, kTabId1)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "http://url1", "title1"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "http://url1", "title1"));
     session_tracker()->GetTab(kForeignSessionTag1, kTabId1)->timestamp =
         kTimestamp1;
     session_tracker()->GetSession(kForeignSessionTag1)->modified_time =
@@ -592,9 +586,8 @@
     session_tracker()->PutTabInWindow(kForeignSessionTag1, kWindowId1, kTabId1);
     session_tracker()
         ->GetTab(kForeignSessionTag1, kTabId1)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "data://url1", "title1"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "data://url1", "title1"));
     session_tracker()->GetTab(kForeignSessionTag1, kTabId1)->timestamp =
         kTimestamp1;
     session_tracker()->GetSession(kForeignSessionTag1)->modified_time =
@@ -617,9 +610,8 @@
     session_tracker()->PutTabInWindow(kForeignSessionTag1, kWindowId1, kTabId1);
     session_tracker()
         ->GetTab(kForeignSessionTag1, kTabId1)
-        ->navigations.push_back(
-            sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-                "http://url1", "title1"));
+        ->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
+            "http://url1", "title1"));
     session_tracker()->GetTab(kForeignSessionTag1, kTabId1)->timestamp =
         kTimestamp1;
     session_tracker()->GetSession(kForeignSessionTag1)->modified_time =
diff --git a/chrome/browser/ui/ash/assistant/assistant_client.cc b/chrome/browser/ui/ash/assistant/assistant_client.cc
index a8edca5e..031614f 100644
--- a/chrome/browser/ui/ash/assistant/assistant_client.cc
+++ b/chrome/browser/ui/ash/assistant/assistant_client.cc
@@ -8,7 +8,6 @@
 
 #include "ash/public/cpp/assistant/assistant_interface_binder.h"
 #include "ash/public/cpp/network_config_service.h"
-#include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/public/mojom/voice_interaction_controller.mojom.h"
 #include "chrome/browser/chromeos/arc/voice_interaction/voice_interaction_controller_client.h"
 #include "chrome/browser/chromeos/assistant/assistant_util.h"
@@ -173,9 +172,10 @@
       std::move(receiver));
 }
 
-void AssistantClient::RequestVoiceInteractionController(
-    mojo::PendingReceiver<ash::mojom::VoiceInteractionController> receiver) {
-  ash::VoiceInteractionController::Get()->BindRequest(std::move(receiver));
+void AssistantClient::RequestAssistantStateController(
+    mojo::PendingReceiver<ash::mojom::AssistantStateController> receiver) {
+  ash::AssistantInterfaceBinder::GetInstance()->BindStateController(
+      std::move(receiver));
 }
 
 void AssistantClient::RequestPrefStoreConnector(
@@ -247,9 +247,6 @@
 }
 
 void AssistantClient::OnUserProfileLoaded(const AccountId& account_id) {
-  if (!chromeos::features::IsAssistantEnabled())
-    return;
-
   // Initialize Assistant when primary user profile is loaded so that it could
   // be used in post oobe steps. OnUserSessionStarted() is too late
   // because it happens after post oobe steps
diff --git a/chrome/browser/ui/ash/assistant/assistant_client.h b/chrome/browser/ui/ash/assistant/assistant_client.h
index f80cc40..c941b40 100644
--- a/chrome/browser/ui/ash/assistant/assistant_client.h
+++ b/chrome/browser/ui/ash/assistant/assistant_client.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "ash/public/mojom/assistant_state_controller.mojom.h"
 #include "base/macros.h"
 #include "chrome/browser/ui/ash/assistant/device_actions.h"
 #include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
@@ -57,8 +58,8 @@
   void RequestAssistantVolumeControl(
       mojo::PendingReceiver<ash::mojom::AssistantVolumeControl> receiver)
       override;
-  void RequestVoiceInteractionController(
-      mojo::PendingReceiver<ash::mojom::VoiceInteractionController> receiver)
+  void RequestAssistantStateController(
+      mojo::PendingReceiver<ash::mojom::AssistantStateController> receiver)
       override;
   void RequestPrefStoreConnector(
       mojo::PendingReceiver<prefs::mojom::PrefStoreConnector> receiver)
diff --git a/chrome/browser/ui/ash/assistant/proactive_suggestions_client_impl.cc b/chrome/browser/ui/ash/assistant/proactive_suggestions_client_impl.cc
index 6e0d0ef..208d95f2 100644
--- a/chrome/browser/ui/ash/assistant/proactive_suggestions_client_impl.cc
+++ b/chrome/browser/ui/ash/assistant/proactive_suggestions_client_impl.cc
@@ -17,8 +17,8 @@
     Profile* profile)
     : profile_(profile) {
   // Initialize the Assistant state proxy.
-  mojo::PendingRemote<ash::mojom::VoiceInteractionController> controller;
-  client->RequestVoiceInteractionController(
+  mojo::PendingRemote<ash::mojom::AssistantStateController> controller;
+  client->RequestAssistantStateController(
       controller.InitWithNewPipeAndPassReceiver());
   assistant_state_.Init(std::move(controller));
 
@@ -86,15 +86,13 @@
   SetActiveUrl(active_contents_->GetURL());
 }
 
-void ProactiveSuggestionsClientImpl::OnVoiceInteractionSettingsEnabled(
-    bool enabled) {
+void ProactiveSuggestionsClientImpl::OnAssistantSettingsEnabled(bool enabled) {
   // When Assistant is enabled/disabled in settings we may need to resume/pause
   // observation of the browser. We accomplish this by updating active state.
   UpdateActiveState();
 }
 
-void ProactiveSuggestionsClientImpl::OnVoiceInteractionContextEnabled(
-    bool enabled) {
+void ProactiveSuggestionsClientImpl::OnAssistantContextEnabled(bool enabled) {
   // When Assistant screen context is enabled/disabled in settings we may need
   // to resume/pause observation of the browser. We accomplish this by updating
   // active state.
diff --git a/chrome/browser/ui/ash/assistant/proactive_suggestions_client_impl.h b/chrome/browser/ui/ash/assistant/proactive_suggestions_client_impl.h
index bdcdc74..834d2ca 100644
--- a/chrome/browser/ui/ash/assistant/proactive_suggestions_client_impl.h
+++ b/chrome/browser/ui/ash/assistant/proactive_suggestions_client_impl.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "ash/public/cpp/assistant/assistant_state_proxy.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
 #include "ash/public/cpp/assistant/proactive_suggestions_client.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
@@ -23,12 +22,11 @@
 // A browser client which observes changes to the singleton BrowserList on
 // behalf of Assistant to provide it with information necessary to retrieve
 // proactive content suggestions.
-class ProactiveSuggestionsClientImpl
-    : public ash::ProactiveSuggestionsClient,
-      public BrowserListObserver,
-      public TabStripModelObserver,
-      public content::WebContentsObserver,
-      public ash::DefaultVoiceInteractionObserver {
+class ProactiveSuggestionsClientImpl : public ash::ProactiveSuggestionsClient,
+                                       public BrowserListObserver,
+                                       public TabStripModelObserver,
+                                       public content::WebContentsObserver,
+                                       public ash::AssistantStateObserver {
  public:
   ProactiveSuggestionsClientImpl(AssistantClient* client, Profile* profile);
   ~ProactiveSuggestionsClientImpl() override;
@@ -51,9 +49,9 @@
   void DidStartNavigation(
       content::NavigationHandle* navigation_handle) override;
 
-  // DefaultVoiceInteractionObserver:
-  void OnVoiceInteractionSettingsEnabled(bool enabled) override;
-  void OnVoiceInteractionContextEnabled(bool enabled) override;
+  // AssistantStateObserver:
+  void OnAssistantSettingsEnabled(bool enabled) override;
+  void OnAssistantContextEnabled(bool enabled) override;
 
  private:
   void SetActiveBrowser(Browser* browser);
diff --git a/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm b/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
index 772091b..f8363a1 100644
--- a/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
+++ b/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
@@ -20,6 +20,7 @@
 #include "chrome/browser/ui/cocoa/test/cocoa_profile_test.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/favicon_base/favicon_types.h"
+#include "components/sessions/content/content_test_helper.h"
 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
 #include "components/sessions/core/tab_restore_service_impl.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -115,8 +116,7 @@
     tab->id = SessionID::FromSerializedValue(id);
     tab->current_navigation_index = 0;
     tab->navigations.push_back(
-        sessions::SerializedNavigationEntryTestHelper::CreateNavigation(url,
-                                                                        title));
+        sessions::ContentTestHelper::CreateNavigation(url, title));
     return tab;
   }
 
diff --git a/chrome/browser/ui/input_method/input_method_engine_base.cc b/chrome/browser/ui/input_method/input_method_engine_base.cc
index 46a3344..0c19b51 100644
--- a/chrome/browser/ui/input_method/input_method_engine_base.cc
+++ b/chrome/browser/ui/input_method/input_method_engine_base.cc
@@ -209,7 +209,7 @@
 void InputMethodEngineBase::Disable() {
   std::string last_component_id{active_component_id_};
   active_component_id_.clear();
-  ConfirmCompositionText();
+  ConfirmCompositionText(/* reset_engine */ true);
   observer_->OnDeactivated(last_component_id);
 }
 
@@ -412,7 +412,7 @@
 
   // When there is composition text, commit it to the text field first before
   // changing the composition range.
-  ConfirmCompositionText();
+  ConfirmCompositionText(/* reset_engine */ false);
 
   std::vector<ui::ImeTextSpan> text_spans;
   for (const auto& segment : segments) {
@@ -487,11 +487,11 @@
     input_context->DeleteSurroundingText(offset, number_of_chars);
 }
 
-void InputMethodEngineBase::ConfirmCompositionText() {
+void InputMethodEngineBase::ConfirmCompositionText(bool reset_engine) {
   ui::IMEInputContextHandlerInterface* input_context =
       ui::IMEBridge::Get()->GetInputContextHandler();
   if (input_context)
-    input_context->ConfirmCompositionText();
+    input_context->ConfirmCompositionText(reset_engine);
 }
 
 }  // namespace input_method
diff --git a/chrome/browser/ui/input_method/input_method_engine_base.h b/chrome/browser/ui/input_method/input_method_engine_base.h
index b92acf9..7ed1807 100644
--- a/chrome/browser/ui/input_method/input_method_engine_base.h
+++ b/chrome/browser/ui/input_method/input_method_engine_base.h
@@ -230,7 +230,8 @@
   virtual bool SendKeyEvent(ui::KeyEvent* ui_event,
                             const std::string& code) = 0;
   // Notifies InputContextHandler to commit any composition text.
-  void ConfirmCompositionText();
+  // Set |reset_engine| to false if the event was from the extension.
+  void ConfirmCompositionText(bool reset_engine);
 
   ui::TextInputType current_input_type_;
 
diff --git a/chrome/browser/ui/toolbar/media_router_action_controller.cc b/chrome/browser/ui/toolbar/media_router_action_controller.cc
index 48e3ac6..4a5c2c2 100644
--- a/chrome/browser/ui/toolbar/media_router_action_controller.cc
+++ b/chrome/browser/ui/toolbar/media_router_action_controller.cc
@@ -95,8 +95,9 @@
 void MediaRouterActionController::OnContextMenuShown() {
   DCHECK(!context_menu_shown_);
   context_menu_shown_ = true;
-  // If the context menu was shown, right mouse button must have been released.
-  keep_visible_for_right_mouse_button_ = false;
+  // Once the context menu is shown, we no longer need to keep track of the
+  // mouse or touch press.
+  keep_visible_for_right_click_or_hold_ = false;
   MaybeAddOrRemoveAction();
 }
 
@@ -106,13 +107,14 @@
   MaybeAddOrRemoveAction();
 }
 
-void MediaRouterActionController::KeepIconOnRightMousePressed() {
-  DCHECK(!keep_visible_for_right_mouse_button_);
-  keep_visible_for_right_mouse_button_ = true;
+void MediaRouterActionController::KeepIconShownOnPressed() {
+  DCHECK(!keep_visible_for_right_click_or_hold_);
+  keep_visible_for_right_click_or_hold_ = true;
+  MaybeAddOrRemoveAction();
 }
 
-void MediaRouterActionController::MaybeHideIconOnRightMouseReleased() {
-  keep_visible_for_right_mouse_button_ = false;
+void MediaRouterActionController::MaybeHideIconOnReleased() {
+  keep_visible_for_right_click_or_hold_ = false;
   MaybeAddOrRemoveAction();
 }
 
@@ -127,7 +129,7 @@
 bool MediaRouterActionController::ShouldEnableAction() const {
   return shown_by_policy_ || has_local_display_route_ || has_issue_ ||
          dialog_count_ || context_menu_shown_ ||
-         keep_visible_for_right_mouse_button_ ||
+         keep_visible_for_right_click_or_hold_ ||
          GetAlwaysShowActionPref(profile_);
 }
 
diff --git a/chrome/browser/ui/toolbar/media_router_action_controller.h b/chrome/browser/ui/toolbar/media_router_action_controller.h
index 6b69e50..1d0c880 100644
--- a/chrome/browser/ui/toolbar/media_router_action_controller.h
+++ b/chrome/browser/ui/toolbar/media_router_action_controller.h
@@ -72,12 +72,12 @@
   void OnContextMenuShown() override;
   void OnContextMenuHidden() override;
 
-  // On Windows, when the user right-clicks on the toolbar icon, the context
-  // menu appears on mouse release. Since the dialog, if shown, disappears on
-  // mouse press, we need to make sure that the icon does not get hidden between
-  // mouse press and mouse release. These methods ensure that.
-  void KeepIconOnRightMousePressed();
-  void MaybeHideIconOnRightMouseReleased();
+  // On Windows (with a right click) and Chrome OS (with touch), pressing the
+  // toolbar icon makes the dialog disappear, but the context menu does not
+  // appear until mouse/touch release. These methods ensure that the icon is
+  // still shown at mouse/touch release so that the context menu can be shown.
+  void KeepIconShownOnPressed();
+  void MaybeHideIconOnReleased();
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
@@ -114,12 +114,12 @@
   // should not be hidden while a context menu is shown.
   bool context_menu_shown_ = false;
 
-  // Whether the right mouse button is pressed on the toolbar icon. On Windows,
-  // when the user right clicks on the toolbar icon, the dialog gets hidden on
-  // mouse press, and the context menu gets shown on mouse release. If the icon
-  // is ephemeral, it gets hidden on mouse press and can't show the context
-  // menu. So we must keep the icon shown while right mouse button is pressed.
-  bool keep_visible_for_right_mouse_button_ = false;
+  // On Windows (with the right mouse button) and on Chrome OS (with touch),
+  // when the user presses the toolbar icon, the dialog gets hidden, but the
+  // context menu is not shown until mouse/touch release. If the icon is
+  // ephemeral, it gets hidden on press and can't show the context menu. So we
+  // must keep the icon shown while the right click or touch is held.
+  bool keep_visible_for_right_click_or_hold_ = false;
 
   PrefChangeRegistrar pref_change_registrar_;
 
diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
index 3385a46..f2c0e85 100644
--- a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
@@ -31,6 +31,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/menu_model_test.h"
+#include "components/sessions/content/content_test_helper.h"
 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
 #include "components/sessions/core/session_types.h"
 #include "components/sessions/core/tab_restore_service_impl.h"
@@ -291,8 +292,8 @@
   session_service->SetSelectedTabInWindow(window_id, 0);
   session_service->UpdateTabNavigation(
       window_id, tab_id,
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://wnd1/tab0", "title"));
+      sessions::ContentTestHelper::CreateNavigation("http://wnd1/tab0",
+                                                    "title"));
   // Set this, otherwise previous session won't be loaded.
   profile()->set_last_session_exited_cleanly(false);
   // Move this session to the last so that TabRestoreService will load it as the
diff --git a/chrome/browser/ui/views/media_router/cast_dialog_view.cc b/chrome/browser/ui/views/media_router/cast_dialog_view.cc
index ca160fb..b51c223 100644
--- a/chrome/browser/ui/views/media_router/cast_dialog_view.cc
+++ b/chrome/browser/ui/views/media_router/cast_dialog_view.cc
@@ -404,6 +404,9 @@
   } else if (sink.issue) {
     controller_->ClearIssue(sink.issue->id());
   } else {
+    // |sink| may get invalidated during CastDialogController::StartCasting()
+    // due to a model update, so we must use a copy of its |id| field.
+    const std::string sink_id = sink.id;
     base::Optional<MediaCastMode> cast_mode = GetCastModeToUse(sink);
     if (cast_mode) {
       // Starting local file casting may open a new tab synchronously on the UI
@@ -411,7 +414,7 @@
       // closing and getting destroyed.
       if (cast_mode.value() == LOCAL_FILE)
         set_close_on_deactivate(false);
-      controller_->StartCasting(sink.id, cast_mode.value());
+      controller_->StartCasting(sink_id, cast_mode.value());
       // Re-enable close on deactivate so the user can click elsewhere to close
       // the dialog.
       if (cast_mode.value() == LOCAL_FILE)
diff --git a/chrome/browser/ui/views/media_router/cast_toolbar_button.cc b/chrome/browser/ui/views/media_router/cast_toolbar_button.cc
index 54c85a51..a97538f2 100644
--- a/chrome/browser/ui/views/media_router/cast_toolbar_button.cc
+++ b/chrome/browser/ui/views/media_router/cast_toolbar_button.cc
@@ -134,14 +134,29 @@
 
 bool CastToolbarButton::OnMousePressed(const ui::MouseEvent& event) {
   if (event.IsRightMouseButton() && GetActionController())
-    GetActionController()->KeepIconOnRightMousePressed();
+    GetActionController()->KeepIconShownOnPressed();
   return ToolbarButton::OnMousePressed(event);
 }
 
 void CastToolbarButton::OnMouseReleased(const ui::MouseEvent& event) {
   ToolbarButton::OnMouseReleased(event);
   if (event.IsRightMouseButton() && GetActionController())
-    GetActionController()->MaybeHideIconOnRightMouseReleased();
+    GetActionController()->MaybeHideIconOnReleased();
+}
+
+void CastToolbarButton::OnGestureEvent(ui::GestureEvent* event) {
+  switch (event->type()) {
+    case ui::ET_GESTURE_TAP_DOWN:
+      GetActionController()->KeepIconShownOnPressed();
+      break;
+    case ui::ET_GESTURE_END:
+    case ui::ET_GESTURE_TAP_CANCEL:
+      GetActionController()->MaybeHideIconOnReleased();
+      break;
+    default:
+      break;
+  }
+  ToolbarButton::OnGestureEvent(event);
 }
 
 void CastToolbarButton::ButtonPressed(views::Button* sender,
diff --git a/chrome/browser/ui/views/media_router/cast_toolbar_button.h b/chrome/browser/ui/views/media_router/cast_toolbar_button.h
index 4fa6ae01..102606b 100644
--- a/chrome/browser/ui/views/media_router/cast_toolbar_button.h
+++ b/chrome/browser/ui/views/media_router/cast_toolbar_button.h
@@ -59,6 +59,7 @@
   // ToolbarButton:
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnMouseReleased(const ui::MouseEvent& event) override;
+  void OnGestureEvent(ui::GestureEvent* event) override;
 
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
diff --git a/chrome/browser/ui/views/tabs/tab_close_button.cc b/chrome/browser/ui/views/tabs/tab_close_button.cc
index f110c79..b2f06cd 100644
--- a/chrome/browser/ui/views/tabs/tab_close_button.cc
+++ b/chrome/browser/ui/views/tabs/tab_close_button.cc
@@ -23,6 +23,7 @@
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/animation/ink_drop.h"
+#include "ui/views/animation/ink_drop_mask.h"
 #include "ui/views/rect_based_targeting_utils.h"
 #include "ui/views/view_class_properties.h"
 
@@ -113,7 +114,7 @@
 void TabCloseButton::Layout() {
   ImageButton::Layout();
   auto path = std::make_unique<SkPath>();
-  gfx::Point center = GetMirroredRect(GetContentsBounds()).CenterPoint();
+  gfx::Point center = GetContentsBounds().CenterPoint();
   path->addCircle(center.x(), center.y(), GetWidth() / 2);
   SetProperty(views::kHighlightPathKey, path.release());
 }
@@ -126,6 +127,12 @@
   return size;
 }
 
+std::unique_ptr<views::InkDropMask> TabCloseButton::CreateInkDropMask() const {
+  gfx::Rect bounds = GetContentsBounds();
+  return std::make_unique<views::CircleInkDropMask>(
+      size(), GetMirroredRect(bounds).CenterPoint(), bounds.width() / 2);
+}
+
 void TabCloseButton::PaintButtonContents(gfx::Canvas* canvas) {
   cc::PaintFlags flags;
   constexpr float kStrokeWidth = 1.5f;
diff --git a/chrome/browser/ui/views/tabs/tab_close_button.h b/chrome/browser/ui/views/tabs/tab_close_button.h
index d03918a..9aa7a1a 100644
--- a/chrome/browser/ui/views/tabs/tab_close_button.h
+++ b/chrome/browser/ui/views/tabs/tab_close_button.h
@@ -46,6 +46,7 @@
   const char* GetClassName() const override;
   void Layout() override;
   gfx::Size CalculatePreferredSize() const override;
+  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
 
  protected:
   void PaintButtonContents(gfx::Canvas* canvas) override;
diff --git a/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc
index 5aeaa1a8..7550ea28 100644
--- a/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc
@@ -75,6 +75,8 @@
        IDS_ONC_CELLULAR_ACTIVATION_STATE_NOT_ACTIVATED},
       {"OncCellular-ActivationState_PartiallyActivated",
        IDS_ONC_CELLULAR_ACTIVATION_STATE_PARTIALLY_ACTIVATED},
+      {"OncCellular-ActivationState_NoService",
+       IDS_ONC_CELLULAR_ACTIVATION_STATE_NO_SERVICE},
       {"OncCellular-Family", IDS_ONC_CELLULAR_FAMILY},
       {"OncCellular-FirmwareRevision", IDS_ONC_CELLULAR_FIRMWARE_REVISION},
       {"OncCellular-HardwareRevision", IDS_ONC_CELLULAR_HARDWARE_REVISION},
@@ -136,6 +138,7 @@
       {"OncVPN-IPsec-PSK", IDS_ONC_VPN_IPSEC_PSK},
       {"OncVPN-L2TP-Password", IDS_ONC_VPN_PASSWORD},
       {"OncVPN-L2TP-Username", IDS_ONC_VPN_USERNAME},
+      {"OncVPN-OpenVPN-ExtraHosts", IDS_ONC_VPN_OPENVPN_EXTRA_HOSTS},
       {"OncVPN-OpenVPN-OTP", IDS_ONC_VPN_OPENVPN_OTP},
       {"OncVPN-OpenVPN-Password", IDS_ONC_VPN_PASSWORD},
       {"OncVPN-OpenVPN-Username", IDS_ONC_VPN_USERNAME},
diff --git a/chrome/browser/ui/webui/devtools_ui.cc b/chrome/browser/ui/webui/devtools_ui.cc
index 943c280..6f16167 100644
--- a/chrome/browser/ui/webui/devtools_ui.cc
+++ b/chrome/browser/ui/webui/devtools_ui.cc
@@ -393,7 +393,8 @@
 
   base::PostTaskAndReplyWithResult(
       FROM_HERE,
-      {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+      {base::MayBlock(), base::ThreadPool(),
+       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
        base::TaskPriority::USER_VISIBLE},
       // The usage of BindRepeating below is only because the type of
       // task callback needs to match that of response callback, which
diff --git a/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.cc b/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.cc
index 9a239c5..a734cd0d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.cc
@@ -76,8 +76,7 @@
 void GoogleAssistantHandler::HandleShowGoogleAssistantSettings(
     const base::ListValue* args) {
   CHECK_EQ(0U, args->GetSize());
-  if (chromeos::features::IsAssistantEnabled())
-    ash::OpenAssistantSettings();
+  ash::OpenAssistantSettings();
 }
 
 void GoogleAssistantHandler::HandleRetrainVoiceModel(
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index c05bec50..a2d73594 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -394,10 +394,8 @@
       chromeos::settings::DateTimeHandler::Create(html_source)));
   web_ui->AddMessageHandler(
       std::make_unique<chromeos::settings::FingerprintHandler>(profile));
-  if (chromeos::features::IsAssistantEnabled()) {
-    web_ui->AddMessageHandler(
-        std::make_unique<chromeos::settings::GoogleAssistantHandler>(profile));
-  }
+  web_ui->AddMessageHandler(
+      std::make_unique<chromeos::settings::GoogleAssistantHandler>(profile));
   if (g_browser_process->local_state()->GetBoolean(prefs::kKerberosEnabled)) {
     // Note that UI is also dependent on this pref.
     web_ui->AddMessageHandler(
@@ -510,9 +508,6 @@
   html_source->AddBoolean("isDemoSession",
                           chromeos::DemoSession::IsDeviceInDemoMode());
 
-  html_source->AddBoolean("assistantEnabled",
-                          chromeos::features::IsAssistantEnabled());
-
   // We have 2 variants of Android apps settings. Default case, when the Play
   // Store app exists we show expandable section that allows as to
   // enable/disable the Play Store and link to Android settings which is
diff --git a/chrome/browser/vr/service/vr_service_impl.cc b/chrome/browser/vr/service/vr_service_impl.cc
index 735f5aa..e58c3f48 100644
--- a/chrome/browser/vr/service/vr_service_impl.cc
+++ b/chrome/browser/vr/service/vr_service_impl.cc
@@ -152,13 +152,13 @@
 }
 
 void VRServiceImpl::SetClient(
-    device::mojom::VRServiceClientPtr service_client) {
+    mojo::PendingRemote<device::mojom::VRServiceClient> service_client) {
   if (service_client_) {
     mojo::ReportBadMessage("ServiceClient should only be set once.");
     return;
   }
 
-  service_client_ = std::move(service_client);
+  service_client_.Bind(std::move(service_client));
 }
 
 void VRServiceImpl::ResolvePendingRequests() {
diff --git a/chrome/browser/vr/service/vr_service_impl.h b/chrome/browser/vr/service/vr_service_impl.h
index 40dc5918..acafcfa 100644
--- a/chrome/browser/vr/service/vr_service_impl.h
+++ b/chrome/browser/vr/service/vr_service_impl.h
@@ -22,6 +22,8 @@
 #include "device/vr/vr_device.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 
 namespace content {
@@ -50,7 +52,8 @@
                      device::mojom::VRServiceRequest request);
 
   // device::mojom::VRService implementation
-  void SetClient(device::mojom::VRServiceClientPtr service_client) override;
+  void SetClient(mojo::PendingRemote<device::mojom::VRServiceClient>
+                     service_client) override;
   void RequestSession(
       device::mojom::XRSessionOptionsPtr options,
       device::mojom::VRService::RequestSessionCallback callback) override;
@@ -149,7 +152,7 @@
 
   scoped_refptr<XRRuntimeManager> runtime_manager_;
   mojo::InterfacePtrSet<device::mojom::XRSessionClient> session_clients_;
-  device::mojom::VRServiceClientPtr service_client_;
+  mojo::Remote<device::mojom::VRServiceClient> service_client_;
   content::RenderFrameHost* render_frame_host_;
   mojo::StrongBindingPtr<VRService> binding_;
   InterfaceSet<device::mojom::XRSessionControllerPtr> magic_window_controllers_;
diff --git a/chrome/browser/vr/service/xr_runtime_manager_unittest.cc b/chrome/browser/vr/service/xr_runtime_manager_unittest.cc
index 449628c..68c1f00 100644
--- a/chrome/browser/vr/service/xr_runtime_manager_unittest.cc
+++ b/chrome/browser/vr/service/xr_runtime_manager_unittest.cc
@@ -17,6 +17,7 @@
 #include "device/vr/test/fake_vr_device_provider.h"
 #include "device/vr/test/fake_vr_service_client.h"
 #include "device/vr/vr_device_provider.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace vr {
@@ -41,8 +42,8 @@
   }
 
   std::unique_ptr<VRServiceImpl> BindService() {
-    device::mojom::VRServiceClientPtr proxy;
-    device::FakeVRServiceClient client(mojo::MakeRequest(&proxy));
+    mojo::PendingRemote<device::mojom::VRServiceClient> proxy;
+    device::FakeVRServiceClient client(proxy.InitWithNewPipeAndPassReceiver());
     auto service = base::WrapUnique(new VRServiceImpl());
     service->SetClient(std::move(proxy));
     return service;
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index f488a4e..0392b15 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -44,11 +44,11 @@
     return device::FidoTransportProtocol::kInternal;
   }
 
-  // If caBLE is listed as one of the allowed transports, it indicates that the
-  // RP has bothered to supply the |cable_extension|. Respect that and always
-  // select caBLE in that case for GetAssertion operations.
+  // If the RP supplied the caBLE extension then respect that and always
+  // select caBLE for GetAssertion operations.
   if (transport_availability.request_type ==
           device::FidoRequestHandlerBase::RequestType::kGetAssertion &&
+      transport_availability.cable_pairing_data_supplied &&
       base::Contains(
           candidate_transports,
           AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy)) {
@@ -199,7 +199,10 @@
       break;
     }
     case AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy:
-      EnsureBleAdapterIsPoweredBeforeContinuingWithStep(Step::kCableActivate);
+      EnsureBleAdapterIsPoweredBeforeContinuingWithStep(
+          transport_availability_.cable_pairing_data_supplied
+              ? Step::kCableActivate
+              : Step::kQRCode);
       break;
     default:
       break;
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
index 4dc96ca..99aef8db 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
@@ -108,6 +108,7 @@
   enum class TransportAvailabilityParam {
     kHasTouchIdCredential,
     kHasWinNativeAuthenticator,
+    kHasCableExtension,
   };
   const struct {
     RequestType request_type;
@@ -145,7 +146,7 @@
       {RequestType::kGetAssertion,
        {AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy},
        base::nullopt,
-       {},
+       {TransportAvailabilityParam::kHasCableExtension},
        Step::kCableActivate},
 
       // The last used transport is available (and caBLE is not).
@@ -225,13 +226,13 @@
       {RequestType::kGetAssertion,
        kAllTransports,
        base::nullopt,
-       {},
+       {TransportAvailabilityParam::kHasCableExtension},
        Step::kCableActivate},
       {RequestType::kGetAssertion,
        {AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy,
         AuthenticatorTransport::kUsbHumanInterfaceDevice},
        AuthenticatorTransport::kUsbHumanInterfaceDevice,
-       {},
+       {TransportAvailabilityParam::kHasCableExtension},
        Step::kCableActivate},
 
       // caBLE should not enjoy this same high priority for MakeCredential
@@ -277,7 +278,7 @@
       {RequestType::kMakeCredential,
        {AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy},
        base::nullopt,
-       {},
+       {TransportAvailabilityParam::kHasCableExtension},
        Step::kCableActivate},
       {RequestType::kMakeCredential,
        {AuthenticatorTransport::kUsbHumanInterfaceDevice},
@@ -294,7 +295,8 @@
       {RequestType::kGetAssertion,
        {AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy},
        base::nullopt,
-       {TransportAvailabilityParam::kHasWinNativeAuthenticator},
+       {TransportAvailabilityParam::kHasWinNativeAuthenticator,
+        TransportAvailabilityParam::kHasCableExtension},
        Step::kCableActivate},
   };
 
@@ -315,6 +317,11 @@
       transports_info.win_native_api_authenticator_id = "some_authenticator_id";
     }
 
+    if (base::Contains(test_case.transport_params,
+                       TransportAvailabilityParam::kHasCableExtension)) {
+      transports_info.cable_pairing_data_supplied = true;
+    }
+
     AuthenticatorRequestDialogModel model(/*relying_party_id=*/"example.com");
     model.StartFlow(std::move(transports_info), test_case.last_used_transport,
                     &test_paired_device_list_);
@@ -486,6 +493,7 @@
     transports_info.available_transports = {test_case.transport};
     transports_info.can_power_on_ble_adapter = true;
     transports_info.is_ble_powered = true;
+    transports_info.cable_pairing_data_supplied = true;
 
     BluetoothAdapterPowerOnCallbackReceiver power_receiver;
     AuthenticatorRequestDialogModel model(/*relying_party_id=*/"example.com");
@@ -513,6 +521,7 @@
     transports_info.available_transports = {test_case.transport};
     transports_info.can_power_on_ble_adapter = false;
     transports_info.is_ble_powered = false;
+    transports_info.cable_pairing_data_supplied = true;
 
     testing::NiceMock<MockDialogModelObserver> mock_observer;
     BluetoothAdapterPowerOnCallbackReceiver power_receiver;
@@ -555,6 +564,7 @@
     transports_info.available_transports = {test_case.transport};
     transports_info.can_power_on_ble_adapter = true;
     transports_info.is_ble_powered = false;
+    transports_info.cable_pairing_data_supplied = true;
 
     BluetoothAdapterPowerOnCallbackReceiver power_receiver;
     AuthenticatorRequestDialogModel model(/*relying_party_id=*/"example.com");
diff --git a/chrome/common/extensions/manifest_handlers/extension_action_handler.cc b/chrome/common/extensions/manifest_handlers/extension_action_handler.cc
index c50a9bd..0730741 100644
--- a/chrome/common/extensions/manifest_handlers/extension_action_handler.cc
+++ b/chrome/common/extensions/manifest_handlers/extension_action_handler.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 
-#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "chrome/common/extensions/api/extension_action/action_info.h"
@@ -86,12 +85,6 @@
       return true;  // Don't synthesize actions for component extensions.
     if (extension->was_installed_by_default())
       return true;  // Don't synthesize actions for default extensions.
-    if (extension->manifest()->HasKey(
-            manifest_keys::kSynthesizeExtensionAction)) {
-      *error = base::ASCIIToUTF16(base::StringPrintf(
-          "Key %s is reserved.", manifest_keys::kSynthesizeExtensionAction));
-      return false;  // No one should use this key.
-    }
 
     // Set an empty page action. We use a page action (instead of a browser
     // action) because the action should not be seen as enabled on every page.
@@ -133,8 +126,9 @@
 
 base::span<const char* const> ExtensionActionHandler::Keys() const {
   static constexpr const char* kKeys[] = {
-      manifest_keys::kPageAction, manifest_keys::kBrowserAction,
-      manifest_keys::kSynthesizeExtensionAction};
+      manifest_keys::kPageAction,
+      manifest_keys::kBrowserAction,
+  };
   return kKeys;
 }
 
diff --git a/chrome/test/chromedriver/chrome/chrome.h b/chrome/test/chromedriver/chrome/chrome.h
index 14d2daa..2e708f7f 100644
--- a/chrome/test/chromedriver/chrome/chrome.h
+++ b/chrome/test/chromedriver/chrome/chrome.h
@@ -21,6 +21,12 @@
     kTab,
   };
 
+  enum class PermissionState {
+    kGranted,
+    kDenied,
+    kPrompt,
+  };
+
   virtual ~Chrome() {}
 
   virtual Status GetAsDesktop(ChromeDesktopImpl** desktop) = 0;
@@ -89,6 +95,13 @@
   // Enables acceptInsecureCerts mode for the browser.
   virtual Status SetAcceptInsecureCerts() = 0;
 
+  // Requests altering permission setting for given permission.
+  virtual Status SetPermission(
+      std::unique_ptr<base::DictionaryValue> permission_descriptor,
+      PermissionState desired_state,
+      bool one_realm,
+      WebView* current_view) = 0;
+
   // Get the operation system where Chrome is running.
   virtual std::string GetOperatingSystemName() = 0;
 
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc
index 67b8fe6..9e207f2 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_impl.cc
@@ -14,6 +14,7 @@
 #include "chrome/test/chromedriver/chrome/page_load_strategy.h"
 #include "chrome/test/chromedriver/chrome/status.h"
 #include "chrome/test/chromedriver/chrome/web_view_impl.h"
+#include "url/gurl.h"
 
 ChromeImpl::~ChromeImpl() {
 }
@@ -438,6 +439,38 @@
   return Status(kOk);
 }
 
+Status ChromeImpl::SetPermission(
+    std::unique_ptr<base::DictionaryValue> permission_descriptor,
+    PermissionState desired_state,
+    bool unused_one_realm,  // This is ignored. https://crbug.com/977612.
+    WebView* current_view) {
+  Status status = devtools_websocket_client_->ConnectIfNecessary();
+  if (status.IsError())
+    return status;
+
+  // Process URL.
+  std::string current_url;
+  status = current_view->GetUrl(&current_url);
+  if (status.IsError())
+    current_url = "";
+
+  std::string permission_setting;
+  if (desired_state == PermissionState::kGranted)
+    permission_setting = "granted";
+  else if (desired_state == PermissionState::kDenied)
+    permission_setting = "denied";
+  else if (desired_state == PermissionState::kPrompt)
+    permission_setting = "prompt";
+  else
+    return Status(kInvalidArgument, "unsupported PermissionState");
+
+  base::DictionaryValue args;
+  args.SetString("origin", current_url);
+  args.SetDictionary("permission", std::move(permission_descriptor));
+  args.SetString("setting", permission_setting);
+  return devtools_websocket_client_->SendCommand("Browser.setPermission", args);
+}
+
 bool ChromeImpl::IsMobileEmulationEnabled() const {
   return false;
 }
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.h b/chrome/test/chromedriver/chrome/chrome_impl.h
index 652e8e2..695ff72 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_impl.h
@@ -51,6 +51,11 @@
   Status CloseWebView(const std::string& id) override;
   Status ActivateWebView(const std::string& id) override;
   Status SetAcceptInsecureCerts() override;
+  Status SetPermission(
+      std::unique_ptr<base::DictionaryValue> permission_descriptor,
+      PermissionState desired_state,
+      bool unused_one_realm,
+      WebView* current_view) override;
   bool IsMobileEmulationEnabled() const override;
   bool HasTouchScreen() const override;
   std::string page_load_strategy() const override;
@@ -87,6 +92,11 @@
   std::unique_ptr<DevToolsClient> devtools_websocket_client_;
 
  private:
+  static Status PermissionNameToChromePermissions(
+      const base::DictionaryValue& permission_descriptor,
+      Chrome::PermissionState setting,
+      std::vector<std::string>* chrome_permissions);
+
   void UpdateWebViews(const WebViewsInfo& views_info, bool w3c_compliant);
 
   // Web views in this list are in the same order as they are opened.
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.cc b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
index 1c2adef..4d008c2c 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.cc
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
@@ -28,6 +28,12 @@
 const char kInspectorContextError[] =
     "Cannot find execution context with given id";
 const char kInspectorInvalidURL[] = "Cannot navigate to invalid URL";
+const char kInspectorInsecureContext[] =
+    "Permission can't be granted in current context.";
+const char kInspectorOpaqueOrigins[] =
+    "Permission can't be granted to opaque origins.";
+const char kInspectorPushPermissionError[] =
+    "Push Permission without userVisibleOnly:true isn't supported";
 static constexpr int kInvalidParamsInspectorCode = -32602;
 
 class ScopedIncrementer {
@@ -650,6 +656,12 @@
       return Status(kNoSuchExecutionContext);
     } else if (error_message == kInspectorInvalidURL) {
       return Status(kInvalidArgument);
+    } else if (error_message == kInspectorInsecureContext) {
+      return Status(kInvalidArgument,
+                    "feature cannot be used in insecure context");
+    } else if (error_message == kInspectorPushPermissionError ||
+               error_message == kInspectorOpaqueOrigins) {
+      return Status(kInvalidArgument, error_message);
     }
     base::Optional<int> error_code = error_dict->FindIntPath("code");
     if (error_code == kInvalidParamsInspectorCode)
diff --git a/chrome/test/chromedriver/chrome/stub_chrome.cc b/chrome/test/chromedriver/chrome/stub_chrome.cc
index 471b9dc..7a5113ae 100644
--- a/chrome/test/chromedriver/chrome/stub_chrome.cc
+++ b/chrome/test/chromedriver/chrome/stub_chrome.cc
@@ -94,6 +94,14 @@
   return Status(kOk);
 }
 
+Status StubChrome::SetPermission(
+    std::unique_ptr<base::DictionaryValue> permission_descriptor,
+    Chrome::PermissionState desired_state,
+    bool one_realm,
+    WebView* current_view) {
+  return Status(kOk);
+}
+
 std::string StubChrome::GetOperatingSystemName() {
   return std::string();
 }
diff --git a/chrome/test/chromedriver/chrome/stub_chrome.h b/chrome/test/chromedriver/chrome/stub_chrome.h
index 067a8c0..f76572a0 100644
--- a/chrome/test/chromedriver/chrome/stub_chrome.h
+++ b/chrome/test/chromedriver/chrome/stub_chrome.h
@@ -46,6 +46,11 @@
   Status CloseWebView(const std::string& id) override;
   Status ActivateWebView(const std::string& id) override;
   Status SetAcceptInsecureCerts() override;
+  Status SetPermission(
+      std::unique_ptr<base::DictionaryValue> permission_descriptor,
+      Chrome::PermissionState desired_state,
+      bool one_realm,
+      WebView* current_view) override;
   std::string GetOperatingSystemName() override;
   bool IsMobileEmulationEnabled() const override;
   bool HasTouchScreen() const override;
diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py
index 38e9514..54b4df0 100644
--- a/chrome/test/chromedriver/client/chromedriver.py
+++ b/chrome/test/chromedriver/client/chromedriver.py
@@ -365,6 +365,9 @@
     return self.ExecuteCommand(
         Command.EXECUTE_SCRIPT, {'script': script, 'args': converted_args})
 
+  def SetPermission(self, parameters):
+    return self.ExecuteCommand(Command.SET_PERMISSION, parameters)
+
   def ExecuteAsyncScript(self, script, *args):
     converted_args = list(args)
     return self.ExecuteCommand(
diff --git a/chrome/test/chromedriver/client/command_executor.py b/chrome/test/chromedriver/client/command_executor.py
index f718f03..00379b2 100644
--- a/chrome/test/chromedriver/client/command_executor.py
+++ b/chrome/test/chromedriver/client/command_executor.py
@@ -189,6 +189,8 @@
   SET_USER_VERIFIED = (
       _Method.POST,
       '/session/:sessionId/webauthn/authenticator/:authenticatorId/uv')
+  SET_PERMISSION = (
+      _Method.POST, '/session/:sessionId/permissions')
 
   # Custom Chrome commands.
   IS_LOADING = (_Method.GET, '/session/:sessionId/is_loading')
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index d25b87f..c9357a9 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -801,6 +801,11 @@
                         base::BindRepeating(
                             &ExecuteWebAuthnCommand,
                             base::BindRepeating(&ExecuteSetUserVerified)))),
+      // Extension for Permissions Standard Automation "set permission" command:
+      // https://w3c.github.io/permissions/#set-permission-command
+      CommandMapping(kPost, "session/:sessionId/permissions",
+                     WrapToCommand("SetPermission",
+                                   base::BindRepeating(&ExecuteSetPermission))),
 
       //
       // Non-standard extension commands
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index b36efa6..33be2ad 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -2043,6 +2043,263 @@
     self.assertEquals('test', report['type']);
     self.assertEquals('test report message', report['body']['message']);
 
+  def GetPermissionWithQuery(self, query):
+    script = """
+        let query = arguments[0];
+        let done = arguments[1];
+        console.log(done);
+        navigator.permissions.query(query)
+          .then(function(value) {
+              done({ status: 'success', value: value && value.state });
+            }, function(error) {
+              done({ status: 'error', value: error && error.message });
+            });
+    """
+    return self._driver.ExecuteAsyncScript(script, query)
+
+  def GetPermission(self, name):
+    return self.GetPermissionWithQuery({ 'name': name })
+
+  def CheckPermission(self, response, expected_state):
+    self.assertEquals(response['status'], 'success')
+    self.assertEquals(response['value'], expected_state)
+
+  def testPermissionsOpaqueOriginsThrowError(self):
+    """ Confirms that opaque origins cannot have overrides. """
+    self._driver.Load("about:blank")
+    self.assertRaises(chromedriver.InvalidArgument,
+      self._driver.SetPermission, {'descriptor': { 'name': 'geolocation' },
+        'state': 'denied'})
+
+  def testPermissionStates(self):
+    """ Confirms that denied, granted, and prompt can be set. """
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    self._driver.SetPermission({
+      'descriptor': { 'name': 'geolocation' },
+      'state': 'denied'
+    })
+    self.CheckPermission(self.GetPermission('geolocation'), 'denied')
+    self._driver.SetPermission({
+      'descriptor': { 'name': 'geolocation' },
+      'state': 'granted'
+    })
+    self.CheckPermission(self.GetPermission('geolocation'), 'granted')
+    self._driver.SetPermission({
+      'descriptor': { 'name': 'geolocation' },
+      'state': 'prompt'
+    })
+    self.CheckPermission(self.GetPermission('geolocation'), 'prompt')
+
+  def testSettingPermissionDoesNotAffectOthers(self):
+    """ Confirm permissions do not affect unset permissions. """
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    response = self.GetPermission('geolocation')
+    self.assertEquals(response['status'], 'success')
+    status = response['value']
+    self._driver.SetPermission({
+      'descriptor': { 'name': 'background-sync' },
+      'state': 'denied'
+    })
+    self.CheckPermission(self.GetPermission('background-sync'), 'denied')
+    self.CheckPermission(self.GetPermission('geolocation'), status)
+
+  def testMultiplePermissions(self):
+    """ Confirms multiple custom permissions can be set simultaneously. """
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    self._driver.SetPermission({
+      'descriptor': { 'name': 'geolocation' },
+      'state': 'denied'
+    })
+    self._driver.SetPermission({
+      'descriptor': { 'name': 'background-fetch' },
+      'state': 'prompt'
+    })
+    self._driver.SetPermission({
+      'descriptor': { 'name': 'background-sync' },
+      'state': 'granted'
+    })
+    self.CheckPermission(self.GetPermission('geolocation'), 'denied')
+    self.CheckPermission(self.GetPermission('background-fetch'), 'prompt')
+    self.CheckPermission(self.GetPermission('background-sync'), 'granted')
+
+  def testSensorPermissions(self):
+    """ Tests sensor permissions.
+
+    Currently, Chrome controls all sensor permissions (accelerometer,
+    magnetometer, gyroscope, ambient-light-sensor) with the 'sensors'
+    permission. This test demonstrates this internal implementation detail so
+    developers are aware of this behavior.
+    """
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    parameters = {
+      'descriptor': { 'name': 'magnetometer' },
+      'state': 'granted'
+    }
+    self._driver.SetPermission(parameters)
+    # Light sensor is not enabled by default, so it cannot be queried or set.
+    #self.CheckPermission(self.GetPermission('ambient-light-sensor'), 'granted')
+    self.CheckPermission(self.GetPermission('magnetometer'), 'granted')
+    self.CheckPermission(self.GetPermission('accelerometer'), 'granted')
+    self.CheckPermission(self.GetPermission('gyroscope'), 'granted')
+    parameters = {
+      'descriptor': { 'name': 'gyroscope' },
+      'state': 'denied'
+    }
+    self._driver.SetPermission(parameters)
+    #self.CheckPermission(self.GetPermission('ambient-light-sensor'), 'denied')
+    self.CheckPermission(self.GetPermission('magnetometer'), 'denied')
+    self.CheckPermission(self.GetPermission('accelerometer'), 'denied')
+    self.CheckPermission(self.GetPermission('gyroscope'), 'denied')
+
+  def testMidiPermissions(self):
+    """ Tests midi permission requirements.
+
+    MIDI, sysex: true, when granted, should automatically grant regular MIDI
+    permissions.
+    When regular MIDI is denied, this should also imply MIDI with sysex is
+    denied.
+    """
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    parameters = {
+      'descriptor': { 'name': 'midi', 'sysex': True },
+      'state': 'granted'
+    }
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermissionWithQuery(parameters['descriptor']),
+                         'granted')
+    parameters['descriptor']['sysex'] = False
+    self.CheckPermission(self.GetPermissionWithQuery(parameters['descriptor']),
+                         'granted')
+
+    parameters = {
+      'descriptor': { 'name': 'midi', 'sysex': False },
+      'state': 'denied'
+    }
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermissionWithQuery(parameters['descriptor']),
+                         'denied')
+    # While this should be denied, Chrome does not do this.
+    # parameters['descriptor']['sysex'] = True should be denied.
+
+  def testClipboardPermissions(self):
+    """ Assures both clipboard permissions are set simultaneously. """
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    parameters = {
+      'descriptor': { 'name': 'clipboard-read' },
+      'state': 'granted'
+    }
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermission('clipboard-read'), 'granted')
+    parameters = {
+      'descriptor': { 'name': 'clipboard-write' },
+      'state': 'prompt'
+    }
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermission('clipboard-read'), 'granted')
+    self.CheckPermission(self.GetPermission('clipboard-write'), 'prompt')
+
+  def testPersistentStoragePermissions(self):
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    parameters = {
+      'descriptor': { 'name': 'persistent-storage' },
+      'state': 'granted'
+    }
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermission('persistent-storage'), 'granted')
+    parameters['state'] = 'denied'
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermission('persistent-storage'), 'denied')
+
+  def testPushAndNotificationsPermissions(self):
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    parameters = {
+      'descriptor': { 'name': 'notifications' },
+      'state': 'granted'
+    }
+    push_descriptor = {
+      'name': 'push',
+      'userVisibleOnly': True
+    }
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermission('notifications'), 'granted')
+    self.CheckPermission(self.GetPermissionWithQuery(push_descriptor),
+                         'granted')
+    parameters['state'] = 'denied'
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermission('notifications'), 'denied')
+    self.CheckPermission(self.GetPermissionWithQuery(push_descriptor), 'denied')
+    push_descriptor['userVisibleOnly'] = False
+    parameters = {
+      'descriptor': push_descriptor,
+      'state': 'prompt'
+    }
+    self.assertRaises(chromedriver.InvalidArgument,
+                      self._driver.SetPermission, parameters)
+
+  def testPermissionsSameOrigin(self):
+    """ Assures permissions are shared between same-domain windows. """
+    window_handle = self._driver.NewWindow()['handle']
+    self._driver.SwitchToWindow(window_handle)
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/link_nav.html'))
+    another_window_handle = self._driver.NewWindow()['handle']
+    self._driver.SwitchToWindow(another_window_handle)
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+
+    # Set permission.
+    parameters = { 'descriptor': { 'name': 'geolocation' }, 'state': 'granted' }
+
+    # Test that they are present across the same domain.
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermission('geolocation'), 'granted')
+    self._driver.SwitchToWindow(window_handle)
+    self.CheckPermission(self.GetPermission('geolocation'), 'granted')
+
+  def testNewWindowSameDomainHasSamePermissions(self):
+    """ Assures permissions are shared between same-domain windows, even when
+    window is created after permissions are set. """
+    window_handle = self._driver.NewWindow()['handle']
+    self._driver.SwitchToWindow(window_handle)
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    self._driver.SetPermission({ 'descriptor': { 'name': 'geolocation' },
+                                  'state': 'denied' })
+    self.CheckPermission(self.GetPermission('geolocation'), 'denied')
+    same_domain = self._driver.NewWindow()['handle']
+    self._driver.SwitchToWindow(same_domain)
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/link_nav.html'))
+    self.CheckPermission(self.GetPermission('geolocation'), 'denied')
+
+
+  def testPermissionsSameOriginDoesNotAffectOthers(self):
+    """ Tests whether permissions set between two domains affect others. """
+    window_handle = self._driver.NewWindow()['handle']
+    self._driver.SwitchToWindow(window_handle)
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/link_nav.html'))
+    another_window_handle = self._driver.NewWindow()['handle']
+    self._driver.SwitchToWindow(another_window_handle)
+    self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
+    different_domain = self._driver.NewWindow()['handle']
+    self._driver.SwitchToWindow(different_domain)
+    self._driver.Load('https://google.com')
+    self._driver.SetPermission({ 'descriptor': {'name': 'geolocation'},
+                                  'state': 'denied' })
+
+    # Switch for permissions.
+    self._driver.SwitchToWindow(another_window_handle)
+
+    # Set permission.
+    parameters = { 'descriptor': { 'name': 'geolocation' }, 'state': 'prompt' }
+
+    # Test that they are present across the same domain.
+    self._driver.SetPermission(parameters)
+    self.CheckPermission(self.GetPermission('geolocation'), 'prompt')
+
+    self._driver.SwitchToWindow(window_handle)
+    self.CheckPermission(self.GetPermission('geolocation'), 'prompt')
+
+    # Assert different domain is not the same.
+    self._driver.SwitchToWindow(different_domain)
+    self.CheckPermission(self.GetPermission('geolocation'), 'denied')
+
 # Tests that require a secure context.
 class ChromeDriverSecureContextTest(ChromeDriverBaseTestWithWebServer):
   # The example attestation private key from the U2F spec at
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index 938b289..6830a2f 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -2181,3 +2181,41 @@
   *value = web_view->GetCastIssueMessage();
   return Status(kOk);
 }
+
+Status ExecuteSetPermission(Session* session,
+                            WebView* web_view,
+                            const base::DictionaryValue& params,
+                            std::unique_ptr<base::Value>* value,
+                            Timeout* timeout) {
+  const base::DictionaryValue* descriptor;
+  if (!params.GetDictionary("descriptor", &descriptor))
+    return Status(kInvalidArgument, "no descriptor dictionary");
+
+  std::string name;
+  if (!descriptor->GetString("name", &name))
+    return Status(kInvalidArgument, "no name in descriptor");
+
+  std::string permission_state;
+  if (!params.GetString("state", &permission_state))
+    return Status(kInvalidArgument, "no permission state");
+
+  bool one_realm = false;
+  if (!GetOptionalBool(&params, "oneRealm", &one_realm, nullptr))
+    return Status(kInvalidArgument, "oneRealm defined but not a boolean");
+
+  Chrome::PermissionState valid_state;
+  if (permission_state == "granted")
+    valid_state = Chrome::PermissionState::kGranted;
+  else if (permission_state == "denied")
+    valid_state = Chrome::PermissionState::kDenied;
+  else if (permission_state == "prompt")
+    valid_state = Chrome::PermissionState::kPrompt;
+  else
+    return Status(kInvalidArgument, "unrecognized permission state");
+
+  auto val = base::Value::ToUniquePtrValue(descriptor->Clone());
+  auto dict = base::DictionaryValue::From(std::move(val));
+
+  return session->chrome->SetPermission(std::move(dict), valid_state, one_realm,
+                                        web_view);
+}
diff --git a/chrome/test/chromedriver/window_commands.h b/chrome/test/chromedriver/window_commands.h
index fd52c4b..32597d8 100644
--- a/chrome/test/chromedriver/window_commands.h
+++ b/chrome/test/chromedriver/window_commands.h
@@ -448,4 +448,11 @@
                               std::unique_ptr<base::Value>* value,
                               Timeout* timeout);
 
+// Sets permissions.
+Status ExecuteSetPermission(Session* session,
+                            WebView* web_view,
+                            const base::DictionaryValue& params,
+                            std::unique_ptr<base::Value>* value,
+                            Timeout* timeout);
+
 #endif  // CHROME_TEST_CHROMEDRIVER_WINDOW_COMMANDS_H_
diff --git a/chrome/test/data/extensions/api_test/autotest_private/test.js b/chrome/test/data/extensions/api_test/autotest_private/test.js
index 06bc3a1..86ece693 100644
--- a/chrome/test/data/extensions/api_test/autotest_private/test.js
+++ b/chrome/test/data/extensions/api_test/autotest_private/test.js
@@ -234,21 +234,21 @@
   function setAssistantEnabled() {
     chrome.autotestPrivate.setAssistantEnabled(true, 1000 /* timeout_ms */,
         chrome.test.callbackFail(
-            'Assistant not allowed - state: 9'));
+            'Assistant not allowed - state: 8'));
   },
   function sendAssistantTextQuery() {
     chrome.autotestPrivate.sendAssistantTextQuery(
         'what time is it?' /* query */,
         1000 /* timeout_ms */,
         chrome.test.callbackFail(
-            'Assistant not allowed - state: 9'));
+            'Assistant not allowed - state: 8'));
   },
   function setWhitelistedPref() {
     chrome.autotestPrivate.setWhitelistedPref(
         'settings.voice_interaction.hotword.enabled' /* pref_name */,
         true /* value */,
         chrome.test.callbackFail(
-            'Assistant not allowed - state: 9'));
+            'Assistant not allowed - state: 8'));
   },
   // This test verifies that getArcState returns provisioned False in case ARC
   // is not provisioned by default.
diff --git a/chrome/test/data/extensions/api_test/chromeos_info_private/extended/background.js b/chrome/test/data/extensions/api_test/chromeos_info_private/extended/background.js
index 85da476..282c9c1 100644
--- a/chrome/test/data/extensions/api_test/chromeos_info_private/extended/background.js
+++ b/chrome/test/data/extensions/api_test/chromeos_info_private/extended/background.js
@@ -57,9 +57,6 @@
             case 'stylus seen':
               chrome.test.assertEq('seen', values['stylusStatus']);
               break;
-            case 'assistant unsupported':
-              chrome.test.assertEq('unsupported', values['assistantStatus']);
-              break;
             case 'assistant supported':
               chrome.test.assertEq('supported', values['assistantStatus']);
               break;
diff --git a/chrome/test/data/extensions/context_menus/incognito/service_worker/manifest.json b/chrome/test/data/extensions/context_menus/incognito/service_worker/manifest.json
new file mode 100644
index 0000000..859dd44a
--- /dev/null
+++ b/chrome/test/data/extensions/context_menus/incognito/service_worker/manifest.json
@@ -0,0 +1,8 @@
+{
+  "name": "Tests that context menus work properly in incognito split mode",
+  "version": "0.1",
+  "manifest_version": 2,
+  "permissions": ["contextMenus"],
+  "incognito": "split",
+  "background": { "service_worker": "test.js" }
+}
diff --git a/chrome/test/data/extensions/context_menus/incognito/service_worker/test.js b/chrome/test/data/extensions/context_menus/incognito/service_worker/test.js
new file mode 100644
index 0000000..7568bc2
--- /dev/null
+++ b/chrome/test/data/extensions/context_menus/incognito/service_worker/test.js
@@ -0,0 +1,14 @@
+// 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.
+
+var inIncognitoContext = chrome.extension.inIncognitoContext;
+var incognitoStr = inIncognitoContext ? 'incognito' : 'regular';
+
+chrome.contextMenus.create({title: 'item ' + incognitoStr}, function() {
+  chrome.test.assertNoLastError();
+  chrome.contextMenus.onClicked.addListener(function(info, tab) {
+    chrome.test.sendMessage('onclick fired ' + incognitoStr);
+  });
+  chrome.test.sendMessage('created item ' + incognitoStr);
+});
diff --git a/chrome/test/data/webui/chromeos/fake_network_config_mojom.js b/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
index c7d75631..51547083 100644
--- a/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
+++ b/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
@@ -56,7 +56,8 @@
   getNetworkState(guid) {
     return new Promise(resolve => {
       this.extensionApi_.getState(guid, network => {
-        resolve({result: this.networkStateToMojo_(network)});
+        const result = network ? this.networkStateToMojo_(network) : null;
+        resolve({result: result});
       });
     });
   }
@@ -72,7 +73,8 @@
         networkType: OncMojo.getNetworkTypeString(filter.networkType)
       };
       this.extensionApi_.getNetworks(extensionFilter, networks => {
-        let result = networks.map(network => this.networkStateToMojo_(network));
+        const result =
+            networks.map(network => this.networkStateToMojo_(network));
         resolve({result: result});
       });
     });
@@ -85,7 +87,7 @@
   getDeviceStateList() {
     return new Promise(resolve => {
       this.extensionApi_.getDeviceStates(devices => {
-        let result = devices.map(device => this.deviceToMojo(device));
+        const result = devices.map(device => this.deviceToMojo(device));
         resolve({result: result});
       });
     });
@@ -99,7 +101,8 @@
   getManagedProperties(guid) {
     return new Promise(resolve => {
       this.extensionApi_.getManagedProperties(guid, network => {
-        resolve({result: this.managedPropertiesToMojo_(network)});
+        const result = network ? this.managedPropertiesToMojo_(network) : null;
+        resolve({result: result});
       });
     });
   }
diff --git a/chrome/test/data/webui/settings/a11y/google_assistant_a11y_test.js b/chrome/test/data/webui/settings/a11y/google_assistant_a11y_test.js
index a9758b6..43102b6 100644
--- a/chrome/test/data/webui/settings/a11y/google_assistant_a11y_test.js
+++ b/chrome/test/data/webui/settings/a11y/google_assistant_a11y_test.js
@@ -23,7 +23,6 @@
     // legacy combined settings and we don't want to test everything twice.
     return {
       enabled: [
-        'chromeos::features::kAssistantFeature',
         'chromeos::features::kSplitSettings'
       ]
     };
diff --git a/chrome/test/data/webui/tab_strip/tab_list_test.js b/chrome/test/data/webui/tab_strip/tab_list_test.js
index d4e362d..55139cd 100644
--- a/chrome/test/data/webui/tab_strip/tab_list_test.js
+++ b/chrome/test/data/webui/tab_strip/tab_list_test.js
@@ -39,6 +39,15 @@
     ],
   };
 
+  function getUnpinnedTabs() {
+    return tabList.shadowRoot.querySelectorAll('#tabsContainer tabstrip-tab');
+  }
+
+  function getPinnedTabs() {
+    return tabList.shadowRoot.querySelectorAll(
+        '#pinnedTabsContainer tabstrip-tab');
+  }
+
   setup(() => {
     document.body.innerHTML = '';
 
@@ -54,7 +63,7 @@
   });
 
   test('creates a tab element for each tab', () => {
-    const tabElements = tabList.shadowRoot.querySelectorAll('tabstrip-tab');
+    const tabElements = getUnpinnedTabs();
     assertEquals(currentWindow.tabs.length, tabElements.length);
     currentWindow.tabs.forEach((tab, index) => {
       assertEquals(tabElements[index].tab, tab);
@@ -69,7 +78,7 @@
       windowId: currentWindow.id,
     };
     callbackRouter.onCreated.dispatchEvent(appendedTab);
-    let tabElements = tabList.shadowRoot.querySelectorAll('tabstrip-tab');
+    let tabElements = getUnpinnedTabs();
     assertEquals(currentWindow.tabs.length + 1, tabElements.length);
     assertEquals(tabElements[currentWindow.tabs.length].tab, appendedTab);
 
@@ -80,7 +89,7 @@
       windowId: currentWindow.id,
     };
     callbackRouter.onCreated.dispatchEvent(prependedTab);
-    tabElements = tabList.shadowRoot.querySelectorAll('tabstrip-tab');
+    tabElements = getUnpinnedTabs();
     assertEquals(currentWindow.tabs.length + 2, tabElements.length);
     assertEquals(tabElements[0].tab, prependedTab);
   });
@@ -95,7 +104,7 @@
           windowId: currentWindow.id + 1,
         };
         callbackRouter.onCreated.dispatchEvent(newTab);
-        const tabElements = tabList.shadowRoot.querySelectorAll('tabstrip-tab');
+        const tabElements = getUnpinnedTabs();
         assertEquals(currentWindow.tabs.length, tabElements.length);
       });
 
@@ -104,7 +113,7 @@
     callbackRouter.onRemoved.dispatchEvent(tabToRemove.id, {
       windowId: currentWindow.id,
     });
-    const tabElements = tabList.shadowRoot.querySelectorAll('tabstrip-tab');
+    const tabElements = getUnpinnedTabs();
     assertEquals(currentWindow.tabs.length - 1, tabElements.length);
   });
 
@@ -115,12 +124,12 @@
     callbackRouter.onUpdated.dispatchEvent(
         tabToUpdate.id, changeInfo, updatedTab);
 
-    const tabElements = tabList.shadowRoot.querySelectorAll('tabstrip-tab');
+    const tabElements = getUnpinnedTabs();
     assertEquals(tabElements[0].tab, updatedTab);
   });
 
   test('updates tabs when a new tab is activated', () => {
-    const tabElements = tabList.shadowRoot.querySelectorAll('tabstrip-tab');
+    const tabElements = getUnpinnedTabs();
 
     // Mock activating the 2nd tab
     callbackRouter.onActivated.dispatchEvent({
@@ -131,4 +140,38 @@
     assertTrue(tabElements[1].tab.active);
     assertFalse(tabElements[2].tab.active);
   });
+
+  test('adds a pinned tab to its designated container', () => {
+    callbackRouter.onCreated.dispatchEvent({
+      index: 0,
+      title: 'New pinned tab',
+      pinned: true,
+      windowId: currentWindow.id,
+    });
+    const pinnedTabElements = getPinnedTabs();
+    assertEquals(pinnedTabElements.length, 1);
+    assertTrue(pinnedTabElements[0].tab.pinned);
+  });
+
+  test('moves pinned tabs to designated containers', () => {
+    const tabToPin = currentWindow.tabs[1];
+    const changeInfo = {index: 0, pinned: true};
+    let updatedTab = Object.assign({}, tabToPin, changeInfo);
+    callbackRouter.onUpdated.dispatchEvent(tabToPin.id, changeInfo, updatedTab);
+    let pinnedTabElements = getPinnedTabs();
+    assertEquals(pinnedTabElements.length, 1);
+    assertTrue(pinnedTabElements[0].tab.pinned);
+    assertEquals(pinnedTabElements[0].tab.id, tabToPin.id);
+    assertEquals(getUnpinnedTabs().length, 2);
+
+    // Unpin the tab so that it's now at index 0
+    changeInfo.index = 0;
+    changeInfo.pinned = false;
+    updatedTab = Object.assign({}, updatedTab, changeInfo);
+    callbackRouter.onUpdated.dispatchEvent(tabToPin.id, changeInfo, updatedTab);
+    const unpinnedTabElements = getUnpinnedTabs();
+    assertEquals(getPinnedTabs().length, 0);
+    assertEquals(unpinnedTabElements.length, 3);
+    assertEquals(unpinnedTabElements[0].tab.id, tabToPin.id);
+  });
 });
diff --git a/chrome/test/data/webui/tab_strip/tab_test.js b/chrome/test/data/webui/tab_strip/tab_test.js
index 56f11dee..c0ac7a0 100644
--- a/chrome/test/data/webui/tab_strip/tab_test.js
+++ b/chrome/test/data/webui/tab_strip/tab_test.js
@@ -36,6 +36,13 @@
     assertFalse(tabElement.hasAttribute('active'));
   });
 
+  test('toggles a [pinned] attribute when pinned', () => {
+    tabElement.tab = Object.assign({}, tab, {pinned: true});
+    assertTrue(tabElement.hasAttribute('pinned'));
+    tabElement.tab = Object.assign({}, tab, {pinned: false});
+    assertFalse(tabElement.hasAttribute('pinned'));
+  });
+
   test('clicking on the element activates the tab', () => {
     tabElement.click();
     return testTabsApiProxy.whenCalled('activateTab', tabId => {
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc
index b280bbf2..b96904e 100644
--- a/chrome/test/ppapi/ppapi_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_browsertest.cc
@@ -1236,8 +1236,9 @@
     UDPSocket_DropListenerPipeOnConstruction,
     UDPSocket_ReadFails,
     WrappedUDPSocket::FailureType::kDropListenerPipeOnConstruction)
+// Flaky on all platforms. http://crbug.com/997785.
 UDPSOCKET_FAILURE_TEST(
-    UDPSocket_DropListenerPipeOnReceiveMore,
+    DISABLED_UDPSocket_DropListenerPipeOnReceiveMore,
     UDPSocket_ReadFails,
     WrappedUDPSocket::FailureType::kDropListenerPipeOnReceiveMore)
 
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 8b62a934..2bdab86 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -238,8 +238,7 @@
   # To diagnose tast failures or disable tests, see go/tast-failures
   tast_test("chrome_all_tast_tests") {
     # To disable a specific test, add it the following list and cite a bug.
-    # crbug.com/968155
-    tast_disabled_tests = [ "security.SELinuxFilesARC" ]
+    tast_disabled_tests = []
   }
 
   group("cros_chrome_deploy") {
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 20a101e4..92916bb 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -25,10 +25,6 @@
 const base::Feature kAmbientModeFeature{"ChromeOSAmbientMode",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Controls whether to enable Google Assistant feature.
-const base::Feature kAssistantFeature{"ChromeOSAssistant",
-                                      base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables or disables auto screen-brightness adjustment when ambient light
 // changes.
 const base::Feature kAutoScreenBrightness{"AutoScreenBrightness",
@@ -219,10 +215,6 @@
   return base::FeatureList::IsEnabled(kAmbientModeFeature);
 }
 
-bool IsAssistantEnabled() {
-  return base::FeatureList::IsEnabled(kAssistantFeature);
-}
-
 bool IsImeDecoderWithSandboxEnabled() {
   return base::FeatureList::IsEnabled(kImeDecoderWithSandbox);
 }
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index dfe77cfc..b01393c 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -19,8 +19,6 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kAmbientModeFeature;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
-extern const base::Feature kAssistantFeature;
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kAutoScreenBrightness;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kBluetoothAggressiveAppearanceFilter;
diff --git a/chromeos/services/assistant/fake_client.h b/chromeos/services/assistant/fake_client.h
index 483b851..9686268 100644
--- a/chromeos/services/assistant/fake_client.h
+++ b/chromeos/services/assistant/fake_client.h
@@ -43,11 +43,11 @@
   void RequestAssistantVolumeControl(
       mojo::PendingReceiver<ash::mojom::AssistantVolumeControl> receiver)
       override {}
-  void RequestVoiceInteractionController(
-      mojo::PendingReceiver<ash::mojom::VoiceInteractionController> receiver)
+  void RequestAssistantStateController(
+      mojo::PendingReceiver<ash::mojom::AssistantStateController> receiver)
       override {}
   void RequestPrefStoreConnector(
-      mojo::PendingReceiver<prefs::mojom::PrefStoreConnector> receiver)
+      mojo::PendingReceiver<::prefs::mojom::PrefStoreConnector> receiver)
       override {}
   void RequestBatteryMonitor(
       mojo::PendingReceiver<device::mojom::BatteryMonitor> receiver) override {}
diff --git a/chromeos/services/assistant/public/mojom/assistant.mojom b/chromeos/services/assistant/public/mojom/assistant.mojom
index d273771..920a9fc 100644
--- a/chromeos/services/assistant/public/mojom/assistant.mojom
+++ b/chromeos/services/assistant/public/mojom/assistant.mojom
@@ -6,7 +6,7 @@
 
 import "ash/public/mojom/assistant_controller.mojom";
 import "ash/public/mojom/assistant_volume_control.mojom";
-import "ash/public/mojom/voice_interaction_controller.mojom";
+import "ash/public/mojom/assistant_state_controller.mojom";
 import "chromeos/services/assistant/public/mojom/assistant_audio_decoder.mojom";
 import "chromeos/services/assistant/public/mojom/assistant_notification.mojom";
 import "chromeos/services/assistant/public/mojom/settings.mojom";
@@ -227,9 +227,9 @@
   RequestAssistantVolumeControl(
       pending_receiver<ash.mojom.AssistantVolumeControl> receiver);
 
-  // Requests a VoiceInteractionController from the browser.
-  RequestVoiceInteractionController(
-      pending_receiver<ash.mojom.VoiceInteractionController> receiver);
+  // Requests Ash's AssistantStateController interface from the browser.
+  RequestAssistantStateController(
+      pending_receiver<ash.mojom.AssistantStateController> receiver);
 
   // Requests a PrefStoreConnector from the browser.
   RequestPrefStoreConnector(
diff --git a/chromeos/services/assistant/service.cc b/chromeos/services/assistant/service.cc
index 08f7d4d..9be4361 100644
--- a/chromeos/services/assistant/service.cc
+++ b/chromeos/services/assistant/service.cc
@@ -30,8 +30,6 @@
 #include "chromeos/services/assistant/pref_connection_delegate.h"
 #include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
 #include "chromeos/services/assistant/public/features.h"
-#include "components/prefs/pref_change_registrar.h"
-#include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_manager/known_user.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -86,6 +84,7 @@
 
 Service::~Service() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  assistant_state_.RemoveObserver(this);
   auto* const session_controller = ash::SessionController::Get();
   if (observing_ash_session_ && session_controller) {
     session_controller->RemoveSessionActivationObserverForAccountId(account_id_,
@@ -118,8 +117,7 @@
 
   // Disable hotword if hotword is not set to always on and power source is not
   // connected.
-  if (!dsp_available &&
-      !pref_service_->GetBoolean(prefs::kAssistantHotwordAlwaysOn) &&
+  if (!dsp_available && !assistant_state_.hotword_always_on().value_or(false) &&
       !power_source_connected_) {
     return false;
   }
@@ -149,8 +147,9 @@
   client_ = std::move(client);
   device_actions_ = std::move(device_actions);
 
-  mojo::PendingRemote<ash::mojom::VoiceInteractionController> remote_controller;
-  client_->RequestVoiceInteractionController(
+  // TODO(b/138679823): Moving the logics into AssistantStateProxy.
+  mojo::PendingRemote<ash::mojom::AssistantStateController> remote_controller;
+  client_->RequestAssistantStateController(
       remote_controller.InitWithNewPipeAndPassReceiver());
   assistant_state_.Init(std::move(remote_controller));
   assistant_state_.AddObserver(this);
@@ -238,7 +237,7 @@
   UpdateListeningState();
 }
 
-void Service::OnAssistantHotwordAlwaysOn() {
+void Service::OnAssistantHotwordAlwaysOn(bool hotword_always_on) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // No need to update hotword status if power source is connected.
   if (power_source_connected_)
@@ -247,11 +246,11 @@
   UpdateAssistantManagerState();
 }
 
-void Service::OnVoiceInteractionSettingsEnabled(bool enabled) {
+void Service::OnAssistantSettingsEnabled(bool enabled) {
   UpdateAssistantManagerState();
 }
 
-void Service::OnVoiceInteractionHotwordEnabled(bool enabled) {
+void Service::OnAssistantHotwordEnabled(bool enabled) {
   UpdateAssistantManagerState();
 }
 
@@ -336,14 +335,7 @@
     return;
 
   pref_service_ = std::move(pref_service);
-
-  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
-  pref_change_registrar_->Init(pref_service_.get());
-
-  pref_change_registrar_->Add(
-      chromeos::assistant::prefs::kAssistantHotwordAlwaysOn,
-      base::BindRepeating(&Service::OnAssistantHotwordAlwaysOn,
-                          base::Unretained(this)));
+  assistant_state_.RegisterPrefChanges(pref_service_.get());
 }
 
 identity::mojom::IdentityAccessor* Service::GetIdentityAccessor() {
diff --git a/chromeos/services/assistant/service.h b/chromeos/services/assistant/service.h
index 19d623a..253d8f8 100644
--- a/chromeos/services/assistant/service.h
+++ b/chromeos/services/assistant/service.h
@@ -9,7 +9,6 @@
 #include <string>
 
 #include "ash/public/cpp/assistant/assistant_state_proxy.h"
-#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
 #include "ash/public/cpp/session/session_activation_observer.h"
 #include "ash/public/mojom/assistant_controller.mojom.h"
 #include "base/callback.h"
@@ -33,7 +32,6 @@
 #include "services/identity/public/mojom/identity_accessor.mojom.h"
 
 class GoogleServiceAuthError;
-class PrefChangeRegistrar;
 class PrefService;
 
 namespace base {
@@ -63,7 +61,7 @@
     : public mojom::AssistantService,
       public chromeos::PowerManagerClient::Observer,
       public ash::SessionActivationObserver,
-      public ash::DefaultVoiceInteractionObserver {
+      public ash::AssistantStateObserver {
  public:
   Service(mojo::PendingReceiver<mojom::AssistantService> receiver,
           std::unique_ptr<network::SharedURLLoaderFactoryInfo>
@@ -139,12 +137,10 @@
   void OnSessionActivated(bool activated) override;
   void OnLockStateChanged(bool locked) override;
 
-  // Called when the hotword always on status is changed from the pref service.
-  void OnAssistantHotwordAlwaysOn();
-
-  // ash::mojom::VoiceInteractionObserver:
-  void OnVoiceInteractionSettingsEnabled(bool enabled) override;
-  void OnVoiceInteractionHotwordEnabled(bool enabled) override;
+  // ash::AssistantStateObserver:
+  void OnAssistantHotwordAlwaysOn(bool hotword_always_on) override;
+  void OnAssistantSettingsEnabled(bool enabled) override;
+  void OnAssistantHotwordEnabled(bool enabled) override;
   void OnLocaleChanged(const std::string& locale) override;
   void OnArcPlayStoreEnabledChanged(bool enabled) override;
   void OnLockedFullScreenStateChanged(bool enabled) override;
@@ -209,6 +205,11 @@
   base::Optional<std::string> access_token_;
 
   mojom::AssistantControllerPtr assistant_controller_;
+
+  // NOTE: |pref_service_| is used by |assistant_state_| and must be declared
+  // before so it will be destructed after.
+  std::unique_ptr<PrefService> pref_service_;
+
   ash::mojom::AssistantAlarmTimerControllerPtr
       assistant_alarm_timer_controller_;
   ash::mojom::AssistantNotificationControllerPtr
@@ -220,13 +221,8 @@
   // non-null until |assistant_manager_service_| is created.
   std::unique_ptr<network::SharedURLLoaderFactoryInfo> url_loader_factory_info_;
 
-  std::unique_ptr<PrefService> pref_service_;
-
   std::unique_ptr<PrefConnectionDelegate> pref_connection_delegate_;
 
-  // Observes user profile prefs for the Assistant.
-  std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
-
   base::CancelableOnceClosure update_assistant_manager_callback_;
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/chromeos/services/assistant/service_unittest.cc b/chromeos/services/assistant/service_unittest.cc
index 4364945..654df1d 100644
--- a/chromeos/services/assistant/service_unittest.cc
+++ b/chromeos/services/assistant/service_unittest.cc
@@ -8,7 +8,8 @@
 #include <utility>
 #include <vector>
 
-#include "ash/public/cpp/voice_interaction_controller.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
+#include "ash/public/mojom/assistant_state_controller.mojom.h"
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/macros.h"
@@ -26,6 +27,7 @@
 #include "chromeos/services/assistant/pref_connection_delegate.h"
 #include "chromeos/services/assistant/public/cpp/assistant_prefs.h"
 #include "components/prefs/testing_pref_service.h"
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "services/identity/public/mojom/identity_accessor.mojom.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -126,9 +128,8 @@
 
 class FakeAssistantClient : public FakeClient {
  public:
-  explicit FakeAssistantClient(
-      ash::VoiceInteractionController* voice_interaction_controller)
-      : voice_interaction_controller_(voice_interaction_controller) {}
+  explicit FakeAssistantClient(ash::AssistantState* assistant_state)
+      : assistant_state_(assistant_state) {}
 
   mojom::ClientPtr CreateInterfacePtrAndBind() {
     mojom::ClientPtr ptr;
@@ -138,13 +139,13 @@
 
  private:
   // FakeClient:
-  void RequestVoiceInteractionController(
-      mojo::PendingReceiver<ash::mojom::VoiceInteractionController> receiver)
+  void RequestAssistantStateController(
+      mojo::PendingReceiver<ash::mojom::AssistantStateController> receiver)
       override {
-    voice_interaction_controller_->BindRequest(std::move(receiver));
+    assistant_state_->BindRequest(std::move(receiver));
   }
 
-  ash::VoiceInteractionController* const voice_interaction_controller_;
+  ash::AssistantState* const assistant_state_;
   mojo::Binding<mojom::Client> binding_{this};
 
   DISALLOW_COPY_AND_ASSIGN(FakeAssistantClient);
@@ -202,13 +203,13 @@
         base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
             &url_loader_factory_);
 
-    voice_interaction_controller()->NotifyArcPlayStoreEnabledChanged(true);
-    voice_interaction_controller()->NotifyContextEnabled(true);
-    voice_interaction_controller()->NotifyFeatureAllowed(
+    assistant_state()->NotifyArcPlayStoreEnabledChanged(true);
+    assistant_state()->NotifyContextEnabled(true);
+    assistant_state()->NotifyFeatureAllowed(
         ash::mojom::AssistantAllowedState::ALLOWED);
-    voice_interaction_controller()->NotifyHotwordEnabled(true);
-    voice_interaction_controller()->NotifyLocaleChanged("en_US");
-    voice_interaction_controller()->NotifySettingsEnabled(true);
+    assistant_state()->NotifyHotwordEnabled(true);
+    assistant_state()->NotifyLocaleChanged("en_US");
+    assistant_state()->NotifySettingsEnabled(true);
 
     auto fake_pref_connection = std::make_unique<FakePrefConnectionDelegate>();
     fake_pref_connection_ = fake_pref_connection.get();
@@ -251,9 +252,7 @@
 
   FakeIdentityAccessor* identity_accessor() { return &fake_identity_accessor_; }
 
-  ash::VoiceInteractionController* voice_interaction_controller() {
-    return &voice_interaction_controller_;
-  }
+  ash::AssistantState* assistant_state() { return &assistant_state_; }
 
   base::TestMockTimeTaskRunner* mock_task_runner() {
     return mock_task_runner_.get();
@@ -265,10 +264,10 @@
   std::unique_ptr<Service> service_;
   mojo::Remote<mojom::AssistantService> remote_service_;
 
-  ash::VoiceInteractionController voice_interaction_controller_;
+  ash::AssistantState assistant_state_;
 
   FakeIdentityAccessor fake_identity_accessor_;
-  FakeAssistantClient fake_assistant_client_{&voice_interaction_controller_};
+  FakeAssistantClient fake_assistant_client_{&assistant_state_};
   FakeDeviceActions fake_device_actions_;
 
   FakePrefConnectionDelegate* fake_pref_connection_;
@@ -333,7 +332,7 @@
   EXPECT_EQ(assistant_manager()->GetState(),
             AssistantManagerService::State::RUNNING);
 
-  voice_interaction_controller()->NotifySettingsEnabled(false);
+  assistant_state()->NotifySettingsEnabled(false);
 
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(assistant_manager()->GetState(),
@@ -343,7 +342,7 @@
 TEST_F(AssistantServiceTest, StopDelayedIfAssistantNotFinishedStarting) {
   // Test is set up as |State::STARTED|, turning settings off will trigger
   // logic to try to stop it.
-  voice_interaction_controller()->NotifySettingsEnabled(false);
+  assistant_state()->NotifySettingsEnabled(false);
 
   EXPECT_EQ(assistant_manager()->GetState(),
             AssistantManagerService::State::STARTED);
diff --git a/chromeos/services/network_config/cros_network_config.cc b/chromeos/services/network_config/cros_network_config.cc
index 48d230f..cb197835b 100644
--- a/chromeos/services/network_config/cros_network_config.cc
+++ b/chromeos/services/network_config/cros_network_config.cc
@@ -117,6 +117,21 @@
   return mojom::VPNType::kOpenVPN;
 }
 
+std::string MojoVpnTypeToOnc(mojom::VPNType mojo_vpn_type) {
+  switch (mojo_vpn_type) {
+    case mojom::VPNType::kL2TPIPsec:
+      return ::onc::vpn::kTypeL2TP_IPsec;
+    case mojom::VPNType::kOpenVPN:
+      return ::onc::vpn::kOpenVPN;
+    case mojom::VPNType::kThirdPartyVPN:
+      return ::onc::vpn::kThirdPartyVpn;
+    case mojom::VPNType::kArcVPN:
+      return ::onc::vpn::kArcVpn;
+  }
+  NOTREACHED();
+  return ::onc::vpn::kOpenVPN;
+}
+
 mojom::DeviceStateType GetMojoDeviceStateType(
     NetworkStateHandler::TechnologyState technology_state) {
   switch (technology_state) {
@@ -1557,6 +1572,21 @@
     onc.SetKey(::onc::network_config::kStaticIPConfig,
                std::move(ip_config_dict));
   }
+  if (properties->vpn) {
+    const mojom::VPNConfigProperties& vpn = *properties->vpn;
+    base::Value vpn_dict(base::Value::Type::DICTIONARY);
+    SetString(::onc::vpn::kType, MojoVpnTypeToOnc(vpn.type), &vpn_dict);
+    SetString(::onc::vpn::kHost, vpn.host, &vpn_dict);
+    if (vpn.open_vpn) {
+      const mojom::OpenVPNConfigProperties& open_vpn = *vpn.open_vpn;
+      base::Value open_vpn_dict(base::Value::Type::DICTIONARY);
+      SetStringList(::onc::openvpn::kExtraHosts, open_vpn.extra_hosts,
+                    &open_vpn_dict);
+      SetString(::onc::vpn::kUsername, open_vpn.username, &open_vpn_dict);
+      vpn_dict.SetKey(::onc::vpn::kOpenVPN, std::move(open_vpn_dict));
+    }
+    onc.SetKey(::onc::network_config::kVPN, std::move(vpn_dict));
+  }
   if (!type_dict.DictEmpty()) {
     std::string type = network_util::TranslateShillTypeToONC(network->type());
     onc.SetKey(type, std::move(type_dict));
diff --git a/chromeos/services/network_config/public/mojom/cros_network_config.mojom b/chromeos/services/network_config/public/mojom/cros_network_config.mojom
index ab29e5b2..742e576 100644
--- a/chromeos/services/network_config/public/mojom/cros_network_config.mojom
+++ b/chromeos/services/network_config/public/mojom/cros_network_config.mojom
@@ -623,6 +623,19 @@
   ApnProperties? apn;
 };
 
+struct OpenVPNConfigProperties {
+  array<string>? extra_hosts;
+  string? username;
+};
+
+struct VPNConfigProperties {
+  string? host;
+  OpenVPNConfigProperties? open_vpn;
+  // For existing configurations, this must match the VPN type. Only
+  // configuration of internal VPN types (kOpenVPN and kL2TPIPsec) is supported.
+  VPNType type;
+};
+
 struct ConfigProperties {
   AutoConnectConfig? auto_connect;
   CellularConfigProperties? cellular;
@@ -631,6 +644,7 @@
   PriorityConfig? priority;
   ProxySettings? proxy_settings;
   IPConfigProperties? static_ip_config;
+  VPNConfigProperties? vpn;
 };
 
 // Properties provided to SetCellularSimState.
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 0bf0114d2b..4d5acc6 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -92,18 +92,9 @@
                            ash::WindowStateType requested_state,
                            const gfx::Rect& bounds_in_display,
                            int64_t display_id) override {
-    const display::Screen* screen = display::Screen::GetScreen();
-    display::Display target_display;
-    if (!screen->GetDisplayWithDisplayId(display_id, &target_display))
-      return;
-
-    // TODO(oshima): Remove the conversion here and one in zcr_remote_shell.
-    gfx::Rect bounds_in_screen(bounds_in_display);
-    bounds_in_screen.Offset(target_display.bounds().OffsetFromOrigin());
-
     shell_surface_->OnBoundsChangeEvent(
         window_state->GetStateType(), requested_state, display_id,
-        bounds_in_screen,
+        bounds_in_display,
         window_state->drag_details() && shell_surface_->IsDragging()
             ? window_state->drag_details()->bounds_change
             : 0);
@@ -811,8 +802,6 @@
   display::Display display;
   if (screen->GetDisplayWithDisplayId(display_id_, &display)) {
     bool is_display_stale = display_id_ != current_display.id();
-    LOG(ERROR) << "DisplayId:" << display_id_
-               << ", current:" << current_display.id();
 
     // Preserve widget bounds until client acknowledges display move.
     if (preserve_widget_bounds_ && is_display_stale)
@@ -846,7 +835,6 @@
 
   bool set_bounds_locally =
       GetWindowState()->is_dragged() && !is_display_move_pending;
-  LOG(ERROR) << "Updating Locally";
 
   if (set_bounds_locally || client_controlled_state_->set_bounds_locally()) {
     // Convert from screen to display coordinates.
@@ -861,7 +849,6 @@
     UpdateSurfaceBounds();
     return;
   }
-  LOG(ERROR) << "Updating Remotely";
 
   {
     ScopedSetBoundsLocally scoped_set_bounds(this);
@@ -869,12 +856,15 @@
   }
 
   if (bounds != adjusted_bounds || is_display_move_pending) {
-    LOG(ERROR) << "Sending Bounds:" << bounds.ToString()
-               << ", adjusted=" << adjusted_bounds.ToString();
     // Notify client that bounds were adjusted or window moved across displays.
     auto state_type = GetWindowState()->GetStateType();
+    gfx::Rect adjusted_bounds_in_display(adjusted_bounds);
+
+    adjusted_bounds_in_display.Offset(
+        -target_display.bounds().OffsetFromOrigin());
+
     OnBoundsChangeEvent(state_type, state_type, target_display.id(),
-                        adjusted_bounds, 0);
+                        adjusted_bounds_in_display, 0);
   }
 
   UpdateSurfaceBounds();
diff --git a/components/exo/client_controlled_shell_surface.h b/components/exo/client_controlled_shell_surface.h
index bcbad24d0..749d654 100644
--- a/components/exo/client_controlled_shell_surface.h
+++ b/components/exo/client_controlled_shell_surface.h
@@ -95,7 +95,7 @@
       base::RepeatingCallback<void(ash::WindowStateType current_state,
                                    ash::WindowStateType requested_state,
                                    int64_t display_id,
-                                   const gfx::Rect& bounds,
+                                   const gfx::Rect& bounds_in_display,
                                    bool is_resize,
                                    int bounds_change)>;
   void set_bounds_changed_callback(
diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc
index ba40dec..9dbfb039 100644
--- a/components/exo/client_controlled_shell_surface_unittest.cc
+++ b/components/exo/client_controlled_shell_surface_unittest.cc
@@ -1280,18 +1280,10 @@
                            ash::WindowStateType current_state,
                            ash::WindowStateType requested_state,
                            int64_t display_id,
-                           const gfx::Rect& bounds_in_screen,
+                           const gfx::Rect& bounds_in_display,
                            bool is_resize,
                            int bounds_change) {
     bounds_change_count_++;
-
-    display::Display target_display;
-    const display::Screen* screen = display::Screen::GetScreen();
-
-    ASSERT_TRUE(screen->GetDisplayWithDisplayId(display_id, &target_display));
-    gfx::Rect bounds_in_display(bounds_in_screen);
-    bounds_in_display.Offset(-target_display.bounds().OffsetFromOrigin());
-
     requested_bounds_.push_back(bounds_in_display);
     requested_display_ids_.push_back(display_id);
   }
diff --git a/components/exo/wayland/zcr_remote_shell.cc b/components/exo/wayland/zcr_remote_shell.cc
index d0148ee5..cf03b225 100644
--- a/components/exo/wayland/zcr_remote_shell.cc
+++ b/components/exo/wayland/zcr_remote_shell.cc
@@ -863,7 +863,7 @@
 
     for (const auto& bounds_change : pending_bounds_changes_) {
       SendBoundsChanged(bounds_change.first, bounds_change.second.display_id,
-                        bounds_change.second.bounds,
+                        bounds_change.second.bounds_in_display,
                         bounds_change.second.reason);
       clients.insert(wl_resource_get_client(bounds_change.first));
     }
@@ -911,7 +911,7 @@
       ash::WindowStateType current_state,
       ash::WindowStateType requested_state,
       int64_t display_id,
-      const gfx::Rect& bounds,
+      const gfx::Rect& bounds_in_display,
       bool resize,
       int bounds_change) {
     zcr_remote_surface_v1_bounds_change_reason reason =
@@ -940,34 +940,35 @@
         pending_bounds_changes_.emplace(
             std::make_pair<wl_resource*, BoundsChangeData>(
                 std::move(resource),
-                BoundsChangeData(display_id, bounds, reason)));
+                BoundsChangeData(display_id, bounds_in_display, reason)));
         return;
       }
-      SendBoundsChanged(resource, display_id, bounds, reason);
+      SendBoundsChanged(resource, display_id, bounds_in_display, reason);
     } else {
+      gfx::Rect bounds_in_screen = gfx::Rect(bounds_in_display);
+      display::Display display;
+      display::Screen::GetScreen()->GetDisplayWithDisplayId(display_id,
+                                                            &display);
+      // The display ID should be valid.
+      DCHECK(display.is_valid());
+      if (display.is_valid())
+        bounds_in_screen.Offset(display.bounds().OffsetFromOrigin());
+      else
+        LOG(ERROR) << "Invalid Display in send_bounds_changed:" << display_id;
+
       zcr_remote_surface_v1_send_bounds_changed(
           resource, static_cast<uint32_t>(display_id >> 32),
-          static_cast<uint32_t>(display_id), bounds.x(), bounds.y(),
-          bounds.width(), bounds.height(), reason);
+          static_cast<uint32_t>(display_id), bounds_in_screen.x(),
+          bounds_in_screen.y(), bounds_in_screen.width(),
+          bounds_in_screen.height(), reason);
     }
     wl_client_flush(wl_resource_get_client(resource));
   }
 
   void SendBoundsChanged(wl_resource* resource,
                          int64_t display_id,
-                         const gfx::Rect& bounds,
+                         const gfx::Rect& bounds_in_display,
                          zcr_remote_surface_v1_bounds_change_reason reason) {
-    // Notify bounds change by local bounds.
-    gfx::Rect bounds_in_display = gfx::Rect(bounds);
-    display::Display display;
-    display::Screen::GetScreen()->GetDisplayWithDisplayId(display_id, &display);
-    // The display ID should be valid.
-    DCHECK(display.is_valid());
-    if (display.is_valid())
-      bounds_in_display.Offset(-display.bounds().OffsetFromOrigin());
-    else
-      LOG(ERROR) << "Invalid Display in send_bounds_changed:" << display_id;
-
     zcr_remote_surface_v1_send_bounds_changed(
         resource, static_cast<uint32_t>(display_id >> 32),
         static_cast<uint32_t>(display_id), bounds_in_display.x(),
@@ -1031,12 +1032,12 @@
 
   struct BoundsChangeData {
     int64_t display_id;
-    gfx::Rect bounds;
+    gfx::Rect bounds_in_display;
     zcr_remote_surface_v1_bounds_change_reason reason;
     BoundsChangeData(int64_t display_id,
                      const gfx::Rect& bounds,
                      zcr_remote_surface_v1_bounds_change_reason reason)
-        : display_id(display_id), bounds(bounds), reason(reason) {}
+        : display_id(display_id), bounds_in_display(bounds), reason(reason) {}
   };
 
   // The exo display instance. Not owned.
diff --git a/components/previews/core/previews_experiments.cc b/components/previews/core/previews_experiments.cc
index d61bdef..08bbe1e 100644
--- a/components/previews/core/previews_experiments.cc
+++ b/components/previews/core/previews_experiments.cc
@@ -251,6 +251,18 @@
       features::kLitePageServerPreviews, "probe_interval_in_seconds", 30));
 }
 
+bool LitePageRedirectShouldProbeOrigin() {
+  return base::GetFieldTrialParamByFeatureAsBool(
+      features::kLitePageServerPreviews, "should_probe_origin", false);
+}
+
+base::TimeDelta LitePageRedirectPreviewOriginProbeTimeout() {
+  return base::TimeDelta::FromMilliseconds(
+      base::GetFieldTrialParamByFeatureAsInt(features::kLitePageServerPreviews,
+                                             "origin_probe_timeout_ms",
+                                             30 * 1000));
+}
+
 net::EffectiveConnectionType GetECTThresholdForPreview(
     previews::PreviewsType type) {
   switch (type) {
diff --git a/components/previews/core/previews_experiments.h b/components/previews/core/previews_experiments.h
index 003257c..ac5dd2f 100644
--- a/components/previews/core/previews_experiments.h
+++ b/components/previews/core/previews_experiments.h
@@ -152,6 +152,12 @@
 // The duration in between probes to the lite page redirect server.
 base::TimeDelta LitePageRedirectPreviewProbeInterval();
 
+// Whether the origin should be successfully probed before showing a preview.
+bool LitePageRedirectShouldProbeOrigin();
+
+// The timeout for the origin probe on lite page redirect previews.
+base::TimeDelta LitePageRedirectPreviewOriginProbeTimeout();
+
 // The maximum number of seconds to loadshed the Previews server for.
 int PreviewServerLoadshedMaxSeconds();
 
diff --git a/components/services/storage/BUILD.gn b/components/services/storage/BUILD.gn
index 4f1c3209..cb8a6ada 100644
--- a/components/services/storage/BUILD.gn
+++ b/components/services/storage/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("storage") {
   sources = [
+    "dom_storage/dom_storage_database.cc",
+    "dom_storage/dom_storage_database.h",
     "origin_context_impl.cc",
     "origin_context_impl.h",
     "partition_impl.cc",
@@ -16,6 +18,7 @@
     "//base",
     "//components/services/storage/public/mojom",
     "//mojo/public/cpp/bindings",
+    "//third_party/leveldatabase",
     "//url",
   ]
 }
@@ -24,6 +27,7 @@
   testonly = true
 
   sources = [
+    "dom_storage/dom_storage_database_unittest.cc",
     "partition_impl_unittest.cc",
     "storage_service_impl_unittest.cc",
   ]
@@ -32,6 +36,7 @@
     ":storage",
     "//base",
     "//base/test:test_support",
+    "//testing/gmock",
     "//testing/gtest",
   ]
 }
diff --git a/components/services/storage/DEPS b/components/services/storage/DEPS
new file mode 100644
index 0000000..743a2f3b
--- /dev/null
+++ b/components/services/storage/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+third_party/leveldatabase",
+]
diff --git a/components/services/storage/dom_storage/dom_storage_database.cc b/components/services/storage/dom_storage/dom_storage_database.cc
new file mode 100644
index 0000000..184a550
--- /dev/null
+++ b/components/services/storage/dom_storage/dom_storage_database.cc
@@ -0,0 +1,340 @@
+// 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/services/storage/dom_storage/dom_storage_database.h"
+
+#include <utility>
+
+#include "base/no_destructor.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/trace_event/memory_allocator_dump.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "third_party/leveldatabase/leveldb_chrome.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace storage {
+
+namespace {
+
+class DomStorageDatabaseEnv : public leveldb_env::ChromiumEnv {
+ public:
+  DomStorageDatabaseEnv() : ChromiumEnv("ChromiumEnv.StorageService") {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DomStorageDatabaseEnv);
+};
+
+DomStorageDatabaseEnv* GetDomStorageDatabaseEnv() {
+  static base::NoDestructor<DomStorageDatabaseEnv> env;
+  return env.get();
+}
+
+std::string MakeFullPersistentDBName(const base::FilePath& directory,
+                                     const std::string& db_name) {
+  // ChromiumEnv treats DB name strings as UTF-8 file paths.
+  return directory.Append(base::FilePath::FromUTF8Unsafe(db_name))
+      .AsUTF8Unsafe();
+}
+
+leveldb_env::Options CreateDefaultInMemoryOptions() {
+  leveldb_env::Options options;
+  options.create_if_missing = true;
+  options.max_open_files = 0;
+  return options;
+}
+
+leveldb_env::Options AddEnvToOptions(const leveldb_env::Options& options,
+                                     leveldb::Env* env) {
+  leveldb_env::Options new_options = options;
+  new_options.env = env;
+  return new_options;
+}
+
+std::unique_ptr<leveldb::DB> TryOpenDB(
+    const leveldb_env::Options& options,
+    const std::string& name,
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+    DomStorageDatabase::StatusCallback callback) {
+  std::unique_ptr<leveldb::DB> db;
+  leveldb::Status status = leveldb_env::OpenDB(options, name, &db);
+  callback_task_runner->PostTask(FROM_HERE,
+                                 base::BindOnce(std::move(callback), status));
+  return db;
+}
+
+leveldb::Slice MakeSlice(base::span<const uint8_t> data) {
+  if (data.empty())
+    return leveldb::Slice();
+  return leveldb::Slice(reinterpret_cast<const char*>(data.data()),
+                        data.size());
+}
+
+DomStorageDatabase::KeyValuePair MakeKeyValuePair(const leveldb::Slice& key,
+                                                  const leveldb::Slice& value) {
+  auto key_span = base::make_span(key);
+  auto value_span = base::make_span(value);
+  return DomStorageDatabase::KeyValuePair(
+      DomStorageDatabase::Key(key_span.begin(), key_span.end()),
+      DomStorageDatabase::Value(value_span.begin(), value_span.end()));
+}
+
+template <typename Func>
+DomStorageDatabase::Status ForEachWithPrefix(leveldb::DB* db,
+                                             DomStorageDatabase::KeyView prefix,
+                                             Func function) {
+  // NOTE: We disable filling the cache for bulk scans. Either this is for
+  // deletion (where caching doesn't make sense) or a mass-read, which the user
+  // should be caching or only needing once.
+  leveldb::ReadOptions options;
+  options.fill_cache = false;
+  std::unique_ptr<leveldb::Iterator> iter(db->NewIterator(options));
+  const leveldb::Slice prefix_slice(MakeSlice(prefix));
+  iter->Seek(prefix_slice);
+  for (; iter->Valid(); iter->Next()) {
+    if (!iter->key().starts_with(prefix_slice))
+      break;
+    function(iter->key(), iter->value());
+  }
+  return iter->status();
+}
+
+}  // namespace
+
+DomStorageDatabase::KeyValuePair::KeyValuePair() = default;
+
+DomStorageDatabase::KeyValuePair::~KeyValuePair() = default;
+
+DomStorageDatabase::KeyValuePair::KeyValuePair(KeyValuePair&&) = default;
+
+DomStorageDatabase::KeyValuePair::KeyValuePair(const KeyValuePair&) = default;
+
+DomStorageDatabase::KeyValuePair::KeyValuePair(Key key, Value value)
+    : key(std::move(key)), value(std::move(value)) {}
+
+DomStorageDatabase::KeyValuePair& DomStorageDatabase::KeyValuePair::operator=(
+    KeyValuePair&&) = default;
+
+DomStorageDatabase::KeyValuePair& DomStorageDatabase::KeyValuePair::operator=(
+    const KeyValuePair&) = default;
+
+bool DomStorageDatabase::KeyValuePair::operator==(
+    const KeyValuePair& rhs) const {
+  return std::tie(key, value) == std::tie(rhs.key, rhs.value);
+}
+
+DomStorageDatabase::DomStorageDatabase(
+    const base::FilePath& directory,
+    const std::string& name,
+    const leveldb_env::Options& options,
+    const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>&
+        memory_dump_id,
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+    StatusCallback callback)
+    : DomStorageDatabase(MakeFullPersistentDBName(directory, name),
+                         /*env=*/nullptr,
+                         options,
+                         memory_dump_id,
+                         std::move(callback_task_runner),
+                         std::move(callback)) {}
+
+DomStorageDatabase::DomStorageDatabase(
+    const std::string& tracking_name,
+    const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>&
+        memory_dump_id,
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+    StatusCallback callback)
+    : DomStorageDatabase("",
+                         leveldb_chrome::NewMemEnv(tracking_name),
+                         CreateDefaultInMemoryOptions(),
+                         memory_dump_id,
+                         std::move(callback_task_runner),
+                         std::move(callback)) {}
+
+DomStorageDatabase::DomStorageDatabase(
+    const std::string& name,
+    std::unique_ptr<leveldb::Env> env,
+    const leveldb_env::Options& options,
+    const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>
+        memory_dump_id,
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+    StatusCallback callback)
+    : name_(name),
+      env_(std::move(env)),
+      options_(AddEnvToOptions(options,
+                               env_ ? env_.get() : GetDomStorageDatabaseEnv())),
+      db_(TryOpenDB(options_,
+                    name,
+                    std::move(callback_task_runner),
+                    std::move(callback))),
+      memory_dump_id_(memory_dump_id) {
+  base::trace_event::MemoryDumpManager::GetInstance()
+      ->RegisterDumpProviderWithSequencedTaskRunner(
+          this, "MojoLevelDB", base::SequencedTaskRunnerHandle::Get(),
+          MemoryDumpProvider::Options());
+}
+
+DomStorageDatabase::~DomStorageDatabase() {
+  base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
+      this);
+}
+
+// static
+void DomStorageDatabase::OpenDirectory(
+    const base::FilePath& directory,
+    const std::string& name,
+    const leveldb_env::Options& options,
+    const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>&
+        memory_dump_id,
+    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+    OpenCallback callback) {
+  DCHECK(directory.IsAbsolute());
+  auto database = std::make_unique<base::SequenceBound<DomStorageDatabase>>();
+  auto* database_ptr = database.get();
+  *database_ptr = base::SequenceBound<DomStorageDatabase>(
+      blocking_task_runner, directory, name, options, memory_dump_id,
+      base::SequencedTaskRunnerHandle::Get(),
+      base::BindOnce(
+          [](std::unique_ptr<base::SequenceBound<DomStorageDatabase>> database,
+             OpenCallback callback, leveldb::Status status) {
+            if (status.ok())
+              std::move(callback).Run(std::move(*database), status);
+            else
+              std::move(callback).Run({}, status);
+          },
+          std::move(database), std::move(callback)));
+}
+
+// static
+void DomStorageDatabase::OpenInMemory(
+    const std::string& name,
+    const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>&
+        memory_dump_id,
+    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+    OpenCallback callback) {
+  auto database = std::make_unique<base::SequenceBound<DomStorageDatabase>>();
+  auto* database_ptr = database.get();
+  *database_ptr = base::SequenceBound<DomStorageDatabase>(
+      blocking_task_runner, name, memory_dump_id,
+      base::SequencedTaskRunnerHandle::Get(),
+      base::BindOnce(
+          [](std::unique_ptr<base::SequenceBound<DomStorageDatabase>> database,
+             OpenCallback callback, leveldb::Status status) {
+            if (status.ok())
+              std::move(callback).Run(std::move(*database), status);
+            else
+              std::move(callback).Run({}, status);
+          },
+          std::move(database), std::move(callback)));
+}
+
+// static
+void DomStorageDatabase::Destroy(
+    const base::FilePath& directory,
+    const std::string& name,
+    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+    StatusCallback callback) {
+  blocking_task_runner->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](const std::string& db_name,
+             scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+             StatusCallback callback) {
+            leveldb_env::Options options;
+            options.env = GetDomStorageDatabaseEnv();
+            callback_task_runner->PostTask(
+                FROM_HERE,
+                base::BindOnce(std::move(callback),
+                               leveldb::DestroyDB(db_name, options)));
+          },
+          MakeFullPersistentDBName(directory, name),
+          base::SequencedTaskRunnerHandle::Get(), std::move(callback)));
+}
+
+DomStorageDatabase::Status DomStorageDatabase::Get(KeyView key,
+                                                   Value* out_value) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::string value;
+  Status status = db_->Get(leveldb::ReadOptions(), MakeSlice(key), &value);
+  *out_value = Value(value.begin(), value.end());
+  return status;
+}
+
+DomStorageDatabase::Status DomStorageDatabase::Put(KeyView key,
+                                                   ValueView value) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return db_->Put(leveldb::WriteOptions(), MakeSlice(key), MakeSlice(value));
+}
+
+DomStorageDatabase::Status DomStorageDatabase::Delete(KeyView key) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return db_->Delete(leveldb::WriteOptions(), MakeSlice(key));
+}
+
+DomStorageDatabase::Status DomStorageDatabase::GetPrefixed(
+    KeyView prefix,
+    std::vector<KeyValuePair>* entries) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return ForEachWithPrefix(
+      db_.get(), prefix,
+      [&](const leveldb::Slice& key, const leveldb::Slice& value) {
+        entries->push_back(MakeKeyValuePair(key, value));
+      });
+}
+
+DomStorageDatabase::Status DomStorageDatabase::DeletePrefixed(
+    KeyView prefix) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  leveldb::WriteBatch batch;
+  Status status = ForEachWithPrefix(
+      db_.get(), prefix,
+      [&](const leveldb::Slice& key, const leveldb::Slice& value) {
+        batch.Delete(key);
+      });
+  if (!status.ok())
+    return status;
+  return db_->Write(leveldb::WriteOptions(), &batch);
+}
+
+DomStorageDatabase::Status DomStorageDatabase::CopyPrefixed(
+    KeyView prefix,
+    KeyView new_prefix) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  leveldb::WriteBatch batch;
+  Key new_key(new_prefix.begin(), new_prefix.end());
+  Status status = ForEachWithPrefix(
+      db_.get(), prefix,
+      [&](const leveldb::Slice& key, const leveldb::Slice& value) {
+        DCHECK_GE(key.size(), prefix.size());  // By definition.
+        size_t suffix_length = key.size() - prefix.size();
+        new_key.resize(new_prefix.size() + suffix_length);
+        std::copy(key.data() + prefix.size(), key.data() + key.size(),
+                  new_key.begin() + new_prefix.size());
+        batch.Put(MakeSlice(new_key), value);
+      });
+  if (!status.ok())
+    return status;
+  return db_->Write(leveldb::WriteOptions(), &batch);
+}
+
+bool DomStorageDatabase::OnMemoryDump(
+    const base::trace_event::MemoryDumpArgs& args,
+    base::trace_event::ProcessMemoryDump* pmd) {
+  auto* dump = leveldb_env::DBTracker::GetOrCreateAllocatorDump(pmd, db_.get());
+  if (!dump)
+    return true;
+  auto* global_dump = pmd->CreateSharedGlobalAllocatorDump(*memory_dump_id_);
+  pmd->AddOwnershipEdge(global_dump->guid(), dump->guid());
+  // Add size to global dump to propagate the size of the database to the
+  // client's dump.
+  global_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
+                         base::trace_event::MemoryAllocatorDump::kUnitsBytes,
+                         dump->GetSizeInternal());
+  return true;
+}
+
+}  // namespace storage
diff --git a/components/services/storage/dom_storage/dom_storage_database.h b/components/services/storage/dom_storage/dom_storage_database.h
new file mode 100644
index 0000000..aac805a0
--- /dev/null
+++ b/components/services/storage/dom_storage/dom_storage_database.h
@@ -0,0 +1,190 @@
+// 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_SERVICES_STORAGE_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
+#define COMPONENTS_SERVICES_STORAGE_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/span.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/sequence_bound.h"
+#include "base/trace_event/memory_allocator_dump_guid.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/env.h"
+
+namespace storage {
+
+// Wraps its own leveldb::DB instance on behalf of the DOM Storage backend
+// implementation. This object is not sequence-safe and must be instantiated on
+// a sequence which allows use of blocking file operations.
+//
+// Use the static |OpenInMemory()| or |OpenDirectory()| helpers to
+// asynchronously create an instance of this type from any sequence.
+// When owning a SequenceBound<DomStorageDatabase> as produced by these helpers,
+// all work on the DomStorageDatabase can be safely done via
+// |SequenceBound::PostTaskWithThisObject|.
+class DomStorageDatabase : private base::trace_event::MemoryDumpProvider {
+ public:
+  using Key = std::vector<uint8_t>;
+  using KeyView = base::span<const uint8_t>;
+  using Value = std::vector<uint8_t>;
+  using ValueView = base::span<const uint8_t>;
+  using Status = leveldb::Status;
+
+  // Callback used for basic async operations on this class.
+  using StatusCallback = base::OnceCallback<void(Status)>;
+
+  struct KeyValuePair {
+    KeyValuePair();
+    KeyValuePair(KeyValuePair&&);
+    KeyValuePair(const KeyValuePair&);
+    KeyValuePair(Key key, Value value);
+    ~KeyValuePair();
+    KeyValuePair& operator=(KeyValuePair&&);
+    KeyValuePair& operator=(const KeyValuePair&);
+
+    bool operator==(const KeyValuePair& rhs) const;
+
+    Key key;
+    Value value;
+  };
+
+  ~DomStorageDatabase() override;
+
+  // Callback invoked asynchronously with the result of both |OpenDirectory()|
+  // and |OpenInMemory()| defined below. Includes both the status and the
+  // (possibly null, on failure) sequence-bound DomStorageDatabase instance.
+  using OpenCallback =
+      base::OnceCallback<void(base::SequenceBound<DomStorageDatabase> database,
+                              leveldb::Status status)>;
+
+  // Creates a DomStorageDatabase instance for a persistent database within a
+  // filesystem directory given by |directory|, which must be an absolute path.
+  // The database may or may not already exist at this path, and whether or not
+  // this operation succeeds in either case depends on options set in |options|,
+  // e.g. |create_if_missing| and/or |error_if_exists|.
+  //
+  // The instance will be bound to and perform all operations on |task_runner|,
+  // which must support blocking operations. |callback| is called on the calling
+  // sequence once the operation completes.
+  static void OpenDirectory(
+      const base::FilePath& directory,
+      const std::string& name,
+      const leveldb_env::Options& options,
+      const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>&
+          memory_dump_id,
+      scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+      OpenCallback callback);
+
+  // Creates a DomStorageDatabase instance for a new in-memory database.
+  //
+  // The instance will be bound to and perform all operations on |task_runner|,
+  // which must support blocking operations. |callback| is called on the calling
+  // sequence once the operation completes.
+  static void OpenInMemory(
+      const std::string& name,
+      const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>&
+          memory_dump_id,
+      scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+      OpenCallback callback);
+
+  // Destroys the persistent database named |name| within the filesystem
+  // directory identified by the absolute path in |directory|.
+  //
+  // All work is done on |task_runner|, which must support blocking operations,
+  // and upon completion |callback| is called on the calling sequence.
+  static void Destroy(
+      const base::FilePath& directory,
+      const std::string& name,
+      scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+      StatusCallback callback);
+
+  // Retrieves the value for |key| in the database.
+  Status Get(KeyView key, Value* out_value) const;
+
+  // Sets the database entry for |key| to |value|.
+  Status Put(KeyView key, ValueView value) const;
+
+  // Deletes the database entry for |key|.
+  Status Delete(KeyView key) const;
+
+  // Gets all database entries whose key starts with |prefix|.
+  Status GetPrefixed(KeyView prefix, std::vector<KeyValuePair>* entries) const;
+
+  // Deletes all database entries whose key starts with |prefix|.
+  Status DeletePrefixed(KeyView prefix) const;
+
+  // Copies all database entries whose key starts with |prefix| over to new
+  // entries with |prefix| replaced by |new_prefix| in each new key.
+  Status CopyPrefixed(KeyView prefix, KeyView new_prefix) const;
+
+ private:
+  friend class base::SequenceBound<DomStorageDatabase>;
+
+  // Constructs a new DomStorageDatabase, creating or opening persistent
+  // on-filesystem database as specified. Asynchronously invokes |callback| on
+  // |callback_task_runner| when done.
+  //
+  // This must be called on a sequence that allows blocking operations. Prefer
+  // to instead call one of the static methods defined below, which can be
+  // called from any sequence.
+  DomStorageDatabase(
+      const base::FilePath& directory,
+      const std::string& name,
+      const leveldb_env::Options& options,
+      const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>&
+          memory_dump_id,
+      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+      StatusCallback callback);
+
+  // Same as above, but for an in-memory database. |tracking_name| is used
+  // internally for memory dump details.
+  DomStorageDatabase(
+      const std::string& tracking_name,
+      const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>&
+          memory_dump_id,
+      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+      StatusCallback callback);
+
+  DomStorageDatabase(
+      const std::string& name,
+      std::unique_ptr<leveldb::Env> env,
+      const leveldb_env::Options& options,
+      const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>
+          memory_dump_id_,
+      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+      StatusCallback callback);
+
+  // base::trace_event::MemoryDumpProvider implementation:
+  bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+                    base::trace_event::ProcessMemoryDump* pmd) override;
+
+  const std::string name_;
+  const std::unique_ptr<leveldb::Env> env_;
+  const leveldb_env::Options options_;
+  const std::unique_ptr<leveldb::DB> db_;
+  const base::Optional<base::trace_event::MemoryAllocatorDumpGuid>
+      memory_dump_id_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(DomStorageDatabase);
+};
+
+}  // namespace storage
+
+#endif  // COMPONENTS_SERVICES_STORAGE_DOM_STORAGE_DOM_STORAGE_DATABASE_H_
diff --git a/components/services/storage/dom_storage/dom_storage_database_unittest.cc b/components/services/storage/dom_storage/dom_storage_database_unittest.cc
new file mode 100644
index 0000000..e3982fc2
--- /dev/null
+++ b/components/services/storage/dom_storage/dom_storage_database_unittest.cc
@@ -0,0 +1,437 @@
+// 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/services/storage/dom_storage/dom_storage_database.h"
+
+#include <algorithm>
+#include <functional>
+#include <iostream>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/string_piece.h"
+#include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/env_chromium.h"
+
+using ::testing::UnorderedElementsAreArray;
+
+// Helper to make Status checks a little more legible in test failures.
+#define EXPECT_STATUS(expectation, value)                                 \
+  ([](auto s) {                                                           \
+    EXPECT_TRUE(s.expectation()) << "Actual status is: " << s.ToString(); \
+  }(value))
+
+#define EXPECT_STATUS_OK(value) EXPECT_STATUS(ok, value)
+
+// Helper to make database value expectation checks and failures more legible
+#define EXPECT_VALUE_EQ(expectation, value) \
+  EXPECT_EQ(std::string(expectation), std::string(value.begin(), value.end()))
+
+namespace storage {
+
+std::ostream& operator<<(std::ostream& os,
+                         const DomStorageDatabase::KeyValuePair& kvp) {
+  os << "<\"" << std::string(kvp.key.begin(), kvp.key.end()) << "\", \""
+     << std::string(kvp.value.begin(), kvp.value.end()) << "\">";
+  return os;
+}
+
+namespace {
+
+// Helper for tests to create a |uint8_t| span from a StringPiece.
+base::span<const uint8_t> MakeBytes(base::StringPiece s) {
+  return base::as_bytes(base::make_span(s));
+}
+
+DomStorageDatabase::KeyValuePair MakeKeyValuePair(base::StringPiece key,
+                                                  base::StringPiece value) {
+  return {DomStorageDatabase::Key(key.begin(), key.end()),
+          DomStorageDatabase::Value(value.begin(), value.end())};
+}
+
+std::string MakePrefixedKey(base::StringPiece prefix, base::StringPiece key) {
+  return prefix.as_string() + key.as_string();
+}
+
+class StorageServiceDomStorageDatabaseTest : public testing::Test {
+ public:
+  StorageServiceDomStorageDatabaseTest()
+      : blocking_task_runner_(base::CreateSequencedTaskRunner(
+            {base::MayBlock(), base::ThreadPool(),
+             base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {}
+
+ protected:
+  // Helper for tests to block on the result of an OpenInMemory call.
+  base::SequenceBound<DomStorageDatabase> OpenInMemorySync(
+      const std::string& db_name) {
+    base::SequenceBound<DomStorageDatabase> result;
+    base::RunLoop loop;
+    DomStorageDatabase::OpenInMemory(
+        db_name, /*memory_dump_id=*/base::nullopt, blocking_task_runner_,
+        base::BindLambdaForTesting(
+            [&](base::SequenceBound<DomStorageDatabase> database,
+                leveldb::Status status) {
+              result = std::move(database);
+              loop.Quit();
+            }));
+    loop.Run();
+    return result;
+  }
+
+  // Helper for tests to block on the result of an OpenDirectory call.
+  base::SequenceBound<DomStorageDatabase> OpenDirectorySync(
+      const base::FilePath& directory,
+      const std::string& db_name,
+      const leveldb_env::Options& options) {
+    base::SequenceBound<DomStorageDatabase> result;
+    base::RunLoop loop;
+    DomStorageDatabase::OpenDirectory(
+        directory, db_name, options, /*memory_dump_id=*/base::nullopt,
+        blocking_task_runner_,
+        base::BindLambdaForTesting(
+            [&](base::SequenceBound<DomStorageDatabase> database,
+                leveldb::Status status) {
+              result = std::move(database);
+              loop.Quit();
+            }));
+    loop.Run();
+    return result;
+  }
+
+  // Helper for tests to block on the result of a Destroy call.
+  leveldb::Status DestroySync(const base::FilePath& directory,
+                              const std::string& db_name) {
+    leveldb::Status result;
+    base::RunLoop loop;
+    DomStorageDatabase::Destroy(
+        directory, db_name, blocking_task_runner_,
+        base::BindLambdaForTesting([&](leveldb::Status status) {
+          result = status;
+          loop.Quit();
+        }));
+    loop.Run();
+    return result;
+  }
+
+  // Helper to run an async operation on a DomStorageDatabase and wait for it to
+  // finish.
+  template <typename Func>
+  static void DoSync(const base::SequenceBound<DomStorageDatabase>& database,
+                     Func operation) {
+    base::RunLoop loop;
+    database.PostTaskWithThisObject(
+        FROM_HERE,
+        base::BindLambdaForTesting([&](const DomStorageDatabase& database) {
+          operation(database);
+          loop.Quit();
+        }));
+    loop.Run();
+  }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(StorageServiceDomStorageDatabaseTest);
+};
+
+}  // namespace
+
+TEST_F(StorageServiceDomStorageDatabaseTest, BasicOpenInMemory) {
+  // Basic smoke test to verify that we can successfully create and destroy an
+  // in-memory database with no problems.
+  base::SequenceBound<DomStorageDatabase> database =
+      OpenInMemorySync("test_db");
+  EXPECT_TRUE(database);
+}
+
+TEST_F(StorageServiceDomStorageDatabaseTest, BasicOpenDirectory) {
+  // Basic smoke test to verify that we can successfully create and destroy a
+  // persistent database with no problems.
+
+  base::ScopedTempDir temp_dir;
+  CHECK(temp_dir.CreateUniqueTempDir());
+  const char kTestDbName[] = "test_db";
+
+  leveldb_env::Options options;
+  options.create_if_missing = true;
+  options.error_if_exists = true;
+  base::SequenceBound<DomStorageDatabase> database =
+      OpenDirectorySync(temp_dir.GetPath(), kTestDbName, options);
+  EXPECT_TRUE(database);
+
+  // Because the database owns filesystem artifacts in the temp directory, we
+  // will wait for the DomStorageDatabase instance to actually be destroyed
+  // before completing the test.
+  base::RunLoop loop;
+  database.ResetWithCallbackAfterDestruction(loop.QuitClosure());
+
+  // Destroy the database. Note that this should be safe to call immediately
+  // after |Reset()| as long as the same TaskRunner is used to open and destroy
+  // the database.
+  EXPECT_STATUS_OK(DestroySync(temp_dir.GetPath(), kTestDbName));
+
+  loop.Run();
+
+  // Verify that the database can't be reopened.
+  options.create_if_missing = false;
+  options.error_if_exists = false;
+  database = OpenDirectorySync(temp_dir.GetPath(), kTestDbName, options);
+  EXPECT_FALSE(database);
+}
+
+TEST_F(StorageServiceDomStorageDatabaseTest, BasicOperations) {
+  // Exercises basic Put, Get, Delete.
+
+  base::SequenceBound<DomStorageDatabase> database =
+      OpenInMemorySync("test_db");
+  ASSERT_TRUE(database);
+
+  // Write a key and read it back.
+  const char kTestKey[] = "test_key";
+  const char kTestValue[] = "test_value";
+  DoSync(database, [&](const DomStorageDatabase& db) {
+    EXPECT_STATUS_OK(db.Put(MakeBytes(kTestKey), MakeBytes(kTestValue)));
+
+    DomStorageDatabase::Value value;
+    EXPECT_STATUS_OK(db.Get(MakeBytes(kTestKey), &value));
+    EXPECT_VALUE_EQ(kTestValue, value);
+  });
+
+  // Now delete the key and expect the following read to fail.
+  DoSync(database, [&](const DomStorageDatabase& db) {
+    EXPECT_STATUS_OK(db.Delete(MakeBytes(kTestKey)));
+
+    DomStorageDatabase::Value value;
+    EXPECT_STATUS(IsNotFound, db.Get(MakeBytes(kTestKey), &value));
+  });
+}
+
+TEST_F(StorageServiceDomStorageDatabaseTest, Reopen) {
+  // Verifies that if we Put() something into a persistent database, we can
+  // Get() it back out when we re-open the same database later.
+
+  base::ScopedTempDir temp_dir;
+  CHECK(temp_dir.CreateUniqueTempDir());
+  const char kTestDbName[] = "test_db";
+  const char kTestKey[] = "test_key";
+  const char kTestValue[] = "test_value";
+
+  leveldb_env::Options options;
+  options.create_if_missing = true;
+  options.error_if_exists = true;
+  base::SequenceBound<DomStorageDatabase> database =
+      OpenDirectorySync(temp_dir.GetPath(), kTestDbName, options);
+  ASSERT_TRUE(database);
+  DoSync(database, [&](const DomStorageDatabase& db) {
+    EXPECT_STATUS_OK(db.Put(MakeBytes(kTestKey), MakeBytes(kTestValue)));
+  });
+  database.Reset();
+
+  // Re-open and verify that we can read what was written above.
+  options.create_if_missing = false;
+  options.error_if_exists = false;
+  database = OpenDirectorySync(temp_dir.GetPath(), kTestDbName, options);
+  ASSERT_TRUE(database);
+  DoSync(database, [&](const DomStorageDatabase& db) {
+    DomStorageDatabase::Value value;
+    EXPECT_STATUS_OK(db.Get(MakeBytes(kTestKey), &value));
+    EXPECT_VALUE_EQ(kTestValue, value);
+  });
+
+  // Because the database owns filesystem artifacts in the temp directory, block
+  // scope teardown until the DomStorageDatabase instance is actually destroyed
+  // on its background sequence.
+  base::RunLoop loop;
+  database.ResetWithCallbackAfterDestruction(loop.QuitClosure());
+  loop.Run();
+}
+
+TEST_F(StorageServiceDomStorageDatabaseTest, GetPrefixed) {
+  // Verifies basic prefixed reading behavior.
+
+  base::SequenceBound<DomStorageDatabase> database =
+      OpenInMemorySync("test_db");
+  ASSERT_TRUE(database);
+
+  const char kTestPrefix1[] = "prefix";
+  const char kTestPrefix2[] = "something_completely_different";
+  const char kTestUnprefixedKey[] = "moot!";
+  const char kTestKeyBase1[] = "key1";
+  const char kTestKeyBase2[] = "key2";
+  auto kTestPrefix1Key1 = MakePrefixedKey(kTestPrefix1, kTestKeyBase1);
+  auto kTestPrefix1Key2 = MakePrefixedKey(kTestPrefix1, kTestKeyBase2);
+  auto kTestPrefix2Key1 = MakePrefixedKey(kTestPrefix2, kTestKeyBase1);
+  auto kTestPrefix2Key2 = MakePrefixedKey(kTestPrefix2, kTestKeyBase2);
+  DoSync(database, [&](const DomStorageDatabase& db) {
+    std::vector<DomStorageDatabase::KeyValuePair> entries;
+
+    // No keys, so GetPrefixed should return nothing.
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix1), &entries));
+    EXPECT_TRUE(entries.empty());
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix2), &entries));
+    EXPECT_TRUE(entries.empty());
+
+    // Insert a key which matches neither test prefix. GetPrefixed should still
+    // return nothing.
+    EXPECT_STATUS_OK(db.Put(MakeBytes(kTestUnprefixedKey), MakeBytes("meh")));
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix1), &entries));
+    EXPECT_TRUE(entries.empty());
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix2), &entries));
+    EXPECT_TRUE(entries.empty());
+
+    // Insert a single prefixed key. GetPrefixed should return it when called
+    // with kTestPrefix1.
+    const char kTestValue1[] = "beep beep";
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestPrefix1Key1), MakeBytes(kTestValue1)));
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix1), &entries));
+    EXPECT_THAT(entries, UnorderedElementsAreArray({MakeKeyValuePair(
+                             kTestPrefix1Key1, kTestValue1)}));
+
+    // But not when called with kTestPrefix2.
+    entries.clear();
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix2), &entries));
+    EXPECT_TRUE(entries.empty());
+
+    // Insert a second prefixed key with kTestPrefix1, and also insert some
+    // keys with kTestPrefix2.
+    const char kTestValue2[] = "beep bop boop";
+    const char kTestValue3[] = "vroom vroom";
+    const char kTestValue4[] = "this data is lit fam";
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestPrefix1Key2), MakeBytes(kTestValue2)));
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestPrefix2Key1), MakeBytes(kTestValue3)));
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestPrefix2Key2), MakeBytes(kTestValue4)));
+
+    // Verify that getting each prefix yields only the expected results.
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix1), &entries));
+    EXPECT_THAT(entries,
+                UnorderedElementsAreArray(
+                    {MakeKeyValuePair(kTestPrefix1Key1, kTestValue1),
+                     MakeKeyValuePair(kTestPrefix1Key2, kTestValue2)}));
+    entries.clear();
+
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix2), &entries));
+    EXPECT_THAT(entries,
+                UnorderedElementsAreArray(
+                    {MakeKeyValuePair(kTestPrefix2Key1, kTestValue3),
+                     MakeKeyValuePair(kTestPrefix2Key2, kTestValue4)}));
+  });
+}
+
+TEST_F(StorageServiceDomStorageDatabaseTest, DeletePrefixed) {
+  // Verifies basic prefixed deletion behavior.
+
+  base::SequenceBound<DomStorageDatabase> database =
+      OpenInMemorySync("test_db");
+  ASSERT_TRUE(database);
+
+  const char kTestPrefix1[] = "prefix";
+  const char kTestPrefix2[] = "something_completely_different";
+  const char kTestUnprefixedKey[] = "moot!";
+  const char kTestKeyBase1[] = "key1";
+  const char kTestKeyBase2[] = "key2";
+  auto kTestPrefix1Key1 = MakePrefixedKey(kTestPrefix1, kTestKeyBase1);
+  auto kTestPrefix1Key2 = MakePrefixedKey(kTestPrefix1, kTestKeyBase2);
+  auto kTestPrefix2Key1 = MakePrefixedKey(kTestPrefix2, kTestKeyBase1);
+  auto kTestPrefix2Key2 = MakePrefixedKey(kTestPrefix2, kTestKeyBase2);
+  DoSync(database, [&](const DomStorageDatabase& db) {
+    // Insert a bunch of entries. One unprefixed, two with one prefix, and two
+    // with another prefix.
+    const char kTestValue1[] = "meh";
+    const char kTestValue2[] = "bah";
+    const char kTestValue3[] = "doh";
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestUnprefixedKey), MakeBytes(kTestValue1)));
+    EXPECT_STATUS_OK(db.Put(MakeBytes(kTestPrefix1Key1), MakeBytes("x")));
+    EXPECT_STATUS_OK(db.Put(MakeBytes(kTestPrefix1Key2), MakeBytes("x")));
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestPrefix2Key1), MakeBytes(kTestValue2)));
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestPrefix2Key2), MakeBytes(kTestValue3)));
+
+    // Wipe out the first prefix. We should still see the second prefix.
+    std::vector<DomStorageDatabase::KeyValuePair> entries;
+    EXPECT_STATUS_OK(db.DeletePrefixed(MakeBytes(kTestPrefix1)));
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix1), &entries));
+    EXPECT_TRUE(entries.empty());
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix2), &entries));
+    EXPECT_THAT(entries,
+                UnorderedElementsAreArray(
+                    {MakeKeyValuePair(kTestPrefix2Key1, kTestValue2),
+                     MakeKeyValuePair(kTestPrefix2Key2, kTestValue3)}));
+
+    // Wipe out the second prefix.
+    EXPECT_STATUS_OK(db.DeletePrefixed(MakeBytes(kTestPrefix2)));
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix2), &entries));
+
+    // The lone unprefixed value should still exist.
+    DomStorageDatabase::Value value;
+    EXPECT_STATUS_OK(db.Get(MakeBytes(kTestUnprefixedKey), &value));
+    EXPECT_VALUE_EQ(kTestValue1, value);
+  });
+}
+
+TEST_F(StorageServiceDomStorageDatabaseTest, CopyPrefixed) {
+  // Verifies basic prefixed copying behavior.
+
+  base::SequenceBound<DomStorageDatabase> database =
+      OpenInMemorySync("test_db");
+  ASSERT_TRUE(database);
+
+  const char kTestUnprefixedKey[] = "moot!";
+  const char kTestPrefix1[] = "prefix";
+  const char kTestPrefix2[] = "something_completely_different";
+  const char kTestKeyBase1[] = "key1";
+  const char kTestKeyBase2[] = "key2";
+  auto kTestPrefix1Key1 = MakePrefixedKey(kTestPrefix1, kTestKeyBase1);
+  auto kTestPrefix1Key2 = MakePrefixedKey(kTestPrefix1, kTestKeyBase2);
+  auto kTestPrefix2Key1 = MakePrefixedKey(kTestPrefix2, kTestKeyBase1);
+  auto kTestPrefix2Key2 = MakePrefixedKey(kTestPrefix2, kTestKeyBase2);
+  const char kTestValue1[] = "a value";
+  const char kTestValue2[] = "another value";
+  const char kTestValue3[] = "the only other value in the world";
+
+  DoSync(database, [&](const DomStorageDatabase& db) {
+    // Populate the database with one unprefixed entry, and two values with
+    // a key prefix of |kTestPrefix1|.
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestUnprefixedKey), MakeBytes(kTestValue1)));
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestPrefix1Key1), MakeBytes(kTestValue2)));
+    EXPECT_STATUS_OK(
+        db.Put(MakeBytes(kTestPrefix1Key2), MakeBytes(kTestValue3)));
+
+    // Copy the prefixed entries to |kTestPrefix2| and verify that we have the
+    // expected entries.
+    EXPECT_STATUS_OK(
+        db.CopyPrefixed(MakeBytes(kTestPrefix1), MakeBytes(kTestPrefix2)));
+
+    std::vector<DomStorageDatabase::KeyValuePair> entries;
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix2), &entries));
+    EXPECT_THAT(entries,
+                UnorderedElementsAreArray(
+                    {MakeKeyValuePair(kTestPrefix2Key1, kTestValue2),
+                     MakeKeyValuePair(kTestPrefix2Key2, kTestValue3)}));
+
+    // The original prefixed values should still be there too.
+    entries.clear();
+    EXPECT_STATUS_OK(db.GetPrefixed(MakeBytes(kTestPrefix1), &entries));
+    EXPECT_THAT(entries,
+                UnorderedElementsAreArray(
+                    {MakeKeyValuePair(kTestPrefix1Key1, kTestValue2),
+                     MakeKeyValuePair(kTestPrefix1Key2, kTestValue3)}));
+  });
+}
+
+}  // namespace storage
diff --git a/components/sessions/BUILD.gn b/components/sessions/BUILD.gn
index 652e153..ff1e1c08 100644
--- a/components/sessions/BUILD.gn
+++ b/components/sessions/BUILD.gn
@@ -151,6 +151,17 @@
       "core/base_session_service_test_helper.h",
     ]
   }
+
+  if (!is_ios) {
+    sources += [
+      "content/content_test_helper.cc",
+      "content/content_test_helper.h",
+    ]
+    deps += [
+      "//content/public/browser",
+      "//content/public/common",
+    ]
+  }
 }
 
 source_set("unit_tests") {
diff --git a/components/sessions/content/content_test_helper.cc b/components/sessions/content/content_test_helper.cc
new file mode 100644
index 0000000..5eda913
--- /dev/null
+++ b/components/sessions/content/content_test_helper.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/sessions/content/content_test_helper.h"
+
+#include <memory>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "components/sessions/content/content_serialized_navigation_builder.h"
+#include "components/sessions/core/serialized_navigation_entry_test_helper.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/common/referrer.h"
+#include "url/gurl.h"
+
+namespace sessions {
+
+// static
+SerializedNavigationEntry ContentTestHelper::CreateNavigation(
+    const std::string& virtual_url,
+    const std::string& title) {
+  std::unique_ptr<content::NavigationEntry> navigation_entry =
+      content::NavigationEntry::Create();
+  navigation_entry->SetReferrer(
+      content::Referrer(GURL("http://www.referrer.com"),
+                        network::mojom::ReferrerPolicy::kDefault));
+  navigation_entry->SetURL(GURL(virtual_url));
+  navigation_entry->SetVirtualURL(GURL(virtual_url));
+  navigation_entry->SetTitle(base::UTF8ToUTF16(title));
+  navigation_entry->SetTimestamp(base::Time::Now());
+  navigation_entry->SetHttpStatusCode(200);
+  return ContentSerializedNavigationBuilder::FromNavigationEntry(
+      test_data::kIndex, navigation_entry.get());
+}
+
+}  // namespace sessions
diff --git a/components/sessions/content/content_test_helper.h b/components/sessions/content/content_test_helper.h
new file mode 100644
index 0000000..59711f15
--- /dev/null
+++ b/components/sessions/content/content_test_helper.h
@@ -0,0 +1,31 @@
+// 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_SESSIONS_CONTENT_CONTENT_TEST_HELPER_H_
+#define COMPONENTS_SESSIONS_CONTENT_CONTENT_TEST_HELPER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/sessions/core/serialized_navigation_entry.h"
+
+namespace sessions {
+
+// Set of test functions to manipulate a SerializedNavigationEntry.
+class ContentTestHelper {
+ public:
+  // Creates a SerializedNavigationEntry with the given URL and title and some
+  // common values for the other fields.
+  static SerializedNavigationEntry CreateNavigation(
+      const std::string& virtual_url,
+      const std::string& title);
+
+ private:
+  // Only static methods.
+  DISALLOW_IMPLICIT_CONSTRUCTORS(ContentTestHelper);
+};
+
+}  // namespace sessions
+
+#endif  // COMPONENTS_SESSIONS_CONTENT_CONTENT_TEST_HELPER_H_
diff --git a/components/sessions/core/serialized_navigation_entry_test_helper.cc b/components/sessions/core/serialized_navigation_entry_test_helper.cc
index a78f63c..f5f08bc 100644
--- a/components/sessions/core/serialized_navigation_entry_test_helper.cc
+++ b/components/sessions/core/serialized_navigation_entry_test_helper.cc
@@ -68,21 +68,6 @@
 }
 
 // static
-SerializedNavigationEntry SerializedNavigationEntryTestHelper::CreateNavigation(
-    const std::string& virtual_url,
-    const std::string& title) {
-  SerializedNavigationEntry navigation;
-  navigation.index_ = 0;
-  navigation.referrer_url_ = GURL("http://www.referrer.com");
-  navigation.virtual_url_ = GURL(virtual_url);
-  navigation.title_ = base::UTF8ToUTF16(title);
-  navigation.encoded_page_state_ = "fake state";
-  navigation.timestamp_ = base::Time::Now();
-  navigation.http_status_code_ = 200;
-  return navigation;
-}
-
-// static
 SerializedNavigationEntry
 SerializedNavigationEntryTestHelper::CreateNavigationForTest() {
   SerializedNavigationEntry navigation;
diff --git a/components/sessions/core/serialized_navigation_entry_test_helper.h b/components/sessions/core/serialized_navigation_entry_test_helper.h
index aa770c6..606b235 100644
--- a/components/sessions/core/serialized_navigation_entry_test_helper.h
+++ b/components/sessions/core/serialized_navigation_entry_test_helper.h
@@ -62,12 +62,6 @@
   static void ExpectNavigationEquals(const SerializedNavigationEntry& expected,
                                      const SerializedNavigationEntry& actual);
 
-  // Creates a SerializedNavigationEntry with the given URL and title and some
-  // common values for the other fields.
-  static SerializedNavigationEntry CreateNavigation(
-      const std::string& virtual_url,
-      const std::string& title);
-
   // Creates a SerializedNavigationEntry using the |test_data| constants above.
   static SerializedNavigationEntry CreateNavigationForTest();
 
diff --git a/components/sync_sessions/DEPS b/components/sync_sessions/DEPS
index c1e43a7..7a0379d15 100644
--- a/components/sync_sessions/DEPS
+++ b/components/sync_sessions/DEPS
@@ -5,7 +5,7 @@
   "+components/history/core/browser",
   "+components/keyed_service/core",
   "+components/prefs",
-  "+components/sessions",
+  "+components/sessions/core",
   "+components/sync",
   "+components/sync_device_info",
   "+components/sync_user_events",
diff --git a/components/sync_sessions/open_tabs_ui_delegate_impl_unittest.cc b/components/sync_sessions/open_tabs_ui_delegate_impl_unittest.cc
index 1b30ab8..92c692b 100644
--- a/components/sync_sessions/open_tabs_ui_delegate_impl_unittest.cc
+++ b/components/sync_sessions/open_tabs_ui_delegate_impl_unittest.cc
@@ -7,6 +7,7 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
 #include "components/sync_sessions/mock_sync_sessions_client.h"
@@ -55,28 +56,31 @@
   // Create three sessions, with one window and tab each.
   session_tracker_.PutWindowInSession(kSessionTag1, kWindowId1);
   session_tracker_.PutTabInWindow(kSessionTag1, kWindowId1, kTabId1);
-  session_tracker_.GetTab(kSessionTag1, kTabId1)
-      ->navigations.push_back(
-          sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-              "http://url1", "title1"));
+  sessions::SerializedNavigationEntry entry1 =
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest();
+  entry1.set_virtual_url(GURL("http://url1"));
+  entry1.set_title(base::UTF8ToUTF16("title1"));
+  session_tracker_.GetTab(kSessionTag1, kTabId1)->navigations.push_back(entry1);
   session_tracker_.GetSession(kSessionTag1)->modified_time =
       kTime0 + base::TimeDelta::FromSeconds(3);
 
   session_tracker_.PutWindowInSession(kSessionTag2, kWindowId2);
   session_tracker_.PutTabInWindow(kSessionTag2, kWindowId2, kTabId2);
-  session_tracker_.GetTab(kSessionTag2, kTabId2)
-      ->navigations.push_back(
-          sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-              "http://url2", "title2"));
+  sessions::SerializedNavigationEntry entry2 =
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest();
+  entry2.set_virtual_url(GURL("http://url2"));
+  entry2.set_title(base::UTF8ToUTF16("title2"));
+  session_tracker_.GetTab(kSessionTag2, kTabId2)->navigations.push_back(entry2);
   session_tracker_.GetSession(kSessionTag2)->modified_time =
       kTime0 + base::TimeDelta::FromSeconds(1);
 
   session_tracker_.PutWindowInSession(kSessionTag3, kWindowId3);
   session_tracker_.PutTabInWindow(kSessionTag3, kWindowId3, kTabId3);
-  session_tracker_.GetTab(kSessionTag3, kTabId3)
-      ->navigations.push_back(
-          sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-              "http://url3", "title3"));
+  sessions::SerializedNavigationEntry entry3 =
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest();
+  entry3.set_virtual_url(GURL("http://url3"));
+  entry3.set_title(base::UTF8ToUTF16("title3"));
+  session_tracker_.GetTab(kSessionTag3, kTabId3)->navigations.push_back(entry3);
   session_tracker_.GetSession(kSessionTag3)->modified_time =
       kTime0 + base::TimeDelta::FromSeconds(2);
 
@@ -98,20 +102,17 @@
 
   sessions::SessionTab* tab1 = session_tracker_.GetTab(kSessionTag1, kTabId1);
   tab1->navigations.push_back(
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://url1", "title1"));
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest());
   tab1->timestamp = kTime0 + base::TimeDelta::FromSeconds(3);
 
   sessions::SessionTab* tab2 = session_tracker_.GetTab(kSessionTag1, kTabId2);
   tab2->navigations.push_back(
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://url1", "title1"));
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest());
   tab2->timestamp = kTime0 + base::TimeDelta::FromSeconds(1);
 
   sessions::SessionTab* tab3 = session_tracker_.GetTab(kSessionTag1, kTabId3);
   tab3->navigations.push_back(
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-          "http://url1", "title1"));
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest());
   tab3->timestamp = kTime0 + base::TimeDelta::FromSeconds(2);
 
   std::vector<const SessionTab*> tabs;
@@ -136,9 +137,8 @@
   session_tracker_.PutWindowInSession(kSessionTag2, kWindowId2);
   session_tracker_.PutTabInWindow(kSessionTag2, kWindowId2, kTabId2);
   session_tracker_.GetTab(kSessionTag2, kTabId2)
-      ->navigations.push_back(
-          sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-              "http://url2", "title2"));
+      ->navigations.push_back(sessions::SerializedNavigationEntryTestHelper::
+                                  CreateNavigationForTest());
 
   std::vector<const SyncedSession*> sessions;
   EXPECT_TRUE(delegate_.GetAllForeignSessions(&sessions));
@@ -152,19 +152,23 @@
 
   // Create two sessions, with one window and tab each. The first of the two
   // contains a URL that should not be synced.
+  sessions::SerializedNavigationEntry nonsyncable_entry =
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest();
+  nonsyncable_entry.set_virtual_url(GURL("http://url1"));
+  nonsyncable_entry.set_title(base::UTF8ToUTF16("title1"));
   session_tracker_.PutWindowInSession(kSessionTag1, kWindowId1);
   session_tracker_.PutTabInWindow(kSessionTag1, kWindowId1, kTabId1);
   session_tracker_.GetTab(kSessionTag1, kTabId1)
-      ->navigations.push_back(
-          sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-              "http://url1", "title1"));
+      ->navigations.push_back(nonsyncable_entry);
 
+  sessions::SerializedNavigationEntry syncable_entry =
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest();
+  syncable_entry.set_virtual_url(GURL("http://otherurl"));
+  syncable_entry.set_title(base::UTF8ToUTF16("title1"));
   session_tracker_.PutWindowInSession(kSessionTag2, kWindowId2);
   session_tracker_.PutTabInWindow(kSessionTag2, kWindowId2, kTabId2);
   session_tracker_.GetTab(kSessionTag2, kTabId2)
-      ->navigations.push_back(
-          sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-              "http://url2", "title2"));
+      ->navigations.push_back(syncable_entry);
 
   std::vector<const SyncedSession*> sessions;
   EXPECT_TRUE(delegate_.GetAllForeignSessions(&sessions));
diff --git a/components/sync_sessions/synced_session_tracker_unittest.cc b/components/sync_sessions/synced_session_tracker_unittest.cc
index 8d8ea54..98379746 100644
--- a/components/sync_sessions/synced_session_tracker_unittest.cc
+++ b/components/sync_sessions/synced_session_tracker_unittest.cc
@@ -38,7 +38,6 @@
 const char kTag[] = "tag";
 const char kTag2[] = "tag2";
 const char kTag3[] = "tag3";
-const char kTitle[] = "title";
 const int kTabNode1 = 0;
 const int kTabNode2 = 1;
 const int kTabNode3 = 2;
@@ -203,8 +202,7 @@
   sessions::SessionTab* tab = tracker_.GetTab(kTag, kTab1);
   ASSERT_TRUE(tab);
   tab->navigations.push_back(
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(kValidUrl,
-                                                                      kTitle));
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest());
   EXPECT_THAT(tracker_.LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
               ElementsAre(HasSessionTag(kTag)));
 
@@ -215,8 +213,7 @@
   sessions::SessionTab* tab2 = tracker_.GetTab(kTag2, kTab2);
   ASSERT_TRUE(tab2);
   tab2->navigations.push_back(
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(kValidUrl,
-                                                                      kTitle));
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest());
   EXPECT_THAT(tracker_.LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
               ElementsAre(HasSessionTag(kTag), HasSessionTag(kTag2)));
 }
@@ -235,8 +232,8 @@
   sessions::SessionTab* tab = tracker_.GetTab(kTag, kTab1);
   ASSERT_TRUE(tab);
   tab->navigations.push_back(
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(kValidUrl,
-                                                                      kTitle));
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest());
+  tab->navigations.back().set_virtual_url(GURL(kValidUrl));
   tracker_.GetSession(kTag2);
   tracker_.GetSession(kTag3);
   tracker_.PutWindowInSession(kTag3, kWindow1);
@@ -244,8 +241,8 @@
   tab = tracker_.GetTab(kTag3, kTab1);
   ASSERT_TRUE(tab);
   tab->navigations.push_back(
-      sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
-          kInvalidUrl, kTitle));
+      sessions::SerializedNavigationEntryTestHelper::CreateNavigationForTest());
+  tab->navigations.back().set_virtual_url(GURL(kInvalidUrl));
   // Only the session with a valid window and tab gets returned.
   EXPECT_THAT(
       tracker_.LookupAllForeignSessions(SyncedSessionTracker::PRESENTABLE),
diff --git a/components/sync_sessions/synced_session_unittest.cc b/components/sync_sessions/synced_session_unittest.cc
index 67c31047..07ff950 100644
--- a/components/sync_sessions/synced_session_unittest.cc
+++ b/components/sync_sessions/synced_session_unittest.cc
@@ -253,9 +253,11 @@
   tab.user_agent_override = "fake";
   tab.timestamp = base::Time::FromInternalValue(100);
   for (int i = 0; i < 5; ++i) {
-    tab.navigations.push_back(
-        SerializedNavigationEntryTestHelper::CreateNavigation(
-            "http://foo/" + base::NumberToString(i), "title"));
+    sessions::SerializedNavigationEntry entry =
+        SerializedNavigationEntryTestHelper::CreateNavigationForTest();
+    entry.set_virtual_url(GURL("http://foo/" + base::NumberToString(i)));
+    entry.set_title(base::UTF8ToUTF16("title" + base::NumberToString(i)));
+    tab.navigations.push_back(entry);
   }
   tab.session_storage_persistent_id = "fake";
 
diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
index c18971eb..83b13c6 100644
--- a/content/app/content_main_runner_impl.cc
+++ b/content/app/content_main_runner_impl.cc
@@ -913,8 +913,6 @@
 
     tracing::InitTracingPostThreadPoolStartAndFeatureList();
 
-    delegate_->PostTaskSchedulerStart();
-
     if (should_start_service_manager_only)
       ForceInProcessNetworkService(true);
 
diff --git a/content/browser/devtools/protocol/browser_handler.cc b/content/browser/devtools/protocol/browser_handler.cc
index 3ef3ec3..85173ee 100644
--- a/content/browser/devtools/protocol/browser_handler.cc
+++ b/content/browser/devtools/protocol/browser_handler.cc
@@ -24,6 +24,7 @@
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/user_agent.h"
+#include "url/gurl.h"
 #include "v8/include/v8-version-string.h"
 
 namespace content {
@@ -316,10 +317,13 @@
 
   PermissionControllerImpl* permission_controller =
       PermissionControllerImpl::FromBrowserContext(browser_context);
-  GURL url = GURL(origin).GetOrigin();
+  url::Origin overridden_origin = url::Origin::Create(GURL(origin));
+  if (overridden_origin.opaque())
+    return Response::InvalidParams(
+        "Permission can't be granted to opaque origins.");
 
   PermissionControllerImpl::OverrideStatus status =
-      permission_controller->SetOverrideForDevTools(url, type,
+      permission_controller->SetOverrideForDevTools(overridden_origin, type,
                                                     permission_status);
   if (status != PermissionControllerImpl::OverrideStatus::kOverrideSet) {
     return Response::InvalidParams(
@@ -352,9 +356,13 @@
 
   PermissionControllerImpl* permission_controller =
       PermissionControllerImpl::FromBrowserContext(browser_context);
-  GURL url = GURL(origin).GetOrigin();
+  url::Origin overridden_origin = url::Origin::Create(GURL(origin));
+  if (overridden_origin.opaque())
+    return Response::InvalidParams(
+        "Permission can't be granted to opaque origins.");
+
   PermissionControllerImpl::OverrideStatus status =
-      permission_controller->GrantOverridesForDevTools(url,
+      permission_controller->GrantOverridesForDevTools(overridden_origin,
                                                        internal_permissions);
   if (status != PermissionControllerImpl::OverrideStatus::kOverrideSet) {
     return Response::InvalidParams(
diff --git a/content/browser/media/midi_host.cc b/content/browser/media/midi_host.cc
index 1e9c2138..34997ce 100644
--- a/content/browser/media/midi_host.cc
+++ b/content/browser/media/midi_host.cc
@@ -48,8 +48,7 @@
       midi_service_(midi_service),
       sent_bytes_in_flight_(0),
       bytes_sent_since_last_acknowledgement_(0),
-      output_port_count_(0),
-      midi_session_(this) {
+      output_port_count_(0) {
   DCHECK(midi_service_);
 }
 
@@ -73,7 +72,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK(midi_client_);
   if (result == Result::OK)
-    midi_session_.Bind(std::move(pending_session_request_));
+    midi_session_.Bind(std::move(pending_session_receiver_));
   midi_client_->SessionStarted(result);
 }
 
@@ -164,18 +163,18 @@
   midi_service_ = nullptr;
 }
 
-void MidiHost::StartSession(midi::mojom::MidiSessionRequest request,
-                            midi::mojom::MidiSessionClientPtr client) {
+void MidiHost::StartSession(
+    mojo::PendingReceiver<midi::mojom::MidiSession> session_receiver,
+    mojo::PendingRemote<midi::mojom::MidiSessionClient> client) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(!pending_session_request_);
+  DCHECK(!pending_session_receiver_);
   // Checks to see if |midi_session_| isn't already bound to another
   // MidiSessionRequest.
-  DCHECK(!midi_session_);
-  pending_session_request_ = std::move(request);
+  pending_session_receiver_ = std::move(session_receiver);
 
   DCHECK(!midi_client_);
-  midi_client_ = std::move(client);
-  midi_client_.set_connection_error_handler(
+  midi_client_.Bind(std::move(client));
+  midi_client_.set_disconnect_handler(
       base::BindOnce(&MidiHost::EndSession, base::Unretained(this)));
 
   if (midi_service_)
@@ -247,7 +246,7 @@
   if (midi_service_)
     midi_service_->EndSession(this);
   midi_client_.reset();
-  midi_session_.Close();
+  midi_session_.reset();
 }
 
 }  // namespace content
diff --git a/content/browser/media/midi_host.h b/content/browser/media/midi_host.h
index 8484b52..abb3006 100644
--- a/content/browser/media/midi_host.h
+++ b/content/browser/media/midi_host.h
@@ -24,7 +24,10 @@
 #include "content/public/browser/browser_thread.h"
 #include "media/midi/midi_manager.h"
 #include "media/midi/midi_service.mojom.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
 
 namespace midi {
 class MidiService;
@@ -62,8 +65,9 @@
   void Detach() override;
 
   // midi::mojom::MidiSessionProvider implementation.
-  void StartSession(midi::mojom::MidiSessionRequest session_request,
-                    midi::mojom::MidiSessionClientPtr client) override;
+  void StartSession(
+      mojo::PendingReceiver<midi::mojom::MidiSession> session_receiver,
+      mojo::PendingRemote<midi::mojom::MidiSessionClient> client) override;
 
   // midi::mojom::MidiSession implementation.
   void SendData(uint32_t port,
@@ -117,14 +121,14 @@
 
   // Stores a session request sent from the renderer until CompleteStartSession
   // is called.
-  midi::mojom::MidiSessionRequest pending_session_request_;
+  mojo::PendingReceiver<midi::mojom::MidiSession> pending_session_receiver_;
 
   // Bound on the IO thread if a session is successfully started by MidiService.
-  mojo::Binding<midi::mojom::MidiSession> midi_session_;
+  mojo::Receiver<midi::mojom::MidiSession> midi_session_{this};
 
   // Bound on the IO thread and should only be called there. Use CallClient to
   // call midi::mojom::MidiSessionClient methods.
-  midi::mojom::MidiSessionClientPtr midi_client_;
+  mojo::Remote<midi::mojom::MidiSessionClient> midi_client_;
 
   DISALLOW_COPY_AND_ASSIGN(MidiHost);
 };
diff --git a/content/browser/media/midi_host_unittest.cc b/content/browser/media/midi_host_unittest.cc
index 633d780a..20c19364 100644
--- a/content/browser/media/midi_host_unittest.cc
+++ b/content/browser/media/midi_host_unittest.cc
@@ -16,6 +16,9 @@
 #include "content/public/test/test_browser_context.h"
 #include "media/midi/midi_manager.h"
 #include "media/midi/midi_service.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -131,13 +134,18 @@
     factory_ = factory->GetWeakPtr();
     service_ = std::make_unique<midi::MidiService>(std::move(factory));
     host_ = std::make_unique<MidiHostForTesting>(rph_->GetID(), service_.get());
-    midi::mojom::MidiSessionClientPtr ptr;
-    midi::mojom::MidiSessionClientRequest request = mojo::MakeRequest(&ptr);
-    mojo::MakeStrongBinding(std::make_unique<MidiSessionClientForTesting>(),
-                            std::move(request));
+    mojo::PendingRemote<midi::mojom::MidiSessionClient> client_remote;
+    // mojo::PendingReceiver<midi::mojom::MidiSessionClient> receiver =
+    //     client_remote.InitWithNewPipeAndPassReceiver();
+    // mojo::MakeStrongBinding(
+    //     std::make_unique<MidiSessionClientForTesting>(),
+    //     mojo::InterfaceRequest<midi::mojom::MidiSessionClient>(
+    //         std::move(receiver)));
+    mojo::MakeSelfOwnedReceiver(std::make_unique<MidiSessionClientForTesting>(),
+                                client_remote.InitWithNewPipeAndPassReceiver());
     midi::mojom::MidiSessionRequest session_request =
         mojo::MakeRequest(&session_);
-    host_->StartSession(std::move(session_request), std::move(ptr));
+    host_->StartSession(std::move(session_request), std::move(client_remote));
   }
   ~MidiHostTest() override {
     session_.reset();
diff --git a/content/browser/permissions/permission_controller_impl.cc b/content/browser/permissions/permission_controller_impl.cc
index 6cd74f2f..a018bcbf 100644
--- a/content/browser/permissions/permission_controller_impl.cc
+++ b/content/browser/permissions/permission_controller_impl.cc
@@ -15,7 +15,6 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
-#include "url/origin.h"
 
 class GURL;
 
@@ -185,7 +184,7 @@
 
 PermissionControllerImpl::OverrideStatus
 PermissionControllerImpl::SetOverrideForDevTools(
-    const GURL& origin,
+    const url::Origin& origin,
     const PermissionType& permission,
     const blink::mojom::PermissionStatus& status) {
   PermissionControllerDelegate* delegate =
@@ -194,10 +193,9 @@
       !delegate->IsPermissionOverridableByDevTools(permission, origin)) {
     return OverrideStatus::kOverrideNotSet;
   }
-  const auto old_statuses = GetSubscriptionsStatuses(origin);
+  const auto old_statuses = GetSubscriptionsStatuses(origin.GetURL());
 
-  devtools_permission_overrides_.Set(url::Origin::Create(origin), permission,
-                                     status);
+  devtools_permission_overrides_.Set(origin, permission, status);
   NotifyChangedSubscriptions(old_statuses);
 
   UpdateDelegateOverridesForDevTools(origin);
@@ -206,7 +204,7 @@
 
 PermissionControllerImpl::OverrideStatus
 PermissionControllerImpl::GrantOverridesForDevTools(
-    const GURL& origin,
+    const url::Origin& origin,
     const std::vector<PermissionType>& permissions) {
   PermissionControllerDelegate* delegate =
       browser_context_->GetPermissionControllerDelegate();
@@ -215,9 +213,8 @@
       if (!delegate->IsPermissionOverridableByDevTools(permission, origin))
         return OverrideStatus::kOverrideNotSet;
 
-  const auto old_statuses = GetSubscriptionsStatuses(origin);
-  devtools_permission_overrides_.GrantPermissions(url::Origin::Create(origin),
-                                                  permissions);
+  const auto old_statuses = GetSubscriptionsStatuses(origin.GetURL());
+  devtools_permission_overrides_.GrantPermissions(origin, permissions);
   // If any statuses changed because they lose overrides or the new overrides
   // modify their previous state (overridden or not), subscribers must be
   // notified manually.
@@ -242,7 +239,7 @@
 }
 
 void PermissionControllerImpl::UpdateDelegateOverridesForDevTools(
-    const GURL& origin) {
+    const url::Origin& origin) {
   PermissionControllerDelegate* delegate =
       browser_context_->GetPermissionControllerDelegate();
   if (!delegate)
@@ -250,7 +247,7 @@
 
   // If no overrides exist, still want to update with "blank" overrides.
   PermissionOverrides current_overrides =
-      devtools_permission_overrides_.GetAll(url::Origin::Create(origin));
+      devtools_permission_overrides_.GetAll(origin);
   delegate->SetPermissionOverridesForDevTools(origin, current_overrides);
 }
 
diff --git a/content/browser/permissions/permission_controller_impl.h b/content/browser/permissions/permission_controller_impl.h
index 63be1d0..86f121c 100644
--- a/content/browser/permissions/permission_controller_impl.h
+++ b/content/browser/permissions/permission_controller_impl.h
@@ -10,6 +10,7 @@
 #include "content/public/browser/devtools_permission_overrides.h"
 #include "content/public/browser/permission_controller.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace content {
 
@@ -32,10 +33,10 @@
   // For the given |origin|, grant permissions in |overrides| and reject all
   // others.
   OverrideStatus GrantOverridesForDevTools(
-      const GURL& origin,
+      const url::Origin& origin,
       const std::vector<PermissionType>& permissions);
   OverrideStatus SetOverrideForDevTools(
-      const GURL& origin,
+      const url::Origin& origin,
       const PermissionType& permission,
       const blink::mojom::PermissionStatus& status);
   void ResetOverridesForDevTools();
@@ -91,7 +92,7 @@
   void NotifyChangedSubscriptions(const SubscriptionsStatusMap& old_statuses);
   void OnDelegatePermissionStatusChange(Subscription* subscription,
                                         blink::mojom::PermissionStatus status);
-  void UpdateDelegateOverridesForDevTools(const GURL& origin);
+  void UpdateDelegateOverridesForDevTools(const url::Origin& origin);
 
   DevToolsPermissionOverrides devtools_permission_overrides_;
   SubscriptionsMap subscriptions_;
diff --git a/content/browser/permissions/permission_controller_impl_unittest.cc b/content/browser/permissions/permission_controller_impl_unittest.cc
index 13ab34d..c408b18c 100644
--- a/content/browser/permissions/permission_controller_impl_unittest.cc
+++ b/content/browser/permissions/permission_controller_impl_unittest.cc
@@ -41,10 +41,11 @@
           const base::OnceCallback<void(
               const std::vector<blink::mojom::PermissionStatus>&)> callback));
   MOCK_METHOD2(SetPermissionOverridesForDevTools,
-               void(const GURL& origin, const PermissionOverrides& overrides));
+               void(const url::Origin& origin,
+                    const PermissionOverrides& overrides));
   MOCK_METHOD0(ResetPermissionOverridesForDevTools, void());
   MOCK_METHOD2(IsPermissionOverridableByDevTools,
-               bool(PermissionType, const GURL&));
+               bool(PermissionType, const url::Origin&));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockManagerWithRequests);
@@ -90,7 +91,7 @@
 }
 
 TEST_F(PermissionControllerImplTest, SettingOverridesForwardsUpdates) {
-  GURL kTestOrigin(kTestUrl);
+  url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl));
   EXPECT_CALL(*mock_manager(),
               SetPermissionOverridesForDevTools(
                   kTestOrigin, testing::ElementsAre(testing::Pair(
@@ -103,7 +104,7 @@
 
 TEST_F(PermissionControllerImplTest,
        RequestPermissionsDelegatesIffMissingOverrides) {
-  GURL kTestOrigin(kTestUrl);
+  url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl));
   RenderViewHostTestEnabler enabler;
 
   const std::vector<PermissionType> kTypesToQuery = {
@@ -211,10 +212,11 @@
           });
       // Regular tests can set expectations.
       if (!test_case.expect_death) {
-        EXPECT_CALL(*mock_manager(),
-                    RequestPermissions(testing::ElementsAreArray(
-                                           test_case.delegated_permissions),
-                                       rfh, kTestOrigin, true, testing::_))
+        EXPECT_CALL(
+            *mock_manager(),
+            RequestPermissions(
+                testing::ElementsAreArray(test_case.delegated_permissions), rfh,
+                kTestOrigin.GetURL(), true, testing::_))
             .WillOnce(testing::Invoke(forward_callbacks));
       } else {
         // Death tests cannot track these expectations but arguments should be
@@ -222,7 +224,7 @@
         ON_CALL(*mock_manager(),
                 RequestPermissions(
                     testing::ElementsAreArray(test_case.delegated_permissions),
-                    rfh, kTestOrigin, true, testing::_))
+                    rfh, kTestOrigin.GetURL(), true, testing::_))
             .WillByDefault(testing::Invoke(forward_callbacks));
       }
     } else {
@@ -234,13 +236,13 @@
       EXPECT_CALL(callback,
                   Run(testing::ElementsAreArray(test_case.expected_results)));
       permission_controller()->RequestPermissions(
-          kTypesToQuery, rfh, kTestOrigin,
+          kTypesToQuery, rfh, kTestOrigin.GetURL(),
           /*user_gesture=*/true, callback.Get());
     } else {
       ::testing::FLAGS_gtest_death_test_style = "threadsafe";
       base::MockCallback<RequestsCallback> callback;
       EXPECT_DEATH_IF_SUPPORTED(permission_controller()->RequestPermissions(
-                                    kTypesToQuery, rfh, kTestOrigin,
+                                    kTypesToQuery, rfh, kTestOrigin.GetURL(),
                                     /*user_gesture=*/true, callback.Get()),
                                 "");
     }
@@ -249,49 +251,49 @@
 
 TEST_F(PermissionControllerImplTest,
        GetPermissionStatusDelegatesIffNoOverrides) {
-  GURL kTestOrigin(kTestUrl);
-  EXPECT_CALL(*mock_manager(), GetPermissionStatus(PermissionType::GEOLOCATION,
-                                                   kTestOrigin, kTestOrigin))
+  GURL kUrl = GURL(kTestUrl);
+  url::Origin kTestOrigin = url::Origin::Create(kUrl);
+  EXPECT_CALL(*mock_manager(),
+              GetPermissionStatus(PermissionType::GEOLOCATION, kUrl, kUrl))
       .WillOnce(testing::Return(blink::mojom::PermissionStatus::DENIED));
 
   blink::mojom::PermissionStatus status =
       permission_controller()->GetPermissionStatus(PermissionType::GEOLOCATION,
-                                                   kTestOrigin, kTestOrigin);
+                                                   kUrl, kUrl);
   EXPECT_EQ(status, blink::mojom::PermissionStatus::DENIED);
 
   permission_controller()->SetOverrideForDevTools(
       kTestOrigin, PermissionType::GEOLOCATION,
       blink::mojom::PermissionStatus::GRANTED);
-  EXPECT_CALL(*mock_manager(), GetPermissionStatus(PermissionType::GEOLOCATION,
-                                                   kTestOrigin, kTestOrigin))
+  EXPECT_CALL(*mock_manager(),
+              GetPermissionStatus(PermissionType::GEOLOCATION, kUrl, kUrl))
       .Times(0);
   status = permission_controller()->GetPermissionStatus(
-      PermissionType::GEOLOCATION, kTestOrigin, kTestOrigin);
+      PermissionType::GEOLOCATION, kUrl, kUrl);
   EXPECT_EQ(status, blink::mojom::PermissionStatus::GRANTED);
 }
 
 TEST_F(PermissionControllerImplTest,
        GetPermissionStatusForFrameDelegatesIffNoOverrides) {
-  GURL kTestOrigin(kTestUrl);
-  EXPECT_CALL(*mock_manager(),
-              GetPermissionStatusForFrame(PermissionType::GEOLOCATION, nullptr,
-                                          kTestOrigin))
+  GURL kUrl = GURL(kTestUrl);
+  url::Origin kTestOrigin = url::Origin::Create(kUrl);
+  EXPECT_CALL(*mock_manager(), GetPermissionStatusForFrame(
+                                   PermissionType::GEOLOCATION, nullptr, kUrl))
       .WillOnce(testing::Return(blink::mojom::PermissionStatus::DENIED));
 
   blink::mojom::PermissionStatus status =
       permission_controller()->GetPermissionStatusForFrame(
-          PermissionType::GEOLOCATION, nullptr, kTestOrigin);
+          PermissionType::GEOLOCATION, nullptr, kUrl);
   EXPECT_EQ(status, blink::mojom::PermissionStatus::DENIED);
 
   permission_controller()->SetOverrideForDevTools(
       kTestOrigin, PermissionType::GEOLOCATION,
       blink::mojom::PermissionStatus::GRANTED);
-  EXPECT_CALL(*mock_manager(),
-              GetPermissionStatusForFrame(PermissionType::GEOLOCATION, nullptr,
-                                          kTestOrigin))
+  EXPECT_CALL(*mock_manager(), GetPermissionStatusForFrame(
+                                   PermissionType::GEOLOCATION, nullptr, kUrl))
       .Times(0);
   status = permission_controller()->GetPermissionStatusForFrame(
-      PermissionType::GEOLOCATION, nullptr, kTestOrigin);
+      PermissionType::GEOLOCATION, nullptr, kUrl);
   EXPECT_EQ(status, blink::mojom::PermissionStatus::GRANTED);
 }
 
@@ -299,24 +301,24 @@
        NotifyChangedSubscriptionsCallsOnChangeOnly) {
   using PermissionStatusCallback =
       base::Callback<void(blink::mojom::PermissionStatus)>;
-  GURL kTestOrigin(kTestUrl);
+  GURL kUrl = GURL(kTestUrl);
+  url::Origin kTestOrigin = url::Origin::Create(kUrl);
 
   // Setup.
   blink::mojom::PermissionStatus sync_status =
       permission_controller()->GetPermissionStatus(
-          PermissionType::BACKGROUND_SYNC, kTestOrigin, kTestOrigin);
+          PermissionType::BACKGROUND_SYNC, kUrl, kUrl);
   permission_controller()->SetOverrideForDevTools(
       kTestOrigin, PermissionType::GEOLOCATION,
       blink::mojom::PermissionStatus::DENIED);
 
   base::MockCallback<PermissionStatusCallback> geo_callback;
   permission_controller()->SubscribePermissionStatusChange(
-      PermissionType::GEOLOCATION, nullptr, kTestOrigin, geo_callback.Get());
+      PermissionType::GEOLOCATION, nullptr, kUrl, geo_callback.Get());
 
   base::MockCallback<PermissionStatusCallback> sync_callback;
   permission_controller()->SubscribePermissionStatusChange(
-      PermissionType::BACKGROUND_SYNC, nullptr, kTestOrigin,
-      sync_callback.Get());
+      PermissionType::BACKGROUND_SYNC, nullptr, kUrl, sync_callback.Get());
 
   // Geolocation should change status, so subscriber is updated.
   EXPECT_CALL(geo_callback, Run(blink::mojom::PermissionStatus::ASK));
@@ -336,30 +338,32 @@
 
 TEST_F(PermissionControllerImplTest,
        PermissionsCannotBeOverriddenIfNotOverridable) {
-  GURL kTestOrigin(kTestUrl);
-  EXPECT_EQ(permission_controller()->SetOverrideForDevTools(
+  url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl));
+  EXPECT_EQ(OverrideStatus::kOverrideSet,
+            permission_controller()->SetOverrideForDevTools(
                 kTestOrigin, PermissionType::GEOLOCATION,
-                blink::mojom::PermissionStatus::DENIED),
-            OverrideStatus::kOverrideSet);
+                blink::mojom::PermissionStatus::DENIED));
 
   // Delegate will be called, but prevents override from being set.
   EXPECT_CALL(*mock_manager(), IsPermissionOverridableByDevTools(
                                    PermissionType::GEOLOCATION, testing::_))
       .WillOnce(testing::Return(false));
-  EXPECT_EQ(permission_controller()->SetOverrideForDevTools(
+  EXPECT_EQ(OverrideStatus::kOverrideNotSet,
+            permission_controller()->SetOverrideForDevTools(
                 kTestOrigin, PermissionType::GEOLOCATION,
-                blink::mojom::PermissionStatus::ASK),
-            OverrideStatus::kOverrideNotSet);
+                blink::mojom::PermissionStatus::ASK));
 
   blink::mojom::PermissionStatus status =
       permission_controller()->GetPermissionStatus(PermissionType::GEOLOCATION,
-                                                   kTestOrigin, kTestOrigin);
-  EXPECT_EQ(status, blink::mojom::PermissionStatus::DENIED);
+                                                   kTestOrigin.GetURL(),
+                                                   kTestOrigin.GetURL());
+  EXPECT_EQ(blink::mojom::PermissionStatus::DENIED, status);
 }
 
 TEST_F(PermissionControllerImplTest,
        GrantPermissionsReturnsStatusesBeingSetIfOverridable) {
-  GURL kTestOrigin(kTestUrl);
+  GURL kUrl(kTestUrl);
+  url::Origin kTestOrigin = url::Origin::Create(kUrl);
   permission_controller()->SetOverrideForDevTools(
       kTestOrigin, PermissionType::GEOLOCATION,
       blink::mojom::PermissionStatus::DENIED);
@@ -385,13 +389,13 @@
   // Keep original settings as before.
   EXPECT_EQ(blink::mojom::PermissionStatus::DENIED,
             permission_controller()->GetPermissionStatus(
-                PermissionType::GEOLOCATION, kTestOrigin, kTestOrigin));
+                PermissionType::GEOLOCATION, kUrl, kUrl));
+  EXPECT_EQ(blink::mojom::PermissionStatus::ASK,
+            permission_controller()->GetPermissionStatus(PermissionType::MIDI,
+                                                         kUrl, kUrl));
   EXPECT_EQ(blink::mojom::PermissionStatus::ASK,
             permission_controller()->GetPermissionStatus(
-                PermissionType::MIDI, kTestOrigin, kTestOrigin));
-  EXPECT_EQ(blink::mojom::PermissionStatus::ASK,
-            permission_controller()->GetPermissionStatus(
-                PermissionType::BACKGROUND_SYNC, kTestOrigin, kTestOrigin));
+                PermissionType::BACKGROUND_SYNC, kUrl, kUrl));
 
   EXPECT_CALL(*mock_manager(), IsPermissionOverridableByDevTools(
                                    PermissionType::GEOLOCATION, testing::_))
@@ -409,13 +413,13 @@
   EXPECT_EQ(OverrideStatus::kOverrideSet, result);
   EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
             permission_controller()->GetPermissionStatus(
-                PermissionType::GEOLOCATION, kTestOrigin, kTestOrigin));
+                PermissionType::GEOLOCATION, kUrl, kUrl));
+  EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
+            permission_controller()->GetPermissionStatus(PermissionType::MIDI,
+                                                         kUrl, kUrl));
   EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
             permission_controller()->GetPermissionStatus(
-                PermissionType::MIDI, kTestOrigin, kTestOrigin));
-  EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
-            permission_controller()->GetPermissionStatus(
-                PermissionType::BACKGROUND_SYNC, kTestOrigin, kTestOrigin));
+                PermissionType::BACKGROUND_SYNC, kUrl, kUrl));
 }
 
 }  // namespace
diff --git a/content/public/app/content_main_delegate.h b/content/public/app/content_main_delegate.h
index cecfa58..67ae8559 100644
--- a/content/public/app/content_main_delegate.h
+++ b/content/public/app/content_main_delegate.h
@@ -121,7 +121,7 @@
       const base::Closure& quit_closure,
       service_manager::BackgroundServiceManager* service_manager);
 
-  // Allows the embedder to perform platform-specific initializatioion before
+  // Allows the embedder to perform platform-specific initialization before
   // creating the main message loop.
   virtual void PreCreateMainMessageLoop() {}
 
@@ -131,21 +131,23 @@
   // created should override and return false.
   virtual bool ShouldCreateFeatureList();
 
-  // Allows the embedder to perform its own initialization after content
-  // performed its own and already brought up MessageLoop, ThreadPool, field
-  // trials and FeatureList (by default).
-  // |is_running_tests| indicates whether it is running in tests.
-  virtual void PostEarlyInitialization(bool is_running_tests) {}
-
   // Allows the embedder to perform initialization once field trials/FeatureList
   // initialization has completed if ShouldCreateFeatureList() returns true.
   // Otherwise, the embedder is responsible for calling this method once feature
   // list initialization is complete.
   virtual void PostFieldTrialInitialization() {}
 
-  // Allows the embedder to perform its own initialization after the
-  // TaskScheduler is started.
-  virtual void PostTaskSchedulerStart() {}
+  // Allows the embedder to perform its own initialization after early content
+  // initialization. At this point, it is possible to post to base::ThreadPool
+  // or to the main thread loop via base::ThreadTaskRunnerHandle, but the tasks
+  // won't run immediately.
+  //
+  // If ShouldCreateFeatureList() returns true, the field trials and FeatureList
+  // have been initialized. Otherwise, the implementation must initialize the
+  // field trials and FeatureList and call PostFieldTrialInitialization().
+  //
+  // |is_running_tests| indicates whether it is running in tests.
+  virtual void PostEarlyInitialization(bool is_running_tests) {}
 
  protected:
   friend class ContentClientInitializer;
diff --git a/content/public/browser/permission_controller_delegate.cc b/content/public/browser/permission_controller_delegate.cc
index d176de77..cd833f2 100644
--- a/content/public/browser/permission_controller_delegate.cc
+++ b/content/public/browser/permission_controller_delegate.cc
@@ -8,7 +8,7 @@
 
 bool PermissionControllerDelegate::IsPermissionOverridableByDevTools(
     PermissionType permission,
-    const GURL& origin) {
+    const url::Origin& origin) {
   return true;
 }
 
diff --git a/content/public/browser/permission_controller_delegate.h b/content/public/browser/permission_controller_delegate.h
index c0d853f..fed32f8 100644
--- a/content/public/browser/permission_controller_delegate.h
+++ b/content/public/browser/permission_controller_delegate.h
@@ -100,7 +100,7 @@
   // are tracked by the delegate. This method should only be called by the
   // PermissionController owning the delegate.
   virtual void SetPermissionOverridesForDevTools(
-      const GURL& origin,
+      const url::Origin& origin,
       const PermissionOverrides& overrides) {}
 
   // Removes overrides that have been set, if any, for all origins. If delegate
@@ -110,7 +110,7 @@
   // Returns whether permission can be overridden by
   // DevToolsPermissionOverrides.
   virtual bool IsPermissionOverridableByDevTools(PermissionType permission,
-                                                 const GURL& origin);
+                                                 const url::Origin& origin);
 };
 
 }  // namespace content
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index ccbb48e..512c533 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -420,7 +420,6 @@
     StartBrowserThreadPool();
     BrowserTaskExecutor::PostFeatureListSetup();
     tracing::InitTracingPostThreadPoolStartAndFeatureList();
-    delegate->PostTaskSchedulerStart();
   }
 
   auto discardable_shared_memory_manager =
diff --git a/content/public/test/network_service_test_helper.cc b/content/public/test/network_service_test_helper.cc
index f0628792..c1519560 100644
--- a/content/public/test/network_service_test_helper.cc
+++ b/content/public/test/network_service_test_helper.cc
@@ -23,11 +23,9 @@
 #include "net/cert/mock_cert_verifier.h"
 #include "net/cert/test_root_certs.h"
 #include "net/dns/mock_host_resolver.h"
-#include "net/http/http_network_session.h"
 #include "net/http/transport_security_state.h"
 #include "net/http/transport_security_state_test_util.h"
 #include "net/nqe/network_quality_estimator.h"
-#include "net/socket/client_socket_pool_manager.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/spawned_test_server/spawned_test_server.h"
 #include "net/test/test_data_directory.h"
@@ -227,14 +225,6 @@
     std::move(callback).Run(value);
   }
 
-  void GetMaxConnectionsPerProxy(
-      GetMaxConnectionsPerProxyCallback callback) override {
-    int max_sockets =
-        net::ClientSocketPoolManager::max_sockets_per_proxy_server(
-            net::HttpNetworkSession::NORMAL_SOCKET_POOL);
-    std::move(callback).Run(max_sockets);
-  }
-
   void Log(const std::string& message, LogCallback callback) override {
     LOG(ERROR) << message;
     std::move(callback).Run();
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 305f8d3..94e10e3 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -198,8 +198,6 @@
     "media/renderer_webaudiodevice_impl.h",
     "media/renderer_webmediaplayer_delegate.cc",
     "media/renderer_webmediaplayer_delegate.h",
-    "media/webrtc/audio_codec_factory.cc",
-    "media/webrtc/audio_codec_factory.h",
     "media/webrtc/media_stream_track_metrics.cc",
     "media/webrtc/media_stream_track_metrics.h",
     "media/webrtc/media_stream_video_webrtc_sink.cc",
@@ -501,18 +499,6 @@
     "//third_party/webrtc/api/audio:aec3_config_json",
     "//third_party/webrtc/api/audio:aec3_factory",
     "//third_party/webrtc/api/audio_codecs:audio_codecs_api",
-    "//third_party/webrtc/api/audio_codecs/L16:audio_decoder_L16",
-    "//third_party/webrtc/api/audio_codecs/L16:audio_encoder_L16",
-    "//third_party/webrtc/api/audio_codecs/g711:audio_decoder_g711",
-    "//third_party/webrtc/api/audio_codecs/g711:audio_encoder_g711",
-    "//third_party/webrtc/api/audio_codecs/g722:audio_decoder_g722",
-    "//third_party/webrtc/api/audio_codecs/g722:audio_encoder_g722",
-    "//third_party/webrtc/api/audio_codecs/isac:audio_decoder_isac",
-    "//third_party/webrtc/api/audio_codecs/isac:audio_encoder_isac",
-    "//third_party/webrtc/api/audio_codecs/opus:audio_decoder_multiopus",
-    "//third_party/webrtc/api/audio_codecs/opus:audio_decoder_opus",
-    "//third_party/webrtc/api/audio_codecs/opus:audio_encoder_multiopus",
-    "//third_party/webrtc/api/audio_codecs/opus:audio_encoder_opus",
     "//third_party/webrtc/api/rtc_event_log:rtc_event_log_factory",
     "//third_party/webrtc/api/video:video_bitrate_allocation",
     "//third_party/webrtc/api/video:video_frame",
diff --git a/content/renderer/media/webrtc/audio_codec_factory.h b/content/renderer/media/webrtc/audio_codec_factory.h
deleted file mode 100644
index 6f2448d..0000000
--- a/content/renderer/media/webrtc/audio_codec_factory.h
+++ /dev/null
@@ -1,22 +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.
-
-#ifndef CONTENT_RENDERER_MEDIA_WEBRTC_AUDIO_CODEC_FACTORY_H_
-#define CONTENT_RENDERER_MEDIA_WEBRTC_AUDIO_CODEC_FACTORY_H_
-
-#include "third_party/webrtc/api/audio_codecs/audio_decoder_factory.h"
-#include "third_party/webrtc/api/audio_codecs/audio_encoder_factory.h"
-#include "third_party/webrtc/api/scoped_refptr.h"
-
-namespace content {
-
-rtc::scoped_refptr<webrtc::AudioEncoderFactory>
-CreateWebrtcAudioEncoderFactory();
-
-rtc::scoped_refptr<webrtc::AudioDecoderFactory>
-CreateWebrtcAudioDecoderFactory();
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_MEDIA_WEBRTC_AUDIO_CODEC_FACTORY_H_
diff --git a/content/renderer/media/webrtc/peer_connection_dependency_factory.cc b/content/renderer/media/webrtc/peer_connection_dependency_factory.cc
index 16214933..679eb03a 100644
--- a/content/renderer/media/webrtc/peer_connection_dependency_factory.cc
+++ b/content/renderer/media/webrtc/peer_connection_dependency_factory.cc
@@ -29,7 +29,6 @@
 #include "content/public/common/feature_h264_with_openh264_ffmpeg.h"
 #include "content/public/common/webrtc_ip_handling_policy.h"
 #include "content/public/renderer/content_renderer_client.h"
-#include "content/renderer/media/webrtc/audio_codec_factory.h"
 #include "content/renderer/media/webrtc/rtc_peer_connection_handler.h"
 #include "content/renderer/media/webrtc/stun_field_trial.h"
 #include "content/renderer/media/webrtc/video_codec_factory.h"
@@ -48,6 +47,7 @@
 #include "media/media_buildflags.h"
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "third_party/blink/public/platform/modules/mediastream/webrtc_uma_histograms.h"
+#include "third_party/blink/public/platform/modules/peerconnection/audio_codec_factory.h"
 #include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
 #include "third_party/blink/public/platform/web_media_constraints.h"
 #include "third_party/blink/public/platform/web_media_stream.h"
@@ -329,8 +329,8 @@
   cricket::MediaEngineDependencies media_deps;
   media_deps.task_queue_factory = pcf_deps.task_queue_factory.get();
   media_deps.adm = audio_device_.get();
-  media_deps.audio_encoder_factory = CreateWebrtcAudioEncoderFactory();
-  media_deps.audio_decoder_factory = CreateWebrtcAudioDecoderFactory();
+  media_deps.audio_encoder_factory = blink::CreateWebrtcAudioEncoderFactory();
+  media_deps.audio_decoder_factory = blink::CreateWebrtcAudioDecoderFactory();
   media_deps.video_encoder_factory = std::move(webrtc_encoder_factory);
   media_deps.video_decoder_factory = std::move(webrtc_decoder_factory);
   media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create();
diff --git a/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc b/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
index 12ca71c..dfe8cc3 100644
--- a/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
+++ b/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
@@ -240,9 +240,13 @@
   // to software decoding. See https://crbug.com/webrtc/9304.
   if (video_codec_type_ == webrtc::kVideoCodecVP9 &&
       input_image.SpatialIndex().value_or(0) > 0) {
+#if defined(ARCH_CPU_X86_FAMILY) && defined(OS_CHROMEOS)
     if (!base::FeatureList::IsEnabled(media::kVp9kSVCHWDecoding)) {
       return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
     }
+#else
+    return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+#endif  // defined(ARCH_CPU_X86_FAMILY) && defined(OS_CHROMEOS)
   }
 
   if (missing_frames || !input_image._completeFrame) {
diff --git a/content/renderer/media/webrtc/transmission_encoding_info_handler.cc b/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
index 21fe0dd4..283a7440 100644
--- a/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
+++ b/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
@@ -16,11 +16,11 @@
 #include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
 #include "content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h"
-#include "content/renderer/media/webrtc/audio_codec_factory.h"
 #include "content/renderer/media/webrtc/video_codec_factory.h"
 #include "content/renderer/render_thread_impl.h"
 #include "third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h"
 #include "third_party/blink/public/platform/modules/media_capabilities/web_video_configuration.h"
+#include "third_party/blink/public/platform/modules/peerconnection/audio_codec_factory.h"
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/webrtc/api/audio_codecs/audio_encoder_factory.h"
 #include "third_party/webrtc/api/audio_codecs/audio_format.h"
@@ -100,7 +100,7 @@
   }
 
   rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory =
-      CreateWebrtcAudioEncoderFactory();
+      blink::CreateWebrtcAudioEncoderFactory();
   std::vector<webrtc::AudioCodecSpec> supported_audio_specs =
       audio_encoder_factory->GetSupportedEncoders();
   for (const auto& audio_spec : supported_audio_specs)
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index bc9ea8c..8db1a1a 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -722,6 +722,12 @@
 
 void RenderWidget::OnSynchronizeVisualProperties(
     const VisualProperties& original_params) {
+  // TODO:(https://crbug.com/995981): If there is no WebWidget, then the
+  // RenderWidget should also be destroyed, and this conditional should not be
+  // necessary.
+  if (!GetWebWidget())
+    return;
+
   TRACE_EVENT0("renderer", "RenderWidget::OnSynchronizeVisualProperties");
 
   VisualProperties params = original_params;
diff --git a/content/test/gpu/unittest_data/integration_tests.py b/content/test/gpu/unittest_data/integration_tests.py
index 28c00a0..54d49c4 100644
--- a/content/test/gpu/unittest_data/integration_tests.py
+++ b/content/test/gpu/unittest_data/integration_tests.py
@@ -38,6 +38,13 @@
     cls.StartBrowser()
 
   @classmethod
+  def GenerateTags(cls, possible_browser, finder_options):
+    # TODO(crbug.com/992260) Delete this after crrev.com/c/1769732 is merged.
+    # We should keep this for now so that a browser instance is not spawned
+    del possible_browser, finder_options
+    return []
+
+  @classmethod
   def AddCommandlineArgs(cls, parser):
     super(_BaseSampleIntegrationTest, cls).AddCommandlineArgs(parser)
     parser.add_option('--test-state-json-path',
@@ -60,11 +67,6 @@
   }
 
   @classmethod
-  def GenerateTags(cls, finder_options, possible_browser):
-    del finder_options, possible_browser
-    return ['foo']
-
-  @classmethod
   def Name(cls):
     return 'simple_integration_unittest'
 
@@ -201,8 +203,16 @@
   _flaky_test_run = 0
 
   @classmethod
-  def GenerateTags(cls, finder_options, possible_browser):
-    del finder_options, possible_browser
+  def GenerateTags(cls, possible_browser, finder_options):
+    # TODO(crbug.com/992260) Delete this after crrev.com/c/1769732 is merged.
+    # We should keep this for now so that a browser instance is not spawned
+    del possible_browser, finder_options
+    return cls.GetPlatformTags(
+        fakes.FakeBrowser(fakes.FakeLinuxPlatform, 'debug'))
+
+  @classmethod
+  def GetPlatformTags(cls, browser):
+    assert isinstance(browser, fakes.FakeBrowser)
     return ['foo']
 
   @classmethod
@@ -281,11 +291,6 @@
     return 'test_also_run_disabled_tests'
 
   @classmethod
-  def GenerateTags(cls, finder_options, possible_browser):
-    del finder_options, possible_browser
-    return ['foo']
-
-  @classmethod
   def GenerateGpuTests(cls, options):
     tests = [
       ('skip', 'skip.html', ()),
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h
index a6fc367..46a08dc11 100644
--- a/device/fido/fido_request_handler_base.h
+++ b/device/fido/fido_request_handler_base.h
@@ -81,6 +81,9 @@
     bool has_recognized_mac_touch_id_credential = false;
     bool is_ble_powered = false;
     bool can_power_on_ble_adapter = false;
+    // Whether the RP supplied caBLE pairing data in the request by sending a
+    // caBLE extension.
+    bool cable_pairing_data_supplied = false;
 
     // A random AES-256 key used that can be used to encrypt a coarse timestamp.
     // The UI may display a QR code with the resulting ciphertext which, if
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index 9f4eb07..2c5575e 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -191,6 +191,8 @@
       FidoRequestHandlerBase::RequestType::kGetAssertion;
   transport_availability_info().has_empty_allow_list =
       request_.allow_list.empty();
+  transport_availability_info().cable_pairing_data_supplied =
+      static_cast<bool>(request_.cable_extension);
 
   if (base::Contains(transport_availability_info().available_transports,
                      FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy)) {
diff --git a/device/vr/orientation/orientation_session.h b/device/vr/orientation/orientation_session.h
index 1df070e..50c282885 100644
--- a/device/vr/orientation/orientation_session.h
+++ b/device/vr/orientation/orientation_session.h
@@ -38,7 +38,7 @@
       mojom::XREnvironmentIntegrationProviderAssociatedRequest
           environment_provider) override;
   void SetInputSourceButtonListener(
-      mojom::XRInputSourceButtonListenerAssociatedPtrInfo) override;
+      device::mojom::XRInputSourceButtonListenerAssociatedPtrInfo) override;
 
   // Accessible to tests.
  protected:
diff --git a/device/vr/public/mojom/vr_service.mojom b/device/vr/public/mojom/vr_service.mojom
index ea6d52a..a8bbb47 100644
--- a/device/vr/public/mojom/vr_service.mojom
+++ b/device/vr/public/mojom/vr_service.mojom
@@ -422,7 +422,7 @@
 interface VRService {
   // Optionally supply a VRServiceClient to listen for changes to the XRDevice.
   // A bad message will be reported if this is called multiple times.
-  SetClient(VRServiceClient client);
+  SetClient(pending_remote<VRServiceClient> client);
 
   // WebVR 1.1 functionality compatibility method. To stop listening pass a null
   // client.
diff --git a/device/vr/windows/compositor_base.cc b/device/vr/windows/compositor_base.cc
index ed86ec2..86ac40e 100644
--- a/device/vr/windows/compositor_base.cc
+++ b/device/vr/windows/compositor_base.cc
@@ -356,7 +356,8 @@
 }
 
 void XRCompositorCommon::SetInputSourceButtonListener(
-    mojom::XRInputSourceButtonListenerAssociatedPtrInfo input_listener_info) {
+    device::mojom::XRInputSourceButtonListenerAssociatedPtrInfo
+        input_listener_info) {
   DCHECK(UsesInputEventing());
   input_event_listener_.Bind(std::move(input_listener_info));
 }
diff --git a/device/vr/windows/compositor_base.h b/device/vr/windows/compositor_base.h
index 9624f78..eadcc30 100644
--- a/device/vr/windows/compositor_base.h
+++ b/device/vr/windows/compositor_base.h
@@ -61,8 +61,8 @@
   void GetFrameData(mojom::XRFrameDataRequestOptionsPtr options,
                     XRFrameDataProvider::GetFrameDataCallback callback) final;
   void SetInputSourceButtonListener(
-      mojom::XRInputSourceButtonListenerAssociatedPtrInfo input_listener_info)
-      override;
+      device::mojom::XRInputSourceButtonListenerAssociatedPtrInfo
+          input_listener_info) override;
   void GetControllerDataAndSendFrameData(
       XRFrameDataProvider::GetFrameDataCallback callback,
       mojom::XRFrameDataPtr frame_data);
diff --git a/docs/mojo_and_services.md b/docs/mojo_and_services.md
index 1de450b..173b142 100644
--- a/docs/mojo_and_services.md
+++ b/docs/mojo_and_services.md
@@ -24,25 +24,25 @@
 message, for developers who are familiar with Google protobufs.
 
 Given a mojom interface and a message pipe, one of the endpoints
-can be designated as an **InterfacePtr** and is used to *send* messages described by
-the interface. The other endpoint can be designated as a **Binding** and is used
+can be designated as a **Remote** and is used to *send* messages described by
+the interface. The other endpoint can be designated as a **Receiver** and is used
 to *receive* interface messages.
 
 *** aside
 NOTE: The above generalization is a bit oversimplified. Remember that the
 message pipe is still bidirectional, and it's possible for a mojom message to
-expect a reply. Replies are sent from the Binding endpoint and received by the
-InterfacePtr endpoint.
+expect a reply. Replies are sent from the Receiver endpoint and received by the
+Remote endpoint.
 ***
 
-The Binding endpoint must be associated with (*i.e.* **bound** to) an
+The Receiver endpoint must be associated with (*i.e.* **bound** to) an
 **implementation** of its mojom interface in order to process received messages.
 A received message is dispatched as a scheduled task invoking the corresponding
 interface method on the implementation object.
 
-Another way to think about all this is simply that **an InterfacePtr makes
+Another way to think about all this is simply that **a Remote makes
 calls on a remote implementation of its interface associated with a
-corresponding remote Binding.**
+corresponding remote Receiver.**
 
 ## Example: Defining a New Frame Interface
 
@@ -85,42 +85,36 @@
 
 *** aside
 As a general rule and as a matter of convenience when
-using Mojo, the *client* of an interface (*i.e.* the InterfacePtr side) is
+using Mojo, the *client* of an interface (*i.e.* the Remote side) is
 typically the party who creates a new pipe. This is convenient because the
-InterfacePtr may be used to start sending messages immediately without waiting
+Remote may be used to start sending messages immediately without waiting
 for the InterfaceRequest endpoint to be transferred or bound anywhere.
 ***
 
 This code would be placed somewhere in the renderer:
 
 ```cpp
-example::mojom::PingResponderPtr ping_responder;
-example::mojom::PingResponderRequest request =
-    mojo::MakeRequest(&ping_responder);
+mojo::Remote<example::mojom::PingResponder> ping_responder;
+mojo::PendingReceiver<example::mojom::PingResponder> receiver =
+    ping_responder.BindNewPipeAndPassReceiver();
 ```
 
-In this example, ```ping_responder``` is the InterfacePtr, and ```request```
-is an InterfaceRequest, which is a Binding precursor that will eventually
-be turned into a Binding. `mojo::MakeRequest` is the most common way to create
-a message pipe: it yields both endpoints as strongly-typed objects, with the
-`InterfacePtr` as an output argument and the `InterfaceRequest` as the return
+In this example, ```ping_responder``` is the Remote, and ```receiver```
+is a PendingReceiver, which is a Receiver precursor that will eventually
+be turned into a Receiver. `BindNewPipeAndPassReceiver` is the most common way to create
+a message pipe: it yields the `PendingReceiver` as the return
 value.
 
 *** aside
-NOTE: Every mojom interface `T` generates corresponding C++ type aliases
-`TPtr = InterfacePtr<T>` and `TRequest = InterfaceRequest<T>`. Chromium code
-almost exclusively uses these aliases instead of writing out the more verbose
-templated name.
-
-Also note that an InterfaceRequest doesn't actually **do** anything. It is an
+NOTE: A PendingReceiver doesn't actually **do** anything. It is an
 inert holder of a single message pipe endpoint. It exists only to make its
 endpoint more strongly-typed at compile-time, indicating that the endpoint
-expects to be bound by a Binding of the same interface type.
+expects to be bound by a Receiver of the same interface type.
 ***
 
 ### Sending a Message
 
-Finally, we can call the `Ping()` method on our InterfacePtr to send a message:
+Finally, we can call the `Ping()` method on our Remote to send a message:
 
 ```cpp
 ping_responder->Ping(base::BindOnce(&OnPong));
@@ -136,63 +130,54 @@
 We're almost done! Of course, if everything were this easy, this document
 wouldn't need to exist. We've taken the hard problem of sending a message from
 a renderer process to the browser process, and transformed it into a problem
-where we just need to take the `request` object from above and pass it to the
-browser process somehow where it can be turned into a Binding that dispatches
+where we just need to take the `receiver` object from above and pass it to the
+browser process somehow where it can be turned into a Receiver that dispatches
 its received messages.
 
-### Sending an InterfaceRequest to the Browser
+### Sending a PendingReceiver to the Browser
 
-It's worth noting that InterfaceRequests (and message pipe endpoints in general)
+It's worth noting that PendingReceivers (and message pipe endpoints in general)
 are just another type of object that can be freely sent over mojom messages.
-The most common way to get an InterfaceRequest somewhere is to pass it as a
+The most common way to get a PendingReceiver somewhere is to pass it as a
 method argument on some other already-connected interface.
 
 One such interface which we always have connected between a renderer's
 `RenderFrameImpl` and its corresponding `RenderFrameHostImpl` in the browser
 is
-[`DocumentInterfaceBroker`](https://cs.chromium.org/chromium/src/third_party/blink/public/mojom/frame/document_interface_broker.mojom).
-We can update this definition to add support for our new PingResponder
-interface:
+[`BrowserInterfaceBroker`](https://cs.chromium.org/chromium/src/third_party/blink/public/mojom/browser_interface_broker.mojom).
+This interface is a factory for acquiring other interfaces. Its `GetInterface`
+method takes a `GenericPendingReceiver`, which allows passing arbitrary
+interface receivers.
 
 ``` cpp
-interface DocumentInterfaceBroker {
-  ...
-
-  GetPingResponder(PingResponder& responder);
+interface BrowserInterfaceBroker {
+  GetInterface(mojo_base.mojom.GenericPendingReceiver receiver);
 }
 ```
-
-The `&` syntax is not a reference! In mojom it denotes an InterfaceRequest.
-Specifically in this case, the `GetPingResponder` takes a single
-`PingResponderRequest` argument. If the `&` were omitted, this would instead
-take a `PingResponderPtr`.
-
-Now the renderer can call this method with the `request` object it created
-earlier via `mojo::MakeRequest`:
+Since `GenericPendingReceiver` can be implicitly constructed from any specific
+`PendingReceiver`, can call this method with the `receiver` object it created
+earlier via `BindNewPipeAndPassReceiver`:
 
 ``` cpp
 RenderFrame* my_frame = GetMyFrame();
-my_frame->GetDocumentInterfaceBroker()->GetPingResponder(std::move(request));
+my_frame->GetBrowserInterfaceBrokerProxy()->GetInterface(std::move(receiver));
 ```
 
-This will transfer the PingResponderRequest endpoint to the browser process
-where it will be received by the corresponding `DocumentInterfaceBroker`
+This will transfer the PendingReceiver endpoint to the browser process
+where it will be received by the corresponding `BrowserInterfaceBroker`
 implementation. More on that below.
 
 ### Implementing the Interface
 
-Finally, we need a browser-side implementation of our `PingResponder` interface
-as well as an implementation of the new
-`DocumentInterfaceBroker.GetPingResponder` message. Let's implement
-`PingResponder` first:
+Finally, we need a browser-side implementation of our `PingResponder` interface.
 
 ```cpp
 #include "example/public/mojom/ping_responder.mojom.h"
 
 class PingResponderImpl : example::mojom::PingResponder {
  public:
-  explicit PingResponderImpl(example::mojom::PingResponderRequest request)
-      : binding_(this, std::move(request)) {}
+  explicit PingResponderImpl(mojo::PendingReceiver<example::mojom::PingResponder> receiver)
+      : receiver_(this, std::move(receiver)) {}
 
   // example::mojom::PingResponder:
   void Ping(PingCallback callback) override {
@@ -201,25 +186,21 @@
   }
 
  private:
-  mojo::Binding<example::mojom::PingResponder> binding_;
+  mojo::Receiver<example::mojom::PingResponder> Receiver_;
 
   DISALLOW_COPY_AND_ASSIGN(PingResponderImpl);
 };
 ```
 
-And conveniently `RenderFrameHostImpl` implements `DocumentInterfaceBroker`, and
-any calls made on the object returned by
-`RenderFrameImpl::GetDocumentInterfaceBroker()` will be routed directly to the
-`RenderFrameHostImpl`.  So the only thing left to do is update
-`RenderFrameHostImpl` to implement `GetPingResponder`. If you forget to do this
-the compiler will complain anyway, because generated mojom interface methods are
-pure virtual methods in C++.
+`RenderFrameHostImpl` owns an implementation of `BrowserInterfaceBroker`.
+When this implementation receives a `GetInterface` method call, it calls
+the handler previously registered for this specific interface.
 
 ``` cpp
 // render_frame_host_impl.h
 class RenderFrameHostImpl
   ...
-  void GetPingResponder(example::mojom::PingResponderRequest request) override;
+  void GetPingResponder(mojo::PendingReceiver<example::mojom::PingResponder> receiver);
   ...
  private:
   ...
@@ -229,8 +210,17 @@
 
 // render_frame_host_impl.cc
 void RenderFrameHostImpl::GetPingResponder(
-    example::mojom::PingResponderRequest request) {
-  ping_responder_ = std::make_unique<PingResponderImpl>(std::move(request));
+    mojo::PendingReceiver<example::mojom::PingResponder> receiver) {
+  ping_responder_ = std::make_unique<PingResponderImpl>(std::move(receiver));
+}
+
+// browser_interface_binders.cc
+void PopulateFrameBinders(RenderFrameHostImpl* host,
+                          service_manager::BinderMap* map) {
+...
+  // Register the handler for PingResponder.
+  map->Add<example::mojom::PingResponder>(base::BindRepeating(
+    &RenderFrameHostImpl::GetPingResponder, base::Unretained(host)));
 }
 ```
 
@@ -367,10 +357,10 @@
 
   service_manager::ServiceBinding service_binding_;
 
-  // You could also use a Binding. We use BindingSet to conveniently allow
+  // You could also use a Receiver. We use ReceiverSet to conveniently allow
   // multiple clients to bind to the same instance of this class. See Mojo
   // C++ Bindings documentation for more information.
-  mojo::BindingSet<mojom::Divider> divider_bindings_;
+  mojo::ReceiverSet<mojom::Divider> divider_receivers_;
 
   DISALLOW_COPY_AND_ASSIGN(MathService);
 };
@@ -396,8 +386,8 @@
   // Note that services typically use a service_manager::BinderRegistry if they
   // plan on handling many different interface request types.
   if (interface_name == mojom::Divider::Name_) {
-    divider_bindings_.AddBinding(
-        this, mojom::DividerRequest(std::move(interface_pipe)));
+    divider_receivers_.Add(
+        this, mojo::PendingReceiver<mojom::Divider>(std::move(interface_pipe)));
   }
 }
 
@@ -772,38 +762,34 @@
 
 #### The New Way: Interface Brokers
 
-*** aside
-In classic Google tradition, the New Way is not entirely ready yet. As of this
-writing, worker-scoped interfaces must still use the Old Way described above.
-***
-
 Rather than the confusing spaghetti of interface filter logic, we now define an
 explicit mojom interface with a persistent connection between a renderer's
 frame object and the corresponding `RenderFrameHostImpl` in the browser process.
 This interface is called
-[`DocumentInterfaceBroker`](https://cs.chromium.org/chromium/src/third_party/blink/public/mojom/frame/document_interface_broker.mojom?rcl=ea6921f717f21e9a72d321a15c4bf50d47d10310&l=11)
-and is fairly easy to work with: you simply add a new factory method to the
-interface definition:
-
-``` cpp
-interface DocumentInterfaceBroker {
-  ...
-
-  GetGoatTeleporter(magic.mojom.GoatTeleporter& request);
-};
-```
-
-and implement this new method on `RenderFrameHostImpl`, which is an
-implementation (**the** production implementation) of
-`DocumentInterfaceBroker`:
+[`BrowserInterfaceBroker`](https://cs.chromium.org/chromium/src/third_party/blink/public/mojom/browser_interface_broker.mojom?rcl=09aa5ae71649974cae8ad4f889d7cd093637ccdb&l=11)
+and is fairly easy to work with: you add a new method on `RenderFrameHostImpl`:
 
 ``` cpp
 void RenderFrameHostImpl::GetGoatTeleporter(
-    magic::mojom::GoatTeleporterRequest request) {
-  goat_teleporter_binding_.Bind(std::move(request));
+    mojo::PendingReceiver<magic::mojom::GoatTeleporter> receiver) {
+  goat_teleporter_receiver_.Bind(std::move(receiver));
 }
 ```
 
+and register this method in `PopulateFrameBinders` function in `browser_interface_binders.cc`,
+which maps specific interfaces to their handlers in respective hosts:
+
+``` cpp
+// //content/browser/browser_interface_binders.cc
+void PopulateFrameBinders(RenderFrameHostImpl* host,
+                          service_manager::BinderMap* map) {
+...
+  map->Add<magic::mojom::GoatTeleporter>(base::BindRepeating(
+      &RenderFrameHostImpl::GetGoatTeleporter, base::Unretained(host)));
+}
+```
+
+
 ### Exposing Browser Interfaces to Render Processes
 
 Sometimes (albeit rarely) it's useful to expose a browser interface directly to
@@ -824,8 +810,8 @@
 auto* connector = content::ChildThread::Get()->GetConnector();
 
 // For example...
-connector->BindInterface(content::mojom::kBrowserServiceName,
-                         std::move(some_request));
+connector->Connect(content::mojom::kBrowserServiceName,
+                         std::move(some_receiver));
 ```
 
 ### Exposing Content Child Process Interfaces to the Browser
diff --git a/docs/mojo_ipc_conversion.md b/docs/mojo_ipc_conversion.md
index 7bd22bf..adb6cb2 100644
--- a/docs/mojo_ipc_conversion.md
+++ b/docs/mojo_ipc_conversion.md
@@ -137,8 +137,8 @@
 associated interface requests:
 
 ``` cpp
-magic::mojom::GoatTeleporterAssociatedPtr teleporter;
-channel_->GetRemoteAssociatedInterfaces()->GetInterface(&teleporter);
+mojo::PendingAssociatedRemote<magic::mojom::GoatTeleporter> teleporter;
+channel_->GetRemoteAssociatedInterfaces()->GetInterface(teleporter.BindNewEndpointAndPassReceiver());
 
 // These messages are all guaranteed to arrive in the same order they were sent.
 channel_->Send(new FooMsg_SomeLegacyIPC);
@@ -424,7 +424,7 @@
 Use `WTF::Bind()` and an appropriate wrapper function depending on the type of
 object and the callback.
 
-For garbage-collected (Oilpan) classes owning the `InterfacePtr`, it is recommended
+For garbage-collected (Oilpan) classes owning the `mojo::Remote`, it is recommended
 to use `WrapWeakPersistent(this)` for connection error handlers since they
 are not guaranteed to get called in a finite time period (wrapping the object
 with `WrapPersistent` in this case would cause memory leaks).
@@ -455,14 +455,14 @@
 ```
 
 Non-garbage-collected objects can use `WTF::Unretained(this)` for both response
-and error handler callbacks when the `InterfacePtr` is owned by the object bound
+and error handler callbacks when the `mojo::Remote` is owned by the object bound
 to the callback or the object is guaranteed to outlive the Mojo connection for
 another reason. Otherwise a weak pointer should be used. However, it is not a
 common pattern since using Oilpan is recommended for all Blink code.
 
 ### Implementing Mojo interfaces in Blink
 
-Only a `mojo::Binding` or `mojo::BindingSet` should be used when implementing a
+Only a `mojo::Receiver` or `mojo::ReceiverSet` should be used when implementing a
 Mojo interface in an Oilpan-managed object. The object must then have a pre-finalizer
 to close any open pipes when the object is about to be swept as lazy sweeping
 means that it may be invalid long before the destructor is called. This requires
@@ -481,12 +481,12 @@
   // Implementation of example::mojom::blink::Example.
 
  private:
-  mojo::Binding<example::mojom::blink::Example> m_binding{this};
+  mojo::Receiver<example::mojom::blink::Example> m_receiver{this};
 };
 
 // MyObject.cpp
 void MyObject::Dispose() {
-  m_binding.Close();
+  m_receiver.Close();
 }
 ```
 
diff --git a/docs/security/mojo.md b/docs/security/mojo.md
index ae7d84b9..629885a 100644
--- a/docs/security/mojo.md
+++ b/docs/security/mojo.md
@@ -689,9 +689,9 @@
 
 ### Use the proper abstractions
 
-`mojo::BindingSet` implies multiple clients may connect. If this actually isn't
+`mojo::ReceiverSet` implies multiple clients may connect. If this actually isn't
 the case, please do not use it. For example, if an interface can be rebound,
-then use the singular `mojo::Binding` and simply `Close()` the existing binding
+then use the singular `mojo::Receiver` and simply `reset()` the existing receiver
 before reusing it.
 
 
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn
index b60305aea..6798e718 100644
--- a/extensions/browser/BUILD.gn
+++ b/extensions/browser/BUILD.gn
@@ -338,6 +338,8 @@
     "state_store.h",
     "suggest_permission_util.cc",
     "suggest_permission_util.h",
+    "task_queue_util.cc",
+    "task_queue_util.h",
     "uninstall_ping_sender.cc",
     "uninstall_ping_sender.h",
     "uninstall_reason.h",
@@ -571,7 +573,6 @@
     "api/declarative/deduping_factory_unittest.cc",
     "api/declarative/rules_registry_unittest.cc",
     "api/declarative_net_request/composite_matcher_unittest.cc",
-    "api/declarative_net_request/declarative_net_request_api_unittest.cc",
     "api/declarative_net_request/file_sequence_helper_unittest.cc",
     "api/declarative_net_request/flat_ruleset_indexer_unittest.cc",
     "api/declarative_net_request/indexed_rule_unittest.cc",
diff --git a/extensions/browser/api/declarative_net_request/action_tracker.cc b/extensions/browser/api/declarative_net_request/action_tracker.cc
index 66de566..037a9d4 100644
--- a/extensions/browser/api/declarative_net_request/action_tracker.cc
+++ b/extensions/browser/api/declarative_net_request/action_tracker.cc
@@ -34,11 +34,27 @@
     if (extension_prefs_->GetDNRUseActionCountAsBadgeText(extension_id)) {
       DCHECK(ExtensionsAPIClient::Get());
       ExtensionsAPIClient::Get()->UpdateActionCount(
-          browser_context_, extension_id, tab_id, action_count);
+          browser_context_, extension_id, tab_id, action_count,
+          false /* clear_badge_text */);
     }
   }
 }
 
+void ActionTracker::OnPreferenceEnabled(const ExtensionId& extension_id) const {
+  DCHECK(extension_prefs_->GetDNRUseActionCountAsBadgeText(extension_id));
+
+  for (auto it = actions_matched_.begin(); it != actions_matched_.end(); ++it) {
+    if (it->first.first != extension_id)
+      continue;
+
+    int tab_id = it->first.second;
+    int action_count = it->second;
+    ExtensionsAPIClient::Get()->UpdateActionCount(
+        browser_context_, extension_id, tab_id, action_count,
+        true /* clear_badge_text */);
+  }
+}
+
 void ActionTracker::ClearExtensionData(const ExtensionId& extension_id) {
   auto compare_by_extension_id =
       [&extension_id](const std::pair<ExtensionTabKey, size_t>& it) {
@@ -70,7 +86,8 @@
     if (extension_prefs_->GetDNRUseActionCountAsBadgeText(extension_id)) {
       DCHECK(ExtensionsAPIClient::Get());
       ExtensionsAPIClient::Get()->UpdateActionCount(
-          browser_context_, extension_id, tab_id, 0 /* action_count */);
+          browser_context_, extension_id, tab_id, 0 /* action_count */,
+          false /* clear_badge_text */);
     }
   }
 }
diff --git a/extensions/browser/api/declarative_net_request/action_tracker.h b/extensions/browser/api/declarative_net_request/action_tracker.h
index 76255a27..3933d7e 100644
--- a/extensions/browser/api/declarative_net_request/action_tracker.h
+++ b/extensions/browser/api/declarative_net_request/action_tracker.h
@@ -29,6 +29,11 @@
   // Called whenever a request matches with a rule.
   void OnRuleMatched(const std::vector<ExtensionId>& extension_ids, int tab_id);
 
+  // Updates the action count for all tabs for the specified |extension_id|'s
+  // extension action. Called when chrome.setActionCountAsBadgeText(true) is
+  // called by an extension.
+  void OnPreferenceEnabled(const ExtensionId& extension_id) const;
+
   // Clears the action count for the specified |extension_id| for all tabs.
   // Called when an extension's ruleset is removed.
   void ClearExtensionData(const ExtensionId& extension_id);
diff --git a/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc b/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
index 82b0d93..bef8430 100644
--- a/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
+++ b/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
@@ -13,11 +13,13 @@
 #include "base/task_runner_util.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "extensions/browser/api/declarative_net_request/action_tracker.h"
 #include "extensions/browser/api/declarative_net_request/constants.h"
 #include "extensions/browser/api/declarative_net_request/rules_monitor_service.h"
 #include "extensions/browser/api/declarative_net_request/ruleset_manager.h"
 #include "extensions/browser/api/declarative_net_request/ruleset_source.h"
 #include "extensions/browser/api/declarative_net_request/utils.h"
+#include "extensions/browser/api/extensions_api_client.h"
 #include "extensions/browser/extension_file_task_runner.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/common/api/declarative_net_request.h"
@@ -329,12 +331,29 @@
   EXTENSION_FUNCTION_VALIDATE(error.empty());
 
   ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
+  if (params->enable == prefs->GetDNRUseActionCountAsBadgeText(extension_id()))
+    return RespondNow(NoArguments());
+
   prefs->SetDNRUseActionCountAsBadgeText(extension_id(), params->enable);
 
-  // TODO(crbug.com/979068): If the preference is switched on, update the
-  // extension's badge text with the number of actions matched for this
-  // extension. Otherwise, clear the badge text for the extension's icon and
-  // show the default badge text.
+  // If the preference is switched on, update the extension's badge text with
+  // the number of actions matched for this extension. Otherwise, clear the
+  // action count for the extension's icon and show the default badge text if
+  // set.
+  if (params->enable) {
+    declarative_net_request::RulesMonitorService* rules_monitor_service =
+        declarative_net_request::RulesMonitorService::Get(browser_context());
+    DCHECK(rules_monitor_service);
+
+    const declarative_net_request::ActionTracker& action_tracker =
+        rules_monitor_service->ruleset_manager()->action_tracker();
+    action_tracker.OnPreferenceEnabled(extension_id());
+  } else {
+    DCHECK(ExtensionsAPIClient::Get());
+    ExtensionsAPIClient::Get()->ClearActionCount(browser_context(),
+                                                 *extension());
+  }
+
   return RespondNow(NoArguments());
 }
 
diff --git a/extensions/browser/api/declarative_net_request/declarative_net_request_api_unittest.cc b/extensions/browser/api/declarative_net_request/declarative_net_request_api_unittest.cc
deleted file mode 100644
index 5214289..0000000
--- a/extensions/browser/api/declarative_net_request/declarative_net_request_api_unittest.cc
+++ /dev/null
@@ -1,41 +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/browser/api/declarative_net_request/declarative_net_request_api.h"
-
-#include "base/memory/ref_counted.h"
-#include "components/version_info/version_info.h"
-#include "extensions/browser/api_test_utils.h"
-#include "extensions/browser/api_unittest.h"
-#include "extensions/browser/extension_prefs.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/features/feature_channel.h"
-
-namespace extensions {
-namespace {
-
-using DeclarativeNetRequestApiUnittest = ApiUnitTest;
-
-TEST_F(DeclarativeNetRequestApiUnittest, SetActionCountAsBadgeText) {
-  // Set the current channel to trunk.
-  ScopedCurrentChannel scoped_channel(version_info::Channel::UNKNOWN);
-  ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser_context());
-
-  // Preference should be false by default.
-  EXPECT_FALSE(
-      extension_prefs->GetDNRUseActionCountAsBadgeText(extension()->id()));
-
-  auto function = base::MakeRefCounted<
-      DeclarativeNetRequestSetActionCountAsBadgeTextFunction>();
-  function->set_extension(extension());
-
-  api_test_utils::RunFunction(function.get(), "[true]", browser_context());
-  extension_prefs = ExtensionPrefs::Get(browser_context());
-
-  EXPECT_TRUE(
-      extension_prefs->GetDNRUseActionCountAsBadgeText(extension()->id()));
-}
-
-}  // namespace
-}  // namespace extensions
diff --git a/extensions/browser/api/extensions_api_client.cc b/extensions/browser/api/extensions_api_client.cc
index d5b265f..8251270 100644
--- a/extensions/browser/api/extensions_api_client.cc
+++ b/extensions/browser/api/extensions_api_client.cc
@@ -57,7 +57,11 @@
 void ExtensionsAPIClient::UpdateActionCount(content::BrowserContext* context,
                                             const ExtensionId& extension_id,
                                             int tab_id,
-                                            int action_count) {}
+                                            int action_count,
+                                            bool clear_badge_text) {}
+
+void ExtensionsAPIClient::ClearActionCount(content::BrowserContext* context,
+                                           const Extension& extension) {}
 
 AppViewGuestDelegate* ExtensionsAPIClient::CreateAppViewGuestDelegate() const {
   return NULL;
diff --git a/extensions/browser/api/extensions_api_client.h b/extensions/browser/api/extensions_api_client.h
index 04673c64..b9b9144e 100644
--- a/extensions/browser/api/extensions_api_client.h
+++ b/extensions/browser/api/extensions_api_client.h
@@ -15,6 +15,7 @@
 #include "extensions/browser/api/declarative_content/content_rules_registry.h"
 #include "extensions/browser/api/storage/settings_namespace.h"
 #include "extensions/common/api/clipboard.h"
+#include "extensions/common/extension.h"
 #include "extensions/common/extension_id.h"
 
 class GURL;
@@ -109,11 +110,18 @@
                                         int render_frame_id,
                                         const ExtensionId& extension_id);
 
-  // Updates an extension's matched action count stored in an ExtensionAction.
+  // Updates an extension's matched action count stored in an ExtensionAction
+  // and optionally clears the extension's explicitly set badge text for the
+  // tab specified by |tab_id|.
   virtual void UpdateActionCount(content::BrowserContext* context,
                                  const ExtensionId& extension_id,
                                  int tab_id,
-                                 int action_count);
+                                 int action_count,
+                                 bool clear_badge_text);
+
+  // Clears an extension's matched action count stored in an ExtensionAction.
+  virtual void ClearActionCount(content::BrowserContext* context,
+                                const Extension& extension);
 
   // Creates the AppViewGuestDelegate.
   virtual AppViewGuestDelegate* CreateAppViewGuestDelegate() const;
diff --git a/extensions/browser/extension_registrar.cc b/extensions/browser/extension_registrar.cc
index 7704d273b..64aa0ab 100644
--- a/extensions/browser/extension_registrar.cc
+++ b/extensions/browser/extension_registrar.cc
@@ -25,6 +25,7 @@
 #include "extensions/browser/renderer_startup_helper.h"
 #include "extensions/browser/runtime_data.h"
 #include "extensions/browser/service_worker_task_queue.h"
+#include "extensions/browser/task_queue_util.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 
 using content::DevToolsAgentHost;
@@ -437,10 +438,7 @@
   // TODO(lazyboy): We should move all logic that is required to start up an
   // extension to a separate class, instead of calling adhoc methods like
   // service worker ones below.
-  if (BackgroundInfo::IsServiceWorkerBased(extension)) {
-    DCHECK(extension->is_extension());
-    ServiceWorkerTaskQueue::Get(browser_context_)->ActivateExtension(extension);
-  }
+  ActivateTaskQueueForExtension(browser_context_, extension);
 
   // Tell subsystems that use the ExtensionRegistryObserver::OnExtensionLoaded
   // about the new extension.
@@ -465,10 +463,8 @@
   renderer_helper_->OnExtensionUnloaded(*extension);
   extension_system_->UnregisterExtensionWithRequestContexts(extension->id(),
                                                             reason);
-  if (BackgroundInfo::IsServiceWorkerBased(extension)) {
-    ServiceWorkerTaskQueue::Get(browser_context_)
-        ->DeactivateExtension(extension);
-  }
+  DeactivateTaskQueueForExtension(browser_context_, extension);
+
   delegate_->PostDeactivateExtension(extension);
 }
 
diff --git a/extensions/browser/lazy_context_id.cc b/extensions/browser/lazy_context_id.cc
index f829a546..e360ff7 100644
--- a/extensions/browser/lazy_context_id.cc
+++ b/extensions/browser/lazy_context_id.cc
@@ -6,6 +6,7 @@
 
 #include "extensions/browser/lazy_background_task_queue.h"
 #include "extensions/browser/service_worker_task_queue.h"
+#include "extensions/browser/task_queue_util.h"
 
 namespace extensions {
 
@@ -22,10 +23,7 @@
       service_worker_scope_(service_worker_scope) {}
 
 LazyContextTaskQueue* LazyContextId::GetTaskQueue() const {
-  if (is_for_event_page())
-    return LazyBackgroundTaskQueue::Get(context_);
-  DCHECK(is_for_service_worker());
-  return ServiceWorkerTaskQueue::Get(context_);
+  return GetTaskQueueForLazyContextId(*this);
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/service_worker_task_queue.cc b/extensions/browser/service_worker_task_queue.cc
index 3c00ffb9..b475460 100644
--- a/extensions/browser/service_worker_task_queue.cc
+++ b/extensions/browser/service_worker_task_queue.cc
@@ -288,14 +288,20 @@
 
 base::Version ServiceWorkerTaskQueue::RetrieveRegisteredServiceWorkerVersion(
     const ExtensionId& extension_id) {
-  const base::DictionaryValue* info = nullptr;
-  if (!ExtensionPrefs::Get(browser_context_)
-           ->ReadPrefAsDictionary(extension_id,
-                                  kPrefServiceWorkerRegistrationInfo, &info)) {
-    return base::Version();
-  }
   std::string version_string;
-  info->GetString(kServiceWorkerVersion, &version_string);
+  if (browser_context_->IsOffTheRecord()) {
+    auto it = off_the_record_registrations_.find(extension_id);
+    return it != off_the_record_registrations_.end() ? it->second
+                                                     : base::Version();
+  }
+  const base::DictionaryValue* info = nullptr;
+  ExtensionPrefs::Get(browser_context_)
+      ->ReadPrefAsDictionary(extension_id, kPrefServiceWorkerRegistrationInfo,
+                             &info);
+  if (info != nullptr) {
+    info->GetString(kServiceWorkerVersion, &version_string);
+  }
+
   return base::Version(version_string);
 }
 
@@ -303,18 +309,26 @@
     const ExtensionId& extension_id,
     const base::Version& version) {
   DCHECK(version.IsValid());
-  auto info = std::make_unique<base::DictionaryValue>();
-  info->SetString(kServiceWorkerVersion, version.GetString());
-  ExtensionPrefs::Get(browser_context_)
-      ->UpdateExtensionPref(extension_id, kPrefServiceWorkerRegistrationInfo,
-                            std::move(info));
+  if (browser_context_->IsOffTheRecord()) {
+    off_the_record_registrations_[extension_id] = version;
+  } else {
+    auto info = std::make_unique<base::DictionaryValue>();
+    info->SetString(kServiceWorkerVersion, version.GetString());
+    ExtensionPrefs::Get(browser_context_)
+        ->UpdateExtensionPref(extension_id, kPrefServiceWorkerRegistrationInfo,
+                              std::move(info));
+  }
 }
 
 void ServiceWorkerTaskQueue::RemoveRegisteredServiceWorkerInfo(
     const ExtensionId& extension_id) {
-  ExtensionPrefs::Get(browser_context_)
-      ->UpdateExtensionPref(extension_id, kPrefServiceWorkerRegistrationInfo,
-                            nullptr);
+  if (browser_context_->IsOffTheRecord()) {
+    off_the_record_registrations_.erase(extension_id);
+  } else {
+    ExtensionPrefs::Get(browser_context_)
+        ->UpdateExtensionPref(extension_id, kPrefServiceWorkerRegistrationInfo,
+                              nullptr);
+  }
 }
 
 void ServiceWorkerTaskQueue::RunPendingTasksIfWorkerReady(
diff --git a/extensions/browser/service_worker_task_queue.h b/extensions/browser/service_worker_task_queue.h
index c147595..d487afd 100644
--- a/extensions/browser/service_worker_task_queue.h
+++ b/extensions/browser/service_worker_task_queue.h
@@ -7,6 +7,7 @@
 
 #include <map>
 #include <set>
+#include <unordered_map>
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
@@ -151,6 +152,13 @@
 
   content::BrowserContext* const browser_context_ = nullptr;
 
+  // A map of Service Worker registrations if this instance is for an
+  // off-the-record BrowserContext. These are stored in the ExtensionPrefs
+  // for a regular profile.
+  // TODO(crbug.com/939664): Make this better by passing in something that
+  // will manage storing and retrieving this data.
+  std::unordered_map<ExtensionId, base::Version> off_the_record_registrations_;
+
   base::WeakPtrFactory<ServiceWorkerTaskQueue> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerTaskQueue);
diff --git a/extensions/browser/service_worker_task_queue_factory.cc b/extensions/browser/service_worker_task_queue_factory.cc
index 82704f4..b62d2c1 100644
--- a/extensions/browser/service_worker_task_queue_factory.cc
+++ b/extensions/browser/service_worker_task_queue_factory.cc
@@ -6,7 +6,6 @@
 
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "extensions/browser/extension_registry_factory.h"
-#include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/service_worker_task_queue.h"
 
 using content::BrowserContext;
@@ -40,8 +39,7 @@
 
 BrowserContext* ServiceWorkerTaskQueueFactory::GetBrowserContextToUse(
     BrowserContext* context) const {
-  // Redirected in incognito.
-  return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
+  return context;
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/task_queue_util.cc b/extensions/browser/task_queue_util.cc
new file mode 100644
index 0000000..5ab77e6
--- /dev/null
+++ b/extensions/browser/task_queue_util.cc
@@ -0,0 +1,128 @@
+// 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/browser/task_queue_util.h"
+
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/lazy_background_task_queue.h"
+#include "extensions/browser/lazy_context_id.h"
+#include "extensions/browser/service_worker_task_queue.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_id.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+
+namespace extensions {
+
+namespace {
+
+// Get the ServiceWorkerTaskQueue instance for the BrowserContext.
+//
+ServiceWorkerTaskQueue* GetServiceWorkerTaskQueueForBrowserContext(
+    content::BrowserContext* browser_context,
+    bool is_split_mode) {
+  content::BrowserContext* context_to_use = browser_context;
+  // Incognito extensions in split mode use their own task queue, while those
+  // in spanning mode use the task queue of the original BrowserContext.
+  if (browser_context->IsOffTheRecord() && !is_split_mode) {
+    context_to_use =
+        ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context);
+  }
+  return ServiceWorkerTaskQueue::Get(context_to_use);
+}
+
+// Get the ServiceWorkerTaskQueue instance for the extension.
+//
+// Only call this for a SW-based extension.
+ServiceWorkerTaskQueue* GetServiceWorkerTaskQueueForExtension(
+    content::BrowserContext* browser_context,
+    const Extension* extension) {
+  DCHECK(BackgroundInfo::IsServiceWorkerBased(extension));
+  return GetServiceWorkerTaskQueueForBrowserContext(
+      browser_context, IncognitoInfo::IsSplitMode(extension));
+}
+
+// Get the ServiceWorkerTaskQueue instance for the extension ID.
+//
+// Only call this for a SW-based extension.
+ServiceWorkerTaskQueue* GetServiceWorkerTaskQueueForExtensionId(
+    content::BrowserContext* browser_context,
+    const ExtensionId& extension_id) {
+  // Incognito extensions in split mode use their own task queue, while those
+  // in spanning mode use the task queue of the original BrowserContext.
+  // This is an optimization to avoid looking up an Extension instance,
+  // since we only need it for the off-the-record case.
+  if (!browser_context->IsOffTheRecord()) {
+    return ServiceWorkerTaskQueue::Get(browser_context);
+  }
+
+  const Extension* extension = ExtensionRegistry::Get(browser_context)
+                                   ->enabled_extensions()
+                                   .GetByID(extension_id);
+  DCHECK(extension);
+  return GetServiceWorkerTaskQueueForExtension(browser_context, extension);
+}
+
+// Use a pointer-to-member function so we can use the same logic for the
+// activation and deactivation paths.
+using TaskQueueFunction = void (ServiceWorkerTaskQueue::*)(const Extension*);
+
+void DoTaskQueueFunction(content::BrowserContext* browser_context,
+                         const Extension* extension,
+                         TaskQueueFunction function) {
+  // This is only necessary for service worker-based extensions.
+  if (!BackgroundInfo::IsServiceWorkerBased(extension))
+    return;
+
+  ServiceWorkerTaskQueue* const queue =
+      ServiceWorkerTaskQueue::Get(browser_context);
+  (queue->*function)(extension);
+
+  // There is a separate task queue for the off-the-record context
+  // for any extension running in split mode.
+  if (!ExtensionsBrowserClient::Get()->HasOffTheRecordContext(
+          browser_context) ||
+      !IncognitoInfo::IsSplitMode(extension) ||
+      !ExtensionsBrowserClient::Get()->IsExtensionIncognitoEnabled(
+          extension->id(), browser_context)) {
+    return;
+  }
+
+  content::BrowserContext* off_the_record_context =
+      ExtensionsBrowserClient::Get()->GetOffTheRecordContext(browser_context);
+  DCHECK(off_the_record_context);
+  ServiceWorkerTaskQueue* const off_the_record_queue =
+      ServiceWorkerTaskQueue::Get(off_the_record_context);
+  (off_the_record_queue->*function)(extension);
+}
+
+}  // anonymous namespace
+
+LazyContextTaskQueue* GetTaskQueueForLazyContextId(
+    const LazyContextId& context_id) {
+  if (context_id.is_for_event_page())
+    return LazyBackgroundTaskQueue::Get(context_id.browser_context());
+
+  DCHECK(context_id.is_for_service_worker());
+  return GetServiceWorkerTaskQueueForExtensionId(context_id.browser_context(),
+                                                 context_id.extension_id());
+}
+
+void ActivateTaskQueueForExtension(content::BrowserContext* browser_context,
+                                   const Extension* extension) {
+  DCHECK(!browser_context->IsOffTheRecord());
+  DoTaskQueueFunction(browser_context, extension,
+                      &ServiceWorkerTaskQueue::ActivateExtension);
+}
+
+void DeactivateTaskQueueForExtension(content::BrowserContext* browser_context,
+                                     const Extension* extension) {
+  DCHECK(!browser_context->IsOffTheRecord());
+  DoTaskQueueFunction(browser_context, extension,
+                      &ServiceWorkerTaskQueue::DeactivateExtension);
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/task_queue_util.h b/extensions/browser/task_queue_util.h
new file mode 100644
index 0000000..1ebbec1
--- /dev/null
+++ b/extensions/browser/task_queue_util.h
@@ -0,0 +1,42 @@
+// 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_BROWSER_TASK_QUEUE_UTIL_H_
+#define EXTENSIONS_BROWSER_TASK_QUEUE_UTIL_H_
+
+namespace content {
+class BrowserContext;
+}  // namespace content
+
+namespace extensions {
+class Extension;
+class LazyContextId;
+class LazyContextTaskQueue;
+
+// Determines the correct task queue for |context_id|.
+LazyContextTaskQueue* GetTaskQueueForLazyContextId(
+    const LazyContextId& context_id);
+
+// Activates the service worker task queue for |browser_context| and
+// |extension|. This must be called only once when an extension is loaded
+// and before queueing any tasks.
+//
+// This is called for all extensions, not just for service worker-based
+// ones.
+void ActivateTaskQueueForExtension(content::BrowserContext* browser_context,
+                                   const Extension* extension);
+
+// Deactivates the service worker task queue for |browser_context| and
+// |extension|. This should be called when the extension is unloaded. Once
+// it completes, it's safe to call ActivateTaskQueueForExtension if the
+// extension is reloaded.
+//
+// This is called for all extensions, not just for service worker-based
+// ones.
+void DeactivateTaskQueueForExtension(content::BrowserContext* browser_context,
+                                     const Extension* extension);
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_TASK_QUEUE_UTIL_H_
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc
index 0c13828..81d7cb42 100644
--- a/extensions/common/manifest_constants.cc
+++ b/extensions/common/manifest_constants.cc
@@ -158,7 +158,6 @@
 const char kSpellcheckDictionaryPath[] = "dictionary_path";
 const char kStorageManagedSchema[] = "storage.managed_schema";
 const char kSuggestedKey[] = "suggested_key";
-const char kSynthesizeExtensionAction[] = "_synthesize_extension_action";
 const char kSystemIndicator[] = "system_indicator";
 const char kTheme[] = "theme";
 const char kThemeColors[] = "colors";
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h
index 4f4d72a..09dffe1 100644
--- a/extensions/common/manifest_constants.h
+++ b/extensions/common/manifest_constants.h
@@ -159,7 +159,6 @@
 extern const char kSpellcheckDictionaryPath[];
 extern const char kStorageManagedSchema[];
 extern const char kSuggestedKey[];
-extern const char kSynthesizeExtensionAction[];
 extern const char kSystemIndicator[];
 extern const char kTheme[];
 extern const char kThemeColors[];
diff --git a/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc b/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
index b905288..4c459e41 100644
--- a/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
+++ b/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
@@ -9,6 +9,7 @@
 #include <algorithm>
 #include <unordered_map>
 
+#include "gpu/command_buffer/service/context_state.h"
 #include "gpu/command_buffer/service/decoder_context.h"
 #include "gpu/command_buffer/service/gl_utils.h"
 #include "gpu/command_buffer/service/gles2_cmd_copy_tex_image.h"
@@ -1512,6 +1513,8 @@
     }
 #endif
 
+    if (decoder->GetFeatureInfo()->IsWebGL2OrES3OrHigherContext())
+      glBindSampler(0, 0);
     glUniform1i(info->sampler_handle, 0);
 
     glBindTexture(source_target, source_id);
@@ -1545,6 +1548,8 @@
     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
   }
 
+  if (decoder->GetFeatureInfo()->IsWebGL2OrES3OrHigherContext())
+    decoder->GetContextState()->RestoreSamplerBinding(0, nullptr);
   decoder->RestoreAllAttributes();
   decoder->RestoreTextureState(source_id);
   decoder->RestoreTextureState(dest_id);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index de404bd..e44125a 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -7571,6 +7571,14 @@
         Sampler* sampler =
             state_.sampler_units[state_.active_texture_unit].get();
         *params = sampler ? sampler->client_id() : 0;
+
+#if DCHECK_IS_ON()
+        if (sampler) {
+          GLint bound_sampler = 0;
+          glGetIntegerv(GL_SAMPLER_BINDING, &bound_sampler);
+          DCHECK_EQ(static_cast<GLuint>(bound_sampler), sampler->service_id());
+        }
+#endif
       }
       return true;
     case GL_TRANSFORM_FEEDBACK_BINDING:
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
index 62042a6..a3147bd1 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
@@ -1648,6 +1648,12 @@
   DoCreateSampler(kClientID, kServiceID);
   DoBindSampler(kUnit, kClientID, kServiceID);
 
+#if DCHECK_IS_ON()
+  EXPECT_CALL(*gl_, GetIntegerv(GL_SAMPLER_BINDING, _))
+      .WillOnce(testing::SetArgumentPointee<1>(kServiceID))
+      .RetiresOnSaturation();
+#endif
+
   EXPECT_CALL(*gl_, GetError())
       .WillOnce(Return(GL_NO_ERROR))
       .WillOnce(Return(GL_NO_ERROR))
diff --git a/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc b/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
index 84c9c532..ada152e 100644
--- a/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
+++ b/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
@@ -1269,6 +1269,44 @@
   EXPECT_TRUE(GL_NO_ERROR == glGetError());
 }
 
+TEST_P(GLCopyTextureCHROMIUMES3Test, SamplerStatePreserved) {
+  if (ShouldSkipTest())
+    return;
+
+  CopyType copy_type = GetParam();
+  // Setup the texture used for the extension invocation.
+  uint8_t pixels[1 * 4] = {255u, 0u, 0u, 255u};
+  CreateAndBindDestinationTextureAndFBO(GL_TEXTURE_2D);
+  glBindTexture(GL_TEXTURE_2D, textures_[0]);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+               pixels);
+
+  glBindTexture(GL_TEXTURE_2D, textures_[1]);
+  if (copy_type == TexSubImage) {
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 nullptr);
+  }
+
+  GLuint sampler_id;
+  glGenSamplers(1, &sampler_id);
+  glBindSampler(0, sampler_id);
+
+  if (copy_type == TexImage) {
+    glCopyTextureCHROMIUM(textures_[0], 0, GL_TEXTURE_2D, textures_[1], 0,
+                          GL_RGBA, GL_UNSIGNED_BYTE, false, false, true);
+  } else {
+    glCopySubTextureCHROMIUM(textures_[0], 0, GL_TEXTURE_2D, textures_[1], 0, 0,
+                             0, 0, 0, 1, 1, false, false, true);
+  }
+  EXPECT_TRUE(GL_NO_ERROR == glGetError());
+
+  GLint bound_sampler = 0;
+  glGetIntegerv(GL_SAMPLER_BINDING, &bound_sampler);
+  EXPECT_EQ(sampler_id, static_cast<GLuint>(bound_sampler));
+
+  glDeleteSamplers(1, &sampler_id);
+}
+
 // Verify that invocation of the extension does not modify the bound
 // texture state.
 TEST_P(GLCopyTextureCHROMIUMTest, TextureStatePreserved) {
diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc
index 20c07cfe..ec9fe28 100644
--- a/gpu/ipc/service/gpu_init.cc
+++ b/gpu/ipc/service/gpu_init.cc
@@ -122,6 +122,19 @@
 }
 #endif  // OS_LINUX && !OS_CHROMEOS && !IS_CHROMECAST
 
+class GpuWatchdogInit {
+ public:
+  GpuWatchdogInit() = default;
+  ~GpuWatchdogInit() {
+    if (watchdog_ptr_)
+      watchdog_ptr_->OnInitComplete();
+  }
+
+  void SetGpuWatchdogPtr(gpu::GpuWatchdogThread* ptr) { watchdog_ptr_ = ptr; }
+
+ private:
+  gpu::GpuWatchdogThread* watchdog_ptr_ = nullptr;
+};
 }  // namespace
 
 GpuInit::GpuInit() = default;
@@ -191,6 +204,10 @@
   enable_watchdog = false;
 #endif
 
+  // watchdog_init will call watchdog OnInitComplete() at the end of this
+  // function.
+  GpuWatchdogInit watchdog_init;
+
   bool delayed_watchdog_enable = false;
 
 #if defined(OS_CHROMEOS)
@@ -205,6 +222,7 @@
     if (base::FeatureList::IsEnabled(features::kGpuWatchdogV2)) {
       watchdog_thread_ = gpu::GpuWatchdogThreadImplV2::Create(
           gpu_preferences_.watchdog_starts_backgrounded);
+      watchdog_init.SetGpuWatchdogPtr(watchdog_thread_.get());
     } else {
       watchdog_thread_ = gpu::GpuWatchdogThreadImplV1::Create(
           gpu_preferences_.watchdog_starts_backgrounded);
@@ -411,10 +429,12 @@
     if (watchdog_thread_)
       watchdog_thread_->Stop();
     watchdog_thread_ = nullptr;
+    watchdog_init.SetGpuWatchdogPtr(nullptr);
   } else if (enable_watchdog && delayed_watchdog_enable) {
     if (base::FeatureList::IsEnabled(features::kGpuWatchdogV2)) {
       watchdog_thread_ = gpu::GpuWatchdogThreadImplV2::Create(
           gpu_preferences_.watchdog_starts_backgrounded);
+      watchdog_init.SetGpuWatchdogPtr(watchdog_thread_.get());
     } else {
       watchdog_thread_ = gpu::GpuWatchdogThreadImplV1::Create(
           gpu_preferences_.watchdog_starts_backgrounded);
@@ -430,11 +450,6 @@
   UMA_HISTOGRAM_BOOLEAN("GPU.Sandbox.InitializedSuccessfully",
                         gpu_info_.sandboxed);
 
-  // Notify the gpu watchdog that the gpu init has completed So the watchdog
-  // can be disarmed.
-  if (watchdog_thread_)
-    watchdog_thread_->OnInitComplete();
-
   init_successful_ = true;
 #if defined(USE_OZONE)
   ui::OzonePlatform::GetInstance()->AfterSandboxEntry();
@@ -451,6 +466,9 @@
       std::move(supported_buffer_formats_for_texturing);
 #endif
 
+  if (!watchdog_thread_)
+    watchdog_init.SetGpuWatchdogPtr(nullptr);
+
   return true;
 }
 
diff --git a/gpu/ipc/service/gpu_watchdog_thread_v2.cc b/gpu/ipc/service/gpu_watchdog_thread_v2.cc
index f754418..bee79427 100644
--- a/gpu/ipc/service/gpu_watchdog_thread_v2.cc
+++ b/gpu/ipc/service/gpu_watchdog_thread_v2.cc
@@ -23,7 +23,6 @@
       is_test_mode_(is_test_mode),
       watched_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
   base::MessageLoopCurrent::Get()->AddTaskObserver(this);
-  weak_ptr_ = weak_factory_.GetWeakPtr();
   Arm();
 }
 
@@ -90,8 +89,14 @@
   Disarm();
 }
 
+// Running on the watchdog thread.
+// On Linux, Init() will be called twice for Sandbox Initialization. The
+// watchdog is stopped and then restarted in StartSandboxLinux(). Everything
+// should be the same and continue after the second init().
 void GpuWatchdogThreadImplV2::Init() {
   last_arm_disarm_counter_ = base::subtle::NoBarrier_Load(&arm_disarm_counter_);
+  // Get and Invalidate weak_ptr should be done on the watchdog thread only.
+  weak_ptr_ = weak_factory_.GetWeakPtr();
   task_runner()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce(&GpuWatchdogThreadImplV2::OnWatchdogTimeout, weak_ptr_),
@@ -102,6 +107,7 @@
   GpuWatchdogHistogram(GpuWatchdogThreadEvent::kGpuWatchdogStart);
 }
 
+// Running on the watchdog thread.
 void GpuWatchdogThreadImplV2::CleanUp() {
   weak_factory_.InvalidateWeakPtrs();
 }
@@ -120,6 +126,7 @@
   Disarm();
 }
 
+// Running on the watchdog thread.
 void GpuWatchdogThreadImplV2::OnSuspend() {
   in_power_suspension_ = true;
   // Revoke any pending watchdog timeout task
@@ -127,6 +134,7 @@
   suspend_timeticks_ = base::TimeTicks::Now();
 }
 
+// Running on the watchdog thread.
 void GpuWatchdogThreadImplV2::OnResume() {
   in_power_suspension_ = false;
   RestartWatchdogTimeoutTask();
@@ -155,6 +163,7 @@
   foregrounded_timeticks_ = base::TimeTicks::Now();
 }
 
+// Running on the watchdog thread.
 void GpuWatchdogThreadImplV2::RestartWatchdogTimeoutTask() {
   if (!is_backgrounded_ && !in_power_suspension_) {
     // Make the timeout twice long. The system/gpu might be very slow right
@@ -191,6 +200,7 @@
   DCHECK(base::subtle::NoBarrier_Load(&arm_disarm_counter_) & 1);
 }
 
+// Running on the watchdog thread.
 void GpuWatchdogThreadImplV2::OnWatchdogTimeout() {
   DCHECK(!is_backgrounded_);
   DCHECK(!in_power_suspension_);
diff --git a/ios/chrome/browser/find_in_page/find_tab_helper_unittest.mm b/ios/chrome/browser/find_in_page/find_tab_helper_unittest.mm
index cd61085b..f6a16c4 100644
--- a/ios/chrome/browser/find_in_page/find_tab_helper_unittest.mm
+++ b/ios/chrome/browser/find_in_page/find_tab_helper_unittest.mm
@@ -8,6 +8,7 @@
 #include "base/run_loop.h"
 #import "base/test/ios/wait_util.h"
 #include "base/test/metrics/user_action_tester.h"
+#import "ios/chrome/browser/find_in_page/features.h"
 #import "ios/chrome/browser/find_in_page/find_in_page_model.h"
 #import "ios/chrome/browser/web/chrome_web_test.h"
 #import "ios/web/public/test/fakes/test_web_state.h"
@@ -68,6 +69,11 @@
 
 // Tests the StartFinding(), ContinueFinding(), and StopFinding() methods.
 TEST_F(FindTabHelperTest, FindInPage) {
+  // Tests should not run if Find in Page iFrame feature flag is on. If it is,
+  // FindinPageResponseDelegate is used by FindinPageController to respond.
+  if (base::FeatureList::IsEnabled(kFindInPageiFrame)) {
+    return;
+  }
   LoadTestHtml(5);
   auto* helper = FindTabHelper::FromWebState(web_state());
   ASSERT_TRUE(helper);
@@ -132,6 +138,11 @@
 
 // Tests that ContinueFinding() wraps around when it reaches the last match.
 TEST_F(FindTabHelperTest, ContinueFindingWrapsAround) {
+  // Tests should not run if Find in Page iFrame feature flag is on. If it is,
+  // FindinPageResponseDelegate is used by FindinPageController to respond.
+  if (base::FeatureList::IsEnabled(kFindInPageiFrame)) {
+    return;
+  }
   LoadTestHtml(2);
   auto* helper = FindTabHelper::FromWebState(web_state());
   ASSERT_TRUE(helper);
@@ -183,6 +194,11 @@
 // Tests that the FindInPageModel returned by GetFindResults() is updated to
 // reflect the results of the latest find operation.
 TEST_F(FindTabHelperTest, GetFindResults) {
+  // Tests should not run if Find in Page iFrame feature flag is on. If it is,
+  // FindinPageResponseDelegate is used by FindinPageController to respond.
+  if (base::FeatureList::IsEnabled(kFindInPageiFrame)) {
+    return;
+  }
   LoadTestHtml(2);
   auto* helper = FindTabHelper::FromWebState(web_state());
   ASSERT_TRUE(helper);
@@ -228,6 +244,11 @@
 
 // Tests the IsFindUIActive() getter and setter.
 TEST_F(FindTabHelperTest, IsFindUIActive) {
+  // Tests should not run if Find in Page iFrame feature flag is on. If it is,
+  // FindinPageResponseDelegate is used by FindinPageController to respond.
+  if (base::FeatureList::IsEnabled(kFindInPageiFrame)) {
+    return;
+  }
   auto* helper = FindTabHelper::FromWebState(web_state());
 
   helper->SetFindUIActive(true);
@@ -239,6 +260,11 @@
 
 // Tests that IsFindUIActive() is reset to false on page navigation.
 TEST_F(FindTabHelperTest, FindUIActiveIsResetOnPageNavigation) {
+  // Tests should not run if Find in Page iFrame feature flag is on. If it is,
+  // FindinPageResponseDelegate is used by FindinPageController to respond.
+  if (base::FeatureList::IsEnabled(kFindInPageiFrame)) {
+    return;
+  }
   LoadTestHtml(2);
   auto* helper = FindTabHelper::FromWebState(web_state());
   helper->SetFindUIActive(true);
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 2e331a09..d6d1f7c 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -324,9 +324,11 @@
 const base::Feature kVaapiVP9Encoder{"VaapiVP9Encoder",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
+#if defined(ARCH_CPU_X86_FAMILY) && defined(OS_CHROMEOS)
 // Enable VP9 k-SVC decoding with HW decoder for webrtc use case on ChromeOS.
 const base::Feature kVp9kSVCHWDecoding{"Vp9kSVCHWDecoding",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
+#endif  //  defined(ARCH_CPU_X86_FAMILY) && defined(OS_CHROMEOS)
 
 // Inform video blitter of video color space.
 const base::Feature kVideoBlitColorAccuracy{"video-blit-color-accuracy",
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 35eaeef..8dc7d05 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -139,11 +139,14 @@
 MEDIA_EXPORT extern const base::Feature kVaapiLowPowerEncoder;
 MEDIA_EXPORT extern const base::Feature kVaapiVP8Encoder;
 MEDIA_EXPORT extern const base::Feature kVaapiVP9Encoder;
-MEDIA_EXPORT extern const base::Feature kVp9kSVCHWDecoding;
 MEDIA_EXPORT extern const base::Feature kVideoBlitColorAccuracy;
 MEDIA_EXPORT extern const base::Feature kWidevineAv1;
 MEDIA_EXPORT extern const base::Feature kWidevineAv1ForceSupportForTesting;
 
+#if defined(ARCH_CPU_X86_FAMILY) && defined(OS_CHROMEOS)
+MEDIA_EXPORT extern const base::Feature kVp9kSVCHWDecoding;
+#endif  // defined(ARCH_CPU_X86_FAMILY) && defined(OS_CHROMEOS)
+
 #if defined(OS_ANDROID)
 MEDIA_EXPORT extern const base::Feature kMediaControlsExpandGesture;
 MEDIA_EXPORT extern const base::Feature kMediaDrmPersistentLicense;
diff --git a/media/capabilities/video_decode_stats_db_impl.cc b/media/capabilities/video_decode_stats_db_impl.cc
index 1382142..1a69da9f 100644
--- a/media/capabilities/video_decode_stats_db_impl.cc
+++ b/media/capabilities/video_decode_stats_db_impl.cc
@@ -8,6 +8,7 @@
 #include <tuple>
 
 #include "base/bind.h"
+#include "base/debug/alias.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
@@ -234,9 +235,20 @@
     stats_proto.reset(new DecodeStatsProto());
   }
 
+  // Debug alias the various counts so we can get them in dumps to catch
+  // lingering crashes in http://crbug.com/982009
   uint64_t old_frames_decoded = stats_proto->frames_decoded();
   uint64_t old_frames_dropped = stats_proto->frames_dropped();
   uint64_t old_frames_power_efficient = stats_proto->frames_power_efficient();
+  uint64_t new_frames_decoded = new_entry.frames_decoded;
+  uint64_t new_frames_dropped = new_entry.frames_dropped;
+  uint64_t new_frames_power_efficient = new_entry.frames_power_efficient;
+  base::debug::Alias(&old_frames_decoded);
+  base::debug::Alias(&old_frames_dropped);
+  base::debug::Alias(&old_frames_power_efficient);
+  base::debug::Alias(&new_frames_decoded);
+  base::debug::Alias(&new_frames_dropped);
+  base::debug::Alias(&new_frames_power_efficient);
 
   const uint64_t kMaxFramesPerBuffer = GetMaxFramesPerBuffer();
   DCHECK_GT(kMaxFramesPerBuffer, 0UL);
@@ -269,6 +281,14 @@
     double agg_efficient_ratio = fill_ratio * new_entry_efficient_ratio +
                                  (1 - fill_ratio) * old_efficient_ratio;
 
+    // Debug alias the various counts so we can get them in dumps to catch
+    // lingering crashes in http://crbug.com/982009
+    base::debug::Alias(&fill_ratio);
+    base::debug::Alias(&old_dropped_ratio);
+    base::debug::Alias(&old_efficient_ratio);
+    base::debug::Alias(&agg_dropped_ratio);
+    base::debug::Alias(&agg_efficient_ratio);
+
     stats_proto->set_frames_decoded(kMaxFramesPerBuffer);
     stats_proto->set_frames_dropped(
         std::round(agg_dropped_ratio * kMaxFramesPerBuffer));
diff --git a/media/gpu/vp9_decoder.cc b/media/gpu/vp9_decoder.cc
index 68188ea4..59668bd 100644
--- a/media/gpu/vp9_decoder.cc
+++ b/media/gpu/vp9_decoder.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "base/logging.h"
+#include "build/build_config.h"
 #include "media/base/limits.h"
 #include "media/base/media_switches.h"
 #include "media/gpu/vp9_decoder.h"
@@ -18,6 +19,7 @@
 namespace {
 std::vector<uint32_t> GetSpatialLayerFrameSize(
     const DecoderBuffer& decoder_buffer) {
+#if defined(ARCH_CPU_X86_FAMILY) && defined(OS_CHROMEOS)
   const uint32_t* cue_data =
       reinterpret_cast<const uint32_t*>(decoder_buffer.side_data());
   if (!cue_data)
@@ -33,6 +35,8 @@
     return {};
   }
   return std::vector<uint32_t>(cue_data, cue_data + num_of_layers);
+#endif  // defined(ARCH_CPU_X86_FAMILY) && defined(OS_CHROMEOS)
+  return {};
 }
 }  // namespace
 
diff --git a/media/learning/common/BUILD.gn b/media/learning/common/BUILD.gn
index 4e208d3..b86b09c 100644
--- a/media/learning/common/BUILD.gn
+++ b/media/learning/common/BUILD.gn
@@ -23,6 +23,8 @@
   defines = [ "IS_LEARNING_COMMON_IMPL" ]
 
   sources = [
+    "feature_dictionary.cc",
+    "feature_dictionary.h",
     "feature_library.cc",
     "feature_library.h",
     "labelled_example.cc",
@@ -45,6 +47,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "feature_dictionary_unittest.cc",
     "labelled_example_unittest.cc",
     "value_unittest.cc",
   ]
diff --git a/media/learning/common/feature_dictionary.cc b/media/learning/common/feature_dictionary.cc
new file mode 100644
index 0000000..66cf608
--- /dev/null
+++ b/media/learning/common/feature_dictionary.cc
@@ -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.
+
+#include "media/learning/common/feature_dictionary.h"
+
+namespace media {
+namespace learning {
+
+FeatureDictionary::FeatureDictionary() = default;
+
+FeatureDictionary::~FeatureDictionary() = default;
+
+void FeatureDictionary::Lookup(const LearningTask& task,
+                               FeatureVector* features) {
+  const size_t num_features = task.feature_descriptions.size();
+
+  if (features->size() < num_features)
+    features->resize(num_features);
+
+  for (size_t i = 0; i < num_features; i++) {
+    const auto& name = task.feature_descriptions[i].name;
+    auto entry = dictionary_.find(name);
+    if (entry == dictionary_.end())
+      continue;
+
+    // |name| appears in the dictionary, so add its value to |features|.
+    (*features)[i] = entry->second;
+  }
+}
+
+void FeatureDictionary::Add(const std::string& name,
+                            const FeatureValue& value) {
+  dictionary_[name] = value;
+}
+
+}  // namespace learning
+}  // namespace media
diff --git a/media/learning/common/feature_dictionary.h b/media/learning/common/feature_dictionary.h
new file mode 100644
index 0000000..1498045
--- /dev/null
+++ b/media/learning/common/feature_dictionary.h
@@ -0,0 +1,52 @@
+// 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 MEDIA_LEARNING_COMMON_FEATURE_DICTIONARY_H_
+#define MEDIA_LEARNING_COMMON_FEATURE_DICTIONARY_H_
+
+#include <map>
+#include <string>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "media/learning/common/labelled_example.h"
+#include "media/learning/common/learning_task.h"
+
+namespace media {
+namespace learning {
+
+// Dictionary of feature name => value pairs.
+//
+// This is useful if one simply wants to snapshot some features, and apply them
+// to more than one task without recomputing anything.
+//
+// While it's not required, FeatureLibrary is useful to provide the descriptions
+// that a FeatureDictionary will provide, so that the LearningTask and the
+// dictionary agree on names.
+class COMPONENT_EXPORT(LEARNING_COMMON) FeatureDictionary {
+ public:
+  // [feature name] => snapshotted value.
+  using Dictionary = std::map<std::string, FeatureValue>;
+
+  FeatureDictionary();
+  ~FeatureDictionary();
+
+  // Add features for |task| to |features| from our dictionary.  Features that
+  // aren't present in the dictionary will be ignored.  |features| will be
+  // expanded if needed to match |task|.
+  void Lookup(const LearningTask& task, FeatureVector* features);
+
+  // Add |name| to the dictionary with value |value|.
+  void Add(const std::string& name, const FeatureValue& value);
+
+ private:
+  Dictionary dictionary_;
+
+  DISALLOW_COPY_AND_ASSIGN(FeatureDictionary);
+};
+
+}  // namespace learning
+}  // namespace media
+
+#endif  // MEDIA_LEARNING_COMMON_FEATURE_DICTIONARY_H_
diff --git a/media/learning/common/feature_dictionary_unittest.cc b/media/learning/common/feature_dictionary_unittest.cc
new file mode 100644
index 0000000..9d4a00f
--- /dev/null
+++ b/media/learning/common/feature_dictionary_unittest.cc
@@ -0,0 +1,45 @@
+// 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 "media/learning/common/feature_dictionary.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+namespace learning {
+
+class FeatureDictionaryTest : public testing::Test {};
+
+TEST_F(FeatureDictionaryTest, FillsInFeatures) {
+  FeatureDictionary dict;
+  const std::string feature_name_1("feature 1");
+  const FeatureValue feature_value_1("feature value 1");
+
+  const std::string feature_name_2("feature 2");
+  const FeatureValue feature_value_2("feature value 2");
+
+  const std::string feature_name_3("feature 3");
+  const FeatureValue feature_value_3("feature value 3");
+
+  dict.Add(feature_name_1, feature_value_1);
+  dict.Add(feature_name_2, feature_value_2);
+  dict.Add(feature_name_3, feature_value_3);
+
+  LearningTask task;
+  task.feature_descriptions.push_back({"some other feature"});
+  task.feature_descriptions.push_back({feature_name_3});
+  task.feature_descriptions.push_back({feature_name_1});
+
+  FeatureVector features;
+  features.push_back(FeatureValue(0));  // some other feature
+
+  dict.Lookup(task, &features);
+  EXPECT_EQ(features.size(), 3u);
+  EXPECT_EQ(features[0], FeatureValue(0));
+  EXPECT_EQ(features[1], feature_value_3);
+  EXPECT_EQ(features[2], feature_value_1);
+}
+
+}  // namespace learning
+}  // namespace media
diff --git a/media/midi/midi_service.mojom b/media/midi/midi_service.mojom
index ab49b73..bc32b820 100644
--- a/media/midi/midi_service.mojom
+++ b/media/midi/midi_service.mojom
@@ -60,7 +60,8 @@
 // Interface used by the renderer to start a MIDI session in the browser.
 interface MidiSessionProvider {
   // Start session to access MIDI hardware.
-  StartSession(MidiSession& request, MidiSessionClient client);
+  StartSession(pending_receiver<MidiSession> receiver,
+               pending_remote<MidiSessionClient> client);
 };
 
 // Represents an active MIDI session.
diff --git a/mojo/public/cpp/system/README.md b/mojo/public/cpp/system/README.md
index 4db0d2c..ff1ea91 100644
--- a/mojo/public/cpp/system/README.md
+++ b/mojo/public/cpp/system/README.md
@@ -486,15 +486,15 @@
 // Process B
 auto invitation = mojo::IncomingInvitation::Accept(...);
 auto pipe = invitation->ExtractMessagePipe("x");
-foo::mojom::BarPtr bar(foo::mojom::BarPtrInfo(std::move(pipe), 0));
+mojo::Remote<foo::mojom::Bar> bar(mojo::PendingRemote<foo::mojom::Bar>(std::move(pipe), 0));
 
 // Will asynchronously invoke bar_impl.DoSomething() in process A.
 bar->DoSomething();
 ```
 
 And just to be sure, the usage here could be reversed: the invitation sender
-could just as well treat its pipe endpoint as a `BarPtr` while the receiver
-treats theirs as a `BarRequest` to be bound.
+could just as well treat its pipe endpoint as a `Remote<Bar>` while the receiver
+treats theirs as a `PendingReceiver<Bar>` to be bound.
 
 ### Process Networks
 Accepting an invitation admits the accepting process into the sender's connected
diff --git a/mojo/public/js/README.md b/mojo/public/js/README.md
index 1e426eae..d2b9080 100644
--- a/mojo/public/js/README.md
+++ b/mojo/public/js/README.md
@@ -1,6 +1,12 @@
 # Mojo JavaScript Bindings API
 This document is a subset of the [Mojo documentation](/mojo/README.md).
 
+*** note
+TODO(crbug.com/912327): this document mentions deprecated JavaScript bindings.
+
+We need to update it to describe the new bindings (bindings_lite).
+***
+
 [TOC]
 
 ## Getting Started
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index e425524..0e29f0f 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -7822,7 +7822,6 @@
     { "name": "sampoznay.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sandviks.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sardegnatirocini.it", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "sat4all.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "schont.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "schwarzkopfforyou.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "serveradminz.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -8730,7 +8729,6 @@
     { "name": "e-teacher.pl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "e3amn2l.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "e3kids.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "eagleridgecampground.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "earga.sm", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "easyconstat.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "easycosmetic.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -10025,7 +10023,6 @@
     { "name": "slowfood.es", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "slxh.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "slxh.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "smallplanet.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "smart-ov.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "smartftp.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "smarthdd.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -10351,7 +10348,6 @@
     { "name": "vdrpro.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "venturepro.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "vetdnacenter.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "victorjacobs.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "videogamesartwork.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "videotogel.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "vidid.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -19703,7 +19699,6 @@
     { "name": "beersandco.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "chatxtutti.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "chriswbarry.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "ccl-sti.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "chocolatesandhealth.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "canlidoviz.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "chatt-gratis.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -21939,7 +21934,6 @@
     { "name": "bloomzoomy.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "boatme.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bocamo.it", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "bookluk.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "booq.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "boozinyan.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bottaerisposta.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -22972,7 +22966,6 @@
     { "name": "acme.beer", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "acrepairdrippingsprings.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "acs-chantal.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "actgruppe.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "actiontowingroundrock.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "active-escape.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "addicional.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -26733,7 +26726,6 @@
     { "name": "avspot.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "andrea-m.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "audiolibri.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "auvernet.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "autos-retro-plaisir.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "360live.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "b-boom.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -28084,7 +28076,6 @@
     { "name": "kuruppa.xyz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "kolizaskrap.bg", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "kovspace.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "knight-industries.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "kpop.re", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "labrasaq8.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "koalapress.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -35409,7 +35400,6 @@
     { "name": "spasicilia.it", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "splarty.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sportnesia.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "ssl.doctor", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "steborio.pw", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "stephenj.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "stevenz.science", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -40145,7 +40135,6 @@
     { "name": "roboth.am", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rodarion.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "roelbazuin.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "roligprylar.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rollercoasteritalia.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rollingbarge.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rook-playz.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -45898,7 +45887,6 @@
     { "name": "cheapcaribbean.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "chenkun.pro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "childstats.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "christopherandcharlotte.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ci-suite.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cinay.pw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cineplex.my", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -49319,7 +49307,6 @@
     { "name": "potenzmittelblog.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "privelust.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "programistka.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "prophiler.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "prosurveillancegear.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "prowebcenter.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pru.com.hk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -50257,7 +50244,6 @@
     { "name": "wumai.cloud", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wundernas.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "wuppertal-2018.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "wuppertaler-kurrende.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "www.org.gg", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xants.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xdawn.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -60811,7 +60797,6 @@
     { "name": "pearlsonly.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pearlsonly.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pearlsonly.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "peatsbeast.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "peckcloths.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "peepsfoundation.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pencil2d.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62080,7 +62065,6 @@
     { "name": "frsnpwr.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "fruityfitness.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gakdigital.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "gamerwares.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "garbagedisposalguides.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "gestsal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "giftlist.guru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62095,7 +62079,6 @@
     { "name": "hawawa.kr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "healthyrecharge.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "heikohessenkemper.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "helpwithinsomnia.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hokung.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hostco.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "hsg-kreuzberg.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62317,7 +62300,6 @@
     { "name": "tryplo.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "turingmind.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "tusmedicamentos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "twoleftsticks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "u-chan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "u29dc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "uitvaartvrouwenfriesland.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62468,7 +62450,6 @@
     { "name": "foroaranda.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "foxbnc.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "foyer-laique-segre.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "frankfurt-coworking.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "frietzombie.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "funkfernbedienung-industrie.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "funknotaus.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62512,7 +62493,6 @@
     { "name": "khg-orchester.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kibbesfusion.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kindesfreude.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "krrn.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kt3i.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kys.host", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "laceysfarm.ie", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -70177,7 +70157,6 @@
     { "name": "joinhahobby.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "joshjanzen.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jttech.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "jw1.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "k6729.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "k6957.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "k6957.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -70668,7 +70647,6 @@
     { "name": "dimomaint-sav.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "dimosoftware.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "disabled-world.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "discountpokale.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "discountpokale.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "distortmotion.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "divisasexpress.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -74247,7 +74225,6 @@
     { "name": "zd267.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zd270.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zd2727.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "zd273.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zd275.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zd276.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zd279.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -76709,7 +76686,6 @@
     { "name": "ks0668.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ks257.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ks318.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "ks600.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ks641.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ks8.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kst-service.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -77028,7 +77004,6 @@
     { "name": "omretreats.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "one-news.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "onlineautodealered.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "onlinecasinolisboa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "onurerhan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ooo-santal.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "operanavigation.ro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -78039,27 +78014,7 @@
     { "name": "x77ww.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "x9015.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "x9701.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98d.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98e.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98f.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98g.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98h.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98i.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98k.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98l.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98m.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98n.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98o.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98p.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98q.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98s.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "x98t.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98v.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98w.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98y.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x98z.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x993.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "x998.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xab123.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xab199.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xab4.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -78086,17 +78041,11 @@
     { "name": "xn--bcherbestseller-zvb.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xn--eebao6b.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xn--schcke-yxa.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "xpj000444.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "xpj000555.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "xpj000666.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xpj567088.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xpj567288.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xpj567388.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xpj567888.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "xpj678678.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xpjai.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "xpjbeting.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "xpjcs.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xucha.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "xx6396.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "y09a.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -82427,6 +82376,391 @@
     { "name": "zupzup.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zz606.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "bitbucket.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "00rfb.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "1lc1.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "1lc33.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "1lc44.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "220control.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "33weishang.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "369018.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "50milli.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "5eki.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "690938.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "91fldz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "941988.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "abbeyok.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "abiscrane.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "abminiplex.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "accademiapugilistica.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "acuaticos.top", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "acumed-diagnostic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "addo-addo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "aievaluare.ro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "akeenshort.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "alpine-holiday.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "amethystbodyart.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "andrey.red", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "angkasa.net.id", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "angular-software.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "animalconnect.org.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "antanavagefarbiarz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "aphelis.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "apod-portal-daily.azurewebsites.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "appsimplex.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "appub.co.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "arabic-shirts.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "autolider.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "autorijschoolstorm.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "baneh-academic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "banglarfont.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bankruptcy.ky", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "basebalance.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bauingenieur24.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "become-lucky.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "berksabstract.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bestremote.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "billgradywebdesign.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "biocrafting.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "biofattorietoscane.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bisq.network", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bog8.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bombayfashionclub.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "boschsplit.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bread.red", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "buyplore.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cambiemosjuegos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "camshowhive.to", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "camshowplace.to", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "canobag.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "carding.team", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cas-chauxdefonds.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "celcelulares.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "chalupalokovka.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "chefkoch.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cheraghestan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "chhlin.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cirruslab.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cjenni.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "clearspringinsurance.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "come2cook.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "compliantbusinessprocessing.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "concordiagaming.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "creandoydesarrollando.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "crossformer.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cubsbestteaminbaseball.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cvtshop.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "d88c.vip", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dapperdom.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "davidschubert.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dbw678.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "deanandnatalia.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "deinsparen24.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "desanta.top", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "designs.codes", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "desktopgoldlink.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dewitteprins.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "diabhal-staff.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "digiepoxypaint.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "digitalframe.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "digitaltrust.ae", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "disruptiveadvertising.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dnssecandipv6.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "doctorcalefon.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dotnetdocs.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dsbmradio.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dsdesign.lt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dse-assessments.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "eciso.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ecmeshltd.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ecrownoffire.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "eldercare.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "elektrische-zahnbuerste24.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "enrico-caruso.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "enzoic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "era.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "estela-artes.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "etalktome.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "europainchemnitz.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "europastudien-chemnitz.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "europeanstudies-chemnitz.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "everyvid.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "evilla.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fairbairnrealty.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fairgreenlimited.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "falasteenjobs.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fashworldtrends.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fasthost.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fd020.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "festx.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fgafsaneh.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fish4dogs.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "florida-immigration.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fluidpicturesinc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "flying-dudes.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "forexarby.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fortdodgeradio.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "franklinmagic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "freefinancialhelp.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "freshbooks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "furgetmeknot.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "futuristacademy.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "g2jp.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gadgets-and-accessories.store", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gavlix.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gehatrans.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "genusbag.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "getcertified.pro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "global1.gg", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "goiymua.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "goodfor.us", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "granli.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gregmarziomedia-dev.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "griswoldwellwaterct.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gujun-sky.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "haitou.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "harley-davidson-live.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hauora.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hauora.tech", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hdbits.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "healthcaresuccess.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "helkyn.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "helkyn.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "helkyn.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hella-secure.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "herd-kaufen.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hgyoseo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "highclasseducation.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "holacannx.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "holacbdoils.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "holenergies.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "holenergies.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hongbomiao.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "howmanypeoplearethereinthe.world", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "howmanypeoplearethereintheworld.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "huangzenghao.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "huntcraft.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hurbascooter.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hydra.ly", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hytopcp168.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "i-make.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "i-make.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ibraphotography.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "igkabel.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "igkabel.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "igkabel.gq", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "igkabel.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "igkabel.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ihacker.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ihacker.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "income.wiki", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "inmedsm.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "innovairservices.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "inspired-creations.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ipv4.rip", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ipv6alizer.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ispmedipv6.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "itemstore.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jackspub.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "japansm.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jennysarl.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jerseyink.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "joedeblasio.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "julienstalder.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kajakswaderki.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kapler.family", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "katerinaverbovskaya.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kevinpatel.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kgt10.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "khamphafood.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kiir.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kokomu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kommunermeddnssec.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kommunermedipv6.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kondomshop.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kristoffer.is", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6835.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6836.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6837.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6838.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6839.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6850.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6851.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6852.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6853.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6857.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6861.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6862.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6863.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6867.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6870.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ks6871.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lackierereischmitt.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lauresta.lt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lauresta.lv", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lesptitspasdelyne.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lidl-stikeez.si", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "localhost.cat", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lostinlegends.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "love4musik.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lovechester.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lovelo.store", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lprcommunity.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lunepieters.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "luv2watchmycam.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "manshatech.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "manzalud.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "marbledentalcentre.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "markusjanzen.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "marzio.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mbclegal.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "meran.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "miklagard.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mlathrom.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "moca-2080.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "momocogames.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "monitord.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ms-australia.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mummyandmephotography.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "musasdanet.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mybauingenieur24.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "myfortdodge.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "myinsuranceauto.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "myinsurancelife.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "myinsurancesource.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mylifeinsurancerates.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "myndighetermeddnssec.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "myndighetermedipv6.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "myroofandhome.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "n8solutions.host", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nadex.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "netfoundry.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nextgen-life-insurance.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nightscapesoutdoorlighting.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nms-thoracic-surgery.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "novawatch.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nucleuspanel.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nuestratecnologia.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nycrerc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nyonator.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nysis.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "obsessedwithknives.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "olenergie.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "olenergie.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "olenergies.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "olenergies.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "olhovirtual.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "omanlover.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "oneso.win", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "onload.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "operrhealth.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "origamitutorials.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pabloroblesminister.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pagatuarriendo.cl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "paketbox-systems.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "papelpack.cl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "parkr.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "penguinworld.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pentools.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "peterheery.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pick150.hu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pidibagrik.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "plandegralba.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "plasticosbiobasados.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "platformlms.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "plusreed.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "politsei.ee", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pragata.id", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "prgrmmr.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "profiservis.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "profits.fund", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "profsaranya.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pymeup.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "quiqurl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "r8600.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "reachout-ghana.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "recoba3d.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "remodelingfy.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "resume4dummies.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "rightreview.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "robinloeffel.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "s2i.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "salvameuba.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "salvandoalocombia.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sanix.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "secureenduserconnection.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sevipro.mx", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shakthifacility.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "siamericas.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sianipestcontrolinc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "simplysmartgardening.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sipstix.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "skaginn.tv", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "skalec.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sktorrent.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "smaksbanken.no", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "snizl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "songesdeplumes.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sphacks.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sporttomorrow.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "spoters.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "srimakc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sunshinecoastplumbingcompany.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "svodjapan.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "synthv.fun", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "t2-sit-test.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tacticalvote.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tara.ai", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tatard.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tdstoragebay.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tech-leaders.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "techchip.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "technochat.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "techsystemsa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "templetattoo.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thefranknews.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thegaucompany.healthcare", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "theresabrant.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thierrymazue.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thinkclic.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thornton-le-moors-ince-elton.org.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tinclip.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tinekevanurk.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tinkerers-trunk.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tomgaechter.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "topyad.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "toyokawa-fan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "trinityradioandvideo.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tupianku.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "twelvecornerspediatrics.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "twojapogoda.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "unluco.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "urologyoklahoma.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "v2x.sk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "v800b.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vetustainversion.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "victorunix.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "violarenate.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vivianadavila.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "volatile.pw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vunn.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "waifu.space", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "web-lab.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "weydu.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "whisky.my", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "whitesoxbestteaminbaseball.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wine-route.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--80akjfhoqm2h2a.xn--p1ai", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--pckm3a1bi21a.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--whlefamilie-l8a.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "yana-co.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ykn.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "yogamarlene.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "younameit.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "youregeeks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zedeko.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zhan.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zhina.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zowedo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     // END OF 1-YEAR BULK HSTS ENTRIES
 
     // Only eTLD+1 domains can be submitted automatically to hstspreload.org,
diff --git a/net/quic/mock_crypto_client_stream.cc b/net/quic/mock_crypto_client_stream.cc
index bf80471..52e4756d 100644
--- a/net/quic/mock_crypto_client_stream.cc
+++ b/net/quic/mock_crypto_client_stream.cc
@@ -178,6 +178,7 @@
       session()->connection()->SetDefaultEncryptionLevel(
           ENCRYPTION_FORWARD_SECURE);
       session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+      session()->connection()->OnHandshakeComplete();
       break;
     }
 
diff --git a/net/quic/quic_chromium_client_session_test.cc b/net/quic/quic_chromium_client_session_test.cc
index 925b45ab..2bb4990 100644
--- a/net/quic/quic_chromium_client_session_test.cc
+++ b/net/quic/quic_chromium_client_session_test.cc
@@ -1751,11 +1751,6 @@
 }
 
 TEST_P(QuicChromiumClientSessionTest, RetransmittableOnWireTimeout) {
-  // TODO(crbug/997684): Re-enable this test
-  if (version_.handshake_protocol == quic::PROTOCOL_TLS1_3 &&
-      version_.transport_version == quic::QUIC_VERSION_99)
-    return;
-
   migrate_session_early_v2_ = true;
 
   MockQuicData quic_data(version_);
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index c519b90..16efbc0 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -1117,17 +1117,7 @@
       yield_after_packets_(kQuicYieldAfterPacketsRead),
       yield_after_duration_(quic::QuicTime::Delta::FromMilliseconds(
           kQuicYieldAfterDurationMilliseconds)),
-      migrate_sessions_on_network_change_v2_(
-          params.migrate_sessions_on_network_change_v2 &&
-          NetworkChangeNotifier::AreNetworkHandlesSupported()),
-      migrate_sessions_early_v2_(params.migrate_sessions_early_v2 &&
-                                 migrate_sessions_on_network_change_v2_),
-      retry_on_alternate_network_before_handshake_(
-          params.retry_on_alternate_network_before_handshake &&
-          migrate_sessions_on_network_change_v2_),
       default_network_(NetworkChangeNotifier::kInvalidNetworkHandle),
-      migrate_idle_sessions_(params.migrate_idle_sessions &&
-                             migrate_sessions_on_network_change_v2_),
       need_to_check_persisted_supports_quic_(true),
       num_push_streams_created_(0),
       tick_clock_(nullptr),
@@ -1143,33 +1133,74 @@
   bool prefer_aes_gcm =
       !crypto_config_.aead.empty() && (crypto_config_.aead[0] == quic::kAESG);
   UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.PreferAesGcm", prefer_aes_gcm);
+  InitializeMigrationOptions();
+}
 
-  if (params.migrate_sessions_early_v2 ||
-      params.retry_on_alternate_network_before_handshake)
-    DCHECK(params.migrate_sessions_on_network_change_v2);
+void QuicStreamFactory::InitializeMigrationOptions() {
+  // The following list of options cannot be set immediately until
+  // prerequisites are met. Cache the initial setting in local variables and
+  // reset them in |params_|.
+  bool migrate_sessions_on_network_change =
+      params_.migrate_sessions_on_network_change_v2;
+  bool migrate_sessions_early = params_.migrate_sessions_early_v2;
+  bool retry_on_alternate_network_before_handshake =
+      params_.retry_on_alternate_network_before_handshake;
+  bool migrate_idle_sessions = params_.migrate_idle_sessions;
+  params_.migrate_sessions_on_network_change_v2 = false;
+  params_.migrate_sessions_early_v2 = false;
+  params_.retry_on_alternate_network_before_handshake = false;
+  params_.migrate_idle_sessions = false;
 
-  if (params.retransmittable_on_wire_timeout.is_zero() &&
-      params.migrate_sessions_early_v2) {
-    retransmittable_on_wire_timeout_ = quic::QuicTime::Delta::FromMicroseconds(
-        kDefaultRetransmittableOnWireTimeout.InMicroseconds());
-  }
-
+  // TODO(zhongyi): deprecate |goaway_sessions_on_ip_change| if the experiment
+  // is no longer needed.
   // goaway_sessions_on_ip_change and close_sessions_on_ip_change should never
   // be simultaneously set to true.
   DCHECK(!(params_.close_sessions_on_ip_change &&
            params_.goaway_sessions_on_ip_change));
 
-  // Connection migration should not be set if explicitly handle ip address
-  // change.
   bool handle_ip_change = params_.close_sessions_on_ip_change ||
                           params_.goaway_sessions_on_ip_change;
-  DCHECK(!(handle_ip_change && migrate_sessions_on_network_change_v2_));
+  // If IP address changes are handled explicitly, connection migration should
+  // not be set.
+  DCHECK(!(handle_ip_change && migrate_sessions_on_network_change));
 
   if (handle_ip_change)
     NetworkChangeNotifier::AddIPAddressObserver(this);
 
-  if (NetworkChangeNotifier::AreNetworkHandlesSupported())
-    NetworkChangeNotifier::AddNetworkObserver(this);
+  if (!NetworkChangeNotifier::AreNetworkHandlesSupported())
+    return;
+
+  NetworkChangeNotifier::AddNetworkObserver(this);
+  // Perform checks on the connection migration options.
+  if (!migrate_sessions_on_network_change) {
+    DCHECK(!migrate_sessions_early);
+    return;
+  }
+
+  // Enable migration on platform notifications.
+  params_.migrate_sessions_on_network_change_v2 = true;
+  if (!migrate_sessions_early) {
+    DCHECK(!retry_on_alternate_network_before_handshake &&
+           !migrate_idle_sessions);
+    return;
+  }
+
+  // Enable migration on path degrading.
+  params_.migrate_sessions_early_v2 = true;
+  // Set retransmittable on wire timeout for migration on path degrading if no
+  // value is specified.
+  if (retransmittable_on_wire_timeout_.IsZero()) {
+    retransmittable_on_wire_timeout_ = quic::QuicTime::Delta::FromMicroseconds(
+        kDefaultRetransmittableOnWireTimeout.InMicroseconds());
+  }
+
+  // Enable retry on alternate network before handshake.
+  if (retry_on_alternate_network_before_handshake)
+    params_.retry_on_alternate_network_before_handshake = true;
+
+  // Enable migration for idle sessions.
+  if (migrate_idle_sessions)
+    params_.migrate_idle_sessions = true;
 }
 
 QuicStreamFactory::~QuicStreamFactory() {
@@ -1372,7 +1403,7 @@
   std::unique_ptr<Job> job =
       std::make_unique<Job>(this, quic_version, host_resolver_, key,
                             WasQuicRecentlyBroken(session_key.server_id()),
-                            retry_on_alternate_network_before_handshake_,
+                            params_.retry_on_alternate_network_before_handshake,
                             params_.race_stale_dns_on_connection, priority,
                             cert_verify_flags, net_log);
   int rv = job->Run(
@@ -1591,7 +1622,7 @@
 void QuicStreamFactory::OnIPAddressChanged() {
   LogPlatformNotificationInHistogram(NETWORK_IP_ADDRESS_CHANGED);
   // Do nothing if connection migration is turned on.
-  if (migrate_sessions_on_network_change_v2_)
+  if (params_.migrate_sessions_on_network_change_v2)
     return;
 
   set_require_confirmation(true);
@@ -1605,7 +1636,7 @@
 
 void QuicStreamFactory::OnNetworkConnected(NetworkHandle network) {
   LogPlatformNotificationInHistogram(NETWORK_CONNECTED);
-  if (!migrate_sessions_on_network_change_v2_)
+  if (!params_.migrate_sessions_on_network_change_v2)
     return;
 
   ScopedConnectionMigrationEventLog scoped_event_log(net_log_,
@@ -1621,12 +1652,12 @@
 
 void QuicStreamFactory::OnNetworkMadeDefault(NetworkHandle network) {
   LogPlatformNotificationInHistogram(NETWORK_MADE_DEFAULT);
-  if (!migrate_sessions_on_network_change_v2_)
+  if (!params_.migrate_sessions_on_network_change_v2)
     return;
 
   // Clear alternative services that were marked as broken until default network
   // changes.
-  if (retry_on_alternate_network_before_handshake_ &&
+  if (params_.retry_on_alternate_network_before_handshake &&
       default_network_ != NetworkChangeNotifier::kInvalidNetworkHandle &&
       network != default_network_) {
     http_server_properties_->OnDefaultNetworkChanged();
@@ -1649,7 +1680,7 @@
 
 void QuicStreamFactory::OnNetworkDisconnected(NetworkHandle network) {
   LogPlatformNotificationInHistogram(NETWORK_DISCONNECTED);
-  if (!migrate_sessions_on_network_change_v2_)
+  if (!params_.migrate_sessions_on_network_change_v2)
     return;
 
   ScopedConnectionMigrationEventLog scoped_event_log(net_log_,
@@ -1729,7 +1760,7 @@
   socket->UseNonBlockingIO();
 
   int rv;
-  if (migrate_sessions_on_network_change_v2_) {
+  if (params_.migrate_sessions_on_network_change_v2) {
     // If caller leaves network unspecified, use current default network.
     if (network == NetworkChangeNotifier::kInvalidNetworkHandle) {
       rv = socket->ConnectUsingDefaultNetwork(addr);
@@ -1808,7 +1839,7 @@
   if (rv != OK)
     return rv;
 
-  if (migrate_sessions_on_network_change_v2_ &&
+  if (params_.migrate_sessions_on_network_change_v2 &&
       *network == NetworkChangeNotifier::kInvalidNetworkHandle) {
     *network = socket->GetBoundNetwork();
     if (default_network_ == NetworkChangeNotifier::kInvalidNetworkHandle) {
@@ -1884,9 +1915,9 @@
       connection, std::move(socket), this, quic_crypto_client_stream_factory_,
       clock_, transport_security_state_, ssl_config_service_,
       std::move(server_info), key.session_key(), require_confirmation,
-      params_.max_allowed_push_id, migrate_sessions_early_v2_,
-      migrate_sessions_on_network_change_v2_, default_network_,
-      retransmittable_on_wire_timeout_, migrate_idle_sessions_,
+      params_.max_allowed_push_id, params_.migrate_sessions_early_v2,
+      params_.migrate_sessions_on_network_change_v2, default_network_,
+      retransmittable_on_wire_timeout_, params_.migrate_idle_sessions,
       params_.idle_session_migration_period,
       params_.max_time_on_non_default_network,
       params_.max_migrations_to_non_default_network_on_write_error,
diff --git a/net/quic/quic_stream_factory.h b/net/quic/quic_stream_factory.h
index 664670f..22551ed9 100644
--- a/net/quic/quic_stream_factory.h
+++ b/net/quic/quic_stream_factory.h
@@ -559,6 +559,15 @@
                                            int cert_verify_flags,
                                            const NetLogWithSource& net_log);
 
+  // Helper method to initialize the following migration options and check
+  // pre-requisites:
+  // - |params_.migrate_sessions_on_network_change_v2|
+  // - |params_.migrate_sessions_early_v2|
+  // - |params_.migrate_idle_sessions|
+  // - |params_.retry_on_alternate_network_before_handshake|
+  // If pre-requisites are not met, turn off the corresponding options.
+  void InitializeMigrationOptions();
+
   // Initializes the cached state associated with |server_id| in
   // |crypto_config_| with the information in |server_info|. Populates
   // |connection_id| with the next server designated connection id,
@@ -633,27 +642,11 @@
   int yield_after_packets_;
   quic::QuicTime::Delta yield_after_duration_;
 
-  // Set if migration should be attempted after probing.
-  const bool migrate_sessions_on_network_change_v2_;
-
-  // Set if early migration should be attempted after probing when the
-  // connection experiences poor connectivity.
-  const bool migrate_sessions_early_v2_;
-
-  // Set if a new connection may be kicked off on an alternate network when a
-  // connection fails on the default network before handshake is confirmed.
-  const bool retry_on_alternate_network_before_handshake_;
-
   // If |migrate_sessions_early_v2_| is true, tracks the current default
   // network, and is updated OnNetworkMadeDefault.
   // Otherwise, always set to NetworkChangeNotifier::kInvalidNetwork.
   NetworkChangeNotifier::NetworkHandle default_network_;
 
-  // Set if idle sessions can be migrated within
-  // |params_.idle_session_migration_period| when connection migration is
-  // triggered.
-  const bool migrate_idle_sessions_;
-
   // Local address of socket that was created in CreateSession.
   IPEndPoint local_address_;
   // True if we need to check HttpServerProperties if QUIC was supported last
diff --git a/remoting/client/chromoting_session.cc b/remoting/client/chromoting_session.cc
index 7dde9b6..d8bcabf4 100644
--- a/remoting/client/chromoting_session.cc
+++ b/remoting/client/chromoting_session.cc
@@ -485,6 +485,15 @@
 void ChromotingSession::Core::ConnectOnNetworkThread() {
   DCHECK(network_task_runner()->BelongsToCurrentThread());
 
+  if (session_context_->info.host_ftl_id.empty()) {
+    // Simulate a CONNECTING state to make sure it doesn't skew telemetry.
+    OnConnectionState(protocol::ConnectionToHost::State::CONNECTING,
+                      protocol::OK);
+    OnConnectionState(protocol::ConnectionToHost::State::FAILED,
+                      protocol::INCOMPATIBLE_PROTOCOL);
+    return;
+  }
+
   jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
 
   client_context_.reset(new ClientContext(network_task_runner()));
@@ -546,11 +555,9 @@
   client_auth_config.fetch_secret_callback =
       base::BindRepeating(&Core::FetchSecret, GetWeakPtr());
 
-  std::string signaling_id = session_context_->info.host_ftl_id.empty()
-                                 ? session_context_->info.host_jid
-                                 : session_context_->info.host_ftl_id;
   client_->Start(signaling_.get(), client_auth_config, transport_context,
-                 signaling_id, session_context_->info.capabilities);
+                 session_context_->info.host_ftl_id,
+                 session_context_->info.capabilities);
 }
 
 void ChromotingSession::Core::LogPerfStats() {
diff --git a/remoting/signaling/ftl_signal_strategy.cc b/remoting/signaling/ftl_signal_strategy.cc
index 94307cc1..c4693c7e 100644
--- a/remoting/signaling/ftl_signal_strategy.cc
+++ b/remoting/signaling/ftl_signal_strategy.cc
@@ -325,6 +325,10 @@
   DCHECK(message.xmpp().has_stanza());
   auto stanza = base::WrapUnique<jingle_xmpp::XmlElement>(
       jingle_xmpp::XmlElement::ForStr(message.xmpp().stanza()));
+  if (!stanza) {
+    LOG(WARNING) << "Failed to parse XMPP: " << message.xmpp().stanza();
+    return;
+  }
   OnStanza(sender_address, std::move(stanza));
 }
 
diff --git a/remoting/signaling/ftl_signal_strategy_unittest.cc b/remoting/signaling/ftl_signal_strategy_unittest.cc
index b5b69c71..daf03f9 100644
--- a/remoting/signaling/ftl_signal_strategy_unittest.cc
+++ b/remoting/signaling/ftl_signal_strategy_unittest.cc
@@ -500,4 +500,21 @@
   ASSERT_EQ(0u, received_messages_.size());
 }
 
+TEST_F(FtlSignalStrategyTest, ReceiveStanza_DropMessageWithMalformedXmpp) {
+  ExpectGetOAuthTokenSucceedsWithFakeCreds();
+  registration_manager_->ExpectSignInGaiaSucceeds();
+  signal_strategy_->Connect();
+  messaging_client_->AcceptReceivingMessages();
+
+  ftl::ChromotingMessage message;
+  message.mutable_xmpp()->set_stanza("Malformed!!!");
+  ftl::Id remote_user_id;
+  remote_user_id.set_type(ftl::IdType_Type_EMAIL);
+  remote_user_id.set_id(kFakeRemoteUsername);
+  messaging_client_->OnMessage(remote_user_id, kFakeRemoteRegistrationId,
+                               message);
+
+  ASSERT_EQ(0u, received_messages_.size());
+}
+
 }  // namespace remoting
diff --git a/services/device/public/mojom/time_zone_monitor.mojom b/services/device/public/mojom/time_zone_monitor.mojom
index 207b0a0..2561a05 100644
--- a/services/device/public/mojom/time_zone_monitor.mojom
+++ b/services/device/public/mojom/time_zone_monitor.mojom
@@ -8,7 +8,7 @@
   // Add a client that will get notified on time zone changes. No throttling is
   // employed, as time zone changes are infrequent enough that there is no
   // realistic risk of flooding.
-  AddClient(TimeZoneMonitorClient client);
+  AddClient(pending_remote<TimeZoneMonitorClient> client);
 };
 
 interface TimeZoneMonitorClient {
diff --git a/services/device/time_zone_monitor/time_zone_monitor.cc b/services/device/time_zone_monitor/time_zone_monitor.cc
index 32c04ec..6a861ba 100644
--- a/services/device/time_zone_monitor/time_zone_monitor.cc
+++ b/services/device/time_zone_monitor/time_zone_monitor.cc
@@ -67,8 +67,9 @@
 }
 
 void TimeZoneMonitor::AddClient(
-    device::mojom::TimeZoneMonitorClientPtr client) {
-  clients_.AddPtr(std::move(client));
+    mojo::PendingRemote<device::mojom::TimeZoneMonitorClient> client) {
+  clients_.AddPtr(mojo::InterfacePtr<device::mojom::TimeZoneMonitorClient>(
+      std::move(client)));
 }
 
 }  // namespace device
diff --git a/services/device/time_zone_monitor/time_zone_monitor.h b/services/device/time_zone_monitor/time_zone_monitor.h
index aecf7056..e7a34c6c 100644
--- a/services/device/time_zone_monitor/time_zone_monitor.h
+++ b/services/device/time_zone_monitor/time_zone_monitor.h
@@ -11,6 +11,7 @@
 #include "base/threading/thread_checker.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/device/public/mojom/time_zone_monitor.mojom.h"
 
 template <class T>
@@ -61,7 +62,8 @@
   base::ThreadChecker thread_checker_;
 
   // device::mojom::device::mojom::TimeZoneMonitor:
-  void AddClient(device::mojom::TimeZoneMonitorClientPtr client) override;
+  void AddClient(mojo::PendingRemote<device::mojom::TimeZoneMonitorClient>
+                     client) override;
 
   mojo::BindingSet<device::mojom::TimeZoneMonitor> bindings_;
   mojo::InterfacePtrSet<device::mojom::TimeZoneMonitorClient> clients_;
diff --git a/services/network/public/mojom/network_service_test.mojom b/services/network/public/mojom/network_service_test.mojom
index c02e08ff..fb7c216 100644
--- a/services/network/public/mojom/network_service_test.mojom
+++ b/services/network/public/mojom/network_service_test.mojom
@@ -96,11 +96,6 @@
   [Sync]
   GetEnvironmentVariableValue(string name) => (string value);
 
-  // Gets the max number of concurrent connections per proxy. This may have
-  // originally been set by policy.
-  [Sync]
-  GetMaxConnectionsPerProxy() => (int32 max_connections);
-
   // Logs the given string in the network service. This is used to test log
   // redirection.
   [Sync]
diff --git a/services/service_manager/README.md b/services/service_manager/README.md
index 14ccc771..e58c005 100644
--- a/services/service_manager/README.md
+++ b/services/service_manager/README.md
@@ -163,8 +163,8 @@
 
 interface BlockAllocator {
   // Allocates a new block of persistent storage for the client. If allocation
-  // fails, |request| is discarded.
-  Allocate(uint64 num_bytes, Block& request);
+  // fails, |receiver| is discarded.
+  Allocate(uint64 num_bytes, pending_receiver<Block> receiver);
 };
 
 interface Block {
@@ -203,19 +203,19 @@
     if (interface_name == mojom::BlockAllocator::Name_) {
       // If the Service Manager sends us a request with BlockAllocator's
       // interface name, we should treat |interface_pipe| as a
-      // BlockAllocatorRequest that we can bind.
-      allocator_bindings_.AddBinding(
-          this, mojom::BlockAllocatorRequest(std::move(interface_pipe)));
+      // PendingReceiver<BlockAllocator> that we can bind.
+      allocator_receivers_.Add(
+          this, mojo::PendingReceiver<mojom::BlockAllocator>(std::move(interface_pipe)));
     }
   }
 
   // mojom::BlockAllocator:
-  void Allocate(uint64_t num_bytes, mojom::BlockRequest request) override {
+  void Allocate(uint64_t num_bytes, mojo::PendingReceiver<mojom::Block> receiver) override {
     // This space intentionally left blank.
   }
 
   service_manager::ServiceBinding service_binding_;
-  mojo::BindingSet<mojom::BlockAllocator> allocator_bindings_;
+  mojo::ReceiverSet<mojom::BlockAllocator> allocator_receivers_;
 
   DISALLOW_COPY_AND_ASSIGN(StorageService);
 };
@@ -245,8 +245,8 @@
 else.
 
 *** aside
-NOTE: Because interface requests are just strongly-type message pipe endpoint
-wrappers, you can freely construct any kind of interface request over a raw
+NOTE: Because interface receivers are just strongly-type message pipe endpoint
+wrappers, you can freely construct any kind of interface receiver over a raw
 message pipe handle. If you're planning to pass the endpoint around, it's good
 to do this as early as possible (i.e. as soon as you know the intended interface
 type) to benefit from your compiler's type-checking and avoid having to pass
@@ -389,12 +389,12 @@
 the Service Manager for a `BlockAllocator` from us, like so:
 
 ``` cpp
-storage::mojom::BlockAllocatorPtr allocator;
-connector->BindInterface(storage::mojom::kServiceName,
-                         mojo::MakeRequest(&allocator));
+mojo::Remote<storage::mojom::BlockAllocator> allocator;
+connector->Connect(storage::mojom::kServiceName,
+                         allocator.BindNewPipeAndPassReceiver());
 
-storage::mojom::BlockPtr block;
-allocator->Allocate(42, mojo::MakeRequest(&block));
+mojo::Remote<storage::mojom::Block> block;
+allocator->Allocate(42, block.BindNewPipeAndPassReceiver());
 
 // etc..
 ```
@@ -542,17 +542,17 @@
   service_manager::TestService test_service(
       service_manager.RegisterTestInstance(kTestServiceName));
 
-  storage::mojom::BlockAllocatorPtr allocator;
+  mojo::Remote<storage::mojom::BlockAllocator> allocator;
 
   // This Connector belongs to the test service instance and can reach the
   // storage service through the Service Manager by virtue of the required
   // capability above.
-  test_service.connector()->BindInterface(storage::mojom::kServiceName,
-                                          mojo::MakeRequest(&allocator));
+  test_service.connector()->Connect(storage::mojom::kServiceName,
+                                          allocator.BindNewPipeAndPassReceiver());
 
   // Verify that we can request a small block of storage.
-  storage::mojom::BlockPtr block;
-  allocator->Allocate(64, mojo::MakeRequest(&block));
+  mojo::Remote<storage::mojom::Block> block;
+  allocator->Allocate(64, block.BindNewPipeAndPassReceiver());
 
   // Do some stuff with the block, etc...
 }
@@ -580,13 +580,13 @@
 
 // This helper function can be used by any service which is granted the
 // |kAllocationCapability| capability.
-mojom::BlockPtr AllocateBlock(service_manager::Connector* connector,
+mojo::Remote<mojom::Block> AllocateBlock(service_manager::Connector* connector,
                               uint64_t size) {
-  mojom::BlockAllocatorPtr allocator;
-  connector->BindInterface(mojom::kServiceName, mojo::MakeRequest(&allocator));
+  mojo::Remote<mojom::BlockAllocator> allocator;
+  connector->Connect(mojom::kServiceName, allocator.BindNewPipeAndPassReceiver());
 
-  mojom::BlockPtr block;
-  allocator->Allocate(size, mojo::MakeRequest(block));
+  mojo::Remote<mojom::Block> block;
+  allocator->Allocate(size, block.BindNewPipeAndPassReceiver());
   return block;
 }
 
@@ -602,7 +602,7 @@
       test_connector_factory.RegisterInstance(storage::mojom::kServiceName));
 
   constexpr uint64_t kTestBlockSize = 64;
-  storage::mojom::BlockPtr block = storage::AllocateBlock(
+  mojo::Remote<storage::mojom::Block> block = storage::AllocateBlock(
       test_connector_factory.GetDefaultConnector(), kTestBlockSize);
   block.FlushForTesting();
 
@@ -625,10 +625,10 @@
 [exposes](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service_binding.h?rcl=887b934e0d979f3da81c41cadc396b4ef587257a&l=66)
 it on your behalf.
 
-#### Sending Interface Requests
+#### Sending Interface Receivers
 
-By far the most common and useful method on `Connector` is `BindInterface`,
-which allows your service to send an interface request to another service in the
+By far the most common and useful method on `Connector` is `Connect`,
+which allows your service to send an interface receiver to another service in the
 system, configuration permitting.
 
 Supposing the `storage` service actually depended on an even lower-level storage
@@ -636,14 +636,14 @@
 something like:
 
 ``` cpp
-  real_storage::mojom::ReallyRealStoragePtr storage;
-  service_binding_.GetConnector()->BindInterface(
-      real_storage::mojom::kServiceName, mojo::MakeRequest(&storage));
+  mojo::Remote<real_storage::mojom::ReallyRealStorage> storage;
+  service_binding_.GetConnector()->Connect(
+      real_storage::mojom::kServiceName, storage.BindNewPipeAndPassReceiver());
   storage->AllocateBytes(...);
 ```
 
-Note that the first argument to this particular overload of `BindInterface` is
-a string, but the more generalized form of `BindInterface` takes a
+Note that the first argument to this particular overload of `Connect` is
+a string, but the more generalized form of `Connect` takes a
 `ServiceFilter`. See more about these in the section on
 [Service Filters](#Service-Filters).
 
@@ -784,9 +784,9 @@
 
 Most services in the system do not have the privilege of specifying the
 instance group they want to connect into when passing a `ServiceFilter` to
-`Connector::BindInterface` (see
+`Connector::Connect` (see
 [Additional Capabilities](#Additional-Capabilities)). As such, most
-`BindInterface` calls implicitly inherit the group ID of the caller and only
+`Connect` calls implicitly inherit the group ID of the caller and only
 cross outside of the caller's instance group when targeting a service which
 adopts either a singleton or shared-across-groups
 [sharing policy](#Instance-Sharing) in its manifest.
@@ -796,7 +796,7 @@
 
 ### Service Filters
 
-The most common form of `BindInterface` calls passes a simple string as the
+The most common form of `Connect` calls passes a simple string as the
 first argument. This is essentially telling the Service Manager that the caller
 doesn't care about any details regarding the target instance's identity -- it
 only cares about talking to *some* instance of the named service.
@@ -830,9 +830,9 @@
   `RegisterServiceInstance` on its `Connector` to forcibly introduce new service
   instances into the environment.
 - `CanConnectToInstancesWithAnyId` - If this is `true` the service can specify
-  an instance ID in any `ServiceFilter` it passes to `BindInterface`.
+  an instance ID in any `ServiceFilter` it passes to `Connect`.
 - `CanConnectToInstancesInAnyGroup` - If this is `true` the service can specify
-  an instance group ID in any `ServiceFilter` it passes to `BindInterface`.
+  an instance group ID in any `ServiceFilter` it passes to `Connect`.
 
 ### Packaging
 
diff --git a/services/tracing/public/cpp/stack_sampling/stack_unwinder_android.cc b/services/tracing/public/cpp/stack_sampling/stack_unwinder_android.cc
index 1004ade..9af1914 100644
--- a/services/tracing/public/cpp/stack_sampling/stack_unwinder_android.cc
+++ b/services/tracing/public/cpp/stack_sampling/stack_unwinder_android.cc
@@ -228,12 +228,11 @@
     // Add a nullptr to differentiate addresses found by unwinding and scanning.
     out_trace_[depth_++] = nullptr;
     while (depth_ < max_depth_ &&
-           reinterpret_cast<uintptr_t>(stack) < stack_segment_base_) {
-      if (CFIBacktraceAndroid::is_chrome_address(
-              reinterpret_cast<uintptr_t>(*stack))) {
+           reinterpret_cast<uintptr_t>(stack) + sizeof(uintptr_t) <= stack_segment_base_) {
+      if (CFIBacktraceAndroid::is_chrome_address(*stack)) {
         out_trace_[depth_++] = reinterpret_cast<void*>(*stack);
       }
-      ++stack;
+      stack = reinterpret_cast<uintptr_t*>(reinterpret_cast<uintptr_t>(stack) + 2);
     }
   }
 
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index 9448d8c7..42d03e0 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -156,6 +156,7 @@
     "platform/modules/mediastream/web_platform_media_stream_source.h",
     "platform/modules/mediastream/web_platform_media_stream_track.h",
     "platform/modules/mediastream/webrtc_uma_histograms.h",
+    "platform/modules/peerconnection/audio_codec_factory.h",
     "platform/modules/remoteplayback/web_remote_playback_client.h",
     "platform/modules/service_worker/web_service_worker_error.h",
     "platform/modules/service_worker/web_service_worker_network_provider.h",
@@ -546,6 +547,8 @@
     "//third_party/blink/renderer/platform/wtf:wtf",
     "//third_party/webrtc/api:libjingle_peerconnection_api",
     "//third_party/webrtc/api:rtc_stats_api",
+    "//third_party/webrtc/api:scoped_refptr",
+    "//third_party/webrtc/api/audio_codecs:audio_codecs_api",
     "//third_party/webrtc/api/video:video_frame",
     "//third_party/webrtc/api/video:video_rtp_headers",
     "//third_party/webrtc/media:rtc_media_base",
diff --git a/third_party/blink/public/mojom/appcache/appcache.mojom b/third_party/blink/public/mojom/appcache/appcache.mojom
index 89a71e0..0e53a93 100644
--- a/third_party/blink/public/mojom/appcache/appcache.mojom
+++ b/third_party/blink/public/mojom/appcache/appcache.mojom
@@ -64,11 +64,6 @@
   // Informs the browser of a new appcache host.
   //
   // Unregistering the host is accomplished by closing the AppCacheHost pipe.
-  //
-  // Frames go through DocumentInterfaceBroker and this is now only used for
-  // workers.
-  // TODO(mek): Once it has 'WebContextInterfaceBroker' for workers, move this
-  // to the implementation of 'WebContextInterfaceBroker'. crbug.com/718652
   RegisterHost(pending_receiver<AppCacheHost> host_receiver,
                pending_remote<AppCacheFrontend> frontend,
                mojo_base.mojom.UnguessableToken host_id);
diff --git a/third_party/blink/public/platform/modules/peerconnection/OWNERS b/third_party/blink/public/platform/modules/peerconnection/OWNERS
new file mode 100644
index 0000000..8a124af
--- /dev/null
+++ b/third_party/blink/public/platform/modules/peerconnection/OWNERS
@@ -0,0 +1 @@
+file://third_party/blink/renderer/modules/peerconnection/OWNERS
diff --git a/third_party/blink/public/platform/modules/peerconnection/audio_codec_factory.h b/third_party/blink/public/platform/modules/peerconnection/audio_codec_factory.h
new file mode 100644
index 0000000..ad000a8c
--- /dev/null
+++ b/third_party/blink/public/platform/modules/peerconnection/audio_codec_factory.h
@@ -0,0 +1,23 @@
+// 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.
+
+#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_PEERCONNECTION_AUDIO_CODEC_FACTORY_H_
+#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_PEERCONNECTION_AUDIO_CODEC_FACTORY_H_
+
+#include "third_party/blink/public/platform/web_common.h"
+#include "third_party/webrtc/api/audio_codecs/audio_decoder_factory.h"
+#include "third_party/webrtc/api/audio_codecs/audio_encoder_factory.h"
+#include "third_party/webrtc/api/scoped_refptr.h"
+
+namespace blink {
+
+BLINK_PLATFORM_EXPORT rtc::scoped_refptr<webrtc::AudioEncoderFactory>
+CreateWebrtcAudioEncoderFactory();
+
+BLINK_PLATFORM_EXPORT rtc::scoped_refptr<webrtc::AudioDecoderFactory>
+CreateWebrtcAudioDecoderFactory();
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_PEERCONNECTION_AUDIO_CODEC_FACTORY_H_
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.cc b/third_party/blink/renderer/core/page/chrome_client_impl.cc
index 0a90d089..f670866 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.cc
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.cc
@@ -30,7 +30,6 @@
  */
 
 #include "third_party/blink/renderer/core/page/chrome_client_impl.h"
-#include "third_party/blink/renderer/platform/heap/heap.h"
 
 #include <memory>
 #include <utility>
@@ -101,6 +100,7 @@
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/graphics/touch_action.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
@@ -1021,19 +1021,15 @@
 
   client->SetEventListenerProperties(event_class, properties);
 
-  bool has_touch_end_or_cancel_handler =
-      client->EventListenerProperties(
-          cc::EventListenerClass::kTouchEndOrCancel) !=
-      cc::EventListenerProperties::kNone;
-
-  if (event_class == cc::EventListenerClass::kTouchStartOrMove) {
-    client->SetHasTouchEventHandlers(properties !=
-                                         cc::EventListenerProperties::kNone ||
-                                     has_touch_end_or_cancel_handler);
-  } else if (event_class == cc::EventListenerClass::kTouchEndOrCancel) {
-    client->SetHasTouchEventHandlers(properties !=
-                                         cc::EventListenerProperties::kNone ||
-                                     has_touch_end_or_cancel_handler);
+  if (event_class == cc::EventListenerClass::kTouchStartOrMove ||
+      event_class == cc::EventListenerClass::kTouchEndOrCancel) {
+    client->SetHasTouchEventHandlers(
+        client->EventListenerProperties(
+            cc::EventListenerClass::kTouchStartOrMove) !=
+            cc::EventListenerProperties::kNone ||
+        client->EventListenerProperties(
+            cc::EventListenerClass::kTouchEndOrCancel) !=
+            cc::EventListenerProperties::kNone);
   } else if (event_class == cc::EventListenerClass::kPointerRawUpdate) {
     client->SetHasPointerRawUpdateEventHandlers(
         properties != cc::EventListenerProperties::kNone);
diff --git a/third_party/blink/renderer/core/timezone/DEPS b/third_party/blink/renderer/core/timezone/DEPS
index 0d7c0f69..3fcb405 100644
--- a/third_party/blink/renderer/core/timezone/DEPS
+++ b/third_party/blink/renderer/core/timezone/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
     "+mojo/public/cpp/bindings/binding.h",
     "+services/device/public/mojom/time_zone_monitor.mojom-blink.h",
+    "+services/device/public/mojom/constants.mojom-blink.h",
     "+third_party/icu/source/common/unicode/char16ptr.h",
     "+third_party/icu/source/i18n/unicode/timezone.h",
 ]
diff --git a/third_party/blink/renderer/core/timezone/timezone_controller.cc b/third_party/blink/renderer/core/timezone/timezone_controller.cc
index e7602ec..854ed6ae 100644
--- a/third_party/blink/renderer/core/timezone/timezone_controller.cc
+++ b/third_party/blink/renderer/core/timezone/timezone_controller.cc
@@ -4,6 +4,10 @@
 
 #include "third_party/blink/renderer/core/timezone/timezone_controller.h"
 
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/device/public/mojom/constants.mojom-blink.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/task_type.h"
@@ -53,7 +57,7 @@
 
 }  // namespace
 
-TimeZoneController::TimeZoneController() : binding_(this) {
+TimeZoneController::TimeZoneController() {
   DCHECK(IsMainThread());
   host_timezone_id_ = GetCurrentTimezone();
 }
@@ -68,12 +72,10 @@
   if (!CanInitializeMojo())
     return;
 
-  device::mojom::blink::TimeZoneMonitorPtr monitor;
+  mojo::Remote<device::mojom::blink::TimeZoneMonitor> monitor;
   Platform::Current()->GetBrowserInterfaceBrokerProxy()->GetInterface(
-      mojo::MakeRequest(&monitor));
-  device::mojom::blink::TimeZoneMonitorClientPtr client;
-  instance().binding_.Bind(mojo::MakeRequest(&client));
-  monitor->AddClient(std::move(client));
+      monitor.BindNewPipeAndPassReceiver());
+  monitor->AddClient(instance().receiver_.BindNewPipeAndPassRemote());
 }
 
 // static
diff --git a/third_party/blink/renderer/core/timezone/timezone_controller.h b/third_party/blink/renderer/core/timezone/timezone_controller.h
index a1f23c5a..86d9d01 100644
--- a/third_party/blink/renderer/core/timezone/timezone_controller.h
+++ b/third_party/blink/renderer/core/timezone/timezone_controller.h
@@ -7,7 +7,7 @@
 
 #include <memory>
 
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "services/device/public/mojom/time_zone_monitor.mojom-blink.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -49,7 +49,7 @@
   // device::mojom::blink::TimeZoneMonitorClient:
   void OnTimeZoneChange(const String& timezone_id) override;
 
-  mojo::Binding<device::mojom::blink::TimeZoneMonitorClient> binding_;
+  mojo::Receiver<device::mojom::blink::TimeZoneMonitorClient> receiver_{this};
 
   String host_timezone_id_;
   bool has_timezone_id_override_ = false;
diff --git a/third_party/blink/renderer/devtools/front_end/accessibility/axBreadcrumbs.css b/third_party/blink/renderer/devtools/front_end/accessibility/axBreadcrumbs.css
index 6fd6b13..8db098dd 100644
--- a/third_party/blink/renderer/devtools/front_end/accessibility/axBreadcrumbs.css
+++ b/third_party/blink/renderer/devtools/front_end/accessibility/axBreadcrumbs.css
@@ -80,7 +80,7 @@
     display: block;
     left: 2px;
     right: 2px;
-    background-color: rgba(56, 121, 217, 0.1);
+    background-color: var(--item-hover-color);
     border-radius: 5px;
 }
 
diff --git a/third_party/blink/renderer/devtools/front_end/changes/changesSidebar.css b/third_party/blink/renderer/devtools/front_end/changes/changesSidebar.css
index f10384d4..48a718c3 100644
--- a/third_party/blink/renderer/devtools/front_end/changes/changesSidebar.css
+++ b/third_party/blink/renderer/devtools/front_end/changes/changesSidebar.css
@@ -9,7 +9,7 @@
 
 .tree-outline li:hover:not(.selected) .selection {
   display: block;
-  background-color: rgba(56, 121, 217, 0.1);
+  background-color: var(--item-hover-color);
 }
 
 .navigator-fs-tree-item .icon{
diff --git a/third_party/blink/renderer/devtools/front_end/console/consoleSidebar.css b/third_party/blink/renderer/devtools/front_end/console/consoleSidebar.css
index 2ea4647..f59f245 100644
--- a/third_party/blink/renderer/devtools/front_end/console/consoleSidebar.css
+++ b/third_party/blink/renderer/devtools/front_end/console/consoleSidebar.css
@@ -50,5 +50,5 @@
 
 .tree-outline li:hover:not(.selected) .selection {
     display: block;
-    background-color: rgba(56, 121, 217, 0.1);
+    background-color: var(--item-hover-color);
 }
diff --git a/third_party/blink/renderer/devtools/front_end/elements/elementsTreeOutline.css b/third_party/blink/renderer/devtools/front_end/elements/elementsTreeOutline.css
index 3f28e9f..4fcd96d 100644
--- a/third_party/blink/renderer/devtools/front_end/elements/elementsTreeOutline.css
+++ b/third_party/blink/renderer/devtools/front_end/elements/elementsTreeOutline.css
@@ -70,7 +70,7 @@
     display: block;
     left: 3px;
     right: 3px;
-    background-color: rgba(56, 121, 217, 0.1);
+    background-color: var(--item-hover-color);
     border-radius: 5px;
 }
 
@@ -83,7 +83,7 @@
 }
 
 .elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) .selection {
-    background-color: var(--editor-selection-inactive-bg-color);
+    background-color: var(--item-selection-inactive-bg-color);
 }
 
 .elements-disclosure .elements-tree-outline.hide-selection-when-blurred .selected:focus[data-keyboard-focus="true"] .highlight > * {
@@ -121,7 +121,7 @@
 }
 
 .elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .selection {
-    background-color: var(--editor-selection-bg-color);
+    background-color: var(--item-selection-bg-color);
 }
 
 .elements-tree-outline ol.shadow-root-depth-4 {
diff --git a/third_party/blink/renderer/devtools/front_end/object_ui/objectValue.css b/third_party/blink/renderer/devtools/front_end/object_ui/objectValue.css
index afbc104..bfaf983 100644
--- a/third_party/blink/renderer/devtools/front_end/object_ui/objectValue.css
+++ b/third_party/blink/renderer/devtools/front_end/object_ui/objectValue.css
@@ -20,7 +20,7 @@
 }
 
 .value.object-value-node:hover {
-    background-color: rgba(56, 121, 217, 0.1);
+    background-color: var(--item-hover-color);
 }
 
 .object-value-function-prefix,
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/IsolateSelector.js b/third_party/blink/renderer/devtools/front_end/profiler/IsolateSelector.js
index 0d94cee..8494544 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/IsolateSelector.js
+++ b/third_party/blink/renderer/devtools/front_end/profiler/IsolateSelector.js
@@ -16,6 +16,7 @@
     this._list = new UI.ListControl(this._items, this, UI.ListMode.NonViewport);
     UI.ARIAUtils.markAsListBox(this._list.element);
     this._list.element.tabIndex = 0;
+    this._list.element.classList.add('javascript-vm-instances-list');
     UI.ARIAUtils.setAccessibleName(this._list.element, ls`JavaScript VM instances`);
     this.contentElement.appendChild(this._list.element);
 
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/profileLauncherView.css b/third_party/blink/renderer/devtools/front_end/profiler/profileLauncherView.css
index 34552519..7c8978a 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/profileLauncherView.css
+++ b/third_party/blink/renderer/devtools/front_end/profiler/profileLauncherView.css
@@ -70,13 +70,17 @@
     overflow-x: hidden;
 }
 
-.profile-launcher-target-list .profile-memory-usage-item:hover {
-    background-color: hsla(0, 0%, 0%, 0.05);
+.profile-launcher-target-list .profile-memory-usage-item:hover:not(.selected) {
+    background-color: var(--item-hover-color);
+}
+
+.javascript-vm-instances-list:focus .profile-memory-usage-item.selected {
+    border-color: var(--selection-bg-color);
+    background-color: var(--item-selection-bg-color);
 }
 
 .profile-memory-usage-item.selected {
-    border-color: #4285f4;
-    background-color: #4285f420;
+    background-color: var(--item-selection-inactive-bg-color);
 }
 
 .profile-memory-usage-item > div {
diff --git a/third_party/blink/renderer/devtools/front_end/search/searchResultsPane.css b/third_party/blink/renderer/devtools/front_end/search/searchResultsPane.css
index 829dc11..34c9fbf 100644
--- a/third_party/blink/renderer/devtools/front_end/search/searchResultsPane.css
+++ b/third_party/blink/renderer/devtools/front_end/search/searchResultsPane.css
@@ -82,7 +82,7 @@
 }
 
 li.search-match:hover {
-    background-color: rgba(56, 121, 217, 0.1);
+    background-color: var(--item-hover-color);
 }
 
 li.search-match .highlighted-match {
diff --git a/third_party/blink/renderer/devtools/front_end/sources/navigatorTree.css b/third_party/blink/renderer/devtools/front_end/sources/navigatorTree.css
index 706de5ae..0f4de40 100644
--- a/third_party/blink/renderer/devtools/front_end/sources/navigatorTree.css
+++ b/third_party/blink/renderer/devtools/front_end/sources/navigatorTree.css
@@ -58,7 +58,7 @@
 
 .tree-outline li:hover:not(.selected) .selection {
     display: block;
-    background-color: rgba(56, 121, 217, 0.1);
+    background-color: var(--item-hover-color);
 }
 
 .navigator-folder-tree-item .icon {
diff --git a/third_party/blink/renderer/devtools/front_end/text_editor/cmdevtools.css b/third_party/blink/renderer/devtools/front_end/text_editor/cmdevtools.css
index 7b5253ab..098366c 100644
--- a/third_party/blink/renderer/devtools/front_end/text_editor/cmdevtools.css
+++ b/third_party/blink/renderer/devtools/front_end/text_editor/cmdevtools.css
@@ -633,15 +633,15 @@
 }
 
 .CodeMirror .CodeMirror-selected {
-    background-color: var(--editor-selection-inactive-bg-color);
+    background-color: var(--item-selection-inactive-bg-color);
 }
 
 .CodeMirror-focused .CodeMirror-selected {
-    background-color: var(--editor-selection-bg-color);
+    background-color: var(--item-selection-bg-color);
 }
 
 .CodeMirror .CodeMirror-line::selection,
 .CodeMirror .CodeMirror-line > span::selection,
 .CodeMirror .CodeMirror-line > span > span::selection {
-    background: var(--editor-selection-bg-color);
+    background: var(--item-selection-bg-color);
 }
diff --git a/third_party/blink/renderer/devtools/front_end/ui/inspectorStyle.css b/third_party/blink/renderer/devtools/front_end/ui/inspectorStyle.css
index a91416c..df31c93 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/inspectorStyle.css
+++ b/third_party/blink/renderer/devtools/front_end/ui/inspectorStyle.css
@@ -49,8 +49,8 @@
                    0 2px 6px rgba(0, 0, 0, 0.1);
     --divider-color: #d0d0d0;
     --focus-ring-inactive-shadow: 0 0 0 1px #e0e0e0;
-    --editor-selection-bg-color: #cfe8fc;
-    --editor-selection-inactive-bg-color: #e0e0e0;
+    --item-selection-bg-color: #cfe8fc;
+    --item-selection-inactive-bg-color: #e0e0e0;
 }
 
 .-theme-with-dark-background {
@@ -70,14 +70,15 @@
                    0 2px 6px 2px rgba(0, 0, 0, 0.1);
     --divider-color: #525252;
     --focus-ring-inactive-shadow: 0 0 0 1px #5a5a5a;
-    --editor-selection-bg-color: hsl(207, 88%, 22%);
-    --editor-selection-inactive-bg-color: #454545;
+    --item-selection-bg-color: hsl(207, 88%, 22%);
+    --item-selection-inactive-bg-color: #454545;
 }
 
 :root {
     --focus-ring-active-shadow: 0 0 0 1px var(--accent-color);
     --selection-bg-color: var(--accent-color);
     --divider-border: 1px solid var(--divider-color);
+    --item-hover-color: rgba(56, 121, 217, 0.1);
 }
 
 body {
diff --git a/third_party/blink/renderer/devtools/front_end/ui/suggestBox.css b/third_party/blink/renderer/devtools/front_end/ui/suggestBox.css
index ea8c85e5..b601721 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/suggestBox.css
+++ b/third_party/blink/renderer/devtools/front_end/ui/suggestBox.css
@@ -96,5 +96,5 @@
 }
 
 .suggest-box-content-item:hover:not(.selected) {
-    background-color: rgba(56, 121, 217, 0.1);
+    background-color: var(--item-hover-color);
 }
diff --git a/third_party/blink/renderer/devtools/front_end/ui/treeoutline.css b/third_party/blink/renderer/devtools/front_end/ui/treeoutline.css
index b1676d9..30750d7c 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/treeoutline.css
+++ b/third_party/blink/renderer/devtools/front_end/ui/treeoutline.css
@@ -25,7 +25,7 @@
     display: block;
     left: 3px;
     right: 3px;
-    background-color: rgba(56, 121, 217, 0.1);
+    background-color: var(--item-hover-color);
     border-radius: 5px;
 }
 
diff --git a/third_party/blink/renderer/modules/vr/vr_controller.cc b/third_party/blink/renderer/modules/vr/vr_controller.cc
index 16c8414..6614e4b 100644
--- a/third_party/blink/renderer/modules/vr/vr_controller.cc
+++ b/third_party/blink/renderer/modules/vr/vr_controller.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/vr/vr_controller.h"
 
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -18,7 +19,6 @@
 VRController::VRController(NavigatorVR* navigator_vr)
     : ContextLifecycleObserver(navigator_vr->GetDocument()),
       navigator_vr_(navigator_vr),
-      binding_(this),
       feature_handle_for_scheduler_(
           navigator_vr->GetDocument()->GetScheduler()->RegisterFeature(
               SchedulingPolicy::Feature::kWebVR,
@@ -27,13 +27,11 @@
   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
       navigator_vr->GetDocument()->GetTaskRunner(TaskType::kMiscPlatformAPI);
   navigator_vr->GetDocument()->GetFrame()->GetInterfaceProvider().GetInterface(
-      mojo::MakeRequest(&service_, task_runner));
-  service_.set_connection_error_handler(
+      service_.BindNewPipeAndPassReceiver(task_runner));
+  service_.set_disconnect_handler(
       WTF::Bind(&VRController::Dispose, WrapWeakPersistent(this)));
 
-  device::mojom::blink::VRServiceClientPtr client;
-  binding_.Bind(mojo::MakeRequest(&client, task_runner), task_runner);
-  service_->SetClient(std::move(client));
+  service_->SetClient(receiver_.BindNewPipeAndPassRemote(task_runner));
 
   // Request display info. If we get it, we have a device.
   service_->GetImmersiveVRDisplayInfo(WTF::Bind(
@@ -225,7 +223,7 @@
   // If the document context was destroyed, shut down the client connection
   // and never call the mojo service again.
   service_.reset();
-  binding_.Close();
+  receiver_.reset();
 
   // Shutdown all displays' message pipe
   if (display_) {
diff --git a/third_party/blink/renderer/modules/vr/vr_controller.h b/third_party/blink/renderer/modules/vr/vr_controller.h
index 5e97d3d..68888f5 100644
--- a/third_party/blink/renderer/modules/vr/vr_controller.h
+++ b/third_party/blink/renderer/modules/vr/vr_controller.h
@@ -9,7 +9,8 @@
 
 #include "base/macros.h"
 #include "device/vr/public/mojom/vr_service.mojom-blink.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/context_lifecycle_observer.h"
 #include "third_party/blink/renderer/modules/vr/vr_display.h"
@@ -41,7 +42,7 @@
 
   void Trace(blink::Visitor*) override;
 
-  device::mojom::blink::VRServicePtr& Service() { return service_; }
+  mojo::Remote<device::mojom::blink::VRService>& Service() { return service_; }
 
  private:
   bool ShouldResolveGetDisplays();
@@ -78,8 +79,8 @@
   bool listening_for_activate_ = false;
 
   HeapDeque<Member<ScriptPromiseResolver>> pending_promise_resolvers_;
-  device::mojom::blink::VRServicePtr service_;
-  mojo::Binding<device::mojom::blink::VRServiceClient> binding_;
+  mojo::Remote<device::mojom::blink::VRService> service_;
+  mojo::Receiver<device::mojom::blink::VRServiceClient> receiver_{this};
 
   FrameOrWorkerScheduler::SchedulingAffectingFeatureHandle
       feature_handle_for_scheduler_;
diff --git a/third_party/blink/renderer/modules/webmidi/midi_dispatcher.cc b/third_party/blink/renderer/modules/webmidi/midi_dispatcher.cc
index 282b44f..23663f4 100644
--- a/third_party/blink/renderer/modules/webmidi/midi_dispatcher.cc
+++ b/third_party/blink/renderer/modules/webmidi/midi_dispatcher.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/platform/interface_provider.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_string.h"
@@ -24,15 +25,13 @@
 
 MIDIDispatcher::MIDIDispatcher(
     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : binding_(this), task_runner_(std::move(task_runner)) {
+    : task_runner_(std::move(task_runner)) {
   TRACE_EVENT0("midi", "MIDIDispatcher::MIDIDispatcher");
-  midi::mojom::blink::MidiSessionClientPtr client_ptr;
-  binding_.Bind(mojo::MakeRequest(&client_ptr, task_runner_), task_runner_);
-
   Platform::Current()->GetInterfaceProvider()->GetInterface(
-      mojo::MakeRequest(&midi_session_provider_, task_runner_));
-  midi_session_provider_->StartSession(mojo::MakeRequest(&midi_session_),
-                                       std::move(client_ptr));
+      midi_session_provider_.BindNewPipeAndPassReceiver(task_runner_));
+  midi_session_provider_->StartSession(
+      midi_session_.BindNewPipeAndPassReceiver(),
+      receiver_.BindNewPipeAndPassRemote());
 }
 
 MIDIDispatcher::~MIDIDispatcher() = default;
diff --git a/third_party/blink/renderer/modules/webmidi/midi_dispatcher.h b/third_party/blink/renderer/modules/webmidi/midi_dispatcher.h
index 36918d9..f3bedca 100644
--- a/third_party/blink/renderer/modules/webmidi/midi_dispatcher.h
+++ b/third_party/blink/renderer/modules/webmidi/midi_dispatcher.h
@@ -6,7 +6,8 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBMIDI_MIDI_DISPATCHER_H_
 
 #include "media/midi/midi_service.mojom-blink.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
@@ -75,10 +76,10 @@
   // TODO(toyoshim): Consider to have a per-process limit.
   size_t unacknowledged_bytes_sent_ = 0u;
 
-  midi::mojom::blink::MidiSessionPtr midi_session_;
+  mojo::Remote<midi::mojom::blink::MidiSession> midi_session_;
 
-  mojo::Binding<midi::mojom::blink::MidiSessionClient> binding_;
-  midi::mojom::blink::MidiSessionProviderPtr midi_session_provider_;
+  mojo::Receiver<midi::mojom::blink::MidiSessionClient> receiver_{this};
+  mojo::Remote<midi::mojom::blink::MidiSessionProvider> midi_session_provider_;
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 };
diff --git a/third_party/blink/renderer/modules/xr/xr.cc b/third_party/blink/renderer/modules/xr/xr.cc
index 4d987fd0..2c58548 100644
--- a/third_party/blink/renderer/modules/xr/xr.cc
+++ b/third_party/blink/renderer/modules/xr/xr.cc
@@ -227,7 +227,6 @@
     : ContextLifecycleObserver(frame.GetDocument()),
       FocusChangedObserver(frame.GetPage()),
       ukm_source_id_(ukm_source_id),
-      binding_(this),
       navigation_start_(
           frame.Loader().GetDocumentLoader()->GetTiming().NavigationStart()),
       feature_handle_for_scheduler_(frame.GetFrameScheduler()->RegisterFeature(
@@ -699,11 +698,8 @@
     // See https://bit.ly/2S0zRAS for task types.
     auto task_runner =
         GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI);
-    if (!binding_.is_bound()) {
-      device::mojom::blink::VRServiceClientPtr client;
-      binding_.Bind(mojo::MakeRequest(&client, task_runner), task_runner);
-      service_->SetClient(std::move(client));
-    }
+    if (!receiver_.is_bound())
+      service_->SetClient(receiver_.BindNewPipeAndPassRemote(task_runner));
   }
 }
 
@@ -761,7 +757,7 @@
   // If the document context was destroyed, shut down the client connection
   // and never call the mojo service again.
   service_.reset();
-  binding_.Close();
+  receiver_.reset();
 
   // Shutdown frame provider, which manages the message pipes.
   if (frame_provider_)
diff --git a/third_party/blink/renderer/modules/xr/xr.h b/third_party/blink/renderer/modules/xr/xr.h
index edff122..70f2ac1 100644
--- a/third_party/blink/renderer/modules/xr/xr.h
+++ b/third_party/blink/renderer/modules/xr/xr.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_H_
 
 #include "device/vr/public/mojom/vr_service.mojom-blink.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/execution_context/context_lifecycle_observer.h"
@@ -216,7 +216,7 @@
   device::mojom::blink::XRFrameDataProviderPtr magic_window_provider_;
   device::mojom::blink::XREnvironmentIntegrationProviderAssociatedPtr
       environment_provider_;
-  mojo::Binding<device::mojom::blink::VRServiceClient> binding_;
+  mojo::Receiver<device::mojom::blink::VRServiceClient> receiver_{this};
 
   // Time at which navigation started. Used as the base for relative timestamps,
   // such as for Gamepad objects.
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 949d0e4c..3a73197 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1226,6 +1226,7 @@
     "mojo/revocable_strong_binding.h",
     "mojo/string16_mojom_traits.cc",
     "mojo/string16_mojom_traits.h",
+    "peerconnection/audio_codec_factory.cc",
     "peerconnection/rtc_answer_options_platform.h",
     "peerconnection/rtc_offer_options_platform.h",
     "peerconnection/rtc_session_description_request.h",
@@ -1440,6 +1441,18 @@
     "//third_party/ced",
     "//third_party/emoji-segmenter",
     "//third_party/icu",
+    "//third_party/webrtc/api/audio_codecs/L16:audio_decoder_L16",
+    "//third_party/webrtc/api/audio_codecs/L16:audio_encoder_L16",
+    "//third_party/webrtc/api/audio_codecs/g711:audio_decoder_g711",
+    "//third_party/webrtc/api/audio_codecs/g711:audio_encoder_g711",
+    "//third_party/webrtc/api/audio_codecs/g722:audio_decoder_g722",
+    "//third_party/webrtc/api/audio_codecs/g722:audio_encoder_g722",
+    "//third_party/webrtc/api/audio_codecs/isac:audio_decoder_isac",
+    "//third_party/webrtc/api/audio_codecs/isac:audio_encoder_isac",
+    "//third_party/webrtc/api/audio_codecs/opus:audio_decoder_multiopus",
+    "//third_party/webrtc/api/audio_codecs/opus:audio_decoder_opus",
+    "//third_party/webrtc/api/audio_codecs/opus:audio_encoder_multiopus",
+    "//third_party/webrtc/api/audio_codecs/opus:audio_encoder_opus",
     "//third_party/webrtc/p2p:rtc_p2p",
     "//third_party/webrtc_overrides:init_webrtc",
     "//third_party/zlib/google:compression_utils",
diff --git a/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h b/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
index 6d52c76..a9d7d26 100644
--- a/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
+++ b/third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h
@@ -11,6 +11,15 @@
 #include "third_party/blink/renderer/platform/heap/unified_heap_marking_visitor.h"
 #include "v8/include/v8.h"
 
+namespace v8 {
+
+template <typename T>
+struct TracedGlobalTrait<v8::TracedGlobal<T>> {
+  static constexpr bool kRequiresExplicitDestruction = false;
+};
+
+}  // namespace v8
+
 namespace blink {
 
 /**
@@ -27,8 +36,6 @@
     InternalSet(isolate, handle);
   }
 
-  ~TraceWrapperV8Reference() { Clear(); }
-
   bool operator==(const TraceWrapperV8Reference& other) const {
     return handle_ == other.handle_;
   }
diff --git a/third_party/blink/renderer/platform/heap/unified_heap_controller.cc b/third_party/blink/renderer/platform/heap/unified_heap_controller.cc
index 9b12741..5fd280e 100644
--- a/third_party/blink/renderer/platform/heap/unified_heap_controller.cc
+++ b/third_party/blink/renderer/platform/heap/unified_heap_controller.cc
@@ -167,6 +167,19 @@
   return false;
 }
 
+void UnifiedHeapController::ResetHandleInNonTracingGC(
+    const v8::TracedGlobal<v8::Value>& handle) {
+  const uint16_t class_id = handle.WrapperClassId();
+  // Only consider handles that have not been treated as roots, see
+  // IsRootForNonTracingGCInternal.
+  if (class_id != WrapperTypeInfo::kNodeClassId &&
+      class_id != WrapperTypeInfo::kObjectClassId)
+    return;
+
+  const v8::TracedGlobal<v8::Object>& traced = handle.As<v8::Object>();
+  ToScriptWrappable(traced)->UnsetWrapperIfAny();
+}
+
 bool UnifiedHeapController::IsRootForNonTracingGC(
     const v8::TracedGlobal<v8::Value>& handle) {
   return IsRootForNonTracingGCInternal(handle);
diff --git a/third_party/blink/renderer/platform/heap/unified_heap_controller.h b/third_party/blink/renderer/platform/heap/unified_heap_controller.h
index 8070087..f8ca95c 100644
--- a/third_party/blink/renderer/platform/heap/unified_heap_controller.h
+++ b/third_party/blink/renderer/platform/heap/unified_heap_controller.h
@@ -46,6 +46,7 @@
   bool AdvanceTracing(double) final;
   bool IsTracingDone() final;
   bool IsRootForNonTracingGC(const v8::TracedGlobal<v8::Value>&) final;
+  void ResetHandleInNonTracingGC(const v8::TracedGlobal<v8::Value>&) final;
 
   ThreadState* thread_state() const { return thread_state_; }
 
diff --git a/content/renderer/media/webrtc/audio_codec_factory.cc b/third_party/blink/renderer/platform/peerconnection/audio_codec_factory.cc
similarity index 95%
rename from content/renderer/media/webrtc/audio_codec_factory.cc
rename to third_party/blink/renderer/platform/peerconnection/audio_codec_factory.cc
index 0a6a3c4..abbae4c11 100644
--- a/content/renderer/media/webrtc/audio_codec_factory.cc
+++ b/third_party/blink/renderer/platform/peerconnection/audio_codec_factory.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/renderer/media/webrtc/audio_codec_factory.h"
+#include "third_party/blink/public/platform/modules/peerconnection/audio_codec_factory.h"
 
 #include <memory>
 #include <vector>
@@ -22,7 +22,7 @@
 #include "third_party/webrtc/api/audio_codecs/opus/audio_encoder_multi_channel_opus.h"
 #include "third_party/webrtc/api/audio_codecs/opus/audio_encoder_opus.h"
 
-namespace content {
+namespace blink {
 
 namespace {
 
@@ -88,4 +88,4 @@
       NotAdvertisedDecoder<webrtc::AudioDecoderMultiChannelOpus>>();
 }
 
-}  // namespace content
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index a817830..cc2b679 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -957,7 +957,7 @@
     },
     {
       name: "MediaSessionSeeking",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "MediaSourceExperimental",
diff --git a/third_party/blink/renderer/platform/transforms/transformation_matrix.cc b/third_party/blink/renderer/platform/transforms/transformation_matrix.cc
index ea51d53..ba6b0227 100644
--- a/third_party/blink/renderer/platform/transforms/transformation_matrix.cc
+++ b/third_party/blink/renderer/platform/transforms/transformation_matrix.cc
@@ -1281,8 +1281,6 @@
 // prod.Multiply(rhs);
 // lhs.MapPoint(rhs.MapPoint(p)) == prod.MapPoint(p)
 // Also 'prod' corresponds to CSS transform:rotateZ(90deg)translate(12px,34px).
-// TODO(crbug.com/584508): As of 2017-04-11, the ARM64 CQ bots skip
-// blink_platform_unittests, therefore the ARM64 branch is not tested by CQ.
 TransformationMatrix& TransformationMatrix::Multiply(
     const TransformationMatrix& mat) {
 #if defined(ARCH_CPU_ARM64)
diff --git a/third_party/blink/web_tests/LeakExpectations b/third_party/blink/web_tests/LeakExpectations
index 8f599516..7826e3a 100644
--- a/third_party/blink/web_tests/LeakExpectations
+++ b/third_party/blink/web_tests/LeakExpectations
@@ -15,12 +15,6 @@
 crbug.com/410974 virtual/scroll_customization/fast/scroll-behavior/scroll-customization/touch-scroll-customization.html [ Leak ]
 crbug.com/410974 virtual/scroll_customization/fast/scroll-behavior/scroll-customization/scrollstate-distribute-to-scroll-chain-descendant.html [ Leak ]
 
-# FIXME: rootscroller/set-root-scroller.html on WebKit Linux Trusty
-crbug.com/891575 rootscroller/set-root-scroller.html [ Leak ]
-crbug.com/891575 virtual/android/rootscroller/remove-rootscroller-crash.html [ Leak Pass ]
-crbug.com/891575 virtual/android/rootscroller/set-root-scroller.html [ Leak ]
-crbug.com/891575 virtual/android/fullscreen/video-overlay-scroll.html [ Leak Pass ]
-
 crbug.com/786995 virtual/threaded/http/tests/devtools/tracing/timeline-style/timeline-style-recalc-all-invalidator-types.js [ Leak Pass ]
 crbug.com/859640 http/tests/devtools/tracing/timeline-style/timeline-style-recalc-all-invalidator-types.js  [ Leak Pass ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 47abe17f..71d52851 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5752,9 +5752,6 @@
 # Sheriff 2018-09-25
 crbug.com/888609 [ Mac ] http/tests/devtools/coverage/gutter-css.js [ Pass Timeout ]
 
-# Sheriff 2018-10-03
-crbug.com/891530 [ Linux ] virtual/android/rootscroller/remove-rootscroller-crash.html [ Pass Timeout ]
-
 # Sheriff 2018-10-09
 crbug.com/893869 [ Mac ] css3/masking/mask-repeat-space-padding.html [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/WebDriverExpectations b/third_party/blink/web_tests/WebDriverExpectations
index 68213c91e..88d1b8e 100644
--- a/third_party/blink/web_tests/WebDriverExpectations
+++ b/third_party/blink/web_tests/WebDriverExpectations
@@ -74,10 +74,7 @@
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/switch_to_frame/cross_origin.py>>test_cross_origin_iframe [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/switch_to_window/alerts.py>>test_retain_tab_modal_status [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/minimize_window/minimize.py>>test_fully_exit_fullscreen [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting2-denied] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/perform_actions/pointer_contextmenu.py>>test_control_click[\ue009-ctrlKey] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_non_secure_context[denied] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_invalid_parameters[parameters2] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/element_send_keys/interactability.py>>test_document_element_is_interactable [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/stress.py>>test_stress[4] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/element_click/bubbling.py>>test_spin_event_loop [ Failure ]
@@ -85,15 +82,11 @@
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/element_send_keys/content_editable.py>>test_sets_insertion_point_to_after_last_text_node [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_ignore[capabilities0-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/new_session/create_alwaysMatch.py>>test_valid[pageLoadStrategy-eager] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_non_secure_context[granted] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_dismiss[capabilities0-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/execute_script/json_serialize_windowproxy.py>>test_window_open [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_accept[capabilities0-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/element_click/file_upload.py>>test_file_upload_state [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting2-granted] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_invalid_parameters[parameters1] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/maximize_window/maximize.py>>test_fully_exit_fullscreen [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state_cross_realm[realmSetting1-granted] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_ignore[capabilities0-confirm] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/user_prompts.py>>test_accept[capabilities0-prompt-] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/fullscreen.py>>test_fullscreen [ Failure ]
@@ -103,30 +96,23 @@
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_accept[capabilities0-confirm] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/set_window_rect/set.py>>test_width_height_floats [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/new_session/merge.py>>test_merge_platformName [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting0-denied] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_accept_and_notify[capabilities0-alert] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_default[confirm] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/set_window_rect/set.py>>test_restore_from_minimized [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/element_send_keys/scroll_into_view.py>>test_option_stays_outside_of_scrollable_viewport [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting1-granted] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/perform_actions/key_events.py>>test_special_key_sends_keydown[META-expected11] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/element_send_keys/content_editable.py>>test_sets_insertion_point_to_end [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_invalid_parameters[parameters0] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/perform_actions/key_special_keys.py>>test_codepoint_keys_behave_correctly[\u1100\u1161\u11a8] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/status/status.py>>test_status_with_session_running_on_endpoint_node [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/element_send_keys/scroll_into_view.py>>test_option_select_container_outside_of_scrollable_viewport [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_dismiss[capabilities0-alert] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state_cross_realm[realmSetting1-denied] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/user_prompts.py>>test_dismiss[capabilities0-alert-None] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/maximize_window/maximize.py>>test_maximize_when_resized_to_max_size [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_accept_and_notify[capabilities0-alert] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state_cross_realm[realmSetting0-denied] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/stress.py>>test_stress[1] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_default[alert] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_default[prompt] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting0-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_ignore[capabilities0-alert] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_invalid_parameters[parameters7] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_dismiss_and_notify[capabilities0-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/perform_actions/key_events.py>>test_modifier_key_sends_correct_events[\ue03d-META] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_accept[capabilities0-prompt] [ Failure ]
@@ -138,12 +124,10 @@
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/stress.py>>test_stress[0] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/set_window_rect/set.py>>test_restore_from_maximized [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_dismiss_and_notify[capabilities0-confirm] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_invalid_parameters[parameters6] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_accept[capabilities0-alert] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_default[prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_dismiss[capabilities0-alert] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_accept_and_notify[capabilities0-prompt] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_non_secure_context[prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_dismiss_and_notify[capabilities0-alert] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_accept_and_notify[capabilities0-confirm] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/user_prompts.py>>test_dismiss[capabilities0-confirm-False] [ Failure ]
@@ -153,32 +137,24 @@
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/stress.py>>test_stress[3] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_dismiss[capabilities0-confirm] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_dismiss[capabilities0-prompt] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_invalid_parameters[parameters5] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting1-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/execute_script/json_serialize_windowproxy.py>>test_frame [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/set_window_rect/set.py>>test_fully_exit_fullscreen [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state_cross_realm[realmSetting0-granted] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/user_prompts.py>>test_dismiss[capabilities0-prompt-None] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_dismiss[capabilities0-confirm] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_accept[capabilities0-alert] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/stress.py>>test_stress[2] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_default[alert] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/perform_actions/pointer_contextmenu.py>>test_control_click[\ue051-ctrlKey] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_invalid_parameters[parameters4] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting1-denied] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/new_session/create_firstMatch.py>>test_valid[pageLoadStrategy-eager] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting0-granted] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_ignore[capabilities0-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/user_prompts.py>>test_accept[capabilities0-alert-None] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/new_session/page_load_strategy.py>>test_pageLoadStrategy [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/perform_actions/key_special_keys.py>>test_codepoint_keys_behave_correctly[\u0ba8\u0bbf] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state_cross_realm[realmSetting1-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_accept_and_notify[capabilities0-prompt] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/fullscreen_window/fullscreen.py>>test_fullscreen_twice_is_idempotent [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/navigate_to/navigate.py>>test_file_protocol [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/get_current_url/get.py>>test_get_current_url_file_protocol [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/execute_script/json_serialize_windowproxy.py>>test_initial_window [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_invalid_parameters[parameters3] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/set_timeouts/set.py>>test_parameters_unknown_fields[value1] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/new_session/default_values.py>>test_valid_but_unmatchable_key [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_dismiss_and_notify[capabilities0-confirm] [ Failure ]
@@ -187,5 +163,3 @@
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/new_session/default_values.py>>test_desired [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_screenshot/user_prompts.py>>test_dismiss_and_notify[capabilities0-alert] [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/take_element_screenshot/user_prompts.py>>test_ignore[capabilities0-alert] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state[realmSetting2-prompt] [ Failure ]
-crbug.com/626703 [ Linux ] external/wpt/webdriver/tests/permissions/set.py>>test_set_to_state_cross_realm[realmSetting0-prompt] [ Failure ]
diff --git a/third_party/blink/web_tests/animations/events/play-state-initially-paused-start-event.html b/third_party/blink/web_tests/animations/events/play-state-initially-paused-start-event.html
index 3eb52bd..7a32461 100644
--- a/third_party/blink/web_tests/animations/events/play-state-initially-paused-start-event.html
+++ b/third_party/blink/web_tests/animations/events/play-state-initially-paused-start-event.html
@@ -10,28 +10,18 @@
   .animated {
     animation: test 10ms linear forwards;
     animation-play-state: paused;
-    animation: test 10ms linear forwards;
-    animation-play-state: paused;
   }
   .running {
     animation-play-state: running;
-    animation-play-state: running;
   }
   #animation1 {
     animation-delay: -10ms;
-    animation-delay: -10ms;
   }
   #animation2 {
     animation-delay: 0ms;
-    animation-delay: 0ms;
   }
   #animation3 {
     animation-delay: 10ms;
-    animation-delay: 10ms;
-  }
-  @keyframes test {
-      from { left: 100px; }
-      to   { left: 300px; }
   }
   @keyframes test {
       from { left: 100px; }
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 5a307939..e28d95f 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
@@ -210486,6 +210486,12 @@
      {}
     ]
    ],
+   "css/css-position/position-absolute-crash-chrome-012.html": [
+    [
+     "css/css-position/position-absolute-crash-chrome-012.html",
+     {}
+    ]
+   ],
    "css/css-position/position-absolute-dynamic-containing-block.html": [
     [
      "css/css-position/position-absolute-dynamic-containing-block.html",
@@ -377631,6 +377637,10 @@
    "fd3d63e0cf749d315d06e4fe5067f0840d8c64dd",
    "testharness"
   ],
+  "css/css-position/position-absolute-crash-chrome-012.html": [
+   "f012ff572691d9c42fb8f642a2fa47e524a4cacf",
+   "testharness"
+  ],
   "css/css-position/position-absolute-dynamic-containing-block.html": [
    "3968f685849663574ca213fcb90dc5fb3eaffaa3",
    "testharness"
@@ -433512,19 +433522,19 @@
    "testharness"
   ],
   "html/dom/interfaces.https_exclude=(Document_Window_HTML._)-expected.txt": [
-   "939f4a8910111f0dbff650e4f31ec6df03c4c9c5",
+   "7809b42a567d4bf1d21bee059b907e59cbdc04bc",
    "support"
   ],
   "html/dom/interfaces.https_include=(Document_Window)-expected.txt": [
-   "2c6bb18172e8c73cf7a0e1f0cfe53f2e9095c454",
+   "8213dc41405702cd91b1654923ed051ca539d772",
    "support"
   ],
   "html/dom/interfaces.https_include=HTML._-expected.txt": [
-   "629d77c5d31e0c32c12742965c027cf3d6e1c981",
+   "ff6b6aaa149a80e805cbdfb62dfe3efbb2530613",
    "support"
   ],
   "html/dom/interfaces.worker-expected.txt": [
-   "58625f68c01e2a5f9a56c5b7d7b5dc3a2a0536df",
+   "e9c4f2ff8d1dcb08b2f29d64682828d6e4c08595",
    "support"
   ],
   "html/dom/interfaces.worker.js": [
@@ -448868,7 +448878,7 @@
    "manual"
   ],
   "html/webappapis/scripting/events/event-handler-all-global-events-expected.txt": [
-   "5c6e681e331ba0c23fa44a285fb03a1cdb7fed59",
+   "d36897687510194fefa9bf509bdb18a9582ff3a3",
    "support"
   ],
   "html/webappapis/scripting/events/event-handler-all-global-events.html": [
@@ -448876,7 +448886,7 @@
    "testharness"
   ],
   "html/webappapis/scripting/events/event-handler-attributes-body-window-expected.txt": [
-   "9ef4fed55251ad7139c1ce59895df1a0a8f66542",
+   "e317caa2eed655788bc1829e5d9fe136a1a958cb",
    "support"
   ],
   "html/webappapis/scripting/events/event-handler-attributes-body-window.html": [
@@ -448884,7 +448894,7 @@
    "testharness"
   ],
   "html/webappapis/scripting/events/event-handler-attributes-frameset-window-expected.txt": [
-   "e8c44ac24aeeac39dfaf89bbc0d83084cc958cb1",
+   "c4e1cb872f0b7645b46d61ffc9d1011a7d235e00",
    "support"
   ],
   "html/webappapis/scripting/events/event-handler-attributes-frameset-window.html": [
@@ -448896,7 +448906,7 @@
    "support"
   ],
   "html/webappapis/scripting/events/event-handler-attributes-windowless-body-expected.txt": [
-   "5cc992f0c4ffe4606ff6dbd905071be2d39cbba7",
+   "9ce3c849d501f9042a3d4b59ca860c55707327c6",
    "support"
   ],
   "html/webappapis/scripting/events/event-handler-attributes-windowless-body.html": [
@@ -450812,7 +450822,7 @@
    "support"
   ],
   "interfaces/html.idl": [
-   "6dfb39215d0fec30d56027b16cc1c5b620eba0bc",
+   "98c633af212238cb3507dba7f7a53fee6299aab0",
    "support"
   ],
   "interfaces/image-capture.idl": [
@@ -494680,7 +494690,7 @@
    "wdspec"
   ],
   "webdriver/tests/permissions/set.py": [
-   "fe16599a1e88a3931a857cea0a86313c3ef0541c",
+   "7fb7fa20c98406eb34379c39764c205c1d9e8530",
    "wdspec"
   ],
   "webdriver/tests/refresh/__init__.py": [
diff --git "a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_exclude=\050Document_Window_HTML._\051-expected.txt" "b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_exclude=\050Document_Window_HTML._\051-expected.txt"
index 939f4a89..7809b42 100644
--- "a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_exclude=\050Document_Window_HTML._\051-expected.txt"
+++ "b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_exclude=\050Document_Window_HTML._\051-expected.txt"
@@ -1,7 +1,8 @@
 This is a testharness.js-based test.
-Found 1269 tests; 1221 PASS, 48 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 1270 tests; 1223 PASS, 47 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS Partial interface Document: original interface defined
+PASS Partial interface mixin DocumentOrShadowRoot: original interface mixin defined
 PASS Partial interface mixin NavigatorID: original interface mixin defined
 PASS Partial interface HTMLAnchorElement: original interface defined
 PASS Partial interface HTMLAreaElement: original interface defined
@@ -1204,7 +1205,6 @@
 PASS SVGElement interface: attribute onload
 PASS SVGElement interface: attribute onloadeddata
 PASS SVGElement interface: attribute onloadedmetadata
-FAIL SVGElement interface: attribute onloadend assert_true: The prototype object must have a property "onloadend" expected true got false
 PASS SVGElement interface: attribute onloadstart
 PASS SVGElement interface: attribute onmousedown
 PASS SVGElement interface: attribute onmouseenter
@@ -1269,5 +1269,6 @@
 FAIL SVGAElement interface: attribute pathname assert_true: The prototype object must have a property "pathname" expected true got false
 FAIL SVGAElement interface: attribute search assert_true: The prototype object must have a property "search" expected true got false
 FAIL SVGAElement interface: attribute hash assert_true: The prototype object must have a property "hash" expected true got false
+PASS ShadowRoot interface: attribute activeElement
 Harness: the test ran to completion.
 
diff --git "a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=\050Document_Window\051-expected.txt" "b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=\050Document_Window\051-expected.txt"
index 2c6bb18..8213dc41 100644
--- "a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=\050Document_Window\051-expected.txt"
+++ "b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=\050Document_Window\051-expected.txt"
@@ -1,7 +1,8 @@
 This is a testharness.js-based test.
-Found 808 tests; 778 PASS, 30 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 803 tests; 779 PASS, 24 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS Partial interface Document: original interface defined
+PASS Partial interface mixin DocumentOrShadowRoot: original interface mixin defined
 PASS Partial interface mixin NavigatorID: original interface mixin defined
 PASS Partial interface HTMLAnchorElement: original interface defined
 PASS Partial interface HTMLAreaElement: original interface defined
@@ -117,7 +118,6 @@
 PASS Window interface: attribute onload
 PASS Window interface: attribute onloadeddata
 PASS Window interface: attribute onloadedmetadata
-FAIL Window interface: attribute onloadend assert_own_property: The global object must have a property "onloadend" expected property "onloadend" missing
 PASS Window interface: attribute onloadstart
 PASS Window interface: attribute onmousedown
 PASS Window interface: attribute onmouseenter
@@ -262,7 +262,6 @@
 PASS Window interface: window must inherit property "onload" with the proper type
 PASS Window interface: window must inherit property "onloadeddata" with the proper type
 PASS Window interface: window must inherit property "onloadedmetadata" with the proper type
-FAIL Window interface: window must inherit property "onloadend" with the proper type assert_own_property: expected property "onloadend" missing
 PASS Window interface: window must inherit property "onloadstart" with the proper type
 PASS Window interface: window must inherit property "onmousedown" with the proper type
 PASS Window interface: window must inherit property "onmouseenter" with the proper type
@@ -355,7 +354,6 @@
 PASS Document interface: operation write(DOMString)
 PASS Document interface: operation writeln(DOMString)
 PASS Document interface: attribute defaultView
-PASS Document interface: attribute activeElement
 PASS Document interface: operation hasFocus()
 PASS Document interface: attribute designMode
 PASS Document interface: operation execCommand(DOMString, boolean, DOMString)
@@ -410,7 +408,6 @@
 PASS Document interface: attribute onload
 PASS Document interface: attribute onloadeddata
 PASS Document interface: attribute onloadedmetadata
-FAIL Document interface: attribute onloadend assert_true: The prototype object must have a property "onloadend" expected true got false
 PASS Document interface: attribute onloadstart
 PASS Document interface: attribute onmousedown
 PASS Document interface: attribute onmouseenter
@@ -442,6 +439,7 @@
 PASS Document interface: attribute oncopy
 PASS Document interface: attribute oncut
 PASS Document interface: attribute onpaste
+PASS Document interface: attribute activeElement
 PASS Document interface: iframe.contentDocument must have own property "location"
 PASS Document interface: iframe.contentDocument must inherit property "domain" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "referrer" with the proper type
@@ -471,7 +469,6 @@
 PASS Document interface: iframe.contentDocument must inherit property "writeln(DOMString)" with the proper type
 PASS Document interface: calling writeln(DOMString) on iframe.contentDocument with too few arguments must throw TypeError
 FAIL Document interface: iframe.contentDocument must inherit property "defaultView" with the proper type Unrecognized type WindowProxy
-PASS Document interface: iframe.contentDocument must inherit property "activeElement" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "hasFocus()" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "designMode" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "execCommand(DOMString, boolean, DOMString)" with the proper type
@@ -532,7 +529,6 @@
 PASS Document interface: iframe.contentDocument must inherit property "onload" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "onloadeddata" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "onloadedmetadata" with the proper type
-FAIL Document interface: iframe.contentDocument must inherit property "onloadend" with the proper type assert_inherits: property "onloadend" not found in prototype chain
 PASS Document interface: iframe.contentDocument must inherit property "onloadstart" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "onmousedown" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "onmouseenter" with the proper type
@@ -564,6 +560,7 @@
 PASS Document interface: iframe.contentDocument must inherit property "oncopy" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "oncut" with the proper type
 PASS Document interface: iframe.contentDocument must inherit property "onpaste" with the proper type
+PASS Document interface: iframe.contentDocument must inherit property "activeElement" with the proper type
 PASS Document interface: new Document() must have own property "location"
 PASS Document interface: new Document() must inherit property "domain" with the proper type
 PASS Document interface: new Document() must inherit property "referrer" with the proper type
@@ -593,7 +590,6 @@
 PASS Document interface: new Document() must inherit property "writeln(DOMString)" with the proper type
 PASS Document interface: calling writeln(DOMString) on new Document() with too few arguments must throw TypeError
 PASS Document interface: new Document() must inherit property "defaultView" with the proper type
-PASS Document interface: new Document() must inherit property "activeElement" with the proper type
 PASS Document interface: new Document() must inherit property "hasFocus()" with the proper type
 PASS Document interface: new Document() must inherit property "designMode" with the proper type
 PASS Document interface: new Document() must inherit property "execCommand(DOMString, boolean, DOMString)" with the proper type
@@ -654,7 +650,6 @@
 PASS Document interface: new Document() must inherit property "onload" with the proper type
 PASS Document interface: new Document() must inherit property "onloadeddata" with the proper type
 PASS Document interface: new Document() must inherit property "onloadedmetadata" with the proper type
-FAIL Document interface: new Document() must inherit property "onloadend" with the proper type assert_inherits: property "onloadend" not found in prototype chain
 PASS Document interface: new Document() must inherit property "onloadstart" with the proper type
 PASS Document interface: new Document() must inherit property "onmousedown" with the proper type
 PASS Document interface: new Document() must inherit property "onmouseenter" with the proper type
@@ -686,6 +681,7 @@
 PASS Document interface: new Document() must inherit property "oncopy" with the proper type
 PASS Document interface: new Document() must inherit property "oncut" with the proper type
 PASS Document interface: new Document() must inherit property "onpaste" with the proper type
+PASS Document interface: new Document() must inherit property "activeElement" with the proper type
 PASS Document interface: documentWithHandlers must have own property "location"
 PASS Document interface: documentWithHandlers must inherit property "domain" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "referrer" with the proper type
@@ -715,7 +711,6 @@
 PASS Document interface: documentWithHandlers must inherit property "writeln(DOMString)" with the proper type
 PASS Document interface: calling writeln(DOMString) on documentWithHandlers with too few arguments must throw TypeError
 PASS Document interface: documentWithHandlers must inherit property "defaultView" with the proper type
-PASS Document interface: documentWithHandlers must inherit property "activeElement" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "hasFocus()" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "designMode" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "execCommand(DOMString, boolean, DOMString)" with the proper type
@@ -776,7 +771,6 @@
 PASS Document interface: documentWithHandlers must inherit property "onload" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "onloadeddata" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "onloadedmetadata" with the proper type
-FAIL Document interface: documentWithHandlers must inherit property "onloadend" with the proper type assert_inherits: property "onloadend" found on object expected in prototype chain
 PASS Document interface: documentWithHandlers must inherit property "onloadstart" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "onmousedown" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "onmouseenter" with the proper type
@@ -808,5 +802,6 @@
 PASS Document interface: documentWithHandlers must inherit property "oncopy" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "oncut" with the proper type
 PASS Document interface: documentWithHandlers must inherit property "onpaste" with the proper type
+PASS Document interface: documentWithHandlers must inherit property "activeElement" with the proper type
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=HTML._-expected.txt b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=HTML._-expected.txt
index 629d77c..ff6b6aa 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=HTML._-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_include=HTML._-expected.txt
@@ -1,7 +1,8 @@
 This is a testharness.js-based test.
-Found 3618 tests; 3578 PASS, 40 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 3617 tests; 3579 PASS, 38 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS Partial interface Document: original interface defined
+PASS Partial interface mixin DocumentOrShadowRoot: original interface mixin defined
 PASS Partial interface mixin NavigatorID: original interface mixin defined
 PASS Partial interface HTMLAnchorElement: original interface defined
 PASS Partial interface HTMLAreaElement: original interface defined
@@ -135,7 +136,6 @@
 PASS HTMLElement interface: attribute onload
 PASS HTMLElement interface: attribute onloadeddata
 PASS HTMLElement interface: attribute onloadedmetadata
-FAIL HTMLElement interface: attribute onloadend assert_true: The prototype object must have a property "onloadend" expected true got false
 PASS HTMLElement interface: attribute onloadstart
 PASS HTMLElement interface: attribute onmousedown
 PASS HTMLElement interface: attribute onmouseenter
@@ -225,7 +225,6 @@
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "onload" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "onloadeddata" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "onloadedmetadata" with the proper type
-FAIL HTMLElement interface: document.createElement("noscript") must inherit property "onloadend" with the proper type assert_inherits: property "onloadend" not found in prototype chain
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "onloadstart" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "onmousedown" with the proper type
 PASS HTMLElement interface: document.createElement("noscript") must inherit property "onmouseenter" with the proper type
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.worker-expected.txt
index 58625f68..e9c4f2f 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.worker-expected.txt
@@ -1,7 +1,8 @@
 This is a testharness.js-based test.
-Found 660 tests; 634 PASS, 26 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 662 tests; 636 PASS, 26 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS Partial interface Document: original interface defined
+PASS Partial interface mixin DocumentOrShadowRoot: original interface mixin defined
 PASS Partial interface mixin NavigatorID: original interface mixin defined
 PASS Partial interface HTMLAnchorElement: original interface defined
 PASS Partial interface HTMLAreaElement: original interface defined
@@ -656,6 +657,7 @@
 PASS Node interface: existence and properties of interface object
 PASS Document interface: existence and properties of interface object
 PASS DocumentFragment interface: existence and properties of interface object
+PASS ShadowRoot interface: existence and properties of interface object
 PASS Element interface: existence and properties of interface object
 PASS DOMTokenList interface: existence and properties of interface object
 PASS UIEvent interface: existence and properties of interface object
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-all-global-events-expected.txt b/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-all-global-events-expected.txt
index 5c6e681e..d3689768 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-all-global-events-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-all-global-events-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 310 tests; 298 PASS, 12 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 305 tests; 297 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS onabort: must be on the appropriate locations for GlobalEventHandlers
 PASS onabort: the default value must be null
 PASS onabort: the content attribute must be compiled into a function as the corresponding property
@@ -165,11 +165,6 @@
 PASS onloadedmetadata: the content attribute must be compiled into a function as the corresponding property
 PASS onloadedmetadata: the content attribute must execute when an event is dispatched
 PASS onloadedmetadata: dispatching an Event at a <meta> element must trigger element.onloadedmetadata
-FAIL onloadend: must be on the appropriate locations for GlobalEventHandlers assert_true: Window has an own property named "onloadend" expected true got false
-FAIL onloadend: the default value must be null assert_equals: The default value of the property is null for a Window instance expected (object) null but got (undefined) undefined
-FAIL onloadend: the content attribute must be compiled into a function as the corresponding property assert_equals: The onloadend property must be a function expected "function" but got "undefined"
-FAIL onloadend: the content attribute must execute when an event is dispatched assert_true: Dispatching an event must run the code expected true got undefined
-PASS onloadend: dispatching an Event at a <meta> element must trigger element.onloadend
 PASS onloadstart: must be on the appropriate locations for GlobalEventHandlers
 PASS onloadstart: the default value must be null
 PASS onloadstart: the content attribute must be compiled into a function as the corresponding property
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-body-window-expected.txt b/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-body-window-expected.txt
index 9ef4fed..e317caa 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-body-window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-body-window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 312 tests; 297 PASS, 15 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 309 tests; 297 PASS, 12 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS shadowed blur (document.body)
 PASS shadowed error (document.body)
 PASS shadowed focus (document.body)
@@ -52,7 +52,6 @@
 PASS not shadowed keyup (document.body)
 PASS not shadowed loadeddata (document.body)
 PASS not shadowed loadedmetadata (document.body)
-FAIL not shadowed loadend (document.body) assert_equals: alternative body should reflect expected (object) null but got (undefined) undefined
 PASS not shadowed loadstart (document.body)
 PASS not shadowed mousedown (document.body)
 PASS not shadowed mouseenter (document.body)
@@ -156,7 +155,6 @@
 PASS not shadowed keyup (document.createElement("body"))
 PASS not shadowed loadeddata (document.createElement("body"))
 PASS not shadowed loadedmetadata (document.createElement("body"))
-FAIL not shadowed loadend (document.createElement("body")) assert_equals: body should reflect expected (object) null but got (undefined) undefined
 PASS not shadowed loadstart (document.createElement("body"))
 PASS not shadowed mousedown (document.createElement("body"))
 PASS not shadowed mouseenter (document.createElement("body"))
@@ -260,7 +258,6 @@
 PASS not shadowed keyup (window)
 PASS not shadowed loadeddata (window)
 PASS not shadowed loadedmetadata (window)
-FAIL not shadowed loadend (window) assert_equals: body should reflect expected (object) null but got (undefined) undefined
 PASS not shadowed loadstart (window)
 PASS not shadowed mousedown (window)
 PASS not shadowed mouseenter (window)
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-frameset-window-expected.txt b/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-frameset-window-expected.txt
index e8c44ac2..c4e1cb8 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-frameset-window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-frameset-window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 312 tests; 297 PASS, 15 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 309 tests; 297 PASS, 12 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS shadowed blur (document.body)
 PASS shadowed error (document.body)
 PASS shadowed focus (document.body)
@@ -52,7 +52,6 @@
 PASS not shadowed keyup (document.body)
 PASS not shadowed loadeddata (document.body)
 PASS not shadowed loadedmetadata (document.body)
-FAIL not shadowed loadend (document.body) assert_equals: alternative body should reflect expected (object) null but got (undefined) undefined
 PASS not shadowed loadstart (document.body)
 PASS not shadowed mousedown (document.body)
 PASS not shadowed mouseenter (document.body)
@@ -156,7 +155,6 @@
 PASS not shadowed keyup (document.createElement("frameset"))
 PASS not shadowed loadeddata (document.createElement("frameset"))
 PASS not shadowed loadedmetadata (document.createElement("frameset"))
-FAIL not shadowed loadend (document.createElement("frameset")) assert_equals: body should reflect expected (object) null but got (undefined) undefined
 PASS not shadowed loadstart (document.createElement("frameset"))
 PASS not shadowed mousedown (document.createElement("frameset"))
 PASS not shadowed mouseenter (document.createElement("frameset"))
@@ -260,7 +258,6 @@
 PASS not shadowed keyup (window)
 PASS not shadowed loadeddata (window)
 PASS not shadowed loadedmetadata (window)
-FAIL not shadowed loadend (window) assert_equals: body should reflect expected (object) null but got (undefined) undefined
 PASS not shadowed loadstart (window)
 PASS not shadowed mousedown (window)
 PASS not shadowed mouseenter (window)
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-windowless-body-expected.txt b/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-windowless-body-expected.txt
index 5cc992f..9ce3c84 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-windowless-body-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/events/event-handler-attributes-windowless-body-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 208 tests; 202 PASS, 6 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 206 tests; 202 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Return null when getting the blur event handler of a windowless body
 PASS Ignore setting of blur window event handlers on windowless body
 PASS Return null when getting the error event handler of a windowless body
@@ -74,7 +74,6 @@
 PASS keyup is unaffected on a windowless body
 PASS loadeddata is unaffected on a windowless body
 PASS loadedmetadata is unaffected on a windowless body
-FAIL loadend is unaffected on a windowless body assert_equals: expected (object) null but got (undefined) undefined
 PASS loadstart is unaffected on a windowless body
 PASS mousedown is unaffected on a windowless body
 PASS mouseenter is unaffected on a windowless body
@@ -178,7 +177,6 @@
 PASS keyup is unaffected on a windowless frameset
 PASS loadeddata is unaffected on a windowless frameset
 PASS loadedmetadata is unaffected on a windowless frameset
-FAIL loadend is unaffected on a windowless frameset assert_equals: expected (object) null but got (undefined) undefined
 PASS loadstart is unaffected on a windowless frameset
 PASS mousedown is unaffected on a windowless frameset
 PASS mouseenter is unaffected on a windowless frameset
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/html.idl b/third_party/blink/web_tests/external/wpt/interfaces/html.idl
index 6dfb3921..98c633af 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/html.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/html.idl
@@ -79,7 +79,6 @@
 
   // user interaction
   readonly attribute WindowProxy? defaultView;
-  readonly attribute Element? activeElement;
   boolean hasFocus();
   [CEReactions] attribute DOMString designMode;
   [CEReactions] boolean execCommand(DOMString commandId, optional boolean showUI = false, optional DOMString value = "");
@@ -97,6 +96,10 @@
 Document includes GlobalEventHandlers;
 Document includes DocumentAndElementEventHandlers;
 
+partial interface mixin DocumentOrShadowRoot {
+  readonly attribute Element? activeElement;
+};
+
 [Exposed=Window,
  HTMLConstructor]
 interface HTMLElement : Element {
@@ -1602,7 +1605,7 @@
   attribute any opener;
   [Replaceable] readonly attribute WindowProxy? parent;
   readonly attribute Element? frameElement;
-  WindowProxy? open(optional USVString url = "about:blank", optional DOMString target = "_blank", optional [TreatNullAs=EmptyString] DOMString features = "");
+  WindowProxy? open(optional USVString url = "", optional DOMString target = "_blank", optional [TreatNullAs=EmptyString] DOMString features = "");
   getter object (DOMString name);
   // Since this is the global object, the IDL named getter adds a NamedPropertiesObject exotic
   // object on the prototype chain. Indeed, this does not make the global object an exotic object.
@@ -1813,7 +1816,6 @@
   attribute EventHandler onload;
   attribute EventHandler onloadeddata;
   attribute EventHandler onloadedmetadata;
-  attribute EventHandler onloadend;
   attribute EventHandler onloadstart;
   attribute EventHandler onmousedown;
   [LenientThis] attribute EventHandler onmouseenter;
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/permissions/set.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/permissions/set.py
index fe16599..7fb7fa2 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/permissions/set.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/permissions/set.py
@@ -30,7 +30,8 @@
     [ { "descriptor": { "name": "geolocation" }, "state": "granted" } ],
     { "descriptor": { "name": "geolocation" }, "state": "granted", "oneRealm": 23 }
 ])
-def test_invalid_parameters(session, parameters):
+def test_invalid_parameters(session, url, parameters):
+    session.url = url("/common/blank.html", protocol="https")
     response = session.transport.send(
         "POST",
         "/session/{session_id}/permissions".format(**vars(session)),
@@ -57,7 +58,8 @@
     { "oneRealm": False },
     {}
 ])
-def test_set_to_state(session, state, realmSetting):
+def test_set_to_state(session, url, state, realmSetting):
+    session.url = url("/common/blank.html", protocol="https")
     parameters = { "descriptor": { "name": "geolocation" }, "state": state }
     parameters.update(realmSetting)
     response = session.transport.send(
@@ -95,9 +97,11 @@
     { "oneRealm": False },
     {}
 ])
-def test_set_to_state_cross_realm(session, create_window, state, realmSetting):
+def test_set_to_state_cross_realm(session, create_window, url, state, realmSetting):
+    session.url = url("/common/blank.html", protocol="https")
     original_window = session.window_handle
     session.window_handle = create_window()
+    session.url = url("/common/blank.html", protocol="https")
     parameters = { "descriptor": { "name": "geolocation" }, "state": state }
     parameters.update(realmSetting)
 
diff --git a/third_party/blink/web_tests/external/wpt/xhr/send-redirect-bogus-sync.htm b/third_party/blink/web_tests/external/wpt/xhr/send-redirect-bogus-sync.htm
index 89e6ff0..a9dc73e 100644
--- a/third_party/blink/web_tests/external/wpt/xhr/send-redirect-bogus-sync.htm
+++ b/third_party/blink/web_tests/external/wpt/xhr/send-redirect-bogus-sync.htm
@@ -17,9 +17,9 @@
         }, document.title + " (" + code + ": " + location + ")")
       }
       redirect("301", "foobar://abcd")
-      redirect("302", "http://z")
+      redirect("302", "http://z.")
       redirect("302", "mailto:someone@example.org")
-      redirect("303", "http://z")
+      redirect("303", "http://z.")
       redirect("303", "tel:1234567890")
     </script>
   </body>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
index 2dd9d967..ade00e1 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/inspector-protocol-test.js
@@ -413,66 +413,6 @@
   }
 };
 
-// TODO(johannes): Delete this (can't do it right now since I have
-// 2 PRs going in that remove the last usages).
-class WorkerProtocol {
-  constructor(dp, sessionId) {
-    this._sessionId = sessionId;
-    this._callbacks = new Map();
-    this._dp = dp;
-    this._dp.Target.onReceivedMessageFromTarget(
-        (message) => this._onMessage(message));
-    this.dp = this._setupProtocol();
-  }
-
-  _setupProtocol() {
-    let lastId = 0;
-    return new Proxy({}, {
-      get: (target, agentName, receiver) => new Proxy({}, {
-        get: (target, methodName, receiver) => {
-          const eventPattern = /^(once)?([A-Z][A-Za-z0-9]*)/;
-          var match = eventPattern.exec(methodName);
-          if (!match || match[1] !== 'once') {
-            return args => new Promise(resolve => {
-                     let id = ++lastId;
-                     this._callbacks.set(id, resolve);
-                     this._dp.Target.sendMessageToTarget({
-                       sessionId: this._sessionId,
-                       message: JSON.stringify({
-                         method: `${agentName}.${methodName}`,
-                         params: args || {},
-                         id: id
-                       })
-                     });
-                   });
-          }
-          var eventName = match[2];
-          eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1);
-          return () => new Promise(resolve => {
-                   this._callbacks.set(`${agentName}.${eventName}`, resolve);
-                 });
-        }
-      })
-    });
-  }
-
-  _onMessage(message) {
-    if (message.params.sessionId !== this._sessionId)
-      return;
-    const {id, result, method, params} = JSON.parse(message.params.message);
-    if (id && this._callbacks.has(id)) {
-      let callback = this._callbacks.get(id);
-      this._callbacks.delete(id);
-      callback(result);
-    }
-    if (method && this._callbacks.has(method)) {
-      let callback = this._callbacks.get(method);
-      this._callbacks.delete(method);
-      callback(params);
-    }
-  }
-};
-
 var DevToolsAPI = {};
 DevToolsAPI._requestId = 0;
 DevToolsAPI._embedderMessageId = 0;
diff --git a/third_party/blink/web_tests/rootscroller/remove-rootscroller-crash.html b/third_party/blink/web_tests/rootscroller/remove-rootscroller-crash.html
deleted file mode 100644
index cfc5f64..0000000
--- a/third_party/blink/web_tests/rootscroller/remove-rootscroller-crash.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--No DOCTYPE as the crash only occured without it-->
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script>
-  // This test passes if it doesn't crash.
-  requestAnimationFrame(function () {
-    document.addEventListener('DOMFocusOut', function () { document.open(); });
-    document.designMode = "on";
-    document.execCommand("SelectAll");
-    document.body.outerText = "ABC";
-  });
-</script>
-
-<style>
-  body {
-    margin: 0px;
-  }
-  #rootscroller {
-    height: 100%;
-    overflow: auto;
-  }
-</style>
-<div id="rootscroller">
-<script>
-  var rootScroller = document.querySelector('#rootscroller');
-  document.rootScroller = rootScroller;
-  test(function() {
-      assert_false(typeof document.rootScroller === 'undefined');
-  }, "Didn't crash");
-</script>
diff --git a/third_party/boringssl/err_data.c b/third_party/boringssl/err_data.c
index fee7d1d..33570a0 100644
--- a/third_party/boringssl/err_data.c
+++ b/third_party/boringssl/err_data.c
@@ -189,44 +189,44 @@
     0x283480ac,
     0x283500ea,
     0x28358c6c,
-    0x2c322ed8,
+    0x2c322ef2,
     0x2c3292ce,
-    0x2c332ee6,
-    0x2c33aef8,
-    0x2c342f0c,
-    0x2c34af1e,
-    0x2c352f39,
-    0x2c35af4b,
-    0x2c362f5e,
+    0x2c332f00,
+    0x2c33af12,
+    0x2c342f26,
+    0x2c34af38,
+    0x2c352f53,
+    0x2c35af65,
+    0x2c362f78,
     0x2c36832d,
-    0x2c372f6b,
-    0x2c37af7d,
-    0x2c382fa2,
-    0x2c38afb9,
-    0x2c392fc7,
-    0x2c39afd7,
-    0x2c3a2fe9,
-    0x2c3aaffd,
-    0x2c3b300e,
-    0x2c3bb02d,
+    0x2c372f85,
+    0x2c37af97,
+    0x2c382fbc,
+    0x2c38afd3,
+    0x2c392fe1,
+    0x2c39aff1,
+    0x2c3a3003,
+    0x2c3ab017,
+    0x2c3b3028,
+    0x2c3bb047,
     0x2c3c12e0,
     0x2c3c92f6,
-    0x2c3d3041,
+    0x2c3d305b,
     0x2c3d930f,
-    0x2c3e305e,
-    0x2c3eb06c,
-    0x2c3f3084,
-    0x2c3fb09c,
-    0x2c4030c6,
+    0x2c3e3078,
+    0x2c3eb086,
+    0x2c3f309e,
+    0x2c3fb0b6,
+    0x2c4030e0,
     0x2c4091e1,
-    0x2c4130d7,
-    0x2c41b0ea,
+    0x2c4130f1,
+    0x2c41b104,
     0x2c4211a7,
-    0x2c42b0fb,
+    0x2c42b115,
     0x2c430722,
-    0x2c43b01f,
-    0x2c442f90,
-    0x2c44b0a9,
+    0x2c43b039,
+    0x2c442faa,
+    0x2c44b0c3,
     0x30320000,
     0x30328015,
     0x3033001f,
@@ -419,182 +419,183 @@
     0x404d9f0c,
     0x404e1f20,
     0x404e9f2d,
-    0x404f1f5a,
-    0x404f9fa0,
-    0x40501ff7,
-    0x4050a00b,
-    0x4051203e,
-    0x4052204e,
-    0x4052a072,
-    0x4053208a,
-    0x4053a09d,
-    0x405420b2,
-    0x4054a0d5,
-    0x405520e3,
-    0x4055a120,
-    0x4056212d,
-    0x4056a146,
-    0x4057215e,
-    0x4057a171,
-    0x40582186,
-    0x4058a1ad,
-    0x405921dc,
-    0x4059a209,
-    0x405a221d,
-    0x405aa22d,
-    0x405b2245,
-    0x405ba256,
-    0x405c2269,
-    0x405ca2a8,
-    0x405d22b5,
-    0x405da2da,
-    0x405e2318,
+    0x404f1f74,
+    0x404f9fba,
+    0x40502011,
+    0x4050a025,
+    0x40512058,
+    0x40522068,
+    0x4052a08c,
+    0x405320a4,
+    0x4053a0b7,
+    0x405420cc,
+    0x4054a0ef,
+    0x405520fd,
+    0x4055a13a,
+    0x40562147,
+    0x4056a160,
+    0x40572178,
+    0x4057a18b,
+    0x405821a0,
+    0x4058a1c7,
+    0x405921f6,
+    0x4059a223,
+    0x405a2237,
+    0x405aa247,
+    0x405b225f,
+    0x405ba270,
+    0x405c2283,
+    0x405ca2c2,
+    0x405d22cf,
+    0x405da2f4,
+    0x405e2332,
     0x405e8ab3,
-    0x405f2339,
-    0x405fa346,
-    0x40602354,
-    0x4060a376,
-    0x406123d7,
-    0x4061a40f,
-    0x40622426,
-    0x4062a437,
-    0x4063245c,
-    0x4063a471,
-    0x40642488,
-    0x4064a4b4,
-    0x406524cf,
-    0x4065a4e6,
-    0x406624fe,
-    0x4066a528,
-    0x40672553,
-    0x4067a598,
-    0x406825e0,
-    0x4068a601,
-    0x40692633,
-    0x4069a661,
-    0x406a2682,
-    0x406aa6a2,
-    0x406b282a,
-    0x406ba84d,
-    0x406c2863,
-    0x406cab06,
-    0x406d2b35,
-    0x406dab5d,
-    0x406e2b8b,
-    0x406eabd8,
-    0x406f2c13,
-    0x406fac4b,
-    0x40702c5e,
-    0x4070ac7b,
+    0x405f2353,
+    0x405fa360,
+    0x4060236e,
+    0x4060a390,
+    0x406123f1,
+    0x4061a429,
+    0x40622440,
+    0x4062a451,
+    0x40632476,
+    0x4063a48b,
+    0x406424a2,
+    0x4064a4ce,
+    0x406524e9,
+    0x4065a500,
+    0x40662518,
+    0x4066a542,
+    0x4067256d,
+    0x4067a5b2,
+    0x406825fa,
+    0x4068a61b,
+    0x4069264d,
+    0x4069a67b,
+    0x406a269c,
+    0x406aa6bc,
+    0x406b2844,
+    0x406ba867,
+    0x406c287d,
+    0x406cab20,
+    0x406d2b4f,
+    0x406dab77,
+    0x406e2ba5,
+    0x406eabf2,
+    0x406f2c2d,
+    0x406fac65,
+    0x40702c78,
+    0x4070ac95,
     0x40710802,
-    0x4071ac8d,
-    0x40722ca0,
-    0x4072acd6,
-    0x40732cee,
+    0x4071aca7,
+    0x40722cba,
+    0x4072acf0,
+    0x40732d08,
     0x407394e0,
-    0x40742d02,
-    0x4074ad1c,
-    0x40752d2d,
-    0x4075ad41,
-    0x40762d4f,
+    0x40742d1c,
+    0x4074ad36,
+    0x40752d47,
+    0x4075ad5b,
+    0x40762d69,
     0x407692a4,
-    0x40772d74,
-    0x4077ad96,
-    0x40782db1,
-    0x4078adea,
-    0x40792e01,
-    0x4079ae17,
-    0x407a2e43,
-    0x407aae56,
-    0x407b2e6b,
-    0x407bae7d,
-    0x407c2eae,
-    0x407caeb7,
-    0x407d261c,
-    0x407d9fb0,
-    0x407e2dc6,
-    0x407ea1bd,
+    0x40772d8e,
+    0x4077adb0,
+    0x40782dcb,
+    0x4078ae04,
+    0x40792e1b,
+    0x4079ae31,
+    0x407a2e5d,
+    0x407aae70,
+    0x407b2e85,
+    0x407bae97,
+    0x407c2ec8,
+    0x407caed1,
+    0x407d2636,
+    0x407d9fca,
+    0x407e2de0,
+    0x407ea1d7,
     0x407f1d3e,
     0x407f9ae4,
-    0x40801f6a,
+    0x40801f84,
     0x40809d66,
-    0x40812060,
-    0x40819f44,
-    0x40822b76,
+    0x4081207a,
+    0x40819f5e,
+    0x40822b90,
     0x40829aca,
-    0x40832198,
-    0x4083a499,
+    0x408321b2,
+    0x4083a4b3,
     0x40841d7a,
-    0x4084a1f5,
-    0x4085227a,
-    0x4085a39e,
-    0x408622fa,
-    0x40869fca,
-    0x40872bbc,
-    0x4087a3ec,
+    0x4084a20f,
+    0x40852294,
+    0x4085a3b8,
+    0x40862314,
+    0x40869fe4,
+    0x40872bd6,
+    0x4087a406,
     0x40881b2b,
-    0x4088a5ab,
+    0x4088a5c5,
     0x40891b7a,
     0x40899b07,
-    0x408a289b,
+    0x408a28b5,
     0x408a98f8,
-    0x408b2e92,
-    0x408bac28,
-    0x408c228a,
+    0x408b2eac,
+    0x408bac42,
+    0x408c22a4,
     0x408c9914,
     0x408d1ddb,
     0x408d9dac,
     0x408e1ef5,
-    0x408ea100,
-    0x408f25bf,
-    0x408fa3ba,
-    0x40902574,
-    0x4090a2cc,
-    0x40912883,
+    0x408ea11a,
+    0x408f25d9,
+    0x408fa3d4,
+    0x4090258e,
+    0x4090a2e6,
+    0x4091289d,
     0x4091993a,
     0x40921bc7,
-    0x4092abf7,
-    0x40932cb9,
-    0x40939fdb,
+    0x4092ac11,
+    0x40932cd3,
+    0x40939ff5,
     0x40941d8e,
-    0x4094a8b4,
-    0x40952448,
-    0x4095ae23,
-    0x40962ba3,
-    0x40969f83,
-    0x40972026,
-    0x41f42755,
-    0x41f927e7,
-    0x41fe26da,
-    0x41fea8f7,
-    0x41ff29e8,
-    0x4203276e,
-    0x42082790,
-    0x4208a7cc,
-    0x420926be,
-    0x4209a806,
-    0x420a2715,
-    0x420aa6f5,
-    0x420b2735,
-    0x420ba7ae,
-    0x420c2a04,
-    0x420ca8c4,
-    0x420d28de,
-    0x420da915,
-    0x4212292f,
-    0x421729cb,
-    0x4217a971,
-    0x421c2993,
-    0x421f294e,
-    0x42212a1b,
-    0x422629ae,
-    0x422b2aea,
-    0x422baa98,
-    0x422c2ad2,
-    0x422caa57,
-    0x422d2a36,
-    0x422daab7,
-    0x422e2a7d,
+    0x4094a8ce,
+    0x40952462,
+    0x4095ae3d,
+    0x40962bbd,
+    0x40969f9d,
+    0x40972040,
+    0x40979f44,
+    0x41f4276f,
+    0x41f92801,
+    0x41fe26f4,
+    0x41fea911,
+    0x41ff2a02,
+    0x42032788,
+    0x420827aa,
+    0x4208a7e6,
+    0x420926d8,
+    0x4209a820,
+    0x420a272f,
+    0x420aa70f,
+    0x420b274f,
+    0x420ba7c8,
+    0x420c2a1e,
+    0x420ca8de,
+    0x420d28f8,
+    0x420da92f,
+    0x42122949,
+    0x421729e5,
+    0x4217a98b,
+    0x421c29ad,
+    0x421f2968,
+    0x42212a35,
+    0x422629c8,
+    0x422b2b04,
+    0x422baab2,
+    0x422c2aec,
+    0x422caa71,
+    0x422d2a50,
+    0x422daad1,
+    0x422e2a97,
     0x4432072d,
     0x4432873c,
     0x44330748,
@@ -649,69 +650,69 @@
     0x4c41153d,
     0x4c4193c0,
     0x4c421529,
-    0x5032310d,
-    0x5032b11c,
-    0x50333127,
-    0x5033b137,
-    0x50343150,
-    0x5034b16a,
-    0x50353178,
-    0x5035b18e,
-    0x503631a0,
-    0x5036b1b6,
-    0x503731cf,
-    0x5037b1e2,
-    0x503831fa,
-    0x5038b20b,
-    0x50393220,
-    0x5039b234,
-    0x503a3254,
-    0x503ab26a,
-    0x503b3282,
-    0x503bb294,
-    0x503c32b0,
-    0x503cb2c7,
-    0x503d32e0,
-    0x503db2f6,
-    0x503e3303,
-    0x503eb319,
-    0x503f332b,
+    0x50323127,
+    0x5032b136,
+    0x50333141,
+    0x5033b151,
+    0x5034316a,
+    0x5034b184,
+    0x50353192,
+    0x5035b1a8,
+    0x503631ba,
+    0x5036b1d0,
+    0x503731e9,
+    0x5037b1fc,
+    0x50383214,
+    0x5038b225,
+    0x5039323a,
+    0x5039b24e,
+    0x503a326e,
+    0x503ab284,
+    0x503b329c,
+    0x503bb2ae,
+    0x503c32ca,
+    0x503cb2e1,
+    0x503d32fa,
+    0x503db310,
+    0x503e331d,
+    0x503eb333,
+    0x503f3345,
     0x503f837b,
-    0x5040333e,
-    0x5040b34e,
-    0x50413368,
-    0x5041b377,
-    0x50423391,
-    0x5042b3ae,
-    0x504333be,
-    0x5043b3ce,
-    0x504433dd,
+    0x50403358,
+    0x5040b368,
+    0x50413382,
+    0x5041b391,
+    0x504233ab,
+    0x5042b3c8,
+    0x504333d8,
+    0x5043b3e8,
+    0x504433f7,
     0x50448431,
-    0x504533f1,
-    0x5045b40f,
-    0x50463422,
-    0x5046b438,
-    0x5047344a,
-    0x5047b45f,
-    0x50483485,
-    0x5048b493,
-    0x504934a6,
-    0x5049b4bb,
-    0x504a34d1,
-    0x504ab4e1,
-    0x504b3501,
-    0x504bb514,
-    0x504c3537,
-    0x504cb565,
-    0x504d3577,
-    0x504db594,
-    0x504e35af,
-    0x504eb5cb,
-    0x504f35dd,
-    0x504fb5f4,
-    0x50503603,
+    0x5045340b,
+    0x5045b429,
+    0x5046343c,
+    0x5046b452,
+    0x50473464,
+    0x5047b479,
+    0x5048349f,
+    0x5048b4ad,
+    0x504934c0,
+    0x5049b4d5,
+    0x504a34eb,
+    0x504ab4fb,
+    0x504b351b,
+    0x504bb52e,
+    0x504c3551,
+    0x504cb57f,
+    0x504d3591,
+    0x504db5ae,
+    0x504e35c9,
+    0x504eb5e5,
+    0x504f35f7,
+    0x504fb60e,
+    0x5050361d,
     0x505086f1,
-    0x50513616,
+    0x50513630,
     0x58320f65,
     0x68320f27,
     0x68328c7f,
@@ -1153,6 +1154,7 @@
     "HTTPS_PROXY_REQUEST\0"
     "HTTP_REQUEST\0"
     "INAPPROPRIATE_FALLBACK\0"
+    "INCONSISTENT_CLIENT_HELLO\0"
     "INVALID_ALPN_PROTOCOL\0"
     "INVALID_COMMAND\0"
     "INVALID_COMPRESSION_LIST\0"
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 6e10ff95..3ef27a2 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-10-1-9-g7d1d3b9a0
-Revision: 7d1d3b9a0e9310376a559ad2eac8a9dc4c60ce59
+Version: VER-2-10-1-10-g9adc3b35f
+Revision: 9adc3b35f1a6909c1785c42ae7b8cf369634b225
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
 License File: src/docs/FTL.TXT
diff --git a/third_party/inspector_protocol/README.chromium b/third_party/inspector_protocol/README.chromium
index 622b24b..39092bf 100644
--- a/third_party/inspector_protocol/README.chromium
+++ b/third_party/inspector_protocol/README.chromium
@@ -2,7 +2,7 @@
 Short Name: inspector_protocol
 URL: https://chromium.googlesource.com/deps/inspector_protocol/
 Version: 0
-Revision: d114a62e144cdfdae697fe0af6581ce39a31af37
+Revision: ed2845d5409a04d3c87655f0b55e87a66e806de2
 License: BSD
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/inspector_protocol/encoding/encoding.cc b/third_party/inspector_protocol/encoding/encoding.cc
index 7315283..892a1ee 100644
--- a/third_party/inspector_protocol/encoding/encoding.cc
+++ b/third_party/inspector_protocol/encoding/encoding.cc
@@ -85,8 +85,25 @@
       return ToASCIIString("CBOR: map start expected");
     case Error::CBOR_MAP_STOP_EXPECTED:
       return ToASCIIString("CBOR: map stop expected");
+    case Error::CBOR_ARRAY_START_EXPECTED:
+      return ToASCIIString("CBOR: array start expected");
     case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED:
       return ToASCIIString("CBOR: envelope size limit exceeded");
+
+    case Error::BINDINGS_MANDATORY_FIELD_MISSING:
+      return ToASCIIString("BINDINGS: mandatory field missing");
+    case Error::BINDINGS_BOOL_VALUE_EXPECTED:
+      return ToASCIIString("BINDINGS: bool value expected");
+    case Error::BINDINGS_INT32_VALUE_EXPECTED:
+      return ToASCIIString("BINDINGS: int32 value expected");
+    case Error::BINDINGS_DOUBLE_VALUE_EXPECTED:
+      return ToASCIIString("BINDINGS: double value expected");
+    case Error::BINDINGS_STRING_VALUE_EXPECTED:
+      return ToASCIIString("BINDINGS: string value expected");
+    case Error::BINDINGS_STRING8_VALUE_EXPECTED:
+      return ToASCIIString("BINDINGS: string8 value expected");
+    case Error::BINDINGS_BINARY_VALUE_EXPECTED:
+      return ToASCIIString("BINDINGS: binary value expected");
   }
   // Some compilers can't figure out that we can't get here.
   return "INVALID ERROR CODE";
@@ -707,6 +724,12 @@
   return bytes_.subspan(status_.pos + (token_byte_length_ - length), length);
 }
 
+span<uint8_t> CBORTokenizer::GetEnvelope() const {
+  assert(token_tag_ == CBORTokenTag::ENVELOPE);
+  auto length = static_cast<size_t>(token_start_internal_value_);
+  return bytes_.subspan(status_.pos, length + kEncodedEnvelopeHeaderSize);
+}
+
 span<uint8_t> CBORTokenizer::GetEnvelopeContents() const {
   assert(token_tag_ == CBORTokenTag::ENVELOPE);
   auto length = static_cast<size_t>(token_start_internal_value_);
diff --git a/third_party/inspector_protocol/encoding/encoding.h b/third_party/inspector_protocol/encoding/encoding.h
index 947a4327..aa231ad29 100644
--- a/third_party/inspector_protocol/encoding/encoding.h
+++ b/third_party/inspector_protocol/encoding/encoding.h
@@ -141,7 +141,16 @@
   CBOR_TRAILING_JUNK = 0x1e,
   CBOR_MAP_START_EXPECTED = 0x1f,
   CBOR_MAP_STOP_EXPECTED = 0x20,
-  CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x21,
+  CBOR_ARRAY_START_EXPECTED = 0x21,
+  CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x22,
+
+  BINDINGS_MANDATORY_FIELD_MISSING = 0x23,
+  BINDINGS_BOOL_VALUE_EXPECTED = 0x24,
+  BINDINGS_INT32_VALUE_EXPECTED = 0x25,
+  BINDINGS_DOUBLE_VALUE_EXPECTED = 0x26,
+  BINDINGS_STRING_VALUE_EXPECTED = 0x27,
+  BINDINGS_STRING8_VALUE_EXPECTED = 0x28,
+  BINDINGS_BINARY_VALUE_EXPECTED = 0x29,
 };
 
 // A status value with position that can be copied. The default status
@@ -419,6 +428,17 @@
   span<uint8_t> GetBinary() const;
 
   // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE.
+  // Returns the envelope including its payload; message which
+  // can be passed to the CBORTokenizer constructor, which will
+  // then see the envelope token first (looking at it a second time,
+  // basically).
+  span<uint8_t> GetEnvelope() const;
+
+  // To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE.
+  // Returns only the payload inside the envelope, e.g., a map
+  // or an array. This is not a complete message by our
+  // IsCBORMessage definition, since it doesn't include the
+  // enclosing envelope (the header, basically).
   span<uint8_t> GetEnvelopeContents() const;
 
  private:
diff --git a/third_party/inspector_protocol/encoding/encoding_test.cc b/third_party/inspector_protocol/encoding/encoding_test.cc
index bbdb7a5..4295520d 100644
--- a/third_party/inspector_protocol/encoding/encoding_test.cc
+++ b/third_party/inspector_protocol/encoding/encoding_test.cc
@@ -688,6 +688,71 @@
   }
 }
 
+TEST(EncodeDecodeEnvelopesTest, MessageWithNestingAndEnvelopeContentsAccess) {
+  // This encodes and decodes the following message, which has some nesting
+  // and therefore envelopes.
+  //  { "inner": { "foo" : "bar" } }
+  // The decoding is done with the Tokenizer,
+  // and we test both ::GetEnvelopeContents and GetEnvelope here.
+  std::vector<uint8_t> message;
+  EnvelopeEncoder envelope;
+  envelope.EncodeStart(&message);
+  size_t pos_after_header = message.size();
+  message.push_back(EncodeIndefiniteLengthMapStart());
+  EncodeString8(SpanFrom("inner"), &message);
+  size_t pos_inside_inner = message.size();
+  EnvelopeEncoder inner_envelope;
+  inner_envelope.EncodeStart(&message);
+  size_t pos_inside_inner_contents = message.size();
+  message.push_back(EncodeIndefiniteLengthMapStart());
+  EncodeString8(SpanFrom("foo"), &message);
+  EncodeString8(SpanFrom("bar"), &message);
+  message.push_back(EncodeStop());
+  size_t pos_after_inner = message.size();
+  inner_envelope.EncodeStop(&message);
+  message.push_back(EncodeStop());
+  envelope.EncodeStop(&message);
+
+  CBORTokenizer tokenizer(SpanFrom(message));
+  ASSERT_EQ(CBORTokenTag::ENVELOPE, tokenizer.TokenTag());
+  EXPECT_EQ(message.size(), tokenizer.GetEnvelope().size());
+  EXPECT_EQ(message.data(), tokenizer.GetEnvelope().data());
+  EXPECT_EQ(message.data() + pos_after_header,
+            tokenizer.GetEnvelopeContents().data());
+  EXPECT_EQ(message.size() - pos_after_header,
+            tokenizer.GetEnvelopeContents().size());
+  tokenizer.EnterEnvelope();
+  ASSERT_EQ(CBORTokenTag::MAP_START, tokenizer.TokenTag());
+  tokenizer.Next();
+  ASSERT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+  EXPECT_EQ("inner", std::string(tokenizer.GetString8().begin(),
+                                 tokenizer.GetString8().end()));
+  tokenizer.Next();
+  ASSERT_EQ(CBORTokenTag::ENVELOPE, tokenizer.TokenTag());
+  EXPECT_EQ(message.data() + pos_inside_inner, tokenizer.GetEnvelope().data());
+  EXPECT_EQ(pos_after_inner - pos_inside_inner, tokenizer.GetEnvelope().size());
+  EXPECT_EQ(message.data() + pos_inside_inner_contents,
+            tokenizer.GetEnvelopeContents().data());
+  EXPECT_EQ(pos_after_inner - pos_inside_inner_contents,
+            tokenizer.GetEnvelopeContents().size());
+  tokenizer.EnterEnvelope();
+  ASSERT_EQ(CBORTokenTag::MAP_START, tokenizer.TokenTag());
+  tokenizer.Next();
+  ASSERT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+  EXPECT_EQ("foo", std::string(tokenizer.GetString8().begin(),
+                               tokenizer.GetString8().end()));
+  tokenizer.Next();
+  ASSERT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+  EXPECT_EQ("bar", std::string(tokenizer.GetString8().begin(),
+                               tokenizer.GetString8().end()));
+  tokenizer.Next();
+  ASSERT_EQ(CBORTokenTag::STOP, tokenizer.TokenTag());
+  tokenizer.Next();
+  ASSERT_EQ(CBORTokenTag::STOP, tokenizer.TokenTag());
+  tokenizer.Next();
+  ASSERT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
 // =============================================================================
 // cbor::NewCBOREncoder - for encoding from a streaming parser
 // =============================================================================
diff --git a/third_party/inspector_protocol/lib/DispatcherBase_cpp.template b/third_party/inspector_protocol/lib/DispatcherBase_cpp.template
index 11843f4..84c3efd 100644
--- a/third_party/inspector_protocol/lib/DispatcherBase_cpp.template
+++ b/third_party/inspector_protocol/lib/DispatcherBase_cpp.template
@@ -302,15 +302,21 @@
 UberDispatcher::~UberDispatcher() = default;
 
 // static
-std::unique_ptr<InternalResponse> InternalResponse::createResponse(int callId, std::unique_ptr<Serializable> params)
+std::unique_ptr<Serializable> InternalResponse::createResponse(int callId, std::unique_ptr<Serializable> params)
 {
-    return std::unique_ptr<InternalResponse>(new InternalResponse(callId, String(), std::move(params)));
+    return std::unique_ptr<Serializable>(new InternalResponse(callId, String(), std::move(params)));
 }
 
 // static
-std::unique_ptr<InternalResponse> InternalResponse::createNotification(const String& notification, std::unique_ptr<Serializable> params)
+std::unique_ptr<Serializable> InternalResponse::createNotification(const String& notification, std::unique_ptr<Serializable> params)
 {
-    return std::unique_ptr<InternalResponse>(new InternalResponse(0, notification, std::move(params)));
+    return std::unique_ptr<Serializable>(new InternalResponse(0, notification, std::move(params)));
+}
+
+// static
+std::unique_ptr<Serializable> InternalResponse::createErrorResponse(int callId, DispatchResponse::ErrorCode code, const String& message)
+{
+    return ProtocolError::createErrorResponse(callId, code, message, nullptr);
 }
 
 String InternalResponse::serializeToJSON()
diff --git a/third_party/inspector_protocol/lib/DispatcherBase_h.template b/third_party/inspector_protocol/lib/DispatcherBase_h.template
index 4aa0688a..3862fb9a 100644
--- a/third_party/inspector_protocol/lib/DispatcherBase_h.template
+++ b/third_party/inspector_protocol/lib/DispatcherBase_h.template
@@ -128,8 +128,9 @@
 class InternalResponse : public Serializable {
     PROTOCOL_DISALLOW_COPY(InternalResponse);
 public:
-    static std::unique_ptr<InternalResponse> createResponse(int callId, std::unique_ptr<Serializable> params);
-    static std::unique_ptr<InternalResponse> createNotification(const String& notification, std::unique_ptr<Serializable> params = nullptr);
+    static std::unique_ptr<Serializable> createResponse(int callId, std::unique_ptr<Serializable> params);
+    static std::unique_ptr<Serializable> createNotification(const String& notification, std::unique_ptr<Serializable> params = nullptr);
+    static std::unique_ptr<Serializable> createErrorResponse(int callId, DispatchResponse::ErrorCode code, const String& message);
 
     String serializeToJSON() override;
     std::vector<uint8_t> serializeToBinary() override;
diff --git a/third_party/polymer/v1_0/chromium.patch b/third_party/polymer/v1_0/chromium.patch
index 6102949..3d91889 100644
--- a/third_party/polymer/v1_0/chromium.patch
+++ b/third_party/polymer/v1_0/chromium.patch
@@ -142,6 +142,19 @@
      },
 
      _forwardItemPath: function(path, value) {
+diff --git a/components-chromium/iron-overlay-behavior/iron-overlay-manager-extracted.js b/components-chromium/iron-overlay-behavior/iron-overlay-manager-extracted.js
+index ccd2e3c5b551..7bca4b1ae49a 100644
+--- a/components-chromium/iron-overlay-behavior/iron-overlay-manager-extracted.js
++++ b/components-chromium/iron-overlay-behavior/iron-overlay-manager-extracted.js
+@@ -30,7 +30,7 @@
+     // NOTE: enable tap on <html> to workaround Polymer/polymer#4459
+     // Pass no-op function because MSEdge 15 doesn't handle null as 2nd argument
+     // https://github.com/Microsoft/ChakraCore/issues/3863
+-    Polymer.Gestures.add(document.documentElement, 'tap', function() {});
++    Polymer.Gestures.addListener(document.documentElement, 'tap', function() {});
+     document.addEventListener('tap', this._onCaptureClick.bind(this), true);
+     document.addEventListener('focus', this._onCaptureFocus.bind(this), true);
+     document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true);
 diff --git a/components-chromium/iron-flex-layout/iron-flex-layout.html b/components-chromium/iron-flex-layout/iron-flex-layout.html
 index 082b0c1e4af2..d985829702a2 100644
 --- a/components-chromium/iron-flex-layout/iron-flex-layout.html
diff --git a/third_party/polymer/v1_0/components-chromium/iron-overlay-behavior/iron-overlay-manager-extracted.js b/third_party/polymer/v1_0/components-chromium/iron-overlay-behavior/iron-overlay-manager-extracted.js
index ccd2e3c..a268f1f 100644
--- a/third_party/polymer/v1_0/components-chromium/iron-overlay-behavior/iron-overlay-manager-extracted.js
+++ b/third_party/polymer/v1_0/components-chromium/iron-overlay-behavior/iron-overlay-manager-extracted.js
@@ -30,7 +30,7 @@
     // NOTE: enable tap on <html> to workaround Polymer/polymer#4459
     // Pass no-op function because MSEdge 15 doesn't handle null as 2nd argument
     // https://github.com/Microsoft/ChakraCore/issues/3863
-    Polymer.Gestures.add(document.documentElement, 'tap', function() {});
+    Polymer.Gestures.addListener(document.documentElement, 'tap', function() {});
     document.addEventListener('tap', this._onCaptureClick.bind(this), true);
     document.addEventListener('focus', this._onCaptureFocus.bind(this), true);
     document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true);
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 38e360d..04df1f8a 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -1091,7 +1091,7 @@
       # Skip a few configs that need extra cleanup for now.
       # TODO(https://crbug.com/912946): Fix everything on all platforms and
       # enable check everywhere.
-      if is_android or is_cros or is_mac or is_msan:
+      if is_android or is_cros or is_mac:
         break
 
       # Skip a few existing violations that need to be cleaned up. Each of
@@ -1099,6 +1099,7 @@
       # contents change. Do not add to this list.
       # TODO(https://crbug.com/912946): Remove this if statement.
       if (f == 'angledata/gl_cts/' or  # http://anglebug.com/3827
+          (is_msan and f == 'instrumented_libraries_prebuilt/') or
           f == 'locales/' or
           f.startswith('nacl_test_data/') or
           f.startswith('ppapi_nacl_tests_libs/') or
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 217e071..e0d63f0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -10879,6 +10879,10 @@
   <int value="210" label="Nvmem Pre Erase Mismatch"/>
   <int value="211" label="Nvmem Page List Oveflow"/>
   <int value="212" label="Nvmem Cipher Error"/>
+  <int value="213" label="Nvmem Corrupted Init"/>
+  <int value="214" label="Nvmem Container Hash Mismatch"/>
+  <int value="215" label="Nvmem Unrecoverable Init Error"/>
+  <int value="216" label="Nvmem Wiped Out"/>
 </enum>
 
 <enum name="Cr50U2FCommands">
@@ -35469,6 +35473,7 @@
   <int value="-621044227" label="OmniboxReverseTabSwitchLogic:enabled"/>
   <int value="-620030047" label="CrosCompUpdates:disabled"/>
   <int value="-619740638" label="ListAllDisplayModes:enabled"/>
+  <int value="-619178844" label="NewOverviewLayout:disabled"/>
   <int value="-617452890" label="media-router"/>
   <int value="-616818899" label="SameSiteByDefaultCookies:enabled"/>
   <int value="-614223913"
@@ -37004,6 +37009,7 @@
   <int value="1566084951" label="PrefetchRedirectError:disabled"/>
   <int value="1567839560"
       label="ChromeHomePersonalizedOmniboxSuggestions:disabled"/>
+  <int value="1570178909" label="NewOverviewLayout:enabled"/>
   <int value="1571998166" label="DetectingHeavyPages:disabled"/>
   <int value="1575978252" label="PasswordLeakDetection:enabled"/>
   <int value="1577205328"
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index f182b110..3ef6735 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -34889,7 +34889,7 @@
 </histogram>
 
 <histogram name="Enterprise.EnrollmentAttestationBased"
-    enum="EnterpriseEnrollmentType" expires_after="M78">
+    enum="EnterpriseEnrollmentType" expires_after="M88">
   <owner>drcrash@chromium.org</owner>
   <summary>
     Events related to attestation-based enrollment (Zero-Touch) of Chrome OS
@@ -34907,7 +34907,7 @@
 </histogram>
 
 <histogram name="Enterprise.EnrollmentForced" enum="EnterpriseEnrollmentType"
-    expires_after="M85">
+    expires_after="M88">
   <owner>tnagel@chromium.org</owner>
   <summary>
     Events related to forced re-enrollment (FRE) of Chrome OS devices.
@@ -34915,7 +34915,7 @@
 </histogram>
 
 <histogram name="Enterprise.EnrollmentForcedAttestationBased"
-    enum="EnterpriseEnrollmentType" expires_after="M85">
+    enum="EnterpriseEnrollmentType" expires_after="M88">
   <owner>drcrash@chromium.org</owner>
   <summary>
     Events related to attestation-based re-enrollment (Auto RE) of Chrome OS
@@ -34924,7 +34924,7 @@
 </histogram>
 
 <histogram name="Enterprise.EnrollmentForcedInitial"
-    enum="EnterpriseEnrollmentType" expires_after="2020-05-02">
+    enum="EnterpriseEnrollmentType" expires_after="M88">
   <owner>drcrash@chromium.org</owner>
   <owner>pmarko@chromium.org</owner>
   <summary>
@@ -34934,7 +34934,7 @@
 </histogram>
 
 <histogram name="Enterprise.EnrollmentForcedInitialAttestationBased"
-    enum="EnterpriseEnrollmentType" expires_after="2020-05-02">
+    enum="EnterpriseEnrollmentType" expires_after="M88">
   <owner>drcrash@chromium.org</owner>
   <owner>pmarko@chromium.org</owner>
   <summary>
@@ -34944,7 +34944,7 @@
 </histogram>
 
 <histogram name="Enterprise.EnrollmentForcedInitialManualFallback"
-    enum="EnterpriseEnrollmentType" expires_after="2020-05-02">
+    enum="EnterpriseEnrollmentType" expires_after="M88">
   <owner>drcrash@chromium.org</owner>
   <owner>pmarko@chromium.org</owner>
   <summary>
@@ -159313,6 +159313,8 @@
 
 <histogram_suffixes name="Availability.Prober.Clients" separator=".">
   <suffix name="Litepages" label="Lite page HTTPS Server Previews"/>
+  <suffix name="LitepagesOriginCheck"
+      label="Origin check for Litepage previews"/>
   <affected-histogram name="Availability.Prober.CacheEntryAge"/>
   <affected-histogram name="Availability.Prober.DidSucceed"/>
   <affected-histogram name="Availability.Prober.NetError"/>
diff --git a/tools/perf/core/results_dashboard.py b/tools/perf/core/results_dashboard.py
index ba8449f..f66d9be 100755
--- a/tools/perf/core/results_dashboard.py
+++ b/tools/perf/core/results_dashboard.py
@@ -46,10 +46,8 @@
   pass
 
 
-def LuciAuthTokenGeneratorCallback(service_account_file):
+def LuciAuthTokenGeneratorCallback():
   args = ['luci-auth', 'token']
-  if service_account_file:
-    args += ['-service-account-json', service_account_file]
   p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   if p.wait() == 0:
     return p.stdout.read()
@@ -60,7 +58,6 @@
 
 
 def SendResults(data, data_label, url, send_as_histograms=False,
-                service_account_file=None,
                 token_generator_callback=LuciAuthTokenGeneratorCallback,
                 num_retries=4):
   """Sends results to the Chrome Performance Dashboard.
@@ -73,14 +70,8 @@
     logging purpose.
     url: Performance Dashboard URL (including schema).
     send_as_histograms: True if result is to be sent to /add_histograms.
-    service_account_file: string; path to service account file which is used
-      for authenticating when upload data to perf dashboard. This can be None
-      for the case of LUCI builder, which means the task service account of the
-      builder will be used.
     token_generator_callback: a callback for generating the authentication token
       to upload to perf dashboard.
-      This callback takes |service_account_file| and returns the token
-      string.
       If |token_generator_callback| is not specified, it's default to
       LuciAuthTokenGeneratorCallback.
     num_retries: Number of times to retry uploading to the perf dashboard upon
@@ -104,8 +95,7 @@
       print 'Sending %s result of %s to dashboard (attempt %i out of %i).' % (
           data_type, data_label, i, num_retries)
       if send_as_histograms:
-        _SendHistogramJson(url, dashboard_data_str,
-                           service_account_file, token_generator_callback)
+        _SendHistogramJson(url, dashboard_data_str, token_generator_callback)
       else:
         # TODO(eakuefner): Remove this logic once all bots use histograms.
         _SendResultsJson(url, dashboard_data_str)
@@ -467,8 +457,7 @@
     raise SendResultsRetryException(error)
 
 
-def _SendHistogramJson(url, histogramset_json,
-                       service_account_file, token_generator_callback):
+def _SendHistogramJson(url, histogramset_json, token_generator_callback):
   """POST a HistogramSet JSON to the Performance Dashboard.
 
   Args:
@@ -476,14 +465,14 @@
         "https://chromeperf.appspot.com".
     histogramset_json: JSON string that contains a serialized HistogramSet.
 
-    For |service_account_file| and |token_generator_callback|, see SendResults's
+    For |token_generator_callback|, see SendResults's
     documentation.
 
   Returns:
     None if successful, or an error string if there were errors.
   """
   try:
-    oauth_token = token_generator_callback(service_account_file)
+    oauth_token = token_generator_callback()
 
     data = zlib.compress(histogramset_json)
     headers = {
diff --git a/tools/perf/core/results_dashboard_unittest.py b/tools/perf/core/results_dashboard_unittest.py
index 71df385..966750b5 100644
--- a/tools/perf/core/results_dashboard_unittest.py
+++ b/tools/perf/core/results_dashboard_unittest.py
@@ -12,15 +12,14 @@
 class ResultsDashboardTest(unittest.TestCase):
 
   def setUp(self):
-    self.fake_service = '/foo/bar/kingsman-service-account'
     self.dummy_token_generator = lambda service_file, timeout: 'Arthur-Merlin'
     self.perf_data = {'foo': 1, 'bar': 2}
     self.dashboard_url = 'https://chromeperf.appspot.com'
 
   def testRetryForSendResultRetryException(self):
     def raise_retry_exception(
-        url, histogramset_json, service_account_file, token_generator_callback):
-      del url, histogramset_json, service_account_file  # unused
+        url, histogramset_json, token_generator_callback):
+      del url, histogramset_json  # unused
       del token_generator_callback  # unused
       raise results_dashboard.SendResultsRetryException('Should retry')
 
@@ -30,7 +29,6 @@
         upload_result = results_dashboard.SendResults(
             self.perf_data, 'dummy_benchmark',
             self.dashboard_url, send_as_histograms=True,
-            service_account_file=self.fake_service,
             token_generator_callback=self.dummy_token_generator, num_retries=5)
         self.assertFalse(upload_result)
         self.assertEqual(m.call_count, 5)
@@ -41,8 +39,8 @@
   def testNoRetryForSendResultFatalException(self):
 
     def raise_retry_exception(
-        url, histogramset_json, service_account_file, token_generator_callback):
-      del url, histogramset_json, service_account_file  # unused
+        url, histogramset_json, token_generator_callback):
+      del url, histogramset_json  # unused
       del token_generator_callback  # unused
       raise results_dashboard.SendResultsFatalException('Do not retry')
 
@@ -52,7 +50,6 @@
         upload_result =  results_dashboard.SendResults(
             self.perf_data, 'dummy_benchmark',
             self.dashboard_url, send_as_histograms=True,
-            service_account_file=self.fake_service,
             token_generator_callback=self.dummy_token_generator,
             num_retries=5)
         self.assertFalse(upload_result)
@@ -65,7 +62,6 @@
         upload_result = results_dashboard.SendResults(
             self.perf_data, 'dummy_benchmark',
             self.dashboard_url, send_as_histograms=True,
-            service_account_file=self.fake_service,
             token_generator_callback=self.dummy_token_generator,
             num_retries=5)
         self.assertTrue(upload_result)
@@ -75,8 +71,8 @@
   def testNoRetryAfterSucessfulSendResult(self):
     counter = [0]
     def raise_retry_exception_first_two_times(
-        url, histogramset_json, service_account_file, token_generator_callback):
-      del url, histogramset_json, service_account_file  # unused
+        url, histogramset_json, token_generator_callback):
+      del url, histogramset_json  # unused
       del token_generator_callback  # unused
       counter[0] += 1
       if counter[0] <= 2:
@@ -88,7 +84,6 @@
         upload_result = results_dashboard.SendResults(
             self.perf_data, 'dummy_benchmark',
             self.dashboard_url, send_as_histograms=True,
-            service_account_file=self.fake_service,
             token_generator_callback=self.dummy_token_generator,
             num_retries=5)
         self.assertTrue(upload_result)
diff --git a/tools/perf/core/upload_results_to_perf_dashboard.py b/tools/perf/core/upload_results_to_perf_dashboard.py
index 884dfab..e5ee135 100755
--- a/tools/perf/core/upload_results_to_perf_dashboard.py
+++ b/tools/perf/core/upload_results_to_perf_dashboard.py
@@ -125,7 +125,6 @@
   parser.add_option('--git-revision')
   parser.add_option('--output-json-dashboard-url')
   parser.add_option('--send-as-histograms', action='store_true')
-  parser.add_option('--service-account-file', default=None)
   return parser
 
 
@@ -139,8 +138,6 @@
   if not options.configuration_name or not options.results_url:
     parser.error('configuration_name and results_url are required.')
 
-  service_account_file = options.service_account_file
-
   if not options.perf_dashboard_machine_group:
     print 'Error: Invalid perf dashboard machine group'
     return 1
@@ -178,8 +175,7 @@
           batch,
           options.name,
           options.results_url,
-          send_as_histograms=options.send_as_histograms,
-          service_account_file=service_account_file):
+          send_as_histograms=options.send_as_histograms):
         return 1
   else:
     # The upload didn't fail since there was no data to upload.
diff --git a/tools/perf/process_perf_results.py b/tools/perf/process_perf_results.py
index a341249c..143b55d 100755
--- a/tools/perf/process_perf_results.py
+++ b/tools/perf/process_perf_results.py
@@ -81,7 +81,7 @@
 
 
 def _upload_perf_results(json_to_upload, name, configuration_name,
-    build_properties, service_account_file, output_json_file):
+    build_properties, output_json_file):
   """Upload the contents of result JSON(s) to the perf dashboard."""
   args= [
       '--buildername', build_properties['buildername'],
@@ -96,16 +96,11 @@
       '--output-json-file', output_json_file,
       '--perf-dashboard-machine-group', _GetMachineGroup(build_properties)
   ]
-  is_luci = False
   buildbucket = build_properties.get('buildbucket', {})
   if isinstance(buildbucket, basestring):
     buildbucket = json.loads(buildbucket)
-  if ('build' in buildbucket and
-      buildbucket['build'].get('bucket') == 'luci.chrome.ci'):
-    is_luci = True
 
-  if is_luci and _is_gtest(json_to_upload) and (
-      name in GTEST_CONVERSION_WHITELIST):
+  if _is_gtest(json_to_upload) and name in GTEST_CONVERSION_WHITELIST:
     path_util.AddTracingToPath()
     from tracing.value import (  # pylint: disable=no-name-in-module
         gtest_json_converter)
@@ -118,9 +113,6 @@
       '--buildbucket', buildbucket['build'].get('bucket'),
     ]
 
-  if service_account_file and not is_luci:
-    args += ['--service-account-file', service_account_file]
-
   if build_properties.get('git_revision'):
     args.append('--git-revision')
     args.append(build_properties['git_revision'])
@@ -273,7 +265,6 @@
 
 
 def process_perf_results(output_json, configuration_name,
-                         service_account_file,
                          build_properties, task_output_dir,
                          smoke_test_mode, output_results_dir):
   """Process perf results.
@@ -349,8 +340,7 @@
     try:
       return_code, benchmark_upload_result_map = _handle_perf_results(
           benchmark_enabled_map, benchmark_directory_map,
-          configuration_name, build_properties, service_account_file,
-          extra_links, output_results_dir)
+          configuration_name, build_properties, extra_links, output_results_dir)
     except Exception:
       logging.exception('Error handling perf results jsons')
       return_code = 1
@@ -413,8 +403,8 @@
 
 
 def _upload_individual(
-    benchmark_name, directories, configuration_name,
-    build_properties, output_json_file, service_account_file):
+    benchmark_name, directories, configuration_name, build_properties,
+    output_json_file):
   tmpfile_dir = tempfile.mkdtemp()
   try:
     upload_begin_time = time.time()
@@ -439,8 +429,7 @@
     with open(output_json_file, 'w') as oj:
       upload_return_code = _upload_perf_results(
         results_filename,
-        benchmark_name, configuration_name, build_properties,
-        service_account_file, oj)
+        benchmark_name, configuration_name, build_properties, oj)
       upload_end_time = time.time()
       print_duration(('%s upload time' % (benchmark_name)),
                      upload_begin_time, upload_end_time)
@@ -475,8 +464,7 @@
 
 def _handle_perf_results(
     benchmark_enabled_map, benchmark_directory_map, configuration_name,
-    build_properties, service_account_file, extra_links,
-    output_results_dir):
+    build_properties, extra_links, output_results_dir):
   """
     Upload perf results to the perf dashboard.
 
@@ -505,7 +493,7 @@
     results_dict[benchmark_name] = output_json_file
     invocations.append((
         benchmark_name, directories, configuration_name,
-        build_properties, output_json_file, service_account_file))
+        build_properties, output_json_file))
 
   # Kick off the uploads in multiple processes
   pool = multiprocessing.Pool(_GetCpuCount())
@@ -614,8 +602,6 @@
   # configuration-name and results-url are set in the json file which is going
   # away tools/perf/core/chromium.perf.fyi.extras.json
   parser.add_argument('--configuration-name', help=argparse.SUPPRESS)
-  parser.add_argument('--service-account-file', help=argparse.SUPPRESS,
-                      default=None)
 
   parser.add_argument('--build-properties', help=argparse.SUPPRESS)
   parser.add_argument('--summary-json', help=argparse.SUPPRESS)
@@ -633,7 +619,6 @@
   try:
     return_code, _ = process_perf_results(
         args.output_json, args.configuration_name,
-        args.service_account_file,
         args.build_properties, args.task_output_dir,
         args.smoke_test_mode, output_results_dir)
     return return_code
diff --git a/tools/perf/process_perf_results_unittest.py b/tools/perf/process_perf_results_unittest.py
index 263c61dd..2d230e9 100755
--- a/tools/perf/process_perf_results_unittest.py
+++ b/tools/perf/process_perf_results_unittest.py
@@ -65,10 +65,6 @@
   def setUp(self):
     self.test_dir = tempfile.mkdtemp()
     self.output_json = os.path.join(self.test_dir, 'output.json')
-    self.service_account_file = os.path.join(
-        self.test_dir, 'fake_service_account.json')
-    with open(self.service_account_file, 'w') as f:
-      json.dump([1,2,3,4], f)
     self.task_output_dir = os.path.join(
         os.path.dirname(__file__), 'testdata', 'task_output_dir')
 
@@ -123,7 +119,6 @@
 
     return_code, benchmark_upload_result_map = ppr_module.process_perf_results(
         self.output_json, configuration_name='test-builder',
-        service_account_file=self.service_account_file,
         build_properties=build_properties,
         task_output_dir=self.task_output_dir,
         smoke_test_mode=False,
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index f555288..0b8a4b2d 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -201,6 +201,7 @@
  <item id="prefetch_download" hash_code="44583172" type="0" content_hash_code="21424542" os_list="linux,windows" file_path="components/offline_pages/core/prefetch/prefetch_downloader_impl.cc"/>
  <item id="prefetch_visuals" hash_code="91068704" type="0" content_hash_code="90439946" os_list="linux,windows" file_path="components/offline_pages/core/prefetch/visuals_fetch_by_url.cc"/>
  <item id="previews_litepage_prober" hash_code="33813109" type="0" content_hash_code="52572789" os_list="linux,windows" file_path="chrome/browser/previews/previews_lite_page_decider.cc"/>
+ <item id="previews_litepage_origin_prober" hash_code="33703614" type="0" content_hash_code="116235355" os_list="linux,windows" file_path="chrome/browser/previews/previews_lite_page_redirect_url_loader.cc"/>
  <item id="previews_prober" hash_code="41010697" type="0" deprecated="2019-07-24" content_hash_code="51581107" file_path=""/>
  <item id="printer_job_handler" hash_code="67638271" type="1" second_id="111712433" content_hash_code="75712693" os_list="linux,windows" semantics_fields="2,3,4" file_path="chrome/service/cloud_print/printer_job_handler.cc"/>
  <item id="privet_http_impl" hash_code="71251498" type="0" content_hash_code="107348604" os_list="linux,windows" file_path="chrome/browser/printing/cloud_print/privet_http_impl.cc"/>
diff --git a/ui/accelerated_widget_mac/ca_layer_tree_coordinator.mm b/ui/accelerated_widget_mac/ca_layer_tree_coordinator.mm
index fb4e271..d13adf7 100644
--- a/ui/accelerated_widget_mac/ca_layer_tree_coordinator.mm
+++ b/ui/accelerated_widget_mac/ca_layer_tree_coordinator.mm
@@ -36,8 +36,8 @@
 
 CARendererLayerTree* CALayerTreeCoordinator::GetPendingCARendererLayerTree() {
   if (!pending_ca_renderer_layer_tree_)
-    pending_ca_renderer_layer_tree_.reset(new CARendererLayerTree(
-        allow_av_sample_buffer_display_layer_, false));
+    pending_ca_renderer_layer_tree_ = std::make_unique<CARendererLayerTree>(
+        allow_av_sample_buffer_display_layer_, false);
   return pending_ca_renderer_layer_tree_.get();
 }
 
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index 5005072..e1f1ac2 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -219,7 +219,8 @@
   data_.relative_bounds.offset_container_id = offset_container_id;
   data_.relative_bounds.bounds = location;
   if (transform)
-    data_.relative_bounds.transform.reset(new gfx::Transform(*transform));
+    data_.relative_bounds.transform =
+        std::make_unique<gfx::Transform>(*transform);
   else
     data_.relative_bounds.transform.reset(nullptr);
 }
diff --git a/ui/accessibility/ax_range_unittest.cc b/ui/accessibility/ax_range_unittest.cc
index af2a803..2145f4b 100644
--- a/ui/accessibility/ax_range_unittest.cc
+++ b/ui/accessibility/ax_range_unittest.cc
@@ -273,7 +273,7 @@
   initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
   initial_state.tree_data.title = "Dialog title";
 
-  tree_.reset(new AXTree(initial_state));
+  tree_ = std::make_unique<AXTree>(initial_state);
   AXNodePosition::SetTree(tree_.get());
   AXTreeManagerMap::GetInstance().AddTreeManager(
       initial_state.tree_data.tree_id, this);
diff --git a/ui/accessibility/ax_relative_bounds.cc b/ui/accessibility/ax_relative_bounds.cc
index 300b4145..3ad057f2c 100644
--- a/ui/accessibility/ax_relative_bounds.cc
+++ b/ui/accessibility/ax_relative_bounds.cc
@@ -23,14 +23,14 @@
   offset_container_id = other.offset_container_id;
   bounds = other.bounds;
   if (other.transform)
-    transform.reset(new gfx::Transform(*other.transform));
+    transform = std::make_unique<gfx::Transform>(*other.transform);
 }
 
 AXRelativeBounds& AXRelativeBounds::operator=(AXRelativeBounds other) {
   offset_container_id = other.offset_container_id;
   bounds = other.bounds;
   if (other.transform)
-    transform.reset(new gfx::Transform(*other.transform));
+    transform = std::make_unique<gfx::Transform>(*other.transform);
   else
     transform.reset(nullptr);
   return *this;
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index f64680d..e5e11b72 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -533,13 +533,13 @@
   // TODO(chrishall): do we want to initialize all the time, on demand, or only
   //                  when feature flag is set?
   DCHECK(!language_detection_manager);
-  language_detection_manager.reset(new AXLanguageDetectionManager());
+  language_detection_manager = std::make_unique<AXLanguageDetectionManager>();
 }
 
 AXTree::AXTree(const AXTreeUpdate& initial_state) {
   CHECK(Unserialize(initial_state)) << error();
   DCHECK(!language_detection_manager);
-  language_detection_manager.reset(new AXLanguageDetectionManager());
+  language_detection_manager = std::make_unique<AXLanguageDetectionManager>();
 }
 
 AXTree::~AXTree() {
diff --git a/ui/accessibility/ax_tree_serializer_unittest.cc b/ui/accessibility/ax_tree_serializer_unittest.cc
index 814accfd..4357061 100644
--- a/ui/accessibility/ax_tree_serializer_unittest.cc
+++ b/ui/accessibility/ax_tree_serializer_unittest.cc
@@ -53,13 +53,13 @@
   if (serializer_)
     return;
 
-  tree0_.reset(new AXSerializableTree(treedata0_));
-  tree1_.reset(new AXSerializableTree(treedata1_));
+  tree0_ = std::make_unique<AXSerializableTree>(treedata0_);
+  tree1_ = std::make_unique<AXSerializableTree>(treedata1_);
 
   // Serialize tree0 so that AXTreeSerializer thinks that its client
   // is totally in sync.
   tree0_source_.reset(tree0_->CreateTreeSource());
-  serializer_.reset(new BasicAXTreeSerializer(tree0_source_.get()));
+  serializer_ = std::make_unique<BasicAXTreeSerializer>(tree0_source_.get());
   AXTreeUpdate unused_update;
   ASSERT_TRUE(serializer_->SerializeChanges(tree0_->root(), &unused_update));
 
@@ -328,9 +328,9 @@
   treedata0_.nodes[5].id = 6;
   treedata0_.nodes[6].id = 7;
 
-  tree0_.reset(new AXSerializableTree(treedata0_));
+  tree0_ = std::make_unique<AXSerializableTree>(treedata0_);
   tree0_source_.reset(tree0_->CreateTreeSource());
-  serializer_.reset(new BasicAXTreeSerializer(tree0_source_.get()));
+  serializer_ = std::make_unique<BasicAXTreeSerializer>(tree0_source_.get());
   serializer_->set_max_node_count(4);
   AXTreeUpdate update;
   ASSERT_TRUE(serializer_->SerializeChanges(tree0_->root(), &update));
diff --git a/ui/accessibility/ax_tree_unittest.cc b/ui/accessibility/ax_tree_unittest.cc
index 8706622..25f74936 100644
--- a/ui/accessibility/ax_tree_unittest.cc
+++ b/ui/accessibility/ax_tree_unittest.cc
@@ -1166,7 +1166,8 @@
   tree_update.nodes.resize(3);
   tree_update.nodes[0].id = 1;
   tree_update.nodes[0].relative_bounds.bounds = gfx::RectF(0, 0, 400, 300);
-  tree_update.nodes[0].relative_bounds.transform.reset(new gfx::Transform());
+  tree_update.nodes[0].relative_bounds.transform =
+      std::make_unique<gfx::Transform>();
   tree_update.nodes[0].relative_bounds.transform->Scale(2.0, 2.0);
   tree_update.nodes[0].child_ids.push_back(2);
   tree_update.nodes[0].child_ids.push_back(3);
@@ -1174,7 +1175,8 @@
   tree_update.nodes[1].relative_bounds.bounds = gfx::RectF(20, 10, 50, 5);
   tree_update.nodes[2].id = 3;
   tree_update.nodes[2].relative_bounds.bounds = gfx::RectF(20, 30, 50, 5);
-  tree_update.nodes[2].relative_bounds.transform.reset(new gfx::Transform());
+  tree_update.nodes[2].relative_bounds.transform =
+      std::make_unique<gfx::Transform>();
   tree_update.nodes[2].relative_bounds.transform->Scale(2.0, 2.0);
 
   AXTree tree(tree_update);
diff --git a/ui/accessibility/mojom/ax_relative_bounds_mojom_traits.cc b/ui/accessibility/mojom/ax_relative_bounds_mojom_traits.cc
index 27effb18..6e1fa3b 100644
--- a/ui/accessibility/mojom/ax_relative_bounds_mojom_traits.cc
+++ b/ui/accessibility/mojom/ax_relative_bounds_mojom_traits.cc
@@ -24,7 +24,7 @@
   if (!data.ReadTransform(&transform))
     return false;
   if (!transform.IsIdentity())
-    out->transform.reset(new gfx::Transform(transform));
+    out->transform = std::make_unique<gfx::Transform>(transform);
 
   if (!data.ReadBounds(&out->bounds))
     return false;
diff --git a/ui/accessibility/platform/ax_fragment_root_win_unittest.cc b/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
index a6c36d6..1df70f4 100644
--- a/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
+++ b/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
@@ -148,7 +148,7 @@
   ComPtr<IRawElementProviderFragmentRoot> fragment_root_provider =
       GetFragmentRoot();
 
-  tree_.reset(new AXTree());
+  tree_ = std::make_unique<AXTree>();
   ax_fragment_root_.reset(nullptr);
 
   ComPtr<IRawElementProviderSimple> returned_simple_provider;
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
index e9971bd..31966672 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -203,7 +203,7 @@
   EXPECT_FALSE(atk_state_set_contains_state(state_set, ATK_STATE_DEFUNCT));
   g_object_unref(state_set);
 
-  tree_.reset(new AXTree());
+  tree_ = std::make_unique<AXTree>();
   EXPECT_EQ(nullptr, atk_object_get_name(root_obj));
 
   state_set = atk_object_ref_state_set(root_obj);
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index b0d3ef65..48a16080 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -4188,7 +4188,7 @@
       text_provider->get_DocumentRange(&text_range_provider));
   ASSERT_NE(nullptr, text_range_provider.Get());
 
-  tree_.reset(new AXTree());
+  tree_ = std::make_unique<AXTree>();
 
   BOOL bool_arg = FALSE;
   ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
diff --git a/ui/accessibility/platform/ax_platform_node_unittest.cc b/ui/accessibility/platform/ax_platform_node_unittest.cc
index 6f65627..e8d77a7 100644
--- a/ui/accessibility/platform/ax_platform_node_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_unittest.cc
@@ -13,7 +13,7 @@
 AXPlatformNodeTest::~AXPlatformNodeTest() {}
 
 void AXPlatformNodeTest::Init(const AXTreeUpdate& initial_state) {
-  tree_.reset(new AXTree(initial_state));
+  tree_ = std::make_unique<AXTree>(initial_state);
 }
 
 void AXPlatformNodeTest::Init(
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index 7ce5ef3..fbfa7c5 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -436,7 +436,7 @@
   EXPECT_EQ(S_OK, root_obj->get_accName(SELF, name.Receive()));
   EXPECT_STREQ(L"Name", name);
 
-  tree_.reset(new AXTree());
+  tree_ = std::make_unique<AXTree>();
   ScopedBstr name2;
   EXPECT_EQ(E_FAIL, root_obj->get_accName(SELF, name2.Receive()));
 }
@@ -4800,7 +4800,7 @@
   ComPtr<IWindowProvider> window_provider =
       QueryInterfaceFromNode<IWindowProvider>(GetRootNode());
 
-  tree_.reset(new AXTree());
+  tree_ = std::make_unique<AXTree>();
 
   // IGridItemProvider
   int int_result = 0;
diff --git a/ui/aura/gestures/gesture_recognizer_unittest.cc b/ui/aura/gestures/gesture_recognizer_unittest.cc
index 0b2ebfa..ec817fc5 100644
--- a/ui/aura/gestures/gesture_recognizer_unittest.cc
+++ b/ui/aura/gestures/gesture_recognizer_unittest.cc
@@ -196,7 +196,7 @@
 
   void WaitUntilReceivedGesture(ui::EventType type) {
     wait_until_event_ = type;
-    run_loop_.reset(new base::RunLoop());
+    run_loop_ = std::make_unique<base::RunLoop>();
     run_loop_->Run();
   }
 
@@ -2586,12 +2586,12 @@
 
   // End the two touches, one by a touch-release and one by a touch-cancel; to
   // cover both cases.
-  touch0.reset(new ui::TouchEvent(
+  touch0 = std::make_unique<ui::TouchEvent>(
       ui::ET_TOUCH_RELEASED, gfx::Point(20, 20), tes.Now(),
-      ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0)));
-  touch1.reset(new ui::TouchEvent(
+      ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
+  touch1 = std::make_unique<ui::TouchEvent>(
       ui::ET_TOUCH_CANCELLED, gfx::Point(30, 30), tes.Now(),
-      ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1)));
+      ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
   generator.Dispatch(touch0.get());
   generator.Dispatch(touch1.get());
   RunAllPendingInMessageLoop();
diff --git a/ui/aura/test/aura_test_helper.cc b/ui/aura/test/aura_test_helper.cc
index 2976cad66..3a2e00b 100644
--- a/ui/aura/test/aura_test_helper.cc
+++ b/ui/aura/test/aura_test_helper.cc
@@ -45,8 +45,8 @@
 AuraTestHelper::AuraTestHelper(std::unique_ptr<Env> env)
     : env_(std::move(env)) {
   // Disable animations during tests.
-  zero_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
-      ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
+  zero_duration_mode_ = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
+      ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
   ui::test::EnableTestConfigForPlatformWindows();
 }
 
@@ -112,7 +112,8 @@
 
   client::SetFocusClient(root_window(), focus_client_.get());
   client::SetCaptureClient(root_window(), capture_client());
-  parenting_client_.reset(new TestWindowParentingClient(root_window()));
+  parenting_client_ =
+      std::make_unique<TestWindowParentingClient>(root_window());
 
   root_window()->Show();
   // Ensure width != height so tests won't confuse them.
diff --git a/ui/aura/window_event_dispatcher.cc b/ui/aura/window_event_dispatcher.cc
index 9c5e461a..1fe00525 100644
--- a/ui/aura/window_event_dispatcher.cc
+++ b/ui/aura/window_event_dispatcher.cc
@@ -118,11 +118,12 @@
   // We allow for only one outstanding repostable event. This is used
   // in exiting context menus.  A dropped repost request is allowed.
   if (event->type() == ui::ET_MOUSE_PRESSED) {
-    held_repostable_event_.reset(new ui::MouseEvent(
+    held_repostable_event_ = std::make_unique<ui::MouseEvent>(
         *event->AsMouseEvent(), static_cast<aura::Window*>(event->target()),
-        window()));
+        window());
   } else if (event->type() == ui::ET_TOUCH_PRESSED) {
-    held_repostable_event_.reset(new ui::TouchEvent(*event->AsTouchEvent()));
+    held_repostable_event_ =
+        std::make_unique<ui::TouchEvent>(*event->AsTouchEvent());
   } else {
     DCHECK(event->type() == ui::ET_GESTURE_TAP_DOWN);
     held_repostable_event_.reset();
@@ -890,7 +891,8 @@
 
   if (IsEventCandidateForHold(*event) && !dispatching_held_event_) {
     if (move_hold_count_) {
-      held_move_event_.reset(new ui::MouseEvent(*event, target, window()));
+      held_move_event_ =
+          std::make_unique<ui::MouseEvent>(*event, target, window());
       event->SetHandled();
       return DispatchDetails();
     } else {
@@ -996,7 +998,8 @@
     ui::TouchEvent* event) {
   if (event->type() == ui::ET_TOUCH_MOVED && move_hold_count_ &&
       !dispatching_held_event_) {
-    held_move_event_.reset(new ui::TouchEvent(*event, target, window()));
+    held_move_event_ =
+        std::make_unique<ui::TouchEvent>(*event, target, window());
     event->SetHandled();
     return DispatchDetails();
   }
diff --git a/ui/aura/window_event_dispatcher_unittest.cc b/ui/aura/window_event_dispatcher_unittest.cc
index 006fa860..821688d 100644
--- a/ui/aura/window_event_dispatcher_unittest.cc
+++ b/ui/aura/window_event_dispatcher_unittest.cc
@@ -212,38 +212,38 @@
   std::unique_ptr<ui::MouseEvent> event;
 
   // Press the left button.
-  event.reset(new ui::MouseEvent(
+  event = std::make_unique<ui::MouseEvent>(
       ui::ET_MOUSE_PRESSED, location, location, ui::EventTimeForNow(),
-      ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+      ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
   DispatchEventUsingWindowDispatcher(event.get());
   EXPECT_TRUE(Env::GetInstance()->IsMouseButtonDown());
 
   // Additionally press the right.
-  event.reset(new ui::MouseEvent(
+  event = std::make_unique<ui::MouseEvent>(
       ui::ET_MOUSE_PRESSED, location, location, ui::EventTimeForNow(),
       ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON,
-      ui::EF_RIGHT_MOUSE_BUTTON));
+      ui::EF_RIGHT_MOUSE_BUTTON);
   DispatchEventUsingWindowDispatcher(event.get());
   EXPECT_TRUE(Env::GetInstance()->IsMouseButtonDown());
 
   // Release the left button.
-  event.reset(new ui::MouseEvent(
+  event = std::make_unique<ui::MouseEvent>(
       ui::ET_MOUSE_RELEASED, location, location, ui::EventTimeForNow(),
-      ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+      ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
   DispatchEventUsingWindowDispatcher(event.get());
   EXPECT_TRUE(Env::GetInstance()->IsMouseButtonDown());
 
   // Release the right button.  We should ignore the Shift-is-down flag.
-  event.reset(new ui::MouseEvent(ui::ET_MOUSE_RELEASED, location, location,
-                                 ui::EventTimeForNow(), ui::EF_SHIFT_DOWN,
-                                 ui::EF_RIGHT_MOUSE_BUTTON));
+  event = std::make_unique<ui::MouseEvent>(
+      ui::ET_MOUSE_RELEASED, location, location, ui::EventTimeForNow(),
+      ui::EF_SHIFT_DOWN, ui::EF_RIGHT_MOUSE_BUTTON);
   DispatchEventUsingWindowDispatcher(event.get());
   EXPECT_FALSE(Env::GetInstance()->IsMouseButtonDown());
 
   // Press the middle button.
-  event.reset(new ui::MouseEvent(
+  event = std::make_unique<ui::MouseEvent>(
       ui::ET_MOUSE_PRESSED, location, location, ui::EventTimeForNow(),
-      ui::EF_MIDDLE_MOUSE_BUTTON, ui::EF_MIDDLE_MOUSE_BUTTON));
+      ui::EF_MIDDLE_MOUSE_BUTTON, ui::EF_MIDDLE_MOUSE_BUTTON);
   DispatchEventUsingWindowDispatcher(event.get());
   EXPECT_TRUE(Env::GetInstance()->IsMouseButtonDown());
 }
@@ -512,7 +512,7 @@
 
   void WaitUntilReceivedEvent(ui::EventType type) {
     wait_until_event_ = type;
-    run_loop_.reset(new base::RunLoop());
+    run_loop_ = std::make_unique<base::RunLoop>();
     run_loop_->Run();
   }
 
diff --git a/ui/aura/window_tree_host.cc b/ui/aura/window_tree_host.cc
index 5fe58869..17c00c0 100644
--- a/ui/aura/window_tree_host.cc
+++ b/ui/aura/window_tree_host.cc
@@ -424,8 +424,8 @@
 
 void WindowTreeHost::OnAcceleratedWidgetAvailable() {
   compositor_->SetAcceleratedWidget(GetAcceleratedWidget());
-  prop_.reset(new ui::ViewProp(GetAcceleratedWidget(),
-                               kWindowTreeHostForAcceleratedWidget, this));
+  prop_ = std::make_unique<ui::ViewProp>(
+      GetAcceleratedWidget(), kWindowTreeHostForAcceleratedWidget, this);
 }
 
 void WindowTreeHost::OnHostMovedInPixels(
diff --git a/ui/aura/window_tree_host_platform.cc b/ui/aura/window_tree_host_platform.cc
index f10ec246..fe8e2ba 100644
--- a/ui/aura/window_tree_host_platform.cc
+++ b/ui/aura/window_tree_host_platform.cc
@@ -72,9 +72,9 @@
   platform_window_ = ui::OzonePlatform::GetInstance()->CreatePlatformWindow(
       this, std::move(properties));
 #elif defined(OS_WIN)
-  platform_window_.reset(new ui::WinWindow(this, properties.bounds));
+  platform_window_ = std::make_unique<ui::WinWindow>(this, properties.bounds);
 #elif defined(USE_X11)
-  platform_window_.reset(new ui::X11Window(this, properties.bounds));
+  platform_window_ = std::make_unique<ui::X11Window>(this, properties.bounds);
 #else
   NOTIMPLEMENTED();
 #endif
diff --git a/ui/base/clipboard/clipboard_win.cc b/ui/base/clipboard/clipboard_win.cc
index 9d174a55..34c13c2 100644
--- a/ui/base/clipboard/clipboard_win.cc
+++ b/ui/base/clipboard/clipboard_win.cc
@@ -230,7 +230,7 @@
 // ClipboardWin implementation.
 ClipboardWin::ClipboardWin() {
   if (base::MessageLoopCurrentForUI::IsSet())
-    clipboard_owner_.reset(new base::win::MessageWindow());
+    clipboard_owner_ = std::make_unique<base::win::MessageWindow>();
 }
 
 ClipboardWin::~ClipboardWin() {
diff --git a/ui/base/cursor/cursor_loader_x11.cc b/ui/base/cursor/cursor_loader_x11.cc
index fe80db0..e5c2df2 100644
--- a/ui/base/cursor/cursor_loader_x11.cc
+++ b/ui/base/cursor/cursor_loader_x11.cc
@@ -95,7 +95,8 @@
 
   GetImageCursorBitmap(resource_id, scale(), rotation(), &hotspot, &bitmap);
   XcursorImage* x_image = SkBitmapToXcursorImage(&bitmap, hotspot);
-  image_cursors_[id].reset(new ImageCursor(x_image, scale(), rotation()));
+  image_cursors_[id] =
+      std::make_unique<ImageCursor>(x_image, scale(), rotation());
 }
 
 void CursorLoaderX11::LoadAnimatedCursor(CursorType id,
diff --git a/ui/base/dragdrop/os_exchange_data_provider_win.cc b/ui/base/dragdrop/os_exchange_data_provider_win.cc
index 85a41ec..139dd138 100644
--- a/ui/base/dragdrop/os_exchange_data_provider_win.cc
+++ b/ui/base/dragdrop/os_exchange_data_provider_win.cc
@@ -899,8 +899,8 @@
       // Replace stored data.
       STGMEDIUM* storage =
           GetStorageForFileNames({FileInfo(file_path, base::FilePath())});
-      content.reset(new StoredDataInfo(
-          ClipboardFormatType::GetCFHDropType().ToFormatEtc(), storage));
+      content = std::make_unique<StoredDataInfo>(
+          ClipboardFormatType::GetCFHDropType().ToFormatEtc(), storage);
 
       break;
     }
diff --git a/ui/base/ime/chromeos/component_extension_ime_manager_unittest.cc b/ui/base/ime/chromeos/component_extension_ime_manager_unittest.cc
index 7d9260ca..b36c98fd 100644
--- a/ui/base/ime/chromeos/component_extension_ime_manager_unittest.cc
+++ b/ui/base/ime/chromeos/component_extension_ime_manager_unittest.cc
@@ -115,7 +115,7 @@
 
     mock_delegate_ = new MockComponentExtIMEManagerDelegate();
     mock_delegate_->set_ime_list(ime_list_);
-    component_ext_mgr_.reset(new ComponentExtensionIMEManager());
+    component_ext_mgr_ = std::make_unique<ComponentExtensionIMEManager>();
     component_ext_mgr_->Initialize(base::WrapUnique(mock_delegate_));
   }
 
diff --git a/ui/base/ime/chromeos/ime_keyboard_unittest.cc b/ui/base/ime/chromeos/ime_keyboard_unittest.cc
index 1b8bc55..f51f13b 100644
--- a/ui/base/ime/chromeos/ime_keyboard_unittest.cc
+++ b/ui/base/ime/chromeos/ime_keyboard_unittest.cc
@@ -16,7 +16,7 @@
                         public ImeKeyboard::Observer {
  public:
   void SetUp() override {
-    xkey_.reset(new FakeImeKeyboard());
+    xkey_ = std::make_unique<FakeImeKeyboard>();
     xkey_->AddObserver(this);
     caps_changed_ = false;
   }
diff --git a/ui/base/ime/chromeos/input_method_chromeos.cc b/ui/base/ime/chromeos/input_method_chromeos.cc
index 2915c2f..3b8afc13 100644
--- a/ui/base/ime/chromeos/input_method_chromeos.cc
+++ b/ui/base/ime/chromeos/input_method_chromeos.cc
@@ -42,7 +42,7 @@
 }
 
 InputMethodChromeOS::~InputMethodChromeOS() {
-  ConfirmCompositionText();
+  ConfirmCompositionText(/* reset_engine */ true);
   // We are dead, so we need to ask the client to stop relying on us.
   OnInputMethodChanged();
 
@@ -284,7 +284,7 @@
 void InputMethodChromeOS::OnWillChangeFocusedClient(
     TextInputClient* focused_before,
     TextInputClient* focused) {
-  ConfirmCompositionText();
+  ConfirmCompositionText(/* reset_engine */ true);
 
   if (GetEngine())
     GetEngine()->FocusOut();
@@ -344,13 +344,14 @@
   }
 }
 
-void InputMethodChromeOS::ConfirmCompositionText() {
-  InputMethodBase::ConfirmCompositionText();
+void InputMethodChromeOS::ConfirmCompositionText(bool reset_engine) {
+  InputMethodBase::ConfirmCompositionText(reset_engine);
 
-  ResetContext();
+  // See https://crbug.com/984472.
+  ResetContext(reset_engine);
 }
 
-void InputMethodChromeOS::ResetContext() {
+void InputMethodChromeOS::ResetContext(bool reset_engine) {
   if (!IsNonPasswordInputFieldFocused() || !GetTextInputClient())
     return;
 
@@ -359,7 +360,7 @@
   composing_text_ = false;
   composition_changed_ = false;
 
-  if (GetEngine())
+  if (reset_engine && GetEngine())
     GetEngine()->Reset();
 
   character_composer_.Reset();
diff --git a/ui/base/ime/chromeos/input_method_chromeos.h b/ui/base/ime/chromeos/input_method_chromeos.h
index 54726d0f..dc78754 100644
--- a/ui/base/ime/chromeos/input_method_chromeos.h
+++ b/ui/base/ime/chromeos/input_method_chromeos.h
@@ -52,7 +52,7 @@
       uint32_t before,
       uint32_t after,
       const std::vector<ui::ImeTextSpan>& text_spans) override;
-  void ConfirmCompositionText() override;
+  void ConfirmCompositionText(bool reset_engine) override;
 
  protected:
   // Converts |text| into CompositionText.
@@ -68,7 +68,8 @@
       bool stopped_propagation) WARN_UNUSED_RESULT;
 
   // Resets context and abandon all pending results and key events.
-  void ResetContext();
+  // If |reset_engine| is true, a reset signal will be sent to the IME.
+  void ResetContext(bool reset_engine = true);
 
  private:
   class PendingKeyEvent;
diff --git a/ui/base/ime/chromeos/input_method_chromeos_unittest.cc b/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
index 5b0a506a..16e0be7c 100644
--- a/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
+++ b/ui/base/ime/chromeos/input_method_chromeos_unittest.cc
@@ -910,7 +910,7 @@
   input_type_ = TEXT_INPUT_TYPE_TEXT;
   ime_->OnTextInputTypeChanged(this);
 
-  ime_->ConfirmCompositionText();
+  ime_->ConfirmCompositionText(/* reset_engine */ true);
 
   EXPECT_TRUE(confirmed_text_.text.empty());
   EXPECT_TRUE(composition_text_.text.empty());
@@ -924,7 +924,7 @@
   CompositionText composition_text;
   composition_text.text = base::UTF8ToUTF16("hello");
   SetCompositionText(composition_text);
-  ime_->ConfirmCompositionText();
+  ime_->ConfirmCompositionText(/* reset_engine */ true);
 
   EXPECT_EQ(base::ASCIIToUTF16("hello"), confirmed_text_.text);
   EXPECT_TRUE(composition_text_.text.empty());
@@ -941,7 +941,7 @@
 
   // "abc" is in composition. Put the two characters in composition.
   ime_->SetCompositionRange(0, 2, {});
-  ime_->ConfirmCompositionText();
+  ime_->ConfirmCompositionText(/* reset_engine */ true);
 
   EXPECT_EQ(base::ASCIIToUTF16("ab"), confirmed_text_.text);
   EXPECT_TRUE(composition_text_.text.empty());
diff --git a/ui/base/ime/chromeos/mock_ime_engine_handler.cc b/ui/base/ime/chromeos/mock_ime_engine_handler.cc
index 20f33712..8d3d2db 100644
--- a/ui/base/ime/chromeos/mock_ime_engine_handler.cc
+++ b/ui/base/ime/chromeos/mock_ime_engine_handler.cc
@@ -48,7 +48,7 @@
 void MockIMEEngineHandler::ProcessKeyEvent(const ui::KeyEvent& key_event,
                                            KeyEventDoneCallback callback) {
   ++process_key_event_call_count_;
-  last_processed_key_event_.reset(new ui::KeyEvent(key_event));
+  last_processed_key_event_ = std::make_unique<ui::KeyEvent>(key_event);
   last_passed_callback_ = std::move(callback);
 }
 
diff --git a/ui/base/ime/ime_input_context_handler_interface.h b/ui/base/ime/ime_input_context_handler_interface.h
index 5837b4a..a62302c5 100644
--- a/ui/base/ime/ime_input_context_handler_interface.h
+++ b/ui/base/ime/ime_input_context_handler_interface.h
@@ -52,7 +52,8 @@
   virtual InputMethod* GetInputMethod() = 0;
 
   // Commits any composition text.
-  virtual void ConfirmCompositionText() = 0;
+  // Set |reset_engine| to false if this was triggered from the extension.
+  virtual void ConfirmCompositionText(bool reset_engine) = 0;
 
   // Returns true if there is any composition text.
   virtual bool HasCompositionText() = 0;
diff --git a/ui/base/ime/input_method_base.cc b/ui/base/ime/input_method_base.cc
index 062fa7a7..e2054af 100644
--- a/ui/base/ime/input_method_base.cc
+++ b/ui/base/ime/input_method_base.cc
@@ -284,7 +284,7 @@
   return this;
 }
 
-void InputMethodBase::ConfirmCompositionText() {
+void InputMethodBase::ConfirmCompositionText(bool reset_engine) {
   TextInputClient* client = GetTextInputClient();
   if (client && client->HasCompositionText())
     client->ConfirmCompositionText();
diff --git a/ui/base/ime/input_method_base.h b/ui/base/ime/input_method_base.h
index 04c2e3bd..f1fb5f0 100644
--- a/ui/base/ime/input_method_base.h
+++ b/ui/base/ime/input_method_base.h
@@ -98,7 +98,7 @@
   SurroundingTextInfo GetSurroundingTextInfo() override;
   void SendKeyEvent(KeyEvent* event) override;
   InputMethod* GetInputMethod() override;
-  void ConfirmCompositionText() override;
+  void ConfirmCompositionText(bool reset_engine) override;
   bool HasCompositionText() override;
 
   // Sends a fake key event for IME composing without physical key events.
diff --git a/ui/base/ime/linux/input_method_auralinux.cc b/ui/base/ime/linux/input_method_auralinux.cc
index bd85438..3a4125b 100644
--- a/ui/base/ime/linux/input_method_auralinux.cc
+++ b/ui/base/ime/linux/input_method_auralinux.cc
@@ -411,7 +411,7 @@
 void InputMethodAuraLinux::OnWillChangeFocusedClient(
     TextInputClient* focused_before,
     TextInputClient* focused) {
-  ConfirmCompositionText();
+  ConfirmCompositionText(/* reset_engine */ true);
 }
 
 void InputMethodAuraLinux::OnDidChangeFocusedClient(
@@ -448,9 +448,9 @@
   return details;
 }
 
-void InputMethodAuraLinux::ConfirmCompositionText() {
-  InputMethodBase::ConfirmCompositionText();
-  if (GetEngine())
+void InputMethodAuraLinux::ConfirmCompositionText(bool reset_engine) {
+  InputMethodBase::ConfirmCompositionText(reset_engine);
+  if (reset_engine && GetEngine())
     GetEngine()->Reset();
   ResetContext();
 }
diff --git a/ui/base/ime/linux/input_method_auralinux.h b/ui/base/ime/linux/input_method_auralinux.h
index b7ecac5..fffef79 100644
--- a/ui/base/ime/linux/input_method_auralinux.h
+++ b/ui/base/ime/linux/input_method_auralinux.h
@@ -47,7 +47,7 @@
                                  TextInputClient* focused) override;
   void OnDidChangeFocusedClient(TextInputClient* focused_before,
                                 TextInputClient* focused) override;
-  void ConfirmCompositionText() override;
+  void ConfirmCompositionText(bool reset_engine) override;
 
  private:
   bool HasInputMethodResult();
diff --git a/ui/base/ime/linux/input_method_auralinux_unittest.cc b/ui/base/ime/linux/input_method_auralinux_unittest.cc
index 11581c8..9305047 100644
--- a/ui/base/ime/linux/input_method_auralinux_unittest.cc
+++ b/ui/base/ime/linux/input_method_auralinux_unittest.cc
@@ -338,7 +338,8 @@
   test_result_->Verify();
 
   input_method_auralinux_->DetachTextInputClient(client.get());
-  client.reset(new TextInputClientForTesting(TEXT_INPUT_TYPE_PASSWORD));
+  client =
+      std::make_unique<TextInputClientForTesting>(TEXT_INPUT_TYPE_PASSWORD);
   context_simple_->SetSyncMode(true);
   context_simple_->SetEatKey(false);
 
@@ -371,7 +372,8 @@
   test_result_->Verify();
 
   input_method_auralinux_->DetachTextInputClient(client.get());
-  client.reset(new TextInputClientForTesting(TEXT_INPUT_TYPE_PASSWORD));
+  client =
+      std::make_unique<TextInputClientForTesting>(TEXT_INPUT_TYPE_PASSWORD);
   context_simple_->SetSyncMode(false);
   context_simple_->SetEatKey(false);
 
diff --git a/ui/base/ime/mock_ime_input_context_handler.cc b/ui/base/ime/mock_ime_input_context_handler.cc
index 0325909..bed1795 100644
--- a/ui/base/ime/mock_ime_input_context_handler.cc
+++ b/ui/base/ime/mock_ime_input_context_handler.cc
@@ -72,7 +72,7 @@
   return nullptr;
 }
 
-void MockIMEInputContextHandler::ConfirmCompositionText() {
+void MockIMEInputContextHandler::ConfirmCompositionText(bool reset_engine) {
   if (!HasCompositionText())
     return;
 
diff --git a/ui/base/ime/mock_ime_input_context_handler.h b/ui/base/ime/mock_ime_input_context_handler.h
index 8708214..d75e38b 100644
--- a/ui/base/ime/mock_ime_input_context_handler.h
+++ b/ui/base/ime/mock_ime_input_context_handler.h
@@ -48,7 +48,7 @@
   SurroundingTextInfo GetSurroundingTextInfo() override;
   void SendKeyEvent(KeyEvent* event) override;
   InputMethod* GetInputMethod() override;
-  void ConfirmCompositionText() override;
+  void ConfirmCompositionText(bool reset_engine) override;
   bool HasCompositionText() override;
 
   int commit_text_call_count() const { return commit_text_call_count_; }
diff --git a/ui/base/ime/win/input_method_win_imm32.cc b/ui/base/ime/win/input_method_win_imm32.cc
index 1e09c86b..2cc52439 100644
--- a/ui/base/ime/win/input_method_win_imm32.cc
+++ b/ui/base/ime/win/input_method_win_imm32.cc
@@ -159,7 +159,7 @@
     TextInputClient* focused_before,
     TextInputClient* focused) {
   if (IsWindowFocused(focused_before))
-    ConfirmCompositionText();
+    ConfirmCompositionText(/* reset_engine */ true);
 }
 
 void InputMethodWinImm32::OnDidChangeFocusedClient(
@@ -322,9 +322,10 @@
   }
 }
 
-void InputMethodWinImm32::ConfirmCompositionText() {
-  InputMethodBase::ConfirmCompositionText();
-  InputMethodWinBase::ResetEngine();
+void InputMethodWinImm32::ConfirmCompositionText(bool reset_engine) {
+  InputMethodBase::ConfirmCompositionText(reset_engine);
+  if (reset_engine)
+    InputMethodWinBase::ResetEngine();
 
   // Makes sure the native IME app can be informed about the composition is
   // cleared, so that it can clean up its internal states.
diff --git a/ui/base/ime/win/input_method_win_imm32.h b/ui/base/ime/win/input_method_win_imm32.h
index 672a308..25922ff 100644
--- a/ui/base/ime/win/input_method_win_imm32.h
+++ b/ui/base/ime/win/input_method_win_imm32.h
@@ -27,7 +27,7 @@
 
   // Overridden from InputMethodBase:
   void OnFocus() override;
-  void ConfirmCompositionText() override;
+  void ConfirmCompositionText(bool reset_engine) override;
 
   // Overridden from InputMethod:
   bool OnUntranslatedIMEMessage(const MSG event,
diff --git a/ui/base/ime/win/input_method_win_tsf.cc b/ui/base/ime/win/input_method_win_tsf.cc
index 972945e..cb6ff5c 100644
--- a/ui/base/ime/win/input_method_win_tsf.cc
+++ b/ui/base/ime/win/input_method_win_tsf.cc
@@ -145,7 +145,7 @@
     TextInputClient* focused_before,
     TextInputClient* focused) {
   if (IsWindowFocused(focused_before)) {
-    ConfirmCompositionText();
+    ConfirmCompositionText(/* reset_engine */ true);
     ui::TSFBridge::GetInstance()->RemoveFocusedClient(focused_before);
   }
 }
@@ -170,13 +170,14 @@
   InputMethodWinBase::OnDidChangeFocusedClient(focused_before, focused);
 }
 
-void InputMethodWinTSF::ConfirmCompositionText() {
-  if (!IsTextInputTypeNone()) {
-    if (GetTextInputClient()->HasCompositionText())
-      InputMethodWinBase::ResetEngine();
-    if (ui::TSFBridge::GetInstance())
-      ui::TSFBridge::GetInstance()->ConfirmComposition();
-  }
+void InputMethodWinTSF::ConfirmCompositionText(bool reset_engine) {
+  if (IsTextInputTypeNone())
+    return;
+
+  if (reset_engine && GetTextInputClient()->HasCompositionText())
+    InputMethodWinBase::ResetEngine();
+  if (ui::TSFBridge::GetInstance())
+    ui::TSFBridge::GetInstance()->ConfirmComposition();
 }
 
 }  // namespace ui
diff --git a/ui/base/ime/win/input_method_win_tsf.h b/ui/base/ime/win/input_method_win_tsf.h
index b6c2095..9798bf7 100644
--- a/ui/base/ime/win/input_method_win_tsf.h
+++ b/ui/base/ime/win/input_method_win_tsf.h
@@ -41,7 +41,7 @@
                                  TextInputClient* focused) override;
   void OnDidChangeFocusedClient(TextInputClient* focused_before,
                                 TextInputClient* focused) override;
-  void ConfirmCompositionText() override;
+  void ConfirmCompositionText(bool reset_engine) override;
 
  private:
   class TSFEventObserver;
diff --git a/ui/base/resource/resource_bundle.cc b/ui/base/resource/resource_bundle.cc
index 5caed90..5b2f19834 100644
--- a/ui/base/resource/resource_bundle.cc
+++ b/ui/base/resource/resource_bundle.cc
@@ -449,11 +449,11 @@
   if (!path.empty() && data_pack->LoadFromPath(path))
     AddDataPack(std::move(data_pack));
 
-  data_pack.reset(new DataPack(ui::SCALE_FACTOR_NONE));
+  data_pack = std::make_unique<DataPack>(ui::SCALE_FACTOR_NONE);
   if (!locale_path.empty() && data_pack->LoadFromPath(locale_path)) {
     locale_resources_data_ = std::move(data_pack);
   } else {
-    locale_resources_data_.reset(new DataPack(ui::SCALE_FACTOR_NONE));
+    locale_resources_data_ = std::make_unique<DataPack>(ui::SCALE_FACTOR_NONE);
   }
   // This is necessary to initialize ICU since we won't be calling
   // LoadLocaleResources in this case.
diff --git a/ui/base/test/nswindow_fullscreen_notification_waiter.mm b/ui/base/test/nswindow_fullscreen_notification_waiter.mm
index ad86a54..d29d998 100644
--- a/ui/base/test/nswindow_fullscreen_notification_waiter.mm
+++ b/ui/base/test/nswindow_fullscreen_notification_waiter.mm
@@ -46,7 +46,7 @@
 
   targetEnterCount_ = enterCount;
   targetExitCount_ = exitCount;
-  runLoop_.reset(new base::RunLoop);
+  runLoop_ = std::make_unique<base::RunLoop>();
   runLoop_->Run();
   runLoop_.reset();
 }
diff --git a/ui/base/test/scoped_fake_nswindow_fullscreen.mm b/ui/base/test/scoped_fake_nswindow_fullscreen.mm
index 201a3e7..3ad4b694 100644
--- a/ui/base/test/scoped_fake_nswindow_fullscreen.mm
+++ b/ui/base/test/scoped_fake_nswindow_fullscreen.mm
@@ -193,7 +193,7 @@
 
 ScopedFakeNSWindowFullscreen::ScopedFakeNSWindowFullscreen() {
   DCHECK(!g_fake_fullscreen_impl);
-  impl_.reset(new Impl);
+  impl_ = std::make_unique<Impl>();
   g_fake_fullscreen_impl = impl_.get();
 }
 
diff --git a/ui/base/test/scoped_preferred_scroller_style_mac.mm b/ui/base/test/scoped_preferred_scroller_style_mac.mm
index 84eb2750..e4ef348a 100644
--- a/ui/base/test/scoped_preferred_scroller_style_mac.mm
+++ b/ui/base/test/scoped_preferred_scroller_style_mac.mm
@@ -67,8 +67,8 @@
 
   DCHECK(!g_swizzling);
   g_swizzling = true;
-  swizzler_.reset(new ScopedObjCClassSwizzler(
-      [NSScroller class], style_class, @selector(preferredScrollerStyle)));
+  swizzler_ = std::make_unique<ScopedObjCClassSwizzler>(
+      [NSScroller class], style_class, @selector(preferredScrollerStyle));
 
   if (previous_style != GetScrollerStyle(overlay_))
     NotifyStyleChanged();
diff --git a/ui/base/win/session_change_observer.cc b/ui/base/win/session_change_observer.cc
index f848e25..1a048063 100644
--- a/ui/base/win/session_change_observer.cc
+++ b/ui/base/win/session_change_observer.cc
@@ -29,9 +29,9 @@
 
   WtsRegistrationNotificationManager() {
     DCHECK(!singleton_hwnd_observer_);
-    singleton_hwnd_observer_.reset(new gfx::SingletonHwndObserver(
+    singleton_hwnd_observer_ = std::make_unique<gfx::SingletonHwndObserver>(
         base::BindRepeating(&WtsRegistrationNotificationManager::OnWndProc,
-                            base::Unretained(this))));
+                            base::Unretained(this)));
 
     base::OnceClosure wts_register = base::BindOnce(
         base::IgnoreResult(&WTSRegisterSessionNotification),
diff --git a/ui/base/x/selection_requestor_unittest.cc b/ui/base/x/selection_requestor_unittest.cc
index 5d1bdc8..820391f3 100644
--- a/ui/base/x/selection_requestor_unittest.cc
+++ b/ui/base/x/selection_requestor_unittest.cc
@@ -71,7 +71,8 @@
 
     event_source_ = PlatformEventSource::CreateDefault();
     CHECK(PlatformEventSource::GetInstance());
-    requestor_.reset(new SelectionRequestor(x_display_, x_window_, nullptr));
+    requestor_ =
+        std::make_unique<SelectionRequestor>(x_display_, x_window_, nullptr);
   }
 
   void TearDown() override {
diff --git a/ui/base/x/x11_util.cc b/ui/base/x/x11_util.cc
index 6e8af2f..4f71574 100644
--- a/ui/base/x/x11_util.cc
+++ b/ui/base/x/x11_util.cc
@@ -1425,7 +1425,8 @@
   gfx::XScopedPtr<XVisualInfo[]> visual_list(XGetVisualInfo(
       display_, VisualScreenMask, &visual_template, &visuals_len));
   for (int i = 0; i < visuals_len; ++i)
-    visuals_[visual_list[i].visualid].reset(new XVisualData(visual_list[i]));
+    visuals_[visual_list[i].visualid] =
+        std::make_unique<XVisualData>(visual_list[i]);
 
   XAtom NET_WM_CM_S0 = gfx::GetAtom("_NET_WM_CM_S0");
   using_compositing_wm_ =
diff --git a/ui/chromeos/ime/candidate_view_unittest.cc b/ui/chromeos/ime/candidate_view_unittest.cc
index 24edee4..9eee963 100644
--- a/ui/chromeos/ime/candidate_view_unittest.cc
+++ b/ui/chromeos/ime/candidate_view_unittest.cc
@@ -61,8 +61,8 @@
     widget_->Show();
 
     aura::Window* native_window = widget_->GetNativeWindow();
-    event_generator_.reset(new ui::test::EventGenerator(
-        native_window->GetRootWindow(), native_window));
+    event_generator_ = std::make_unique<ui::test::EventGenerator>(
+        native_window->GetRootWindow(), native_window);
   }
 
   void TearDown() override {
diff --git a/ui/compositor/test/draw_waiter_for_test.cc b/ui/compositor/test/draw_waiter_for_test.cc
index 01787fd..32f89fb 100644
--- a/ui/compositor/test/draw_waiter_for_test.cc
+++ b/ui/compositor/test/draw_waiter_for_test.cc
@@ -33,7 +33,7 @@
 
 void DrawWaiterForTest::WaitImpl(Compositor* compositor) {
   compositor->AddObserver(this);
-  wait_run_loop_.reset(new base::RunLoop());
+  wait_run_loop_ = std::make_unique<base::RunLoop>();
   wait_run_loop_->Run();
   compositor->RemoveObserver(this);
 }
diff --git a/ui/compositor/test/in_process_context_provider.cc b/ui/compositor/test/in_process_context_provider.cc
index 803af87..7e22d82 100644
--- a/ui/compositor/test/in_process_context_provider.cc
+++ b/ui/compositor/test/in_process_context_provider.cc
@@ -159,9 +159,9 @@
   size_t max_glyph_cache_texture_bytes;
   gpu::raster::DefaultGrCacheLimitsForTests(&max_resource_cache_bytes,
                                             &max_glyph_cache_texture_bytes);
-  gr_context_.reset(new skia_bindings::GrContextForGLES2Interface(
+  gr_context_ = std::make_unique<skia_bindings::GrContextForGLES2Interface>(
       ContextGL(), ContextSupport(), ContextCapabilities(),
-      max_resource_cache_bytes, max_glyph_cache_texture_bytes));
+      max_resource_cache_bytes, max_glyph_cache_texture_bytes);
   cache_controller_->SetGrContext(gr_context_->get());
 
   return gr_context_->get();
diff --git a/ui/compositor/test/test_compositor_host_android.cc b/ui/compositor/test/test_compositor_host_android.cc
index 36f1330..845cb8d3 100644
--- a/ui/compositor/test/test_compositor_host_android.cc
+++ b/ui/compositor/test/test_compositor_host_android.cc
@@ -22,10 +22,10 @@
       const gfx::Rect& bounds,
       ui::ContextFactory* context_factory,
       ui::ContextFactoryPrivate* context_factory_private) {
-    compositor_.reset(new ui::Compositor(
+    compositor_ = std::make_unique<ui::Compositor>(
         context_factory_private->AllocateFrameSinkId(), context_factory,
         context_factory_private, base::ThreadTaskRunnerHandle::Get(),
-        false /* enable_pixel_canvas */));
+        false /* enable_pixel_canvas */);
     // TODO(sievers): Support onscreen here.
     compositor_->SetAcceleratedWidget(gfx::kNullAcceleratedWidget);
     compositor_->SetScaleAndSize(1.0f,
diff --git a/ui/compositor/test/test_compositor_host_win.cc b/ui/compositor/test/test_compositor_host_win.cc
index c4098a6..97b0e75 100644
--- a/ui/compositor/test/test_compositor_host_win.cc
+++ b/ui/compositor/test/test_compositor_host_win.cc
@@ -25,10 +25,10 @@
                         ui::ContextFactory* context_factory,
                         ui::ContextFactoryPrivate* context_factory_private) {
     Init(NULL, bounds);
-    compositor_.reset(new ui::Compositor(
+    compositor_ = std::make_unique<ui::Compositor>(
         context_factory_private->AllocateFrameSinkId(), context_factory,
         context_factory_private, base::ThreadTaskRunnerHandle::Get(),
-        false /* enable_pixel_canvas */));
+        false /* enable_pixel_canvas */);
     allocator_.GenerateId();
     compositor_->SetAcceleratedWidget(hwnd());
     compositor_->SetScaleAndSize(
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 6413f266..754f6a67 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -749,12 +749,12 @@
   // be anything scheduled.
   DCHECK(!configuration_task_);
 
-  configuration_task_.reset(new UpdateDisplayConfigurationTask(
+  configuration_task_ = std::make_unique<UpdateDisplayConfigurationTask>(
       native_display_delegate_.get(), layout_manager_.get(),
       requested_display_state_, GetRequestedPowerState(),
       kSetDisplayPowerForceProbe, /*force_configure=*/true,
       base::Bind(&DisplayConfigurator::OnConfigured,
-                 weak_ptr_factory_.GetWeakPtr())));
+                 weak_ptr_factory_.GetWeakPtr()));
   configuration_task_->Run();
 }
 
@@ -964,12 +964,12 @@
     return;
   }
 
-  configuration_task_.reset(new UpdateDisplayConfigurationTask(
+  configuration_task_ = std::make_unique<UpdateDisplayConfigurationTask>(
       native_display_delegate_.get(), layout_manager_.get(),
       requested_display_state_, pending_power_state_, pending_power_flags_,
       force_configure_,
       base::Bind(&DisplayConfigurator::OnConfigured,
-                 weak_ptr_factory_.GetWeakPtr())));
+                 weak_ptr_factory_.GetWeakPtr()));
 
   // Reset the flags before running the task; otherwise it may end up scheduling
   // another configuration.
diff --git a/ui/display/manager/display_configurator_unittest.cc b/ui/display/manager/display_configurator_unittest.cc
index 3948caf..a94cccf 100644
--- a/ui/display/manager/display_configurator_unittest.cc
+++ b/ui/display/manager/display_configurator_unittest.cc
@@ -207,7 +207,7 @@
   ~DisplayConfiguratorTest() override = default;
 
   void SetUp() override {
-    log_.reset(new ActionLogger());
+    log_ = std::make_unique<ActionLogger>();
 
     // Force system compositor mode to simulate on-device configurator behavior.
     base::CommandLine::ForCurrentProcess()->AppendSwitch(
diff --git a/ui/display/win/screen_win_unittest.cc b/ui/display/win/screen_win_unittest.cc
index c69c33a..3487e93 100644
--- a/ui/display/win/screen_win_unittest.cc
+++ b/ui/display/win/screen_win_unittest.cc
@@ -168,9 +168,8 @@
 
   void InitializeScreenWin() {
     ASSERT_EQ(screen_win_, nullptr);
-    screen_win_.reset(new TestScreenWin(display_infos_,
-                                        monitor_infos_,
-                                        hwnd_map_));
+    screen_win_ = std::make_unique<TestScreenWin>(display_infos_,
+                                                  monitor_infos_, hwnd_map_);
     Screen::SetScreenInstance(screen_win_.get());
   }
 
@@ -194,7 +193,7 @@
 
   void SetUp() override {
     testing::Test::SetUp();
-    screen_win_initializer_.reset(new TestScreenWinManager());
+    screen_win_initializer_ = std::make_unique<TestScreenWinManager>();
     SetUpScreen(screen_win_initializer_.get());
     screen_win_initializer_->InitializeScreenWin();
   }
diff --git a/ui/events/blink/input_handler_proxy.cc b/ui/events/blink/input_handler_proxy.cc
index ffec602..20ace0c 100644
--- a/ui/events/blink/input_handler_proxy.cc
+++ b/ui/events/blink/input_handler_proxy.cc
@@ -1197,7 +1197,7 @@
                scroll_result.unused_scroll_delta.y());
 
   // Bundle overscroll message with triggering event response, saving an IPC.
-  current_overscroll_params_.reset(new DidOverscrollParams());
+  current_overscroll_params_ = std::make_unique<DidOverscrollParams>();
   current_overscroll_params_->accumulated_overscroll =
       scroll_result.accumulated_root_overscroll;
   current_overscroll_params_->latest_overscroll_delta =
diff --git a/ui/events/event_utils.cc b/ui/events/event_utils.cc
index 4c4634c..0a4b1b7 100644
--- a/ui/events/event_utils.cc
+++ b/ui/events/event_utils.cc
@@ -23,7 +23,7 @@
   switch(type) {
     case ET_KEY_PRESSED:
     case ET_KEY_RELEASED:
-      event.reset(new KeyEvent(native_event));
+      event = std::make_unique<KeyEvent>(native_event);
       break;
 
     case ET_MOUSE_PRESSED:
@@ -32,24 +32,24 @@
     case ET_MOUSE_MOVED:
     case ET_MOUSE_ENTERED:
     case ET_MOUSE_EXITED:
-      event.reset(new MouseEvent(native_event));
+      event = std::make_unique<MouseEvent>(native_event);
       break;
 
     case ET_MOUSEWHEEL:
-      event.reset(new MouseWheelEvent(native_event));
+      event = std::make_unique<MouseWheelEvent>(native_event);
       break;
 
     case ET_SCROLL_FLING_START:
     case ET_SCROLL_FLING_CANCEL:
     case ET_SCROLL:
-      event.reset(new ScrollEvent(native_event));
+      event = std::make_unique<ScrollEvent>(native_event);
       break;
 
     case ET_TOUCH_RELEASED:
     case ET_TOUCH_PRESSED:
     case ET_TOUCH_MOVED:
     case ET_TOUCH_CANCELLED:
-      event.reset(new TouchEvent(native_event));
+      event = std::make_unique<TouchEvent>(native_event);
       break;
 
     default:
diff --git a/ui/events/gesture_detection/gesture_provider.cc b/ui/events/gesture_detection/gesture_provider.cc
index defe1e24..396cc32 100644
--- a/ui/events/gesture_detection/gesture_provider.cc
+++ b/ui/events/gesture_detection/gesture_provider.cc
@@ -816,7 +816,7 @@
          !config.max_gesture_bounds_length ||
          config.min_gesture_bounds_length <= config.max_gesture_bounds_length);
   TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors");
-  gesture_listener_.reset(new GestureListenerImpl(config, client));
+  gesture_listener_ = std::make_unique<GestureListenerImpl>(config, client);
   UpdateDoubleTapDetectionSupport();
 }
 
diff --git a/ui/events/gesture_detection/gesture_provider_unittest.cc b/ui/events/gesture_detection/gesture_provider_unittest.cc
index 4769ab3..11ba982 100644
--- a/ui/events/gesture_detection/gesture_provider_unittest.cc
+++ b/ui/events/gesture_detection/gesture_provider_unittest.cc
@@ -144,7 +144,7 @@
     EXPECT_EQ(GestureDeviceType::DEVICE_TOUCHSCREEN,
               gesture.details.device_type());
     if (gesture.type() == ET_GESTURE_SCROLL_BEGIN)
-      active_scroll_begin_event_.reset(new GestureEventData(gesture));
+      active_scroll_begin_event_ = std::make_unique<GestureEventData>(gesture);
     gestures_.push_back(gesture);
   }
 
@@ -153,7 +153,7 @@
   }
 
   void SetUpWithConfig(const GestureProvider::Config& config) {
-    gesture_provider_.reset(new GestureProvider(config, this));
+    gesture_provider_ = std::make_unique<GestureProvider>(config, this);
     gesture_provider_->SetMultiTouchZoomSupportEnabled(false);
   }
 
diff --git a/ui/events/gesture_detection/motion_event_buffer.cc b/ui/events/gesture_detection/motion_event_buffer.cc
index 596ca394..88d228a 100644
--- a/ui/events/gesture_detection/motion_event_buffer.cc
+++ b/ui/events/gesture_detection/motion_event_buffer.cc
@@ -130,8 +130,8 @@
         event0, event1, event0_i, static_cast<size_t>(event1_i), alpha);
 
     if (event0_i == 0) {
-      event.reset(new MotionEventGeneric(MotionEvent::Action::MOVE,
-                                         resample_time, pointer));
+      event = std::make_unique<MotionEventGeneric>(MotionEvent::Action::MOVE,
+                                                   resample_time, pointer);
     } else {
       event->PushPointer(pointer);
     }
diff --git a/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc b/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
index 0ecb9e7d..e66ccb8d 100644
--- a/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
@@ -31,7 +31,7 @@
 
   // testing::Test
   void SetUp() override {
-    queue_.reset(new TouchDispositionGestureFilter(this));
+    queue_ = std::make_unique<TouchDispositionGestureFilter>(this);
     touch_event_.set_flags(kDefaultEventFlags);
   }
 
@@ -42,7 +42,7 @@
     EXPECT_EQ(GestureDeviceType::DEVICE_TOUCHSCREEN,
               event.details.device_type());
     ++sent_gesture_count_;
-    last_sent_gesture_.reset(new GestureEventData(event));
+    last_sent_gesture_ = std::make_unique<GestureEventData>(event);
     sent_gestures_.push_back(event.type());
     if (event.type() == ET_GESTURE_SHOW_PRESS)
       show_press_bounding_box_ = event.details.bounding_box();
diff --git a/ui/events/gestures/gesture_provider_aura_unittest.cc b/ui/events/gestures/gesture_provider_aura_unittest.cc
index 38c2fbb9..f270dc4 100644
--- a/ui/events/gestures/gesture_provider_aura_unittest.cc
+++ b/ui/events/gestures/gesture_provider_aura_unittest.cc
@@ -25,8 +25,8 @@
                       GestureEvent* event) override {}
 
   void SetUp() override {
-    consumer_.reset(new GestureConsumer());
-    provider_.reset(new GestureProviderAura(consumer_.get(), this));
+    consumer_ = std::make_unique<GestureConsumer>();
+    provider_ = std::make_unique<GestureProviderAura>(consumer_.get(), this);
   }
 
   void TearDown() override { provider_.reset(); }
diff --git a/ui/events/ozone/evdev/event_converter_evdev_impl_unittest.cc b/ui/events/ozone/evdev/event_converter_evdev_impl_unittest.cc
index 7d3ef4fa..a644cd92 100644
--- a/ui/events/ozone/evdev/event_converter_evdev_impl_unittest.cc
+++ b/ui/events/ozone/evdev/event_converter_evdev_impl_unittest.cc
@@ -68,7 +68,7 @@
     base::ScopedFD events_in(evdev_io[0]);
     events_out_.reset(evdev_io[1]);
 
-    cursor_.reset(new ui::FakeCursorDelegateEvdev());
+    cursor_ = std::make_unique<ui::FakeCursorDelegateEvdev>();
 
     device_manager_ = ui::CreateDeviceManagerForTest();
     event_factory_ = ui::CreateEventFactoryEvdevForTest(
@@ -78,10 +78,10 @@
                             base::Unretained(this)));
     dispatcher_ =
         ui::CreateDeviceEventDispatcherEvdevForTest(event_factory_.get());
-    device_.reset(new ui::MockEventConverterEvdevImpl(
-        std::move(events_in), cursor_.get(), dispatcher_.get()));
+    device_ = std::make_unique<ui::MockEventConverterEvdevImpl>(
+        std::move(events_in), cursor_.get(), dispatcher_.get());
 
-    test_clock_.reset(new ui::test::ScopedEventTestTickClock());
+    test_clock_ = std::make_unique<ui::test::ScopedEventTestTickClock>();
   }
 
   void TearDown() override {
diff --git a/ui/events/ozone/evdev/tablet_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/tablet_event_converter_evdev_unittest.cc
index e3cc487..fc272fcc 100644
--- a/ui/events/ozone/evdev/tablet_event_converter_evdev_unittest.cc
+++ b/ui/events/ozone/evdev/tablet_event_converter_evdev_unittest.cc
@@ -208,7 +208,7 @@
 
   // Overridden from testing::Test:
   void SetUp() override {
-    cursor_.reset(new ui::MockTabletCursorEvdev());
+    cursor_ = std::make_unique<ui::MockTabletCursorEvdev>();
     device_manager_ = ui::CreateDeviceManagerForTest();
     event_factory_ = ui::CreateEventFactoryEvdevForTest(
         cursor_.get(), device_manager_.get(),
@@ -219,7 +219,7 @@
     dispatcher_ =
         ui::CreateDeviceEventDispatcherEvdevForTest(event_factory_.get());
 
-    test_clock_.reset(new ui::test::ScopedEventTestTickClock());
+    test_clock_ = std::make_unique<ui::test::ScopedEventTestTickClock>();
   }
 
   void TearDown() override {
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
index 9091e81..7bc3a88 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
@@ -242,17 +242,17 @@
     // Device creation happens on a worker thread since it may involve blocking
     // operations. Simulate that by creating it before creating a UI message
     // loop.
-    shared_palm_state_.reset(new ui::SharedPalmDetectionFilterState);
+    shared_palm_state_ = std::make_unique<ui::SharedPalmDetectionFilterState>();
     EventDeviceInfo devinfo;
-    dispatcher_.reset(new ui::MockDeviceEventDispatcherEvdev(
+    dispatcher_ = std::make_unique<ui::MockDeviceEventDispatcherEvdev>(
         base::BindRepeating(&TouchEventConverterEvdevTest::DispatchCallback,
-                            base::Unretained(this))));
-    device_.reset(new ui::MockTouchEventConverterEvdev(
+                            base::Unretained(this)));
+    device_ = std::make_unique<ui::MockTouchEventConverterEvdev>(
         std::move(events_in), base::FilePath(kTestDevicePath), devinfo,
-        shared_palm_state_.get(), dispatcher_.get()));
+        shared_palm_state_.get(), dispatcher_.get());
     device_->Initialize(devinfo);
 
-    test_clock_.reset(new ui::test::ScopedEventTestTickClock());
+    test_clock_ = std::make_unique<ui::test::ScopedEventTestTickClock>();
     ui::DeviceDataManager::CreateInstance();
   }
 
diff --git a/ui/events/ozone/evdev/touch_filter/heuristic_stylus_palm_detection_filter_unittest.cc b/ui/events/ozone/evdev/touch_filter/heuristic_stylus_palm_detection_filter_unittest.cc
index da2ba46b..ec224d02 100644
--- a/ui/events/ozone/evdev/touch_filter/heuristic_stylus_palm_detection_filter_unittest.cc
+++ b/ui/events/ozone/evdev/touch_filter/heuristic_stylus_palm_detection_filter_unittest.cc
@@ -16,9 +16,11 @@
  public:
   HeuristicStylusPalmDetectionFilterTest() = default;
   void SetUp() override {
-    shared_palm_state.reset(new SharedPalmDetectionFilterState);
-    palm_detection_filter_.reset(new HeuristicStylusPalmDetectionFilter(
-        shared_palm_state.get(), hold_sample_count, hold_time, suppress_time));
+    shared_palm_state = std::make_unique<SharedPalmDetectionFilterState>();
+    palm_detection_filter_ =
+        std::make_unique<HeuristicStylusPalmDetectionFilter>(
+            shared_palm_state.get(), hold_sample_count, hold_time,
+            suppress_time);
     touches_.resize(kNumTouchEvdevSlots);
     test_start_time_ = base::TimeTicks::Now();
   }
@@ -43,9 +45,10 @@
 TEST_F(HeuristicStylusPalmDetectionFilterDeathTest, TestDCheck) {
   // We run with a time where hold_time < suppress_time, which should DCHECK.
   EXPECT_DCHECK_DEATH(
-      palm_detection_filter_.reset(new HeuristicStylusPalmDetectionFilter(
-          shared_palm_state.get(), hold_sample_count, hold_time,
-          hold_time + base::TimeDelta::FromMillisecondsD(0.1))));
+      palm_detection_filter_ =
+          std::make_unique<HeuristicStylusPalmDetectionFilter>(
+              shared_palm_state.get(), hold_sample_count, hold_time,
+              hold_time + base::TimeDelta::FromMillisecondsD(0.1)));
 }
 
 TEST_F(HeuristicStylusPalmDetectionFilterTest, TestSetsToZero) {
diff --git a/ui/events/ozone/evdev/touch_filter/open_palm_detection_filter_unittest.cc b/ui/events/ozone/evdev/touch_filter/open_palm_detection_filter_unittest.cc
index 44ecf9f..1aeda196 100644
--- a/ui/events/ozone/evdev/touch_filter/open_palm_detection_filter_unittest.cc
+++ b/ui/events/ozone/evdev/touch_filter/open_palm_detection_filter_unittest.cc
@@ -12,7 +12,7 @@
  public:
   OpenPalmDetectionFilterTest() = default;
   void SetUp() override {
-    shared_palm_state.reset(new SharedPalmDetectionFilterState);
+    shared_palm_state = std::make_unique<SharedPalmDetectionFilterState>();
     palm_detection_filter_.reset(
         new OpenPalmDetectionFilter(shared_palm_state.get()));
   }
diff --git a/ui/events/ozone/evdev/touch_filter/palm_detection_filter_factory_unittest.cc b/ui/events/ozone/evdev/touch_filter/palm_detection_filter_factory_unittest.cc
index 4f2c37fa..46d39849 100644
--- a/ui/events/ozone/evdev/touch_filter/palm_detection_filter_factory_unittest.cc
+++ b/ui/events/ozone/evdev/touch_filter/palm_detection_filter_factory_unittest.cc
@@ -26,7 +26,7 @@
     EXPECT_TRUE(
         CapabilitiesToDeviceInfo(kNocturneStylus, &nocturne_stylus_info_));
     EXPECT_TRUE(CapabilitiesToDeviceInfo(kEveStylus, &eve_stylus_info_));
-    scoped_feature_list_.reset(new base::test::ScopedFeatureList);
+    scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
   }
 
  protected:
diff --git a/ui/events/platform/platform_event_source_unittest.cc b/ui/events/platform/platform_event_source_unittest.cc
index eb747ff..a8cdd20 100644
--- a/ui/events/platform/platform_event_source_unittest.cc
+++ b/ui/events/platform/platform_event_source_unittest.cc
@@ -145,7 +145,9 @@
 
  protected:
   // testing::Test:
-  void SetUp() override { source_.reset(new TestPlatformEventSource()); }
+  void SetUp() override {
+    source_ = std::make_unique<TestPlatformEventSource>();
+  }
 
  private:
   std::unique_ptr<TestPlatformEventSource> source_;
diff --git a/ui/events/platform/x11/x11_event_source.cc b/ui/events/platform/x11/x11_event_source.cc
index d442c4d9..79015696 100644
--- a/ui/events/platform/x11/x11_event_source.cc
+++ b/ui/events/platform/x11/x11_event_source.cc
@@ -300,7 +300,7 @@
 
 void X11EventSource::OnDispatcherListChanged() {
   if (!hotplug_event_handler_) {
-    hotplug_event_handler_.reset(new X11HotplugEventHandler());
+    hotplug_event_handler_ = std::make_unique<X11HotplugEventHandler>();
     // Force the initial device query to have an update list of active devices.
     hotplug_event_handler_->OnHotplugEvent();
   }
diff --git a/ui/events/platform/x11/x11_event_source_glib.cc b/ui/events/platform/x11/x11_event_source_glib.cc
index b4ad9be2..600a535 100644
--- a/ui/events/platform/x11/x11_event_source_glib.cc
+++ b/ui/events/platform/x11/x11_event_source_glib.cc
@@ -75,7 +75,7 @@
   DCHECK(!x_source_);
   DCHECK(event_source_.display()) << "Unable to get connection to X server";
 
-  x_poll_.reset(new GPollFD());
+  x_poll_ = std::make_unique<GPollFD>();
   x_poll_->fd = fd;
   x_poll_->events = G_IO_IN;
   x_poll_->revents = 0;
diff --git a/ui/events/x/x11_window_event_manager.cc b/ui/events/x/x11_window_event_manager.cc
index c8cfdbf..751c240 100644
--- a/ui/events/x/x11_window_event_manager.cc
+++ b/ui/events/x/x11_window_event_manager.cc
@@ -99,7 +99,7 @@
 void XWindowEventManager::SelectEvents(XID xid, uint32_t event_mask) {
   std::unique_ptr<MultiMask>& mask = mask_map_[xid];
   if (!mask)
-    mask.reset(new MultiMask());
+    mask = std::make_unique<MultiMask>();
   uint32_t old_mask = mask_map_[xid]->ToMask();
   mask->AddMask(event_mask);
   AfterMaskChanged(xid, old_mask);
diff --git a/ui/gfx/animation/slide_animation.h b/ui/gfx/animation/slide_animation.h
index 1fd6327..c77fccb 100644
--- a/ui/gfx/animation/slide_animation.h
+++ b/ui/gfx/animation/slide_animation.h
@@ -20,7 +20,7 @@
 // class MyClass : public AnimationDelegate {
 //  public:
 //   MyClass() {
-//     animation_.reset(new SlideAnimation(this));
+//     animation_ = std::make_unique<SlideAnimation>(this);
 //     animation_->SetSlideDuration(500);
 //   }
 //   void OnMouseOver() {
diff --git a/ui/gfx/animation/slide_animation_unittest.cc b/ui/gfx/animation/slide_animation_unittest.cc
index 29fe345c..2bb6151 100644
--- a/ui/gfx/animation/slide_animation_unittest.cc
+++ b/ui/gfx/animation/slide_animation_unittest.cc
@@ -31,8 +31,8 @@
  protected:
   SlideAnimationTest()
       : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {
-    slide_animation_.reset(new SlideAnimation(nullptr));
-    animation_api_.reset(new AnimationTestApi(slide_animation_.get()));
+    slide_animation_ = std::make_unique<SlideAnimation>(nullptr);
+    animation_api_ = std::make_unique<AnimationTestApi>(slide_animation_.get());
   }
 
  private:
diff --git a/ui/gfx/font_render_params_win.cc b/ui/gfx/font_render_params_win.cc
index 6693f3af..86604be 100644
--- a/ui/gfx/font_render_params_win.cc
+++ b/ui/gfx/font_render_params_win.cc
@@ -57,7 +57,7 @@
     if (params_)
       return *params_;
 
-    params_.reset(new FontRenderParams());
+    params_ = std::make_unique<FontRenderParams>();
     params_->antialiasing = false;
     params_->subpixel_positioning = false;
     params_->autohinter = false;
@@ -76,9 +76,8 @@
         params_->subpixel_rendering = GetSubpixelRenderingGeometry();
       }
     }
-    singleton_hwnd_observer_.reset(new SingletonHwndObserver(
-        base::Bind(&CachedFontRenderParams::OnWndProc,
-                   base::Unretained(this))));
+    singleton_hwnd_observer_ = std::make_unique<SingletonHwndObserver>(
+        base::Bind(&CachedFontRenderParams::OnWndProc, base::Unretained(this)));
     return *params_;
   }
 
diff --git a/ui/gfx/geometry/cubic_bezier_unittest.cc b/ui/gfx/geometry/cubic_bezier_unittest.cc
index 410d80d..bf86f41 100644
--- a/ui/gfx/geometry/cubic_bezier_unittest.cc
+++ b/ui/gfx/geometry/cubic_bezier_unittest.cc
@@ -80,56 +80,56 @@
   double epsilon = 0.00015;
 
   // Derivative is a constant.
-  std::unique_ptr<CubicBezier> function(
-      new CubicBezier(0.25, (1.0 / 3.0), 0.75, (2.0 / 3.0)));
+  std::unique_ptr<CubicBezier> function =
+      std::make_unique<CubicBezier>(0.25, (1.0 / 3.0), 0.75, (2.0 / 3.0));
   EXPECT_EQ(0, function->range_min());
   EXPECT_EQ(1, function->range_max());
 
   // Derivative is linear.
-  function.reset(new CubicBezier(0.25, -0.5, 0.75, (-1.0 / 6.0)));
+  function = std::make_unique<CubicBezier>(0.25, -0.5, 0.75, (-1.0 / 6.0));
   EXPECT_NEAR(function->range_min(), -0.225, epsilon);
   EXPECT_EQ(1, function->range_max());
 
   // Derivative has no real roots.
-  function.reset(new CubicBezier(0.25, 0.25, 0.75, 0.5));
+  function = std::make_unique<CubicBezier>(0.25, 0.25, 0.75, 0.5);
   EXPECT_EQ(0, function->range_min());
   EXPECT_EQ(1, function->range_max());
 
   // Derivative has exactly one real root.
-  function.reset(new CubicBezier(0.0, 1.0, 1.0, 0.0));
+  function = std::make_unique<CubicBezier>(0.0, 1.0, 1.0, 0.0);
   EXPECT_EQ(0, function->range_min());
   EXPECT_EQ(1, function->range_max());
 
   // Derivative has one root < 0 and one root > 1.
-  function.reset(new CubicBezier(0.25, 0.1, 0.75, 0.9));
+  function = std::make_unique<CubicBezier>(0.25, 0.1, 0.75, 0.9);
   EXPECT_EQ(0, function->range_min());
   EXPECT_EQ(1, function->range_max());
 
   // Derivative has two roots in [0,1].
-  function.reset(new CubicBezier(0.25, 2.5, 0.75, 0.5));
+  function = std::make_unique<CubicBezier>(0.25, 2.5, 0.75, 0.5);
   EXPECT_EQ(0, function->range_min());
   EXPECT_NEAR(function->range_max(), 1.28818, epsilon);
-  function.reset(new CubicBezier(0.25, 0.5, 0.75, -1.5));
+  function = std::make_unique<CubicBezier>(0.25, 0.5, 0.75, -1.5);
   EXPECT_NEAR(function->range_min(), -0.28818, epsilon);
   EXPECT_EQ(1, function->range_max());
 
   // Derivative has one root < 0 and one root in [0,1].
-  function.reset(new CubicBezier(0.25, 0.1, 0.75, 1.5));
+  function = std::make_unique<CubicBezier>(0.25, 0.1, 0.75, 1.5);
   EXPECT_EQ(0, function->range_min());
   EXPECT_NEAR(function->range_max(), 1.10755, epsilon);
 
   // Derivative has one root in [0,1] and one root > 1.
-  function.reset(new CubicBezier(0.25, -0.5, 0.75, 0.9));
+  function = std::make_unique<CubicBezier>(0.25, -0.5, 0.75, 0.9);
   EXPECT_NEAR(function->range_min(), -0.10755, epsilon);
   EXPECT_EQ(1, function->range_max());
 
   // Derivative has two roots < 0.
-  function.reset(new CubicBezier(0.25, 0.3, 0.75, 0.633));
+  function = std::make_unique<CubicBezier>(0.25, 0.3, 0.75, 0.633);
   EXPECT_EQ(0, function->range_min());
   EXPECT_EQ(1, function->range_max());
 
   // Derivative has two roots > 1.
-  function.reset(new CubicBezier(0.25, 0.367, 0.75, 0.7));
+  function = std::make_unique<CubicBezier>(0.25, 0.367, 0.75, 0.7);
   EXPECT_EQ(0.f, function->range_min());
   EXPECT_EQ(1.f, function->range_max());
 }
diff --git a/ui/gfx/icon_util.cc b/ui/gfx/icon_util.cc
index 041e5127..800a2bb0 100644
--- a/ui/gfx/icon_util.cc
+++ b/ui/gfx/icon_util.cc
@@ -207,7 +207,7 @@
     size_t bytes_per_line = (bitmap.width() + 0xF) / 16 * 2;
     size_t mask_bits_size = bytes_per_line * bitmap.height();
 
-    mask_bits.reset(new uint8_t[mask_bits_size]);
+    mask_bits = std::make_unique<uint8_t[]>(mask_bits_size);
     DCHECK(mask_bits.get());
 
     // Make all pixels transparent.
diff --git a/ui/gfx/icon_util_unittest.cc b/ui/gfx/icon_util_unittest.cc
index a9c73c4..ca63b19 100644
--- a/ui/gfx/icon_util_unittest.cc
+++ b/ui/gfx/icon_util_unittest.cc
@@ -175,21 +175,21 @@
   std::unique_ptr<SkBitmap> bitmap;
 
   // Wrong bitmap format.
-  bitmap.reset(new SkBitmap);
+  bitmap = std::make_unique<SkBitmap>();
   ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
   bitmap->setInfo(SkImageInfo::MakeA8(kSmallIconWidth, kSmallIconHeight));
   icon = IconUtil::CreateHICONFromSkBitmap(*bitmap);
   EXPECT_FALSE(icon.is_valid());
 
   // Invalid bitmap size.
-  bitmap.reset(new SkBitmap);
+  bitmap = std::make_unique<SkBitmap>();
   ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
   bitmap->setInfo(SkImageInfo::MakeN32Premul(0, 0));
   icon = IconUtil::CreateHICONFromSkBitmap(*bitmap);
   EXPECT_FALSE(icon.is_valid());
 
   // Valid bitmap configuration but no pixels allocated.
-  bitmap.reset(new SkBitmap);
+  bitmap = std::make_unique<SkBitmap>();
   ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
   bitmap->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth,
                                              kSmallIconHeight));
@@ -208,7 +208,7 @@
       temp_directory_.GetPath().AppendASCII("<>?.ico");
 
   // Wrong bitmap format.
-  bitmap.reset(new SkBitmap);
+  bitmap = std::make_unique<SkBitmap>();
   ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
   // Must allocate pixels or else ImageSkia will ignore the bitmap and just
   // return an empty image.
@@ -221,7 +221,7 @@
 
   // Invalid bitmap size.
   image_family.clear();
-  bitmap.reset(new SkBitmap);
+  bitmap = std::make_unique<SkBitmap>();
   ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
   bitmap->allocPixels(SkImageInfo::MakeN32Premul(0, 0));
   image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap));
@@ -231,7 +231,7 @@
 
   // Bitmap with no allocated pixels.
   image_family.clear();
-  bitmap.reset(new SkBitmap);
+  bitmap = std::make_unique<SkBitmap>();
   ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
   bitmap->setInfo(SkImageInfo::MakeN32Premul(kSmallIconWidth,
                                              kSmallIconHeight));
diff --git a/ui/gfx/image/image.cc b/ui/gfx/image/image.cc
index 5fb3ceb..266c2a1 100644
--- a/ui/gfx/image/image.cc
+++ b/ui/gfx/image/image.cc
@@ -410,8 +410,8 @@
       case kImageRepPNG: {
         const internal::ImageRepPNG* png_rep =
             GetRepresentation(kImageRepPNG, true)->AsImageRepPNG();
-        scoped_rep.reset(new internal::ImageRepSkia(
-            internal::ImageSkiaFromPNG(png_rep->image_reps())));
+        scoped_rep = std::make_unique<internal::ImageRepSkia>(
+            internal::ImageSkiaFromPNG(png_rep->image_reps()));
         break;
       }
 #if defined(OS_IOS)
@@ -419,16 +419,16 @@
         const internal::ImageRepCocoaTouch* native_rep =
             GetRepresentation(kImageRepCocoaTouch, true)
                 ->AsImageRepCocoaTouch();
-        scoped_rep.reset(new internal::ImageRepSkia(
-            ImageSkia(ImageSkiaFromUIImage(native_rep->image()))));
+        scoped_rep = std::make_unique<internal::ImageRepSkia>(
+            ImageSkia(ImageSkiaFromUIImage(native_rep->image())));
         break;
       }
 #elif defined(OS_MACOSX)
       case kImageRepCocoa: {
         const internal::ImageRepCocoa* native_rep =
             GetRepresentation(kImageRepCocoa, true)->AsImageRepCocoa();
-        scoped_rep.reset(new internal::ImageRepSkia(
-            ImageSkia(ImageSkiaFromNSImage(native_rep->image()))));
+        scoped_rep = std::make_unique<internal::ImageRepSkia>(
+            ImageSkia(ImageSkiaFromNSImage(native_rep->image())));
         break;
       }
 #endif
@@ -450,15 +450,15 @@
       case kImageRepPNG: {
         const internal::ImageRepPNG* png_rep =
             GetRepresentation(kImageRepPNG, true)->AsImageRepPNG();
-        scoped_rep.reset(new internal::ImageRepCocoaTouch(
-            internal::UIImageFromPNG(png_rep->image_reps())));
+        scoped_rep = std::make_unique<internal::ImageRepCocoaTouch>(
+            internal::UIImageFromPNG(png_rep->image_reps()));
         break;
       }
       case kImageRepSkia: {
         const internal::ImageRepSkia* skia_rep =
             GetRepresentation(kImageRepSkia, true)->AsImageRepSkia();
         UIImage* image = UIImageFromImageSkia(*skia_rep->image());
-        scoped_rep.reset(new internal::ImageRepCocoaTouch(image));
+        scoped_rep = std::make_unique<internal::ImageRepCocoaTouch>(image);
         break;
       }
       default:
@@ -481,8 +481,9 @@
       case kImageRepPNG: {
         const internal::ImageRepPNG* png_rep =
             GetRepresentation(kImageRepPNG, true)->AsImageRepPNG();
-        scoped_rep.reset(new internal::ImageRepCocoa(internal::NSImageFromPNG(
-            png_rep->image_reps(), default_representation_color_space)));
+        scoped_rep =
+            std::make_unique<internal::ImageRepCocoa>(internal::NSImageFromPNG(
+                png_rep->image_reps(), default_representation_color_space));
         break;
       }
       case kImageRepSkia: {
@@ -490,7 +491,7 @@
             GetRepresentation(kImageRepSkia, true)->AsImageRepSkia();
         NSImage* image = NSImageFromImageSkiaWithColorSpace(*skia_rep->image(),
             default_representation_color_space);
-        scoped_rep.reset(new internal::ImageRepCocoa(image));
+        scoped_rep = std::make_unique<internal::ImageRepCocoa>(image);
         break;
       }
       default:
diff --git a/ui/gfx/mac/coordinate_conversion_unittest.mm b/ui/gfx/mac/coordinate_conversion_unittest.mm
index d1acea8e8..348a2ff 100644
--- a/ui/gfx/mac/coordinate_conversion_unittest.mm
+++ b/ui/gfx/mac/coordinate_conversion_unittest.mm
@@ -53,10 +53,9 @@
   EXPECT_EQ(0, primary_screen_frame.origin.x);
   EXPECT_EQ(0, primary_screen_frame.origin.y);
 
-  swizzle_frame_.reset(new base::mac::ScopedObjCClassSwizzler(
-      [NSScreen class],
-      [MacCoordinateConversionTestScreenDonor class],
-      @selector(frame)));
+  swizzle_frame_ = std::make_unique<base::mac::ScopedObjCClassSwizzler>(
+      [NSScreen class], [MacCoordinateConversionTestScreenDonor class],
+      @selector(frame));
 
   primary_screen_frame = [[[NSScreen screens] firstObject] frame];
   EXPECT_EQ(kTestWidth, primary_screen_frame.size.width);
diff --git a/ui/gfx/render_text_harfbuzz.cc b/ui/gfx/render_text_harfbuzz.cc
index 4ef6920c..cbc0b70 100644
--- a/ui/gfx/render_text_harfbuzz.cc
+++ b/ui/gfx/render_text_harfbuzz.cc
@@ -1505,8 +1505,8 @@
 base::i18n::BreakIterator* RenderTextHarfBuzz::GetGraphemeIterator() {
   if (update_grapheme_iterator_) {
     update_grapheme_iterator_ = false;
-    grapheme_iterator_.reset(new base::i18n::BreakIterator(
-        GetDisplayText(), base::i18n::BreakIterator::BREAK_CHARACTER));
+    grapheme_iterator_ = std::make_unique<base::i18n::BreakIterator>(
+        GetDisplayText(), base::i18n::BreakIterator::BREAK_CHARACTER);
     if (!grapheme_iterator_->Init())
       grapheme_iterator_.reset();
   }
@@ -1675,7 +1675,7 @@
   if (update_display_run_list_) {
     DCHECK(text_elided());
     const base::string16& display_text = GetDisplayText();
-    display_run_list_.reset(new internal::TextRunList);
+    display_run_list_ = std::make_unique<internal::TextRunList>();
 
     if (!display_text.empty())
       ItemizeAndShapeText(display_text, display_run_list_.get());
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index 9775d8bf..5f6d662 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -433,7 +433,7 @@
 
   void ResetRenderTextInstance() {
     render_text_ = std::make_unique<RenderTextHarfBuzz>();
-    test_api_.reset(new test::RenderTextTestApi(GetRenderText()));
+    test_api_ = std::make_unique<test::RenderTextTestApi>(GetRenderText());
   }
 
   void ResetCursorX() { test_api()->reset_cached_cursor_x(); }
diff --git a/ui/gfx/skia_vector_animation_unittest.cc b/ui/gfx/skia_vector_animation_unittest.cc
index 75b6a67..2f9d21d 100644
--- a/ui/gfx/skia_vector_animation_unittest.cc
+++ b/ui/gfx/skia_vector_animation_unittest.cc
@@ -100,8 +100,8 @@
   ~SkiaVectorAnimationTest() override {}
 
   void SetUp() override {
-    canvas_.reset(new gfx::Canvas(gfx::Size(kAnimationWidth, kAnimationHeight),
-                                  1.f, false));
+    canvas_ = std::make_unique<gfx::Canvas>(
+        gfx::Size(kAnimationWidth, kAnimationHeight), 1.f, false);
     skottie_ = base::MakeRefCounted<cc::SkottieWrapper>(
         std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
     animation_ = std::make_unique<SkiaVectorAnimation>(skottie_);
diff --git a/ui/gl/egl_api_unittest.cc b/ui/gl/egl_api_unittest.cc
index 045b8c9..0813a174 100644
--- a/ui/gl/egl_api_unittest.cc
+++ b/ui/gl/egl_api_unittest.cc
@@ -43,7 +43,7 @@
   }
 
   void InitializeAPI(const char* disabled_extensions) {
-    api_.reset(new RealEGLApi());
+    api_ = std::make_unique<RealEGLApi>();
     g_current_egl_context = api_.get();
     api_->Initialize(&g_driver_egl);
     if (disabled_extensions) {
diff --git a/ui/gl/gl_api_unittest.cc b/ui/gl/gl_api_unittest.cc
index 7dc6688..ab033c2 100644
--- a/ui/gl/gl_api_unittest.cc
+++ b/ui/gl/gl_api_unittest.cc
@@ -47,12 +47,12 @@
   }
 
   void InitializeAPI(const char* disabled_extensions) {
-    driver_.reset(new DriverGL());
+    driver_ = std::make_unique<DriverGL>();
     driver_->fn.glGetStringFn = &FakeGetString;
     driver_->fn.glGetStringiFn = &FakeGetStringi;
     driver_->fn.glGetIntegervFn = &FakeGetIntegervFn;
 
-    api_.reset(new RealGLApi());
+    api_ = std::make_unique<RealGLApi>();
     if (disabled_extensions) {
       api_->SetDisabledExtensions(disabled_extensions);
     }
diff --git a/ui/gl/gl_context.cc b/ui/gl/gl_context.cc
index deb062b..f0941e62 100644
--- a/ui/gl/gl_context.cc
+++ b/ui/gl/gl_context.cc
@@ -149,7 +149,7 @@
 
 CurrentGL* GLContext::GetCurrentGL() {
   if (!static_bindings_initialized_) {
-    driver_gl_.reset(new DriverGL);
+    driver_gl_ = std::make_unique<DriverGL>();
     driver_gl_->InitializeStaticBindings();
 
     gl_api_.reset(CreateGLApi(driver_gl_.get()));
@@ -157,16 +157,16 @@
 
     if (base::CommandLine::ForCurrentProcess()->HasSwitch(
             switches::kEnableGPUServiceTracing)) {
-      trace_gl_api_.reset(new TraceGLApi(final_api));
+      trace_gl_api_ = std::make_unique<TraceGLApi>(final_api);
       final_api = trace_gl_api_.get();
     }
 
     if (GetDebugGLBindingsInitializedGL()) {
-      debug_gl_api_.reset(new DebugGLApi(final_api));
+      debug_gl_api_ = std::make_unique<DebugGLApi>(final_api);
       final_api = debug_gl_api_.get();
     }
 
-    current_gl_.reset(new CurrentGL);
+    current_gl_ = std::make_unique<CurrentGL>();
     current_gl_->Driver = driver_gl_.get();
     current_gl_->Api = final_api;
     current_gl_->Version = version_info_.get();
diff --git a/ui/gl/gl_fence.cc b/ui/gl/gl_fence.cc
index 674cdd2..71f0eea 100644
--- a/ui/gl/gl_fence.cc
+++ b/ui/gl/gl_fence.cc
@@ -62,17 +62,17 @@
           g_current_gl_version->is_es3 ||
           g_current_gl_version->is_desktop_core_profile) {
     // Prefer ARB_sync which supports server-side wait.
-    fence.reset(new GLFenceARB);
+    fence = std::make_unique<GLFenceARB>();
 #if defined(OS_MACOSX)
   } else if (g_current_gl_driver->ext.b_GL_APPLE_fence) {
-    fence.reset(new GLFenceAPPLE);
+    fence = std::make_unique<GLFenceAPPLE>();
 #else
   } else if (g_driver_egl.ext.b_EGL_KHR_fence_sync) {
     fence = GLFenceEGL::Create();
     DCHECK(fence);
 #endif
   } else if (g_current_gl_driver->ext.b_GL_NV_fence) {
-    fence.reset(new GLFenceNV);
+    fence = std::make_unique<GLFenceNV>();
   }
 
   DCHECK_EQ(!!fence.get(), GLFence::IsSupported());
diff --git a/ui/gl/glx_api_unittest.cc b/ui/gl/glx_api_unittest.cc
index 1b2dfc9..1c4c927c 100644
--- a/ui/gl/glx_api_unittest.cc
+++ b/ui/gl/glx_api_unittest.cc
@@ -31,7 +31,7 @@
   }
 
   void InitializeAPI(const char* disabled_extensions) {
-    api_.reset(new RealGLXApi());
+    api_ = std::make_unique<RealGLXApi>();
     g_current_glx_context = api_.get();
     api_->Initialize(&g_driver_glx);
     if (disabled_extensions) {
diff --git a/ui/gl/gpu_timing_unittest.cc b/ui/gl/gpu_timing_unittest.cc
index 2384ce24..9184b670 100644
--- a/ui/gl/gpu_timing_unittest.cc
+++ b/ui/gl/gpu_timing_unittest.cc
@@ -51,7 +51,7 @@
     ASSERT_FALSE(setup_) << "Cannot setup GL context twice.";
     SetGLGetProcAddressProc(MockGLInterface::GetGLProcAddress);
     GLSurfaceTestSupport::InitializeOneOffWithMockBindings();
-    gl_.reset(new ::testing::StrictMock<MockGLInterface>());
+    gl_ = std::make_unique<::testing::StrictMock<MockGLInterface>>();
     MockGLInterface::SetGLInterface(gl_.get());
 
     context_ = new GLContextStub;
diff --git a/ui/gl/wgl_api_unittest.cc b/ui/gl/wgl_api_unittest.cc
index a0fe216..40b22c6 100644
--- a/ui/gl/wgl_api_unittest.cc
+++ b/ui/gl/wgl_api_unittest.cc
@@ -38,7 +38,7 @@
   }
 
   void InitializeAPI(const char* disabled_extensions) {
-    api_.reset(new RealWGLApi());
+    api_ = std::make_unique<RealWGLApi>();
     g_current_wgl_context = api_.get();
     api_->Initialize(&g_driver_wgl);
     if (disabled_extensions) {
diff --git a/ui/latency/average_lag_tracker_unittest.cc b/ui/latency/average_lag_tracker_unittest.cc
index dc4d7c7..f5551a4 100644
--- a/ui/latency/average_lag_tracker_unittest.cc
+++ b/ui/latency/average_lag_tracker_unittest.cc
@@ -19,7 +19,7 @@
   AverageLagTrackerTest() { ResetHistograms(); }
 
   void ResetHistograms() {
-    histogram_tester_.reset(new base::HistogramTester());
+    histogram_tester_ = std::make_unique<base::HistogramTester>();
   }
 
   const base::HistogramTester& histogram_tester() { return *histogram_tester_; }
diff --git a/ui/message_center/message_center_impl.cc b/ui/message_center/message_center_impl.cc
index 6b0745b..6d71ab2 100644
--- a/ui/message_center/message_center_impl.cc
+++ b/ui/message_center/message_center_impl.cc
@@ -37,7 +37,7 @@
       lock_screen_controller_(std::move(lock_screen_controller)),
       popup_timers_controller_(std::make_unique<PopupTimersController>(this)),
       stats_collector_(this) {
-  notification_list_.reset(new NotificationList(this));
+  notification_list_ = std::make_unique<NotificationList>(this);
 }
 
 MessageCenterImpl::~MessageCenterImpl() {
@@ -459,7 +459,7 @@
     for (auto& observer : observer_list_)
       observer.OnQuietModeChanged(true);
 
-    quiet_mode_timer_.reset(new base::OneShotTimer);
+    quiet_mode_timer_ = std::make_unique<base::OneShotTimer>();
     quiet_mode_timer_->Start(
         FROM_HERE,
         expires_in,
diff --git a/ui/message_center/message_center_impl_unittest.cc b/ui/message_center/message_center_impl_unittest.cc
index fdb8635..f9bfc75a 100644
--- a/ui/message_center/message_center_impl_unittest.cc
+++ b/ui/message_center/message_center_impl_unittest.cc
@@ -148,8 +148,8 @@
   void SetUp() override {
     MessageCenter::Initialize(std::make_unique<FakeLockScreenController>());
     message_center_ = MessageCenter::Get();
-    loop_.reset(new base::MessageLoop);
-    run_loop_.reset(new base::RunLoop());
+    loop_ = std::make_unique<base::MessageLoop>();
+    run_loop_ = std::make_unique<base::RunLoop>();
     closure_ = run_loop_->QuitClosure();
   }
 
diff --git a/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc b/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
index afe8caf..14b8650 100644
--- a/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
+++ b/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
@@ -177,7 +177,7 @@
     primary_plane_rect_ = gfx::Rect(size_);
 
   for (size_t i = 0; i < base::size(buffers_); ++i) {
-    buffers_[i].reset(new BufferWrapper());
+    buffers_[i] = std::make_unique<BufferWrapper>();
     if (!buffers_[i]->Initialize(gr_context_.get(), widget_,
                                  primary_plane_rect_.size()))
       return false;
@@ -186,7 +186,7 @@
   if (command_line->HasSwitch(kEnableOverlay)) {
     gfx::Size overlay_size = gfx::Size(size_.width() / 8, size_.height() / 8);
     for (size_t i = 0; i < base::size(overlay_buffer_); ++i) {
-      overlay_buffer_[i].reset(new BufferWrapper());
+      overlay_buffer_[i] = std::make_unique<BufferWrapper>();
       overlay_buffer_[i]->Initialize(gr_context_.get(),
                                      gfx::kNullAcceleratedWidget, overlay_size);
       SkCanvas* sk_canvas = overlay_buffer_[i]->sk_surface()->getCanvas();
@@ -275,7 +275,7 @@
   switch (result) {
     case gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS:
       for (size_t i = 0; i < base::size(buffers_); ++i) {
-        buffers_[i].reset(new BufferWrapper());
+        buffers_[i] = std::make_unique<BufferWrapper>();
         if (!buffers_[i]->Initialize(gr_context_.get(), widget_,
                                      primary_plane_rect_.size()))
           LOG(FATAL) << "Failed to recreate buffer";
diff --git a/ui/ozone/demo/surfaceless_gl_renderer.cc b/ui/ozone/demo/surfaceless_gl_renderer.cc
index 6499dd81..30ea492f 100644
--- a/ui/ozone/demo/surfaceless_gl_renderer.cc
+++ b/ui/ozone/demo/surfaceless_gl_renderer.cc
@@ -162,7 +162,7 @@
     primary_plane_rect_ = gfx::Rect(size_);
 
   for (size_t i = 0; i < base::size(buffers_); ++i) {
-    buffers_[i].reset(new BufferWrapper());
+    buffers_[i] = std::make_unique<BufferWrapper>();
     if (!buffers_[i]->Initialize(widget_, primary_plane_rect_.size()))
       return false;
   }
@@ -170,7 +170,7 @@
   if (command_line->HasSwitch("enable-overlay")) {
     gfx::Size overlay_size = gfx::Size(size_.width() / 8, size_.height() / 8);
     for (size_t i = 0; i < base::size(overlay_buffers_); ++i) {
-      overlay_buffers_[i].reset(new BufferWrapper());
+      overlay_buffers_[i] = std::make_unique<BufferWrapper>();
       overlay_buffers_[i]->Initialize(gfx::kNullAcceleratedWidget,
                                       overlay_size);
 
@@ -279,7 +279,7 @@
   switch (result) {
     case gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS:
       for (size_t i = 0; i < base::size(buffers_); ++i) {
-        buffers_[i].reset(new BufferWrapper());
+        buffers_[i] = std::make_unique<BufferWrapper>();
         if (!buffers_[i]->Initialize(widget_, primary_plane_rect_.size()))
           LOG(FATAL) << "Failed to recreate buffer";
       }
diff --git a/ui/ozone/platform/drm/gpu/drm_device.cc b/ui/ozone/platform/drm/gpu/drm_device.cc
index 11b699e..9931859 100644
--- a/ui/ozone/platform/drm/gpu/drm_device.cc
+++ b/ui/ozone/platform/drm/gpu/drm_device.cc
@@ -262,9 +262,9 @@
   // Use atomic only if kernel allows it.
   is_atomic_ = SetCapability(DRM_CLIENT_CAP_ATOMIC, 1);
   if (is_atomic_)
-    plane_manager_.reset(new HardwareDisplayPlaneManagerAtomic(this));
+    plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerAtomic>(this);
   else
-    plane_manager_.reset(new HardwareDisplayPlaneManagerLegacy(this));
+    plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerLegacy>(this);
   if (!plane_manager_->Initialize()) {
     LOG(ERROR) << "Failed to initialize the plane manager for "
                << device_path_.value();
diff --git a/ui/ozone/platform/drm/gpu/drm_overlay_validator_unittest.cc b/ui/ozone/platform/drm/gpu/drm_overlay_validator_unittest.cc
index aef4596..5e64c7d 100644
--- a/ui/ozone/platform/drm/gpu/drm_overlay_validator_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/drm_overlay_validator_unittest.cc
@@ -120,12 +120,12 @@
   }};
   InitializeDrmState({crtc_state});
 
-  screen_manager_.reset(new ui::ScreenManager());
+  screen_manager_ = std::make_unique<ui::ScreenManager>();
   screen_manager_->AddDisplayController(drm_, kCrtcIdBase, kConnectorIdBase);
   screen_manager_->ConfigureDisplayController(
       drm_, kCrtcIdBase, kConnectorIdBase, gfx::Point(), kDefaultMode);
 
-  drm_device_manager_.reset(new ui::DrmDeviceManager(nullptr));
+  drm_device_manager_ = std::make_unique<ui::DrmDeviceManager>(nullptr);
 
   std::unique_ptr<ui::DrmWindow> window(new ui::DrmWindow(
       kDefaultWidgetHandle, drm_device_manager_.get(), screen_manager_.get()));
@@ -134,7 +134,7 @@
       gfx::Rect(gfx::Size(kDefaultMode.hdisplay, kDefaultMode.vdisplay)));
   screen_manager_->AddWindow(kDefaultWidgetHandle, std::move(window));
   window_ = screen_manager_->GetWindow(kDefaultWidgetHandle);
-  overlay_validator_.reset(new ui::DrmOverlayValidator(window_));
+  overlay_validator_ = std::make_unique<ui::DrmOverlayValidator>(window_);
 
   overlay_rect_ =
       gfx::Rect(0, 0, kDefaultMode.hdisplay / 2, kDefaultMode.vdisplay / 2);
diff --git a/ui/ozone/platform/drm/gpu/drm_thread.cc b/ui/ozone/platform/drm/gpu/drm_thread.cc
index 8721444..7bae5fe8 100644
--- a/ui/ozone/platform/drm/gpu/drm_thread.cc
+++ b/ui/ozone/platform/drm/gpu/drm_thread.cc
@@ -125,8 +125,9 @@
 }
 
 void DrmThread::Init() {
-  device_manager_.reset(new DrmDeviceManager(std::move(device_generator_)));
-  screen_manager_.reset(new ScreenManager());
+  device_manager_ =
+      std::make_unique<DrmDeviceManager>(std::move(device_generator_));
+  screen_manager_ = std::make_unique<ScreenManager>();
   display_manager_.reset(
       new DrmGpuDisplayManager(screen_manager_.get(), device_manager_.get()));
 
diff --git a/ui/ozone/platform/drm/gpu/drm_window_unittest.cc b/ui/ozone/platform/drm/gpu/drm_window_unittest.cc
index e3c8d0c..58985bd 100644
--- a/ui/ozone/platform/drm/gpu/drm_window_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/drm_window_unittest.cc
@@ -104,12 +104,12 @@
 
   auto gbm_device = std::make_unique<ui::MockGbmDevice>();
   drm_ = new ui::MockDrmDevice(std::move(gbm_device));
-  screen_manager_.reset(new ui::ScreenManager());
+  screen_manager_ = std::make_unique<ui::ScreenManager>();
   screen_manager_->AddDisplayController(drm_, kDefaultCrtc, kDefaultConnector);
   screen_manager_->ConfigureDisplayController(
       drm_, kDefaultCrtc, kDefaultConnector, gfx::Point(), kDefaultMode);
 
-  drm_device_manager_.reset(new ui::DrmDeviceManager(nullptr));
+  drm_device_manager_ = std::make_unique<ui::DrmDeviceManager>(nullptr);
 
   std::unique_ptr<ui::DrmWindow> window(new ui::DrmWindow(
       kDefaultWidgetHandle, drm_device_manager_.get(), screen_manager_.get()));
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_controller_unittest.cc b/ui/ozone/platform/drm/gpu/hardware_display_controller_unittest.cc
index d5ba180f3..3b4c43f8 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_controller_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_controller_unittest.cc
@@ -92,10 +92,10 @@
   drm_ = new ui::MockDrmDevice(std::move(gbm_device));
   InitializeDrmDevice(/* use_atomic= */ true);
 
-  controller_.reset(new ui::HardwareDisplayController(
-      std::unique_ptr<ui::CrtcController>(
-          new ui::CrtcController(drm_.get(), kPrimaryCrtc, kPrimaryConnector)),
-      gfx::Point()));
+  controller_ = std::make_unique<ui::HardwareDisplayController>(
+      std::make_unique<ui::CrtcController>(drm_.get(), kPrimaryCrtc,
+                                           kPrimaryConnector),
+      gfx::Point());
 }
 
 void HardwareDisplayControllerTest::TearDown() {
@@ -438,8 +438,8 @@
   ASSERT_TRUE(primary_crtc_plane != nullptr);
 
   std::unique_ptr<ui::HardwareDisplayController> hdc_controller;
-  hdc_controller.reset(new ui::HardwareDisplayController(
-      controller_->RemoveCrtc(drm_, kPrimaryCrtc), controller_->origin()));
+  hdc_controller = std::make_unique<ui::HardwareDisplayController>(
+      controller_->RemoveCrtc(drm_, kPrimaryCrtc), controller_->origin());
   SchedulePageFlip(ui::DrmOverlayPlane::Clone(planes));
   drm_->RunCallbacks();
   EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
diff --git a/ui/ozone/platform/drm/gpu/mock_drm_device.cc b/ui/ozone/platform/drm/gpu/mock_drm_device.cc
index 8a457e7..cf6f2dd 100644
--- a/ui/ozone/platform/drm/gpu/mock_drm_device.cc
+++ b/ui/ozone/platform/drm/gpu/mock_drm_device.cc
@@ -93,7 +93,7 @@
       page_flip_expectation_(true),
       create_dumb_buffer_expectation_(true),
       current_framebuffer_(0) {
-  plane_manager_.reset(new HardwareDisplayPlaneManagerLegacy(this));
+  plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerLegacy>(this);
 }
 
 // static
@@ -142,9 +142,9 @@
   plane_properties_ = plane_properties;
   property_names_ = property_names;
   if (use_atomic) {
-    plane_manager_.reset(new HardwareDisplayPlaneManagerAtomic(this));
+    plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerAtomic>(this);
   } else {
-    plane_manager_.reset(new HardwareDisplayPlaneManagerLegacy(this));
+    plane_manager_ = std::make_unique<HardwareDisplayPlaneManagerLegacy>(this);
   }
 
   return plane_manager_->Initialize();
diff --git a/ui/ozone/platform/drm/gpu/proxy_helpers_unittest.cc b/ui/ozone/platform/drm/gpu/proxy_helpers_unittest.cc
index 9904c806..3e5790a 100644
--- a/ui/ozone/platform/drm/gpu/proxy_helpers_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/proxy_helpers_unittest.cc
@@ -19,7 +19,7 @@
 class ProxyHelpersTest : public testing::Test {
  public:
   void SetUp() override {
-    drm_thread_.reset(new base::Thread("drm_thread"));
+    drm_thread_ = std::make_unique<base::Thread>("drm_thread");
     drm_thread_->Start();
   }
 
@@ -123,8 +123,7 @@
                      std::move(safe_value_callback)));
 
   // Test passing a move-only type.
-  move_type_.reset(new int);
-  *move_type_ = 50;
+  move_type_ = std::make_unique<int>(50);
 
   auto move_callback = base::BindOnce(&ProxyHelpersTest::MoveTypeCallback,
                                       base::Unretained(this));
diff --git a/ui/ozone/platform/drm/gpu/screen_manager_unittest.cc b/ui/ozone/platform/drm/gpu/screen_manager_unittest.cc
index c4d137db..29b11e3e 100644
--- a/ui/ozone/platform/drm/gpu/screen_manager_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/screen_manager_unittest.cc
@@ -60,8 +60,8 @@
   void SetUp() override {
     auto gbm = std::make_unique<ui::MockGbmDevice>();
     drm_ = new ui::MockDrmDevice(std::move(gbm));
-    device_manager_.reset(new ui::DrmDeviceManager(nullptr));
-    screen_manager_.reset(new ui::ScreenManager());
+    device_manager_ = std::make_unique<ui::DrmDeviceManager>(nullptr);
+    screen_manager_ = std::make_unique<ui::ScreenManager>();
   }
   void TearDown() override {
     screen_manager_.reset();
diff --git a/ui/ozone/platform/drm/host/drm_display_host_manager.cc b/ui/ozone/platform/drm/host/drm_display_host_manager.cc
index d59677e..360f06c 100644
--- a/ui/ozone/platform/drm/host/drm_display_host_manager.cc
+++ b/ui/ozone/platform/drm/host/drm_display_host_manager.cc
@@ -128,7 +128,7 @@
     base::FilePath primary_graphics_card_path_sysfs =
         MapDevPathToSysPath(primary_graphics_card_path_);
 
-    primary_drm_device_handle_.reset(new DrmDeviceHandle());
+    primary_drm_device_handle_ = std::make_unique<DrmDeviceHandle>();
     if (!primary_drm_device_handle_->Initialize(
             primary_graphics_card_path_, primary_graphics_card_path_sysfs)) {
       LOG(FATAL) << "Failed to open primary graphics card";
diff --git a/ui/ozone/platform/drm/host/host_drm_device.cc b/ui/ozone/platform/drm/host/host_drm_device.cc
index d326a288..c436675 100644
--- a/ui/ozone/platform/drm/host/host_drm_device.cc
+++ b/ui/ozone/platform/drm/host/host_drm_device.cc
@@ -32,22 +32,9 @@
     observer.OnGpuThreadRetired();
 }
 
-// TODO(rjkroege): Remove the need for this entry point.
-void HostDrmDevice::BlockingStartDrmDevice() {
-  // Wait until startup related tasks posted to this thread that must precede
-  // blocking.
-  base::RunLoop().RunUntilIdle();
-
-  OnDrmServiceStarted();
-}
-
 void HostDrmDevice::OnDrmServiceStarted() {
   DCHECK_CALLED_ON_VALID_THREAD(on_ui_thread_);
-
-  // This can be called multiple times in the course of single-threaded startup.
-  // Ignore invocations after we've started.
-  if (connected_)
-    return;
+  DCHECK(!connected_);
 
   connected_ = true;
 
diff --git a/ui/ozone/platform/drm/host/host_drm_device.h b/ui/ozone/platform/drm/host/host_drm_device.h
index 85ec8e9..309ca0c 100644
--- a/ui/ozone/platform/drm/host/host_drm_device.h
+++ b/ui/ozone/platform/drm/host/host_drm_device.h
@@ -38,11 +38,6 @@
  public:
   explicit HostDrmDevice(DrmCursor* cursor);
 
-  // Blocks until the DRM service has come up. Use this entry point only when
-  // supporting launch of the service where the ozone UI and GPU
-  // reponsibilities are performed by the same underlying thread.
-  void BlockingStartDrmDevice();
-
   void ProvideManagers(DrmDisplayHostManager* display_manager,
                        DrmOverlayManagerHost* overlay_manager);
 
diff --git a/ui/ozone/platform/drm/ozone_platform_gbm.cc b/ui/ozone/platform/drm/ozone_platform_gbm.cc
index 446827ab..ca41d8f 100644
--- a/ui/ozone/platform/drm/ozone_platform_gbm.cc
+++ b/ui/ozone/platform/drm/ozone_platform_gbm.cc
@@ -263,13 +263,6 @@
           std::make_unique<DrmOverlayManagerGpu>(drm_thread_proxy_.get());
     }
 
-    // If InitializeGPU and InitializeUI are invoked on the same thread, startup
-    // sequencing is complicated because tasks are queued on the unbound mojo
-    // pipe connecting the UI (the host) to the DRM thread before the DRM thread
-    // is launched above. Special case this sequence via the
-    // BlockingStartDrmDevice API.
-    // TODO(rjkroege): In a future when we have completed splitting Viz, it will
-    // be possible to simplify this logic.
     if (using_mojo_ && single_process_ &&
         host_thread_ == base::PlatformThread::CurrentRef()) {
       CHECK(host_drm_device_) << "Mojo single-thread mode requires "
@@ -284,7 +277,6 @@
       drm_thread_proxy_->AddBindingDrmDevice(
           mojo::MakeRequest(&drm_device_ptr));
       drm_device_connector_->ConnectSingleThreaded(std::move(drm_device_ptr));
-      host_drm_device_->BlockingStartDrmDevice();
     }
   }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index 3d3dddc..267a64c9 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -354,8 +354,9 @@
       LOG(ERROR) << "Failed to bind to wl_data_device_manager global";
       return;
     }
-    connection->data_device_manager_.reset(new WaylandDataDeviceManager(
-        data_device_manager.release(), connection));
+    connection->data_device_manager_ =
+        std::make_unique<WaylandDataDeviceManager>(
+            data_device_manager.release(), connection);
     connection->EnsureDataDevice();
   } else if (!connection->zwp_dmabuf_ &&
              (strcmp(interface, "zwp_linux_dmabuf_v1") == 0)) {
diff --git a/ui/ozone/platform/wayland/host/wayland_data_device.cc b/ui/ozone/platform/wayland/host/wayland_data_device.cc
index 56e568fb..02debda 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_device.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_device.cc
@@ -213,7 +213,7 @@
   self->connection_->clipboard()->UpdateSequenceNumber();
 
   DCHECK(!self->new_offer_);
-  self->new_offer_.reset(new WaylandDataOffer(offer));
+  self->new_offer_ = std::make_unique<WaylandDataOffer>(offer);
 }
 
 void WaylandDataDevice::OnEnter(void* data,
@@ -253,7 +253,8 @@
   // same window and it's not needed to read data through Wayland.
   std::unique_ptr<OSExchangeData> pdata;
   if (!self->IsDraggingExternalData())
-    pdata.reset(new OSExchangeData(self->source_data_->provider().Clone()));
+    pdata = std::make_unique<OSExchangeData>(
+        self->source_data_->provider().Clone());
   self->window_->OnDragEnter(point, std::move(pdata), operation);
 }
 
@@ -290,8 +291,8 @@
     self->HandleReceivedData(nullptr);
   } else {
     // Creates buffer to receive data from Wayland.
-    self->received_data_.reset(
-        new OSExchangeData(std::make_unique<OSExchangeDataProviderAura>()));
+    self->received_data_ = std::make_unique<OSExchangeData>(
+        std::make_unique<OSExchangeDataProviderAura>());
     // In order to guarantee all data received, it sets
     // |is_handling_dropped_data_| and defers OnLeave event handling if it gets
     // OnLeave event before completing to read the data.
@@ -383,7 +384,7 @@
   gfx::Size size(icon_bitmap->width(), icon_bitmap->height());
 
   if (!shm_buffer_ || shm_buffer_->size() != size) {
-    shm_buffer_.reset(new WaylandShmBuffer(connection_->shm(), size));
+    shm_buffer_ = std::make_unique<WaylandShmBuffer>(connection_->shm(), size);
     if (!shm_buffer_->IsValid()) {
       LOG(ERROR) << "Failed to create drag icon buffer.";
       return;
diff --git a/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc
index d5b25bb..133cd04 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_device_unittest.cc
@@ -81,7 +81,8 @@
     data_device_manager_ = server_.data_device_manager();
     DCHECK(data_device_manager_);
 
-    clipboard_client_.reset(new MockClipboardClient(connection_.get()));
+    clipboard_client_ =
+        std::make_unique<MockClipboardClient>(connection_.get());
   }
 
  protected:
diff --git a/ui/ozone/platform/x11/x11_screen_ozone_unittest.cc b/ui/ozone/platform/x11/x11_screen_ozone_unittest.cc
index cafbaa50..b1bfba5b 100644
--- a/ui/ozone/platform/x11/x11_screen_ozone_unittest.cc
+++ b/ui/ozone/platform/x11/x11_screen_ozone_unittest.cc
@@ -60,7 +60,7 @@
     window_manager_ = std::make_unique<X11WindowManagerOzone>();
     primary_display_ = std::make_unique<display::Display>(
         NextDisplayId(), kPrimaryDisplayBounds);
-    screen_.reset(new X11ScreenOzone(window_manager_.get()));
+    screen_ = std::make_unique<X11ScreenOzone>(window_manager_.get());
     UpdateDisplayListForTest({*primary_display_});
     screen_->AddObserver(&display_observer_);
   }
diff --git a/ui/ozone/public/ozone_gpu_test_helper.cc b/ui/ozone/public/ozone_gpu_test_helper.cc
index 355d9b9..3718472 100644
--- a/ui/ozone/public/ozone_gpu_test_helper.cc
+++ b/ui/ozone/public/ozone_gpu_test_helper.cc
@@ -104,18 +104,18 @@
 
 bool OzoneGpuTestHelper::Initialize(
     const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) {
-  io_helper_thread_.reset(new base::Thread("IOHelperThread"));
+  io_helper_thread_ = std::make_unique<base::Thread>("IOHelperThread");
   if (!io_helper_thread_->StartWithOptions(
           base::Thread::Options(base::MessagePumpType::IO, 0)))
     return false;
 
-  fake_gpu_process_.reset(new FakeGpuProcess(ui_task_runner));
+  fake_gpu_process_ = std::make_unique<FakeGpuProcess>(ui_task_runner);
   io_helper_thread_->task_runner()->PostTask(
       FROM_HERE, base::BindOnce(&FakeGpuProcess::InitOnIO,
                                 base::Unretained(fake_gpu_process_.get())));
 
-  fake_gpu_process_host_.reset(new FakeGpuProcessHost(
-      ui_task_runner, io_helper_thread_->task_runner()));
+  fake_gpu_process_host_ = std::make_unique<FakeGpuProcessHost>(
+      ui_task_runner, io_helper_thread_->task_runner());
   io_helper_thread_->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&FakeGpuProcessHost::InitOnIO,
diff --git a/ui/platform_window/x11/x11_window_base.cc b/ui/platform_window/x11/x11_window_base.cc
index 0265e5fc..221ad511 100644
--- a/ui/platform_window/x11/x11_window_base.cc
+++ b/ui/platform_window/x11/x11_window_base.cc
@@ -86,7 +86,8 @@
                     LeaveWindowMask | ExposureMask | VisibilityChangeMask |
                     StructureNotifyMask | PropertyChangeMask |
                     PointerMotionMask;
-  xwindow_events_.reset(new ui::XScopedEventSelector(xwindow_, event_mask));
+  xwindow_events_ =
+      std::make_unique<ui::XScopedEventSelector>(xwindow_, event_mask);
 
   // Setup XInput2 event mask.
   unsigned char mask[XIMaskLen(XI_LASTEVENT)];
diff --git a/ui/snapshot/snapshot_aura_unittest.cc b/ui/snapshot/snapshot_aura_unittest.cc
index a9296cb..8258da44 100644
--- a/ui/snapshot/snapshot_aura_unittest.cc
+++ b/ui/snapshot/snapshot_aura_unittest.cc
@@ -136,7 +136,8 @@
   }
 
   void SetupTestWindow(const gfx::Rect& window_bounds) {
-    delegate_.reset(new TestPaintingWindowDelegate(window_bounds.size()));
+    delegate_ =
+        std::make_unique<TestPaintingWindowDelegate>(window_bounds.size());
     test_window_.reset(aura::test::CreateTestWindowWithDelegate(
         delegate_.get(), 0, window_bounds, root_window()));
   }
diff --git a/ui/touch_selection/touch_selection_controller_unittest.cc b/ui/touch_selection/touch_selection_controller_unittest.cc
index 3fbf7cf..043ebe4 100644
--- a/ui/touch_selection/touch_selection_controller_unittest.cc
+++ b/ui/touch_selection/touch_selection_controller_unittest.cc
@@ -64,7 +64,8 @@
   // testing::Test implementation.
 
   void SetUp() override {
-    controller_.reset(new TouchSelectionController(this, DefaultConfig()));
+    controller_ =
+        std::make_unique<TouchSelectionController>(this, DefaultConfig());
     // Simulate start of a TouchEvent sequence.
     controller_->WillHandleTouchEvent(
         MockMotionEvent(MotionEvent::Action::DOWN));
@@ -117,13 +118,13 @@
   void EnableLongPressDragSelection() {
     TouchSelectionController::Config config = DefaultConfig();
     config.enable_longpress_drag_selection = true;
-    controller_.reset(new TouchSelectionController(this, config));
+    controller_ = std::make_unique<TouchSelectionController>(this, config);
   }
 
   void SetHideActiveHandle(bool hide) {
     TouchSelectionController::Config config = DefaultConfig();
     config.hide_active_handle = hide;
-    controller_.reset(new TouchSelectionController(this, config));
+    controller_ = std::make_unique<TouchSelectionController>(this, config);
     // Simulate start of a TouchEvent sequence.
     controller_->WillHandleTouchEvent(
         MockMotionEvent(MotionEvent::Action::DOWN));
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_property_list_mojo.js b/ui/webui/resources/cr_components/chromeos/network/network_property_list_mojo.js
index 8343a5d..f950990f 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_property_list_mojo.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_property_list_mojo.js
@@ -100,14 +100,33 @@
     subKeys.forEach(subKey => {
       // Check for exceptions to CamelCase vs camelCase naming conventions.
       if (subKey == 'ipv4' || subKey == 'ipv6') {
-        result += subKey + '-';
+        result += subKey;
       } else if (subKey == 'apn') {
-        result += 'APN-';
+        result += 'APN';
       } else if (subKey == 'ipAddress') {
-        result += 'IPAddress-';
+        result += 'IPAddress';
+      } else if (subKey == 'ipSec') {
+        result += 'IPSec';
+      } else if (subKey == 'l2tp') {
+        result += 'L2TP';
+      } else if (subKey == 'modelId') {
+        result += 'ModelID';
+      } else if (subKey == 'openVpn') {
+        result += 'OpenVPN';
+      } else if (subKey == 'otp') {
+        result += 'OTP';
+      } else if (subKey == 'ssid') {
+        result += 'SSID';
+      } else if (subKey == 'serverCa') {
+        result += 'ServerCA';
+      } else if (subKey == 'vpn') {
+        result += 'VPN';
+      } else if (subKey == 'wifi') {
+        result += 'WiFi';
       } else {
-        result += subKey.charAt(0).toUpperCase() + subKey.slice(1) + '-';
+        result += subKey.charAt(0).toUpperCase() + subKey.slice(1);
       }
+      result += '-';
     });
     return 'Onc' + result.slice(0, result.length - 1);
   },
@@ -165,7 +184,7 @@
     const property =
         /** @type{OncMojo.ManagedProperty|undefined} */ (
             this.get(key, propertyDict));
-    if (property === undefined) {
+    if (property === undefined || property === null) {
       // Unspecified properties in policy configurations are not user
       // modifiable. https://crbug.com/819837.
       const source = propertyDict.source;
@@ -228,7 +247,7 @@
    */
   getProperty_: function(key, propertyDict) {
     const property = this.get(key, this.propertyDict);
-    if (property === undefined) {
+    if (property === undefined || property === null) {
       // If the dictionary is policy controlled, provide an empty property
       // object with the network policy source. See https://crbug.com/819837 for
       // more info.
@@ -261,7 +280,7 @@
    */
   getPropertyValue_: function(key, prefix, propertyDict) {
     let value = this.get(key, propertyDict);
-    if (value === undefined) {
+    if (value === undefined || value === null) {
       return '';
     }
     if (typeof value == 'object' && !Array.isArray(value)) {
@@ -277,13 +296,30 @@
     if (customValue) {
       return customValue;
     }
-    if (typeof value == 'number' || typeof value == 'boolean') {
+    if (typeof value == 'boolean') {
       return value.toString();
     }
 
-    assert(typeof value == 'string');
-    const valueStr = /** @type {string} */ (value);
-
+    let valueStr;
+    if (typeof value == 'number') {
+      // Special case typed managed properties.
+      if (key == 'cellular.activationState') {
+        valueStr = OncMojo.getActivationStateTypeString(
+            /** @type{!chromeos.networkConfig.mojom.ActivationStateType}*/ (
+                value));
+      } else if (key == 'vpn.type') {
+        valueStr = OncMojo.getVPNTypeString(
+            /** @type{!chromeos.networkConfig.mojom.VPNType}*/ (value));
+      } else if (key == 'wifi.security') {
+        valueStr = OncMojo.getSecurityTypeString(
+            /** @type{!chromeos.networkConfig.mojom.SecurityType}*/ (value));
+      } else {
+        return value.toString();
+      }
+    } else {
+      assert(typeof value == 'string');
+      valueStr = /** @type {string} */ (value);
+    }
     const oncKey = this.getOncKey_(key, prefix) + '_' + valueStr;
     if (this.i18nExists(oncKey)) {
       return this.i18n(oncKey);
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
index 064776bd..3a5d162 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
@@ -172,7 +172,7 @@
     if (!simLockStatus) {
       return;
     }
-    this.pukRequired_ = simLockStatus.lockType == CrOnc.LockType.PUK;
+    this.pukRequired_ = simLockStatus.lockType == 'sim-puk';
     const lockEnabled = simLockStatus.lockEnabled;
     if (lockEnabled != this.lockEnabled_) {
       this.setLockEnabled_ = lockEnabled;
diff --git a/ui/webui/resources/js/chromeos/onc_mojo.js b/ui/webui/resources/js/chromeos/onc_mojo.js
index 3184eb3..df2c711 100644
--- a/ui/webui/resources/js/chromeos/onc_mojo.js
+++ b/ui/webui/resources/js/chromeos/onc_mojo.js
@@ -352,6 +352,26 @@
   }
 
   /**
+   * @param {!chromeos.networkConfig.mojom.VPNType} value
+   * @return {string}
+   */
+  static getVPNTypeString(value) {
+    const VPNType = chromeos.networkConfig.mojom.VPNType;
+    switch (value) {
+      case VPNType.kL2TPIPsec:
+        return 'L2TP-IPsec';
+      case VPNType.kOpenVPN:
+        return 'OpenVPN';
+      case VPNType.kThirdPartyVPN:
+        return 'ThirdPartyVPN';
+      case VPNType.kArcVPN:
+        return 'ARCVPN';
+    }
+    assertNotReached('Unexpected enum value: ' + OncMojo.getEnumString(value));
+    return '';
+  }
+
+  /**
    * @param {string} value
    * @return {!chromeos.networkConfig.mojom.VPNType}
    */
@@ -662,6 +682,28 @@
   }
 
   /**
+   * Sets the value of a property in an mojo config dictionary.
+   * @param {!chromeos.networkConfig.mojom.ConfigProperties} config
+   * @param {string} key The property key which may be nested, e.g. 'foo.bar'
+   * @param {boolean|number|string|!Object} value The property value
+   */
+  static setConfigProperty(config, key, value) {
+    while (true) {
+      const index = key.indexOf('.');
+      if (index < 0) {
+        break;
+      }
+      const keyComponent = key.substr(0, index);
+      if (!config.hasOwnProperty(keyComponent)) {
+        config[keyComponent] = {};
+      }
+      config = config[keyComponent];
+      key = key.substr(index + 1);
+    }
+    config[key] = value;
+  }
+
+  /**
    * @param {!chromeos.networkConfig.mojom.ManagedBoolean|
    *         !chromeos.networkConfig.mojom.ManagedInt32|
    *         !chromeos.networkConfig.mojom.ManagedString|
@@ -945,9 +987,6 @@
  */
 OncMojo.ManagedProperty;
 
-/** @typedef {chromeos.networkConfig.mojom.ManagedProperties} */
-OncMojo.ManagedProperties;
-
 /**
  * Modified version of mojom.IPConfigProperties to store routingPrefix as a
  * human-readable string instead of as a number. Used in network_ip_config.js.
diff --git a/ui/wm/core/capture_controller_unittest.cc b/ui/wm/core/capture_controller_unittest.cc
index 26a1f6e6..5f13d96 100644
--- a/ui/wm/core/capture_controller_unittest.cc
+++ b/ui/wm/core/capture_controller_unittest.cc
@@ -57,7 +57,7 @@
 
   void SetUp() override {
     AuraTestBase::SetUp();
-    capture_controller_.reset(new ScopedCaptureClient(root_window()));
+    capture_controller_ = std::make_unique<ScopedCaptureClient>(root_window());
 
     second_host_ = aura::WindowTreeHost::Create(
         ui::PlatformWindowInitProperties{gfx::Rect(0, 0, 800, 600)});
diff --git a/ui/wm/core/focus_controller_unittest.cc b/ui/wm/core/focus_controller_unittest.cc
index c21df68..f350012f1 100644
--- a/ui/wm/core/focus_controller_unittest.cc
+++ b/ui/wm/core/focus_controller_unittest.cc
@@ -464,7 +464,7 @@
     // window initializations, including the root_window()'s, so we create it
     // before allowing the base setup.
     test_focus_rules_ = new TestFocusRules;
-    focus_controller_.reset(new FocusController(test_focus_rules_));
+    focus_controller_ = std::make_unique<FocusController>(test_focus_rules_);
     aura::test::AuraTestBase::SetUp();
     root_window()->AddPreTargetHandler(focus_controller_.get());
     aura::client::SetFocusClient(root_window(), focus_controller_.get());
diff --git a/ui/wm/core/transient_window_stacking_client_unittest.cc b/ui/wm/core/transient_window_stacking_client_unittest.cc
index 5ee1896..b1d8ecd 100644
--- a/ui/wm/core/transient_window_stacking_client_unittest.cc
+++ b/ui/wm/core/transient_window_stacking_client_unittest.cc
@@ -25,7 +25,7 @@
 
   void SetUp() override {
     AuraTestBase::SetUp();
-    client_.reset(new TransientWindowStackingClient);
+    client_ = std::make_unique<TransientWindowStackingClient>();
     aura::client::SetWindowStackingClient(client_.get());
   }
 
diff --git a/ui/wm/test/wm_test_helper.cc b/ui/wm/test/wm_test_helper.cc
index 003170b6..b298cd6 100644
--- a/ui/wm/test/wm_test_helper.cc
+++ b/ui/wm/test/wm_test_helper.cc
@@ -31,10 +31,10 @@
 
   aura::client::SetWindowParentingClient(host_->window(), this);
 
-  focus_client_.reset(new aura::test::TestFocusClient);
+  focus_client_ = std::make_unique<aura::test::TestFocusClient>();
   aura::client::SetFocusClient(host_->window(), focus_client_.get());
 
-  root_window_event_filter_.reset(new wm::CompoundEventFilter);
+  root_window_event_filter_ = std::make_unique<wm::CompoundEventFilter>();
   host_->window()->AddPreTargetHandler(root_window_event_filter_.get());
 
   new wm::DefaultActivationClient(host_->window());
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index c21c5927..7e74745c 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -46,11 +46,14 @@
     "browser/content_browser_client_impl.h",
     "browser/navigation_controller_impl.cc",
     "browser/navigation_controller_impl.h",
+    "browser/navigation_impl.cc",
+    "browser/navigation_impl.h",
     "browser/profile_impl.cc",
     "browser/profile_impl.h",
     "common/content_client_impl.cc",
     "common/content_client_impl.h",
     "public/browser_controller.h",
+    "public/browser_observer.h",
     "public/main.h",
     "public/navigation.h",
     "public/navigation_controller.h",
@@ -127,6 +130,7 @@
 
   if (is_android) {
     deps += [
+      ":weblayer_jni",
       "//components/embedder_support/android:view",
       "//ui/android",
     ]
@@ -145,5 +149,40 @@
     ]
   }
 }
+
 # TODO(jam): move weblayer_shell_resources_grit and copy_shell_resources here in
 # a way that's shareable?
+
+if (is_android) {
+  shared_library("libweblayer") {
+    sources = [
+      "app/jni_onload.cc",
+    ]
+    deps = [
+      ":weblayer_lib",
+      "//base",
+      "//content/public/app:both",
+    ]
+    configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
+    configs += [ "//build/config/android:hide_all_but_jni" ]
+  }
+
+  generate_jni("weblayer_jni") {
+    sources = [
+      "browser/java/org/chromium/weblayer/BrowserController.java",
+      "browser/java/org/chromium/weblayer/Profile.java",
+    ]
+  }
+
+  android_library("weblayer_java") {
+    java_files = [
+      "browser/java/org/chromium/weblayer/BrowserController.java",
+      "browser/java/org/chromium/weblayer/Profile.java",
+    ]
+    deps = [
+      "//base:base_java",
+      "//content/public/android:content_java",
+      "//ui/android:ui_java",
+    ]
+  }
+}
diff --git a/weblayer/app/content_main_delegate_impl.cc b/weblayer/app/content_main_delegate_impl.cc
index 8b0098c..94798ce6 100644
--- a/weblayer/app/content_main_delegate_impl.cc
+++ b/weblayer/app/content_main_delegate_impl.cc
@@ -7,6 +7,7 @@
 #include <iostream>
 
 #include "base/base_switches.h"
+#include "base/cpu.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/path_service.h"
@@ -63,7 +64,7 @@
     exit_code = &dummy;
 
 #if defined(OS_ANDROID)
-  Compositor::Initialize();
+  content::Compositor::Initialize();
 #endif
 
   InitLogging(&params_);
diff --git a/weblayer/app/jni_onload.cc b/weblayer/app/jni_onload.cc
new file mode 100644
index 0000000..460607e
--- /dev/null
+++ b/weblayer/app/jni_onload.cc
@@ -0,0 +1,32 @@
+// 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 "base/android/jni_android.h"
+#include "base/logging.h"
+#include "content/public/app/content_jni_onload.h"
+#include "content/public/app/content_main.h"
+#include "weblayer/app/content_main_delegate_impl.h"
+
+namespace weblayer {
+class MainDelegateImpl : public MainDelegate {
+ public:
+  void PreMainMessageLoopRun() override {}
+  void SetMainMessageLoopQuitClosure(base::OnceClosure quit_closure) override {}
+};
+}  // namespace weblayer
+
+// This is called by the VM when the shared library is first loaded.
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+  base::android::InitVM(vm);
+  if (!content::android::OnJNIOnLoadInit())
+    return -1;
+  weblayer::MainParams params;
+  params.delegate = new weblayer::MainDelegateImpl;
+  params.pak_name = "weblayer_shell.pak";
+  params.brand = "weblayer_shell";
+
+  content::SetContentMainDelegate(
+      new weblayer::ContentMainDelegateImpl(params));
+  return JNI_VERSION_1_4;
+}
diff --git a/weblayer/app/main.cc b/weblayer/app/main.cc
index 1aadc8f..5eee961 100644
--- a/weblayer/app/main.cc
+++ b/weblayer/app/main.cc
@@ -20,6 +20,7 @@
 MainParams::MainParams(const MainParams& other) = default;
 MainParams::~MainParams() = default;
 
+#if !defined(OS_ANDROID)
 int Main(MainParams params
 #if defined(OS_WIN)
 #if !defined(WIN_CONSOLE_APP)
@@ -50,5 +51,6 @@
 
   return content::ContentMain(content_params);
 }
+#endif  // !defined(OS_ANDROID)
 
 }  // namespace weblayer
diff --git a/weblayer/browser/DEPS b/weblayer/browser/DEPS
index 372efe2..6e3f1ee 100644
--- a/weblayer/browser/DEPS
+++ b/weblayer/browser/DEPS
@@ -6,6 +6,7 @@
   "+services/service_manager",
   "+third_party/blink/public/common",
   "+ui/aura",
+  "+ui/android",
   "+ui/base",
   "+ui/events",
   "+ui/gfx",
diff --git a/weblayer/browser/browser_controller_impl.cc b/weblayer/browser/browser_controller_impl.cc
index 7d63474..b01fc1c2 100644
--- a/weblayer/browser/browser_controller_impl.cc
+++ b/weblayer/browser/browser_controller_impl.cc
@@ -6,39 +6,105 @@
 
 #include "base/logging.h"
 #include "content/public/browser/web_contents.h"
-#include "ui/views/controls/webview/webview.h"
 #include "weblayer/browser/navigation_controller_impl.h"
 #include "weblayer/browser/profile_impl.h"
+#include "weblayer/public/browser_observer.h"
+
+#if !defined(OS_ANDROID)
+#include "ui/views/controls/webview/webview.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "base/android/jni_string.h"
+#include "weblayer/weblayer_jni/BrowserController_jni.h"
+#endif
 
 namespace weblayer {
 
-BrowserControllerImpl::BrowserControllerImpl(Profile* profile,
+BrowserControllerImpl::BrowserControllerImpl(ProfileImpl* profile,
                                              const gfx::Size& initial_size)
     : profile_(profile) {
-  auto* profile_impl = static_cast<ProfileImpl*>(profile_);
   content::WebContents::CreateParams create_params(
-      profile_impl->GetBrowserContext());
+      profile_->GetBrowserContext());
   create_params.initial_size = initial_size;
   web_contents_ = content::WebContents::Create(create_params);
 
+  web_contents_->SetDelegate(this);
+  Observe(web_contents_.get());
+
   navigation_controller_ = std::make_unique<NavigationControllerImpl>(this);
 }
 
-BrowserControllerImpl::~BrowserControllerImpl() = default;
+BrowserControllerImpl::~BrowserControllerImpl() {
+  // Destruct this now to avoid it calling back when this object is partially
+  // destructed.
+  web_contents_.reset();
+}
+
+void BrowserControllerImpl::AddObserver(BrowserObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void BrowserControllerImpl::RemoveObserver(BrowserObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
 
 NavigationController* BrowserControllerImpl::GetNavigationController() {
   return navigation_controller_.get();
 }
 
+#if !defined(OS_ANDROID)
 void BrowserControllerImpl::AttachToView(views::WebView* web_view) {
   web_view->SetWebContents(web_contents_.get());
   web_contents_->Focus();
 }
+#endif
+
+#if defined(OS_ANDROID)
+static jlong JNI_BrowserController_Init(JNIEnv* env, jlong profile) {
+  return reinterpret_cast<intptr_t>(new BrowserControllerImpl(
+      reinterpret_cast<ProfileImpl*>(profile), gfx::Size()));
+}
+
+base::android::ScopedJavaLocalRef<jobject>
+BrowserControllerImpl::GetWebContents(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj) {
+  return web_contents_->GetJavaWebContents();
+}
+
+void BrowserControllerImpl::Navigate(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    const base::android::JavaParamRef<jstring>& path) {
+  GetNavigationController()->Navigate(
+      GURL(base::android::ConvertJavaStringToUTF8(env, path)));
+}
+#endif
+
+void BrowserControllerImpl::LoadingStateChanged(content::WebContents* source,
+                                                bool to_different_document) {
+  bool is_loading = web_contents_->IsLoading();
+  for (auto& observer : observers_)
+    observer.LoadingStateChanged(is_loading, to_different_document);
+}
+
+void BrowserControllerImpl::DidNavigateMainFramePostCommit(
+    content::WebContents* web_contents) {
+  for (auto& observer : observers_)
+    observer.DisplayedURLChanged(web_contents->GetVisibleURL());
+}
+
+void BrowserControllerImpl::DidFirstVisuallyNonEmptyPaint() {
+  for (auto& observer : observers_)
+    observer.FirstContentfulPaint();
+}
 
 std::unique_ptr<BrowserController> BrowserController::Create(
     Profile* profile,
     const gfx::Size& initial_size) {
-  return std::make_unique<BrowserControllerImpl>(profile, initial_size);
+  return std::make_unique<BrowserControllerImpl>(
+      static_cast<ProfileImpl*>(profile), initial_size);
 }
 
 }  // namespace weblayer
diff --git a/weblayer/browser/browser_controller_impl.h b/weblayer/browser/browser_controller_impl.h
index b4cc0a9..2dd538a 100644
--- a/weblayer/browser/browser_controller_impl.h
+++ b/weblayer/browser/browser_controller_impl.h
@@ -5,30 +5,68 @@
 #ifndef WEBLAYER_BROWSER_BROWSER_CONTROLLER_IMPL_H_
 #define WEBLAYER_BROWSER_BROWSER_CONTROLLER_IMPL_H_
 
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "build/build_config.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "weblayer/public/browser_controller.h"
 
+#if defined(OS_ANDROID)
+#include "base/android/scoped_java_ref.h"
+#endif
+
 namespace content {
 class WebContents;
 }
 
 namespace weblayer {
 class NavigationControllerImpl;
+class ProfileImpl;
 
-class BrowserControllerImpl : public BrowserController {
+class BrowserControllerImpl : public BrowserController,
+                              public content::WebContentsDelegate,
+                              public content::WebContentsObserver {
  public:
-  BrowserControllerImpl(Profile* profile, const gfx::Size& initial_size);
+  BrowserControllerImpl(ProfileImpl* profile, const gfx::Size& initial_size);
   ~BrowserControllerImpl() override;
 
-  // BrowserController implementation:
-  NavigationController* GetNavigationController() override;
-  void AttachToView(views::WebView* web_view) override;
-
   content::WebContents* web_contents() const { return web_contents_.get(); }
 
+#if defined(OS_ANDROID)
+  base::android::ScopedJavaLocalRef<jobject> GetWebContents(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj);
+  void Navigate(JNIEnv* env,
+                const base::android::JavaParamRef<jobject>& obj,
+                const base::android::JavaParamRef<jstring>& path);
+#endif
+
  private:
-  Profile* profile_;
+  // BrowserController implementation:
+  void AddObserver(BrowserObserver* observer) override;
+  void RemoveObserver(BrowserObserver* observer) override;
+  NavigationController* GetNavigationController() override;
+#if !defined(OS_ANDROID)
+  void AttachToView(views::WebView* web_view) override;
+#endif
+
+  // content::WebContentsDelegate implementation:
+  void LoadingStateChanged(content::WebContents* source,
+                           bool to_different_document) override;
+  void DidNavigateMainFramePostCommit(
+      content::WebContents* web_contents) override;
+
+  // content::WebContentsObserver implementation:
+  void DidFirstVisuallyNonEmptyPaint() override;
+
+ private:
+  ProfileImpl* profile_;
   std::unique_ptr<content::WebContents> web_contents_;
   std::unique_ptr<NavigationControllerImpl> navigation_controller_;
+  base::ObserverList<BrowserObserver>::Unchecked observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserControllerImpl);
 };
 
 }  // namespace weblayer
diff --git a/weblayer/browser/java/org/chromium/weblayer/BrowserController.java b/weblayer/browser/java/org/chromium/weblayer/BrowserController.java
new file mode 100644
index 0000000..d3bb396
--- /dev/null
+++ b/weblayer/browser/java/org/chromium/weblayer/BrowserController.java
@@ -0,0 +1,75 @@
+// 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.weblayer;
+
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.content_public.browser.ViewEventSink;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.ViewAndroidDelegate;
+import org.chromium.ui.base.WindowAndroid;
+
+@JNINamespace("weblayer")
+public class BrowserController {
+    private long mNativeBrowserController;
+    private WebContents mWebContents;
+
+    private static class InternalAccessDelegateImpl
+            implements ViewEventSink.InternalAccessDelegate {
+        @Override
+        public boolean super_onKeyUp(int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean super_dispatchKeyEvent(KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean super_onGenericMotionEvent(MotionEvent event) {
+            return false;
+        }
+
+        @Override
+        public void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix) {}
+    }
+
+    public static BrowserController create(Profile profile) {
+        return new BrowserController(nativeInit(profile.getNativeProfile()));
+    }
+
+    private BrowserController(long nativeBrowserController) {
+        mNativeBrowserController = nativeBrowserController;
+        mWebContents = nativeGetWebContents(mNativeBrowserController);
+    }
+
+    public void initialize(ViewGroup containerView, WindowAndroid windowAndroid) {
+        mWebContents.initialize("", ViewAndroidDelegate.createBasicDelegate(containerView),
+                new InternalAccessDelegateImpl(), windowAndroid,
+                WebContents.createDefaultInternalsHolder());
+    }
+
+    public WebContents getWebContents() {
+        return mWebContents;
+    }
+
+    public void show() {
+        mWebContents.onShow();
+    }
+
+    public void navigate(String url) {
+        nativeNavigate(mNativeBrowserController, url);
+    }
+
+    public void destroy() {}
+
+    private static native long nativeInit(long profile);
+    private native WebContents nativeGetWebContents(long nativeBrowserControllerImpl);
+    private native void nativeNavigate(long nativeBrowserControllerImpl, String url);
+}
diff --git a/weblayer/browser/java/org/chromium/weblayer/Profile.java b/weblayer/browser/java/org/chromium/weblayer/Profile.java
new file mode 100644
index 0000000..d788792
--- /dev/null
+++ b/weblayer/browser/java/org/chromium/weblayer/Profile.java
@@ -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.
+
+package org.chromium.weblayer;
+
+import org.chromium.base.annotations.JNINamespace;
+
+@JNINamespace("weblayer")
+public class Profile {
+    private long mNativeProfile;
+
+    public static Profile create(String path) {
+        return new Profile(nativeInit(path));
+    }
+
+    private Profile(long nativeProfile) {
+        mNativeProfile = nativeProfile;
+    }
+
+    public void destroy() {}
+
+    /* package */ long getNativeProfile() {
+        return mNativeProfile;
+    }
+
+    private static native long nativeInit(String path);
+}
diff --git a/weblayer/browser/navigation_controller_impl.cc b/weblayer/browser/navigation_controller_impl.cc
index 1dfd2a3..2f063865a 100644
--- a/weblayer/browser/navigation_controller_impl.cc
+++ b/weblayer/browser/navigation_controller_impl.cc
@@ -5,18 +5,30 @@
 #include "weblayer/browser/navigation_controller_impl.h"
 
 #include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/page_transition_types.h"
 #include "weblayer/browser/browser_controller_impl.h"
+#include "weblayer/public/navigation_observer.h"
 
 namespace weblayer {
 
 NavigationControllerImpl::NavigationControllerImpl(
     BrowserControllerImpl* browser_controller)
-    : browser_controller_(browser_controller) {}
+    : WebContentsObserver(browser_controller->web_contents()),
+      browser_controller_(browser_controller) {}
 
 NavigationControllerImpl::~NavigationControllerImpl() = default;
 
+void NavigationControllerImpl::AddObserver(NavigationObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void NavigationControllerImpl::RemoveObserver(NavigationObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
 void NavigationControllerImpl::Navigate(const GURL& url) {
   content::NavigationController::LoadURLParams params(url);
   params.transition_type = ui::PageTransitionFromInt(
@@ -45,4 +57,76 @@
   browser_controller_->web_contents()->Stop();
 }
 
+int NavigationControllerImpl::GetNavigationListSize() {
+  return browser_controller_->web_contents()->GetController().GetEntryCount();
+}
+
+int NavigationControllerImpl::GetNavigationListCurrentIndex() {
+  return browser_controller_->web_contents()
+      ->GetController()
+      .GetCurrentEntryIndex();
+}
+
+GURL NavigationControllerImpl::GetNavigationEntryDisplayURL(int index) {
+  auto* entry =
+      browser_controller_->web_contents()->GetController().GetEntryAtIndex(
+          index);
+  if (!entry)
+    return GURL();
+  return entry->GetVirtualURL();
+}
+
+void NavigationControllerImpl::DidStartNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame())
+    return;
+
+  navigation_map_[navigation_handle] =
+      std::make_unique<NavigationImpl>(navigation_handle);
+  auto* navigation = navigation_map_[navigation_handle].get();
+  for (auto& observer : observers_)
+    observer.NavigationStarted(*navigation);
+}
+
+void NavigationControllerImpl::DidRedirectNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame())
+    return;
+
+  DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end());
+  auto* navigation = navigation_map_[navigation_handle].get();
+  for (auto& observer : observers_)
+    observer.NavigationRedirected(*navigation);
+}
+
+void NavigationControllerImpl::ReadyToCommitNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame())
+    return;
+
+  DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end());
+  auto* navigation = navigation_map_[navigation_handle].get();
+  for (auto& observer : observers_)
+    observer.NavigationCommitted(*navigation);
+}
+
+void NavigationControllerImpl::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame())
+    return;
+
+  DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end());
+  auto* navigation = navigation_map_[navigation_handle].get();
+  if (navigation_handle->GetNetErrorCode() == net::OK &&
+      !navigation_handle->IsErrorPage()) {
+    for (auto& observer : observers_)
+      observer.NavigationCompleted(*navigation);
+  } else {
+    for (auto& observer : observers_)
+      observer.NavigationFailed(*navigation);
+  }
+
+  navigation_map_.erase(navigation_map_.find(navigation_handle));
+}
+
 }  // namespace weblayer
diff --git a/weblayer/browser/navigation_controller_impl.h b/weblayer/browser/navigation_controller_impl.h
index b624e821..f7a4ea8 100644
--- a/weblayer/browser/navigation_controller_impl.h
+++ b/weblayer/browser/navigation_controller_impl.h
@@ -5,29 +5,52 @@
 #ifndef WEBLAYER_BROWSER_NAVIGATION_CONTROLLER_IMPL_H_
 #define WEBLAYER_BROWSER_NAVIGATION_CONTROLLER_IMPL_H_
 
+#include <map>
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "weblayer/browser/navigation_impl.h"
 #include "weblayer/public/navigation_controller.h"
 
 namespace weblayer {
 class BrowserControllerImpl;
 
-class NavigationControllerImpl : public NavigationController {
+class NavigationControllerImpl : public NavigationController,
+                                 public content::WebContentsObserver {
  public:
   explicit NavigationControllerImpl(BrowserControllerImpl* browser_controller);
   ~NavigationControllerImpl() override;
 
-  // NavigationController implementation:
-  void Navigate(const GURL& url) override;
-
-  void GoBack() override;
-
-  void GoForward() override;
-
-  void Reload() override;
-
-  void Stop() override;
-
  private:
+  // NavigationController implementation:
+  void AddObserver(NavigationObserver* observer) override;
+  void RemoveObserver(NavigationObserver* observer) override;
+  void Navigate(const GURL& url) override;
+  void GoBack() override;
+  void GoForward() override;
+  void Reload() override;
+  void Stop() override;
+  int GetNavigationListSize() override;
+  int GetNavigationListCurrentIndex() override;
+  GURL GetNavigationEntryDisplayURL(int index) override;
+
+  // content::WebContentsObserver implementation:
+  void DidStartNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidRedirectNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void ReadyToCommitNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+
   BrowserControllerImpl* browser_controller_;
+  base::ObserverList<NavigationObserver>::Unchecked observers_;
+  std::map<content::NavigationHandle*, std::unique_ptr<NavigationImpl>>
+      navigation_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(NavigationControllerImpl);
 };
 
 }  // namespace weblayer
diff --git a/weblayer/browser/navigation_impl.cc b/weblayer/browser/navigation_impl.cc
new file mode 100644
index 0000000..f082a22
--- /dev/null
+++ b/weblayer/browser/navigation_impl.cc
@@ -0,0 +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.
+
+#include "weblayer/browser/navigation_impl.h"
+
+#include "content/public/browser/navigation_handle.h"
+
+namespace weblayer {
+
+NavigationImpl::NavigationImpl(content::NavigationHandle* navigation_handle)
+    : navigation_handle_(navigation_handle) {}
+
+NavigationImpl::~NavigationImpl() = default;
+
+GURL NavigationImpl::GetURL() {
+  return navigation_handle_->GetURL();
+}
+
+const std::vector<GURL>& NavigationImpl::GetRedirectChain() {
+  return navigation_handle_->GetRedirectChain();
+}
+
+Navigation::State NavigationImpl::GetState() {
+  NOTIMPLEMENTED() << "TODO: properly implement this";
+  return Navigation::State::kWaitingResponse;
+}
+
+}  // namespace weblayer
diff --git a/weblayer/browser/navigation_impl.h b/weblayer/browser/navigation_impl.h
new file mode 100644
index 0000000..78aa61ed
--- /dev/null
+++ b/weblayer/browser/navigation_impl.h
@@ -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.
+
+#ifndef WEBLAYER_BROWSER_NAVIGATION_IMPL_H_
+#define WEBLAYER_BROWSER_NAVIGATION_IMPL_H_
+
+#include "base/macros.h"
+#include "weblayer/public/navigation.h"
+
+namespace content {
+class NavigationHandle;
+}
+
+namespace weblayer {
+
+class NavigationImpl : public Navigation {
+ public:
+  explicit NavigationImpl(content::NavigationHandle* navigation_handle);
+  ~NavigationImpl() override;
+
+ private:
+  // Navigation implementation:
+  GURL GetURL() override;
+  const std::vector<GURL>& GetRedirectChain() override;
+  State GetState() override;
+
+  content::NavigationHandle* navigation_handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(NavigationImpl);
+};
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_BROWSER_NAVIGATION_IMPL_H_
diff --git a/weblayer/browser/profile_impl.cc b/weblayer/browser/profile_impl.cc
index 4c80e0a..ad541bcc 100644
--- a/weblayer/browser/profile_impl.cc
+++ b/weblayer/browser/profile_impl.cc
@@ -8,6 +8,11 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/resource_context.h"
 
+#if defined(OS_ANDROID)
+#include "base/android/jni_string.h"
+#include "weblayer/weblayer_jni/Profile_jni.h"
+#endif
+
 namespace weblayer {
 
 namespace {
@@ -120,4 +125,13 @@
   return std::make_unique<ProfileImpl>(path);
 }
 
+#if defined(OS_ANDROID)
+static jlong JNI_Profile_Init(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jstring>& path) {
+  return reinterpret_cast<intptr_t>(new ProfileImpl(
+      base::FilePath(base::android::ConvertJavaStringToUTF8(env, path))));
+}
+#endif  // OS_ANDROID
+
 }  // namespace weblayer
diff --git a/weblayer/public/browser_controller.h b/weblayer/public/browser_controller.h
index 75b9a2f..5670977 100644
--- a/weblayer/public/browser_controller.h
+++ b/weblayer/public/browser_controller.h
@@ -7,15 +7,20 @@
 
 #include <algorithm>
 
+#include "build/build_config.h"
+
 namespace gfx {
 class Size;
 }
 
+#if !defined(OS_ANDROID)
 namespace views {
 class WebView;
 }
+#endif
 
 namespace weblayer {
+class BrowserObserver;
 class Profile;
 class NavigationController;
 
@@ -29,12 +34,18 @@
 
   virtual ~BrowserController() {}
 
+  virtual void AddObserver(BrowserObserver* observer) = 0;
+
+  virtual void RemoveObserver(BrowserObserver* observer) = 0;
+
   virtual NavigationController* GetNavigationController() = 0;
 
+#if !defined(OS_ANDROID)
   // TODO: this isn't a stable API, so use it now for expediency in the C++ API,
   // but if we ever want to have backward or forward compatibility in C++ this
   // will have to be something else.
   virtual void AttachToView(views::WebView* web_view) = 0;
+#endif
 };
 
 }  // namespace weblayer
diff --git a/weblayer/public/browser_observer.h b/weblayer/public/browser_observer.h
new file mode 100644
index 0000000..fa43c3b8
--- /dev/null
+++ b/weblayer/public/browser_observer.h
@@ -0,0 +1,33 @@
+// 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 WEBLAYER_PUBLIC_BROWSER_OBSERVER_H_
+#define WEBLAYER_PUBLIC_BROWSER_OBSERVER_H_
+
+class GURL;
+
+namespace weblayer {
+
+class BrowserObserver {
+ public:
+  virtual ~BrowserObserver() {}
+
+  // The URL bar should be updated to |url|.
+  virtual void DisplayedURLChanged(const GURL& url) {}
+
+  // Indicates that loading has started (|is_loading| is true) or is done
+  // (|is_loading| is false). |to_different_document| will be true unless the
+  // load is a fragment navigation, or triggered by
+  // history.pushState/replaceState.
+  virtual void LoadingStateChanged(bool is_loading,
+                                   bool to_different_document) {}
+
+  // This is fired after each navigation has completed, to indicate that the
+  // first paint after a non-empty layout has finished.
+  virtual void FirstContentfulPaint() {}
+};
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_PUBLIC_BROWSER_OBSERVER_H_
diff --git a/weblayer/public/navigation.h b/weblayer/public/navigation.h
index bfa4322..ec17ed4 100644
--- a/weblayer/public/navigation.h
+++ b/weblayer/public/navigation.h
@@ -5,6 +5,8 @@
 #ifndef WEBLAYER_PUBLIC_NAVIGATION_H_
 #define WEBLAYER_PUBLIC_NAVIGATION_H_
 
+#include <vector>
+
 class GURL;
 
 namespace weblayer {
diff --git a/weblayer/public/navigation_controller.h b/weblayer/public/navigation_controller.h
index 8851ad61..1b455a9 100644
--- a/weblayer/public/navigation_controller.h
+++ b/weblayer/public/navigation_controller.h
@@ -10,11 +10,16 @@
 class GURL;
 
 namespace weblayer {
+class NavigationObserver;
 
 class NavigationController {
  public:
   virtual ~NavigationController() {}
 
+  virtual void AddObserver(NavigationObserver* observer) = 0;
+
+  virtual void RemoveObserver(NavigationObserver* observer) = 0;
+
   virtual void Navigate(const GURL& url) = 0;
 
   virtual void GoBack() = 0;
@@ -24,6 +29,17 @@
   virtual void Reload() = 0;
 
   virtual void Stop() = 0;
+
+  // Gets the number of entries in the back/forward list.
+  virtual int GetNavigationListSize() = 0;
+
+  // Gets the index of the current entry in the back/forward list, or -1 if
+  // there are no entries.
+  virtual int GetNavigationListCurrentIndex() = 0;
+
+  // Gets the URL of the given entry in the back/forward list, or an empty GURL
+  // if there is no navigation entry at that index.
+  virtual GURL GetNavigationEntryDisplayURL(int index) = 0;
 };
 
 }  // namespace weblayer
diff --git a/weblayer/public/navigation_observer.h b/weblayer/public/navigation_observer.h
index 3af81b4..1b372bf 100644
--- a/weblayer/public/navigation_observer.h
+++ b/weblayer/public/navigation_observer.h
@@ -5,32 +5,30 @@
 #ifndef WEBLAYER_PUBLIC_NAVIGATION_OBSERVER_H_
 #define WEBLAYER_PUBLIC_NAVIGATION_OBSERVER_H_
 
-#include <algorithm>
-
 namespace weblayer {
 class Navigation;
 
+// An interface for a WebLayer embedder to get notified about navigations. For
+// now this only notifies for the main frame.
+//
 // The lifecycle of a navigation:
 // 1) NavigationStarted
 // 2) 0 or more NavigationRedirected
 // 3) 0 or 1 NavigationCommitted
-// 3) 0 or 1 NavigationHadFirstContentfulPaint
 // 4) NavigationCompleted or NavigationFailed
 class NavigationObserver {
  public:
   virtual ~NavigationObserver() {}
 
-  virtual void NavigationStarted(Navigation* navigation) = 0;
+  virtual void NavigationStarted(const Navigation& navigation) {}
 
-  virtual void NavigationRedirected(Navigation* navigation) = 0;
+  virtual void NavigationRedirected(const Navigation& navigation) {}
 
-  virtual void NavigationCommitted(Navigation* navigation) = 0;
+  virtual void NavigationCommitted(const Navigation& navigation) {}
 
-  virtual void NavigationHadFirstContentfulPaint(Navigation* navigation) = 0;
+  virtual void NavigationCompleted(const Navigation& navigation) {}
 
-  virtual void NavigationCompleted(Navigation* navigation) = 0;
-
-  virtual void NavigationFailed(Navigation* navigation) = 0;
+  virtual void NavigationFailed(const Navigation& navigation) {}
 };
 
 }  // namespace weblayer
diff --git a/weblayer/shell/DEPS b/weblayer/shell/DEPS
index f45c486..11df88b 100644
--- a/weblayer/shell/DEPS
+++ b/weblayer/shell/DEPS
@@ -6,4 +6,8 @@
   "+ui/gfx",
   "+ui/views",
   "+ui/wm",
+
+  "!content/public",
+  "!components/embedder_support/android",
+  "!ui/android",
 ]
diff --git a/weblayer/shell/android/AndroidManifest.xml b/weblayer/shell/android/AndroidManifest.xml
new file mode 100644
index 0000000..42d77ae0
--- /dev/null
+++ b/weblayer/shell/android/AndroidManifest.xml
@@ -0,0 +1,59 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.chromium.weblayer.shell">
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+    <application android:name="WebLayerShellApplication"
+            android:label="WebLayer shell">
+        <activity android:name="WebLayerShellActivity"
+                  android:launchMode="singleTask"
+                  android:theme="@android:style/Theme.Holo.Light.NoActionBar"
+                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
+                  android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- The following service entries exist in order to allow us to
+             start more than one sandboxed process. -->
+
+        <!-- NOTE: If you change the values of "android:process" for any of the below services,
+             you also need to update kHelperProcessExecutableName in chrome_constants.cc. -->
+        {% set num_sandboxed_services = 40 %}
+        <meta-data android:name="org.chromium.content.browser.NUM_SANDBOXED_SERVICES"
+                   android:value="{{ num_sandboxed_services }}"/>
+        {% for i in range(num_sandboxed_services) %}
+        <service android:name="org.chromium.content.app.SandboxedProcessService{{ i }}"
+                 android:process=":sandboxed_process{{ i }}"
+                 android:isolatedProcess="true"
+                 android:exported="false" />
+        {% endfor %}
+
+        {% set num_privileged_services = 5 %}
+        <meta-data android:name="org.chromium.content.browser.NUM_PRIVILEGED_SERVICES"
+                   android:value="{{ num_privileged_services }}"/>
+        {% for i in range(num_privileged_services) %}
+        <service android:name="org.chromium.content.app.PrivilegedProcessService{{ i }}"
+                 android:process=":privileged_process{{ i }}"
+                 android:isolatedProcess="false"
+                 android:exported="false" />
+        {% endfor %}
+    </application>
+</manifest>
diff --git a/weblayer/shell/android/BUILD.gn b/weblayer/shell/android/BUILD.gn
new file mode 100644
index 0000000..4d94596
--- /dev/null
+++ b/weblayer/shell/android/BUILD.gn
@@ -0,0 +1,82 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/config.gni")
+import("//build/config/android/rules.gni")
+import("//third_party/icu/config.gni")
+import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
+
+weblayer_shell_manifest =
+    "$target_gen_dir/weblayer_shell_manifest/AndroidManifest.xml"
+
+jinja_template("weblayer_shell_manifest") {
+  input = "AndroidManifest.xml"
+  output = weblayer_shell_manifest
+}
+
+android_assets("weblayer_shell_assets") {
+  testonly = true
+
+  sources = [
+    "$root_out_dir/weblayer_shell.pak",
+  ]
+  disable_compression = true
+  deps = [
+    "//third_party/icu:icu_assets",
+    "//weblayer/shell:pak",
+  ]
+  if (use_v8_context_snapshot) {
+    deps += [ "//tools/v8_context_snapshot:v8_context_snapshot_assets" ]
+  } else {
+    deps += [ "//v8:v8_external_startup_data_assets" ]
+  }
+}
+
+android_library("weblayer_shell_java") {
+  testonly = true
+
+  deps = [
+    ":weblayer_shell_manifest",
+    "//base:base_java",
+    "//components/embedder_support/android:content_view_java",
+    "//components/embedder_support/android:view_java",
+    "//content/public/android:content_java",
+    "//ui/android:ui_java",
+    "//weblayer:weblayer_java",
+  ]
+
+  # Transitive dependencies
+  deps += [
+    "//components/embedder_support/android:content_view_java",
+    "//components/embedder_support/android:view_java",
+    "//components/viz/service:service_java",
+    "//media/base/android:media_java",
+    "//media/capture/video/android:capture_java",
+    "//mojo/public/java:system_java",
+    "//net/android:net_java",
+  ]
+  java_files = [
+    "src/org/chromium/weblayer/shell/WebLayerShellActivity.java",
+    "src/org/chromium/weblayer/shell/WebLayerShellApplication.java",
+  ]
+
+  android_manifest_for_lint = weblayer_shell_manifest
+}
+
+android_apk("weblayer_shell_apk") {
+  testonly = true
+
+  deps = [
+    ":weblayer_shell_assets",
+    ":weblayer_shell_java",
+    ":weblayer_shell_manifest",
+    "//base:base_java",
+  ]
+  apk_name = "WebLayerShell"
+  android_manifest = weblayer_shell_manifest
+  min_sdk_version = 21
+  target_sdk_version = 28
+  android_manifest_dep = ":weblayer_shell_manifest"
+  shared_libraries = [ "//weblayer:libweblayer" ]
+}
diff --git a/weblayer/shell/android/src/org/chromium/weblayer/shell/WebLayerShellActivity.java b/weblayer/shell/android/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
new file mode 100644
index 0000000..70b5788
--- /dev/null
+++ b/weblayer/shell/android/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
@@ -0,0 +1,90 @@
+// 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.weblayer.shell;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import org.chromium.base.CommandLine;
+import org.chromium.base.Log;
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.LibraryProcessType;
+import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.components.embedder_support.view.ContentViewRenderView;
+import org.chromium.content_public.browser.BrowserStartupController;
+import org.chromium.content_public.browser.DeviceUtils;
+import org.chromium.ui.base.ActivityWindowAndroid;
+import org.chromium.weblayer.BrowserController;
+import org.chromium.weblayer.Profile;
+
+/**
+ * Activity for managing the Demo Shell.
+ */
+public class WebLayerShellActivity extends Activity {
+    private static final String TAG = "WebLayerShell";
+
+    private ActivityWindowAndroid mWindowAndroid;
+    private ContentViewRenderView mContentView;
+    private Profile mProfile;
+    private BrowserController mBrowserController;
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Initializing the command line must occur before loading the library.
+        if (!CommandLine.isInitialized()) {
+            ((WebLayerShellApplication) getApplication()).initCommandLine();
+        }
+
+        DeviceUtils.addDeviceSpecificUserAgentSwitch();
+
+        try {
+            LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
+        } catch (ProcessInitException e) {
+            Log.e(TAG, "ContentView initialization failed.", e);
+            // Since the library failed to initialize nothing in the application
+            // can work, so kill the whole application not just the activity
+            System.exit(-1);
+            return;
+        }
+
+        mWindowAndroid = new ActivityWindowAndroid(this);
+        mContentView = new ContentViewRenderView(this);
+        setContentView(mContentView);
+        mWindowAndroid.setAnimationPlaceholderView(mContentView.getSurfaceView());
+        try {
+            BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
+                    .startBrowserProcessesAsync(
+                            true, false, new BrowserStartupController.StartupCallback() {
+                                @Override
+                                public void onSuccess() {
+                                    mContentView.onNativeLibraryLoaded(mWindowAndroid);
+                                    mProfile = Profile.create("default");
+                                    mBrowserController = BrowserController.create(mProfile);
+                                    mBrowserController.initialize(mContentView, mWindowAndroid);
+                                    mContentView.setCurrentWebContents(
+                                            mBrowserController.getWebContents());
+                                    mBrowserController.show();
+                                    mBrowserController.navigate("https://www.google.com");
+                                }
+
+                                @Override
+                                public void onFailure() {
+                                    Log.e(TAG, "ContentView initialization failed.");
+                                    finish();
+                                }
+                            });
+        } catch (ProcessInitException e) {
+            Log.e(TAG, "Unable to load native library.", e);
+            System.exit(-1);
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+}
diff --git a/weblayer/shell/android/src/org/chromium/weblayer/shell/WebLayerShellApplication.java b/weblayer/shell/android/src/org/chromium/weblayer/shell/WebLayerShellApplication.java
new file mode 100644
index 0000000..8fc0a87
--- /dev/null
+++ b/weblayer/shell/android/src/org/chromium/weblayer/shell/WebLayerShellApplication.java
@@ -0,0 +1,45 @@
+// 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.weblayer.shell;
+
+import android.app.Application;
+import android.content.Context;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.BuildConfig;
+import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
+import org.chromium.base.PathUtils;
+import org.chromium.base.multidex.ChromiumMultiDexInstaller;
+import org.chromium.ui.base.ResourceBundle;
+
+/**
+ * Entry point for the demo shell application.
+ */
+public class WebLayerShellApplication extends Application {
+    public static final String COMMAND_LINE_FILE = "/data/local/tmp/weblayer-shell-command-line";
+    private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "weblayer_shell";
+
+    @Override
+    protected void attachBaseContext(Context base) {
+        super.attachBaseContext(base);
+        boolean isBrowserProcess = !ContextUtils.getProcessName().contains(":");
+        ContextUtils.initApplicationContext(this);
+        ResourceBundle.setNoAvailableLocalePaks();
+        if (isBrowserProcess) {
+            if (BuildConfig.IS_MULTIDEX_ENABLED) {
+                ChromiumMultiDexInstaller.install(this);
+            }
+            PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
+        }
+        ApplicationStatus.initialize(this);
+    }
+
+    public void initCommandLine() {
+        if (!CommandLine.isInitialized()) {
+            CommandLine.initFromFile(COMMAND_LINE_FILE);
+        }
+    }
+}
diff --git a/weblayer/shell/browser/shell.cc b/weblayer/shell/browser/shell.cc
index 319f3e57..1c46cae 100644
--- a/weblayer/shell/browser/shell.cc
+++ b/weblayer/shell/browser/shell.cc
@@ -24,17 +24,19 @@
 // Null until/unless the default main message loop is running.
 base::NoDestructor<base::OnceClosure> g_quit_main_message_loop;
 
-const int kDefaultTestWindowWidthDip = 800;
-const int kDefaultTestWindowHeightDip = 600;
+const int kDefaultTestWindowWidthDip = 1000;
+const int kDefaultTestWindowHeightDip = 700;
 
 std::vector<Shell*> Shell::windows_;
 
 Shell::Shell(std::unique_ptr<BrowserController> browser_controller)
     : browser_controller_(std::move(browser_controller)), window_(nullptr) {
   windows_.push_back(this);
+  browser_controller_->AddObserver(this);
 }
 
 Shell::~Shell() {
+  browser_controller_->RemoveObserver(this);
   PlatformCleanUp();
 
   for (size_t i = 0; i < windows_.size(); ++i) {
@@ -91,6 +93,22 @@
   PlatformInitialize(GetShellDefaultSize());
 }
 
+void Shell::LoadingStateChanged(bool is_loading, bool to_different_document) {
+  int current_index = browser_controller_->GetNavigationController()
+                          ->GetNavigationListCurrentIndex();
+  int max_index =
+      browser_controller_->GetNavigationController()->GetNavigationListSize() -
+      1;
+
+  PlatformEnableUIControl(BACK_BUTTON, current_index > 0);
+  PlatformEnableUIControl(FORWARD_BUTTON, current_index < max_index);
+  PlatformEnableUIControl(STOP_BUTTON, to_different_document && is_loading);
+}
+
+void Shell::DisplayedURLChanged(const GURL& url) {
+  PlatformSetAddressBarURL(url);
+}
+
 gfx::Size Shell::AdjustWindowSize(const gfx::Size& initial_size) {
   if (!initial_size.IsEmpty())
     return initial_size;
@@ -131,18 +149,6 @@
   browser_controller_->GetNavigationController()->Stop();
 }
 
-/* TODO: this depends on getting notifications from BrowserController.
-void Shell::UpdateNavigationControls(bool to_different_document) {
-  int current_index = web_contents_->GetController().GetCurrentEntryIndex();
-  int max_index = web_contents_->GetController().GetEntryCount() - 1;
-
-  PlatformEnableUIControl(BACK_BUTTON, current_index > 0);
-  PlatformEnableUIControl(FORWARD_BUTTON, current_index < max_index);
-  PlatformEnableUIControl(STOP_BUTTON,
-      to_different_document && web_contents_->IsLoading());
-}
-*/
-
 gfx::Size Shell::GetShellDefaultSize() {
   static gfx::Size default_shell_size;
   if (!default_shell_size.IsEmpty())
diff --git a/weblayer/shell/browser/shell.h b/weblayer/shell/browser/shell.h
index fbfcc53..0aebe03 100644
--- a/weblayer/shell/browser/shell.h
+++ b/weblayer/shell/browser/shell.h
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/native_widget_types.h"
+#include "weblayer/public/browser_observer.h"
 
 #if defined(OS_ANDROID)
 #include "base/android/scoped_java_ref.h"
@@ -37,9 +38,9 @@
 
 // This represents one window of the Web Shell, i.e. all the UI including
 // buttons and url bar, as well as the web content area.
-class Shell {
+class Shell : public BrowserObserver {
  public:
-  ~Shell();
+  ~Shell() override;
 
   void LoadURL(const GURL& url);
   void GoBackOrForward(int offset);
@@ -72,6 +73,11 @@
 
   explicit Shell(std::unique_ptr<BrowserController> browser_controller);
 
+  // BrowserObserver implementation:
+  void LoadingStateChanged(bool is_loading,
+                           bool to_different_document) override;
+  void DisplayedURLChanged(const GURL& url) override;
+
   // Helper to create a new Shell.
   static Shell* CreateShell(
       std::unique_ptr<BrowserController> browser_controller,
@@ -113,14 +119,6 @@
   // Set the title of shell window
   void PlatformSetTitle(const base::string16& title);
 
-#if defined(OS_ANDROID)
-  void PlatformToggleFullscreenModeForTab(WebContents* web_contents,
-                                          bool enter_fullscreen);
-
-  bool PlatformIsFullscreenForTabOrPending(
-      const WebContents* web_contents) const;
-#endif
-
   std::unique_ptr<BrowserController> browser_controller_;
 
   gfx::NativeWindow window_;