diff --git a/DEPS b/DEPS
index c27725f..64343446 100644
--- a/DEPS
+++ b/DEPS
@@ -138,11 +138,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': '64a5b671b9c01de6103209764206fdc221be5181',
+  'skia_revision': 'b9416caa367a770e55935916e135572c2461ca37',
   # 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': '6a51e6131f7ffe6afb8cd98d27faaa2af54037b5',
+  'v8_revision': 'eca9f8c887e111480522a0477522b0d5fdbc7095',
   # 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.
@@ -150,15 +150,15 @@
   # 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': '13ac9422cf189cef255bc00627f05bddffa070f0',
+  'angle_revision': '85fef1bc62f851a4de91408cfafa570dbacf544f',
   # 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': 'f9411ce30f132dbcb297a533525000a788b7c951',
+  'swiftshader_revision': 'df84b9466cfd59992e09f1d04bb0453060f3aa0f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'd8ae8f8120dc8d2e63f98999c19236495519137f',
+  'pdfium_revision': 'd050b35a14af9626b0b08c12c563696cd7ed25e6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -201,7 +201,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': '86a9e51b7ad87e1a1a4fbd1f7abf7e8e19f281bf',
+  'catapult_revision': '727d7ca2730f942dd59a2389f8471d579c8ee95b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -261,7 +261,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_headers_revision': '9cf7c3a7d2d203b1ee35896547b9644e28d9280e',
+  'spv_headers_revision': 'de99d4d834aeb51dd9f099baa285bd44fd04bb3d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -273,7 +273,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '970101a1028dbd2ec46028a2e40ae1a0974de1b3',
+  'dawn_revision': '87ab2f96d93bcf28ce97fa27a0c90f870ec040db',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -807,7 +807,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '491e16e8569b19b2877448ca5ff4817b007f03b1',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'bbe067f8952c0e9f230ab853561d7d59291ac619',
       'condition': 'checkout_linux',
   },
 
@@ -1187,7 +1187,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '07b9e93544271ef4ddea3519b73ac6cda236d3d6',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '6b5210e9dfb6bbf58d62d0010e3369e95cd13085',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1355,7 +1355,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6f0b34abee8dba611c253738d955c59f703c147a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '51db421682d67c9ab78cc029d0aa3b2e94d1f3e0',
+    Var('webrtc_git') + '/src.git' + '@' + 'a7acc4dd8d303310fb1bd2cfafbc032f308b1fbc',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1396,7 +1396,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2fcee1474c0d19c97821c6dcef5d8e21bfb41b9e',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d96cf88529977e65f994d950bddaef85bac305f6',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 1abd296..803f596 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2269,9 +2269,7 @@
                         'cywang@chromium.org',
                         'vovoy@chromium.org'],
     'crostini': ['crostini-ui@chromium.org'],
-    'cups_printing': ['baileyberro+watch-printers@chromium.org',
-                      'jimmyxgong+watch-printers@chromium.org',
-                      'zentaro+watch-printers@chromium.org'],
+    'cups_printing': ['print-reviews+cups@chromium.org'],
     'custom_proxy': ['lbendig@opera.com',
                      'wdzierzanowski@opera.com'],
     'custom_tabs': ['amalova+watch@chromium.org',
@@ -2508,7 +2506,7 @@
                   'tburkard+watch@chromium.org'],
     'presentation': ['mfoltz+watch@chromium.org'],
     'preview_features': ['chrome-lite-pages+watch@google.com'],
-    'print_preview': ['rbpotter@chromium.org'],
+    'print_preview': ['print-reviews+preview@chromium.org'],
     'push_messaging': ['peter@chromium.org'],
     'reading_list': ['stkhapugin@chromium.org'],
     'remoteplayback': ['mfoltz+watch@chromium.org'],
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index c8786471..81616ef 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1269,7 +1269,6 @@
     "//services/device/public/mojom",
     "//services/media_session/public/mojom",
     "//services/service_manager/public/cpp",
-    "//services/ws/public/cpp/input_devices",
     "//skia",
     "//ui/aura",
     "//ui/events",
@@ -1360,8 +1359,6 @@
     "//services/data_decoder/public/cpp",
     "//services/preferences/public/cpp",
     "//services/service_manager/public/cpp",
-    "//services/ws/public/cpp/input_devices:input_device_controller",
-    "//services/ws/public/mojom/input_devices",
 
     # TODO(msw): Remove this; ash should not depend on blink/webkit.
     "//third_party/blink/public:blink_headers",
@@ -1386,7 +1383,6 @@
     "//ui/events:events_base",
     "//ui/events:gesture_detection",
     "//ui/events/devices",
-    "//ui/events/devices/mojo",
     "//ui/events/ozone:events_ozone",
     "//ui/message_center",
     "//ui/native_theme",
@@ -2207,7 +2203,6 @@
     "//components/viz/test:test_support",
     "//device/bluetooth",
     "//services/device/public/mojom",
-    "//services/ws/public/cpp/input_devices",
     "//skia",
     "//testing/gtest",
     "//ui/accessibility",
diff --git a/ash/DEPS b/ash/DEPS
index fb790b2..865e325 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -85,20 +85,6 @@
   # InputMethodManager lives in the browser process. Use ImeController.
   "-ui/base/ime/chromeos/input_method_manager.h"
 
-  # ui/events/devices is tied with ozone, which is controlled by mus, and
-  # shouldn't be used by ash.
-  "-ui/events/devices",
-
-  # Enums and supporting classes or observers that are safe (should be moved to
-  # services/ws/public/cpp). http://crbug.com/747544.
-  "+ui/events/devices/device_hotplug_event_observer.h",
-  "+ui/events/devices/input_device.h",
-  "+ui/events/devices/input_device_event_observer.h",
-  "+ui/events/devices/input_device_manager.h",
-  "+ui/events/devices/stylus_state.h",
-  "+ui/events/devices/touch_device_transform.h",
-  "+ui/events/devices/touchscreen_device.h",
-
   # SessionManager/UserManager is not part of ash. Use SessionController.
   "-components/session_manager/core",
   "-components/user_manager/user_manager.h",
diff --git a/ash/accessibility/touch_exploration_manager.h b/ash/accessibility/touch_exploration_manager.h
index 4f11f4e..3cae740 100644
--- a/ash/accessibility/touch_exploration_manager.h
+++ b/ash/accessibility/touch_exploration_manager.h
@@ -11,7 +11,7 @@
 #include "ash/accessibility/touch_accessibility_enabler.h"
 #include "ash/accessibility/touch_exploration_controller.h"
 #include "ash/ash_export.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/shell_observer.h"
 #include "base/macros.h"
 #include "base/scoped_observer.h"
@@ -40,7 +40,7 @@
       public TouchAccessibilityEnablerDelegate,
       public display::DisplayObserver,
       public ::wm::ActivationChangeObserver,
-      public keyboard::KeyboardControllerObserver,
+      public KeyboardControllerObserver,
       public ShellObserver {
  public:
   explicit TouchExplorationManager(
@@ -86,7 +86,7 @@
   void SetTouchAccessibilityAnchorPoint(const gfx::Point& anchor_point);
 
  private:
-  // keyboard::KeyboardControllerObserver overrides:
+  // KeyboardControllerObserver overrides:
   void OnKeyboardVisibleBoundsChanged(const gfx::Rect& new_bounds) override;
   void OnKeyboardEnabledChanged(bool is_enabled) override;
 
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 90072d1..c9b3642 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -625,8 +625,7 @@
     presenter_.GetView()->OnWallpaperColorsChanged();
 }
 
-void AppListControllerImpl::OnKeyboardVisibilityStateChanged(
-    const bool is_visible) {
+void AppListControllerImpl::OnKeyboardVisibilityChanged(const bool is_visible) {
   onscreen_keyboard_shown_ = is_visible;
   app_list::AppListView* app_list_view = presenter_.GetView();
   if (app_list_view)
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index 21b0240..56d9f4d 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -21,9 +21,9 @@
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/home_screen/home_launcher_gesture_handler_observer.h"
 #include "ash/home_screen/home_screen_delegate.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.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/wallpaper_controller_observer.h"
 #include "ash/public/interfaces/voice_interaction_controller.mojom.h"
@@ -56,7 +56,7 @@
       public ash::ShellObserver,
       public OverviewObserver,
       public TabletModeObserver,
-      public keyboard::KeyboardControllerObserver,
+      public KeyboardControllerObserver,
       public WallpaperControllerObserver,
       public DefaultVoiceInteractionObserver,
       public WindowTreeHostManager::Observer,
@@ -222,7 +222,7 @@
   void OnTabletModeEnded() override;
 
   // KeyboardControllerObserver:
-  void OnKeyboardVisibilityStateChanged(bool is_visible) override;
+  void OnKeyboardVisibilityChanged(bool is_visible) override;
 
   // WallpaperControllerObserver:
   void OnWallpaperColorsChanged() override;
diff --git a/ash/app_list/app_list_presenter_delegate_impl.h b/ash/app_list/app_list_presenter_delegate_impl.h
index fe7681a..7c91618 100644
--- a/ash/app_list/app_list_presenter_delegate_impl.h
+++ b/ash/app_list/app_list_presenter_delegate_impl.h
@@ -9,7 +9,6 @@
 
 #include "ash/app_list/presenter/app_list_presenter_delegate.h"
 #include "ash/ash_export.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "ui/display/display_observer.h"
diff --git a/ash/assistant/assistant_ui_controller.cc b/ash/assistant/assistant_ui_controller.cc
index 9437221..11e3bcf 100644
--- a/ash/assistant/assistant_ui_controller.cc
+++ b/ash/assistant/assistant_ui_controller.cc
@@ -480,7 +480,7 @@
                    due_to_interaction);
 }
 
-void AssistantUiController::OnKeyboardWorkspaceOccludedBoundsChanged(
+void AssistantUiController::OnKeyboardOccludedBoundsChanged(
     const gfx::Rect& new_bounds_in_screen) {
   DCHECK(container_view_);
 
@@ -518,7 +518,7 @@
   // inconsistency between normal virtual keyboard and accessibility keyboard in
   // changing the work area (accessibility keyboard will change the display work
   // area but virtual keyboard won't). Display metrics change with keyboard
-  // showing is instead handled by OnKeyboardWorkspaceOccludedBoundsChanged.
+  // showing is instead handled by OnKeyboardOccludedBoundsChanged.
   if (keyboard_workspace_occluded_bounds_.IsEmpty()) {
     aura::Window* root_window =
         container_view_->GetWidget()->GetNativeWindow()->GetRootWindow();
diff --git a/ash/assistant/assistant_ui_controller.h b/ash/assistant/assistant_ui_controller.h
index 1a075bd..946e89a 100644
--- a/ash/assistant/assistant_ui_controller.h
+++ b/ash/assistant/assistant_ui_controller.h
@@ -18,7 +18,7 @@
 #include "ash/assistant/ui/assistant_view_delegate.h"
 #include "ash/assistant/ui/caption_bar.h"
 #include "ash/highlighter/highlighter_controller.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/macros.h"
 #include "base/optional.h"
 #include "base/timer/timer.h"
@@ -54,7 +54,7 @@
       public AssistantViewDelegateObserver,
       public CaptionBarDelegate,
       public HighlighterController::Observer,
-      public keyboard::KeyboardControllerObserver,
+      public KeyboardControllerObserver,
       public display::DisplayObserver,
       public ui::EventObserver {
  public:
@@ -112,9 +112,8 @@
       base::Optional<AssistantEntryPoint> entry_point,
       base::Optional<AssistantExitPoint> exit_point) override;
 
-  // keyboard::KeyboardControllerObserver:
-  void OnKeyboardWorkspaceOccludedBoundsChanged(
-      const gfx::Rect& new_bounds) override;
+  // KeyboardControllerObserver:
+  void OnKeyboardOccludedBoundsChanged(const gfx::Rect& new_bounds) override;
 
   // display::DisplayObserver:
   void OnDisplayMetricsChanged(const display::Display& display,
diff --git a/ash/components/shortcut_viewer/DEPS b/ash/components/shortcut_viewer/DEPS
index 9083c1e..d708fed 100644
--- a/ash/components/shortcut_viewer/DEPS
+++ b/ash/components/shortcut_viewer/DEPS
@@ -1,6 +1,5 @@
 include_rules = [
   "+ash/components/strings",
-  "+services/ws/public",
   "+ui/accessibility",
   "+ui/chromeos/events",
   "+ui/chromeos/search_box",
diff --git a/ash/host/ash_window_tree_host_platform.cc b/ash/host/ash_window_tree_host_platform.cc
index f368a97..b6e5209 100644
--- a/ash/host/ash_window_tree_host_platform.cc
+++ b/ash/host/ash_window_tree_host_platform.cc
@@ -13,7 +13,6 @@
 #include "ash/window_factory.h"
 #include "base/feature_list.h"
 #include "base/trace_event/trace_event.h"
-#include "services/ws/public/cpp/input_devices/input_device_controller_client.h"
 #include "ui/aura/null_window_targeter.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host_platform.h"
@@ -24,6 +23,8 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/transform.h"
+#include "ui/ozone/public/input_controller.h"
+#include "ui/ozone/public/ozone_platform.h"
 #include "ui/platform_window/platform_window.h"
 #include "ui/platform_window/platform_window_init_properties.h"
 
@@ -152,13 +153,9 @@
 }
 
 void AshWindowTreeHostPlatform::SetTapToClickPaused(bool state) {
-  ws::InputDeviceControllerClient* input_device_controller_client =
-      Shell::Get()->shell_delegate()->GetInputDeviceControllerClient();
-  if (!input_device_controller_client)
-    return;  // Happens in tests.
-
   // Temporarily pause tap-to-click when the cursor is hidden.
-  input_device_controller_client->SetTapToClickPaused(state);
+  ui::OzonePlatform::GetInstance()->GetInputController()->SetTapToClickPaused(
+      state);
 }
 
 void AshWindowTreeHostPlatform::DispatchEvent(ui::Event* event) {
diff --git a/ash/keyboard/ash_keyboard_controller.cc b/ash/keyboard/ash_keyboard_controller.cc
index 0e603de..2d10403 100644
--- a/ash/keyboard/ash_keyboard_controller.cc
+++ b/ash/keyboard/ash_keyboard_controller.cc
@@ -4,6 +4,8 @@
 
 #include "ash/keyboard/ash_keyboard_controller.h"
 
+#include <utility>
+
 #include "ash/keyboard/ui/keyboard_controller.h"
 #include "ash/keyboard/ui/keyboard_ui_factory.h"
 #include "ash/keyboard/virtual_keyboard_controller.h"
@@ -80,9 +82,8 @@
   keyboard_controller()->KeyboardContentsLoaded(size);
 }
 
-void AshKeyboardController::GetKeyboardConfig(
-    GetKeyboardConfigCallback callback) {
-  std::move(callback).Run(keyboard_controller_->keyboard_config());
+keyboard::KeyboardConfig AshKeyboardController::GetKeyboardConfig() {
+  return keyboard_controller_->keyboard_config();
 }
 
 void AshKeyboardController::SetKeyboardConfig(
@@ -90,9 +91,8 @@
   keyboard_controller_->UpdateKeyboardConfig(keyboard_config);
 }
 
-void AshKeyboardController::IsKeyboardEnabled(
-    IsKeyboardEnabledCallback callback) {
-  std::move(callback).Run(keyboard_controller_->IsEnabled());
+bool AshKeyboardController::IsKeyboardEnabled() {
+  return keyboard_controller_->IsEnabled();
 }
 
 void AshKeyboardController::SetEnableFlag(KeyboardEnableFlag flag) {
@@ -103,12 +103,9 @@
   keyboard_controller_->ClearEnableFlag(flag);
 }
 
-void AshKeyboardController::GetEnableFlags(GetEnableFlagsCallback callback) {
-  const std::set<keyboard::KeyboardEnableFlag>& keyboard_enable_flags =
-      keyboard_controller_->keyboard_enable_flags();
-  std::vector<keyboard::KeyboardEnableFlag> flags(keyboard_enable_flags.begin(),
-                                                  keyboard_enable_flags.end());
-  std::move(callback).Run(std::move(flags));
+const std::set<keyboard::KeyboardEnableFlag>&
+AshKeyboardController::GetEnableFlags() {
+  return keyboard_controller_->keyboard_enable_flags();
 }
 
 void AshKeyboardController::ReloadKeyboardIfNeeded() {
@@ -122,9 +119,8 @@
   keyboard_controller_->RebuildKeyboardIfEnabled();
 }
 
-void AshKeyboardController::IsKeyboardVisible(
-    IsKeyboardVisibleCallback callback) {
-  std::move(callback).Run(keyboard_controller_->IsKeyboardVisible());
+bool AshKeyboardController::IsKeyboardVisible() {
+  return keyboard_controller_->IsKeyboardVisible();
 }
 
 void AshKeyboardController::ShowKeyboard() {
@@ -174,8 +170,7 @@
   keyboard_controller_->SetDraggableArea(bounds);
 }
 
-void AshKeyboardController::AddObserver(
-    ash::KeyboardControllerObserver* observer) {
+void AshKeyboardController::AddObserver(KeyboardControllerObserver* observer) {
   observers_.AddObserver(observer);
 }
 
@@ -211,13 +206,13 @@
   }
 }
 
-void AshKeyboardController::OnKeyboardConfigChanged() {
-  KeyboardConfig config = keyboard_controller_->keyboard_config();
+void AshKeyboardController::OnKeyboardConfigChanged(
+    const keyboard::KeyboardConfig& config) {
   for (auto& observer : observers_)
     observer.OnKeyboardConfigChanged(config);
 }
 
-void AshKeyboardController::OnKeyboardVisibilityStateChanged(bool is_visible) {
+void AshKeyboardController::OnKeyboardVisibilityChanged(bool is_visible) {
   for (auto& observer : observers_)
     observer.OnKeyboardVisibilityChanged(is_visible);
 }
@@ -227,7 +222,7 @@
   SendOnKeyboardVisibleBoundsChanged(screen_bounds);
 }
 
-void AshKeyboardController::OnKeyboardWorkspaceOccludedBoundsChanged(
+void AshKeyboardController::OnKeyboardOccludedBoundsChanged(
     const gfx::Rect& screen_bounds) {
   DVLOG(1) << "OnKeyboardOccludedBoundsChanged: " << screen_bounds.ToString();
   for (auto& observer : observers_)
@@ -235,12 +230,9 @@
 }
 
 void AshKeyboardController::OnKeyboardEnableFlagsChanged(
-    const std::set<keyboard::KeyboardEnableFlag>& keyboard_enable_flags) {
-  std::vector<keyboard::KeyboardEnableFlag> flags(keyboard_enable_flags.begin(),
-                                                  keyboard_enable_flags.end());
-  for (auto& observer : observers_) {
+    const std::set<keyboard::KeyboardEnableFlag>& flags) {
+  for (auto& observer : observers_)
     observer.OnKeyboardEnableFlagsChanged(flags);
-  }
 }
 
 void AshKeyboardController::OnKeyboardEnabledChanged(bool is_enabled) {
diff --git a/ash/keyboard/ash_keyboard_controller.h b/ash/keyboard/ash_keyboard_controller.h
index 8d42172..6d5cc29 100644
--- a/ash/keyboard/ash_keyboard_controller.h
+++ b/ash/keyboard/ash_keyboard_controller.h
@@ -6,11 +6,13 @@
 #define ASH_KEYBOARD_ASH_KEYBOARD_CONTROLLER_H_
 
 #include <memory>
+#include <set>
+#include <vector>
 
 #include "ash/ash_export.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
 #include "ash/public/cpp/keyboard/keyboard_controller.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/session/session_observer.h"
 #include "base/macros.h"
 #include "base/optional.h"
@@ -34,10 +36,9 @@
 // class. TODO(shend): Consider re-factoring keyboard::KeyboardController so
 // that this can inherit from that class instead. Rename this to
 // KeyboardControllerImpl.
-class ASH_EXPORT AshKeyboardController
-    : public ash::KeyboardController,
-      public keyboard::KeyboardControllerObserver,
-      public SessionObserver {
+class ASH_EXPORT AshKeyboardController : public KeyboardController,
+                                         public KeyboardControllerObserver,
+                                         public SessionObserver {
  public:
   // |session_controller| is expected to outlive AshKeyboardController.
   explicit AshKeyboardController(SessionControllerImpl* session_controller);
@@ -57,16 +58,16 @@
 
   // ash::KeyboardController:
   void KeyboardContentsLoaded(const gfx::Size& size) override;
-  void GetKeyboardConfig(GetKeyboardConfigCallback callback) override;
+  keyboard::KeyboardConfig GetKeyboardConfig() override;
   void SetKeyboardConfig(
       const keyboard::KeyboardConfig& keyboard_config) override;
-  void IsKeyboardEnabled(IsKeyboardEnabledCallback callback) override;
+  bool IsKeyboardEnabled() override;
   void SetEnableFlag(keyboard::KeyboardEnableFlag flag) override;
   void ClearEnableFlag(keyboard::KeyboardEnableFlag flag) override;
-  void GetEnableFlags(GetEnableFlagsCallback callback) override;
+  const std::set<keyboard::KeyboardEnableFlag>& GetEnableFlags() override;
   void ReloadKeyboardIfNeeded() override;
   void RebuildKeyboardIfEnabled() override;
-  void IsKeyboardVisible(IsKeyboardVisibleCallback callback) override;
+  bool IsKeyboardVisible() override;
   void ShowKeyboard() override;
   void HideKeyboard(HideReason reason) override;
   void SetContainerType(keyboard::ContainerType container_type,
@@ -76,7 +77,7 @@
   void SetOccludedBounds(const std::vector<gfx::Rect>& bounds) override;
   void SetHitTestBounds(const std::vector<gfx::Rect>& bounds) override;
   void SetDraggableArea(const gfx::Rect& bounds) override;
-  void AddObserver(ash::KeyboardControllerObserver* observer) override;
+  void AddObserver(KeyboardControllerObserver* observer) override;
 
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
@@ -95,21 +96,19 @@
   void OnRootWindowClosing(aura::Window* root_window);
 
  private:
-  // keyboard::KeyboardControllerObserver
-  void OnKeyboardConfigChanged() override;
-  void OnKeyboardVisibilityStateChanged(bool is_visible) override;
+  // KeyboardControllerObserver:
+  void OnKeyboardConfigChanged(const keyboard::KeyboardConfig& config) override;
+  void OnKeyboardVisibilityChanged(bool is_visible) override;
   void OnKeyboardVisibleBoundsChanged(const gfx::Rect& screen_bounds) override;
-  void OnKeyboardWorkspaceOccludedBoundsChanged(
-      const gfx::Rect& screen_bounds) override;
+  void OnKeyboardOccludedBoundsChanged(const gfx::Rect& screen_bounds) override;
   void OnKeyboardEnableFlagsChanged(
-      const std::set<keyboard::KeyboardEnableFlag>& keyboard_enable_flags)
-      override;
+      const std::set<keyboard::KeyboardEnableFlag>& flags) override;
   void OnKeyboardEnabledChanged(bool is_enabled) override;
 
   SessionControllerImpl* session_controller_;  // unowned
   std::unique_ptr<keyboard::KeyboardController> keyboard_controller_;
   std::unique_ptr<VirtualKeyboardController> virtual_keyboard_controller_;
-  base::ObserverList<ash::KeyboardControllerObserver>::Unchecked observers_;
+  base::ObserverList<KeyboardControllerObserver>::Unchecked observers_;
 
   DISALLOW_COPY_AND_ASSIGN(AshKeyboardController);
 };
diff --git a/ash/keyboard/ash_keyboard_controller_unittest.cc b/ash/keyboard/ash_keyboard_controller_unittest.cc
index 9ac42c1..410e1e8 100644
--- a/ash/keyboard/ash_keyboard_controller_unittest.cc
+++ b/ash/keyboard/ash_keyboard_controller_unittest.cc
@@ -5,7 +5,8 @@
 #include "ash/keyboard/ash_keyboard_controller.h"
 
 #include <memory>
-#include <vector>
+#include <set>
+#include <utility>
 
 #include "ash/keyboard/ui/container_behavior.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
@@ -18,6 +19,7 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
+#include "base/test/bind_test_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/window.h"
@@ -30,117 +32,6 @@
 
 namespace {
 
-// TODO(shend): Remove this as AshKeyboardController no longer uses mojo.
-class TestClient {
- public:
-  explicit TestClient()
-      : keyboard_controller_(Shell::Get()->ash_keyboard_controller()) {}
-
-  ~TestClient() = default;
-
-  bool IsKeyboardEnabled() {
-    keyboard_controller_->IsKeyboardEnabled(base::BindOnce(
-        &TestClient::OnIsKeyboardEnabled, base::Unretained(this)));
-    return is_enabled_;
-  }
-
-  void GetKeyboardConfig() {
-    keyboard_controller_->GetKeyboardConfig(base::BindOnce(
-        &TestClient::OnGetKeyboardConfig, base::Unretained(this)));
-  }
-
-  void SetKeyboardConfig(KeyboardConfig config) {
-    keyboard_controller_->SetKeyboardConfig(std::move(config));
-  }
-
-  void SetEnableFlag(KeyboardEnableFlag flag) {
-    keyboard_controller_->SetEnableFlag(flag);
-  }
-
-  void ClearEnableFlag(KeyboardEnableFlag flag) {
-    keyboard_controller_->ClearEnableFlag(flag);
-  }
-
-  std::vector<keyboard::KeyboardEnableFlag> GetEnableFlags() {
-    std::vector<keyboard::KeyboardEnableFlag> enable_flags;
-    base::RunLoop run_loop;
-    keyboard_controller_->GetEnableFlags(base::BindOnce(
-        [](std::vector<keyboard::KeyboardEnableFlag>* enable_flags,
-           base::OnceClosure callback,
-           const std::vector<keyboard::KeyboardEnableFlag>& flags) {
-          *enable_flags = flags;
-          std::move(callback).Run();
-        },
-        &enable_flags, run_loop.QuitClosure()));
-    run_loop.Run();
-    return enable_flags;
-  }
-
-  void RebuildKeyboardIfEnabled() {
-    keyboard_controller_->RebuildKeyboardIfEnabled();
-  }
-
-  bool IsKeyboardVisible() {
-    keyboard_controller_->IsKeyboardVisible(base::BindOnce(
-        &TestClient::OnIsKeyboardVisible, base::Unretained(this)));
-    return is_visible_;
-  }
-
-  void ShowKeyboard() { keyboard_controller_->ShowKeyboard(); }
-
-  void HideKeyboard() { keyboard_controller_->HideKeyboard(HideReason::kUser); }
-
-  bool SetContainerType(keyboard::ContainerType container_type,
-                        const base::Optional<gfx::Rect>& target_bounds) {
-    bool result;
-    base::RunLoop run_loop;
-    keyboard_controller_->SetContainerType(
-        container_type, target_bounds,
-        base::BindOnce(
-            [](bool* result_ptr, base::OnceClosure callback, bool result) {
-              *result_ptr = result;
-              std::move(callback).Run();
-            },
-            &result, run_loop.QuitClosure()));
-    run_loop.Run();
-    return result;
-  }
-
-  void SetKeyboardLocked(bool locked) {
-    keyboard_controller_->SetKeyboardLocked(locked);
-  }
-
-  void SetOccludedBounds(const std::vector<gfx::Rect>& bounds) {
-    keyboard_controller_->SetOccludedBounds(bounds);
-  }
-
-  void SetHitTestBounds(const std::vector<gfx::Rect>& bounds) {
-    keyboard_controller_->SetHitTestBounds(bounds);
-  }
-
-  void SetDraggableArea(const gfx::Rect& bounds) {
-    keyboard_controller_->SetDraggableArea(bounds);
-  }
-
-  int got_keyboard_config_count() const { return got_keyboard_config_count_; }
-  const KeyboardConfig& keyboard_config() const { return keyboard_config_; }
-
- private:
-  void OnIsKeyboardEnabled(bool enabled) { is_enabled_ = enabled; }
-  void OnIsKeyboardVisible(bool visible) { is_visible_ = visible; }
-
-  void OnGetKeyboardConfig(const KeyboardConfig& config) {
-    ++got_keyboard_config_count_;
-    keyboard_config_ = config;
-  }
-
-  KeyboardController* keyboard_controller_ = nullptr;
-  bool is_enabled_ = false;
-  bool is_visible_ = false;
-  int got_keyboard_config_count_ = 0;
-  KeyboardConfig keyboard_config_;
-};
-
 class TestContainerBehavior : public keyboard::ContainerBehavior {
  public:
   TestContainerBehavior() : keyboard::ContainerBehavior(nullptr) {}
@@ -212,116 +103,130 @@
   void SetUp() override {
     AshTestBase::SetUp();
 
-    test_client_ = std::make_unique<TestClient>();
-
-    // Set the initial observer config to the client (default) config.
-    test_observer()->set_config(test_client()->keyboard_config());
+    // Set the initial observer config to the default config.
+    test_observer()->set_config(ash_keyboard_controller()->GetKeyboardConfig());
   }
 
   void TearDown() override {
-    test_client_.reset();
     AshTestBase::TearDown();
   }
 
-  keyboard::KeyboardController* keyboard_controller() {
-    return Shell::Get()->ash_keyboard_controller()->keyboard_controller();
+  AshKeyboardController* ash_keyboard_controller() {
+    return Shell::Get()->ash_keyboard_controller();
   }
-  TestClient* test_client() { return test_client_.get(); }
+  keyboard::KeyboardController* keyboard_controller() {
+    return ash_keyboard_controller()->keyboard_controller();
+  }
   TestKeyboardControllerObserver* test_observer() {
     return ash_test_helper()->test_keyboard_controller_observer();
   }
 
- private:
-  std::unique_ptr<TestClient> test_client_;
+ protected:
+  bool SetContainerType(keyboard::ContainerType container_type,
+                        const base::Optional<gfx::Rect>& target_bounds) {
+    bool result = false;
+    base::RunLoop run_loop;
+    ash_keyboard_controller()->SetContainerType(
+        container_type, target_bounds,
+        base::BindLambdaForTesting([&](bool success) {
+          result = success;
+          run_loop.QuitWhenIdle();
+        }));
+    run_loop.Run();
+    return result;
+  }
 
+ private:
   DISALLOW_COPY_AND_ASSIGN(AshKeyboardControllerTest);
 };
 
 }  // namespace
 
-TEST_F(AshKeyboardControllerTest, GetKeyboardConfig) {
-  test_client()->GetKeyboardConfig();
-  EXPECT_EQ(1, test_client()->got_keyboard_config_count());
-}
-
 TEST_F(AshKeyboardControllerTest, SetKeyboardConfig) {
   // Enable the keyboard so that config changes trigger observer events.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
 
-  test_client()->GetKeyboardConfig();
-  EXPECT_EQ(1, test_client()->got_keyboard_config_count());
-  KeyboardConfig config = test_client()->keyboard_config();
-  // Set the observer config to the client (default) config.
+  ash_keyboard_controller()->GetKeyboardConfig();
+  KeyboardConfig config = ash_keyboard_controller()->GetKeyboardConfig();
+  // Set the observer config to the default config.
   test_observer()->set_config(config);
 
   // Change the keyboard config.
   bool old_auto_complete = config.auto_complete;
   config.auto_complete = !config.auto_complete;
-  test_client()->SetKeyboardConfig(std::move(config));
+  ash_keyboard_controller()->SetKeyboardConfig(std::move(config));
 
   // Test that the config changes.
-  test_client()->GetKeyboardConfig();
-  EXPECT_NE(old_auto_complete, test_client()->keyboard_config().auto_complete);
+  ash_keyboard_controller()->GetKeyboardConfig();
+  EXPECT_NE(old_auto_complete,
+            ash_keyboard_controller()->GetKeyboardConfig().auto_complete);
 
   // Test that the test observer received the change.
   EXPECT_NE(old_auto_complete, test_observer()->config().auto_complete);
 }
 
 TEST_F(AshKeyboardControllerTest, EnableFlags) {
-  EXPECT_FALSE(test_client()->IsKeyboardEnabled());
+  EXPECT_FALSE(ash_keyboard_controller()->IsKeyboardEnabled());
   // Enable the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
-  std::vector<keyboard::KeyboardEnableFlag> enable_flags =
-      test_client()->GetEnableFlags();
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
+  std::set<keyboard::KeyboardEnableFlag> enable_flags =
+      ash_keyboard_controller()->GetEnableFlags();
   EXPECT_TRUE(
       base::Contains(enable_flags, KeyboardEnableFlag::kExtensionEnabled));
   EXPECT_EQ(enable_flags, test_observer()->enable_flags());
-  EXPECT_TRUE(test_client()->IsKeyboardEnabled());
+  EXPECT_TRUE(ash_keyboard_controller()->IsKeyboardEnabled());
 
   // Set the enable override to disable the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kPolicyDisabled);
-  enable_flags = test_client()->GetEnableFlags();
+  ash_keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kPolicyDisabled);
+  enable_flags = ash_keyboard_controller()->GetEnableFlags();
   EXPECT_TRUE(
       base::Contains(enable_flags, KeyboardEnableFlag::kExtensionEnabled));
   EXPECT_TRUE(
       base::Contains(enable_flags, KeyboardEnableFlag::kPolicyDisabled));
   EXPECT_EQ(enable_flags, test_observer()->enable_flags());
-  EXPECT_FALSE(test_client()->IsKeyboardEnabled());
+  EXPECT_FALSE(ash_keyboard_controller()->IsKeyboardEnabled());
 
   // Clear the enable override; should enable the keyboard.
-  test_client()->ClearEnableFlag(KeyboardEnableFlag::kPolicyDisabled);
-  enable_flags = test_client()->GetEnableFlags();
+  ash_keyboard_controller()->ClearEnableFlag(
+      KeyboardEnableFlag::kPolicyDisabled);
+  enable_flags = ash_keyboard_controller()->GetEnableFlags();
   EXPECT_TRUE(
       base::Contains(enable_flags, KeyboardEnableFlag::kExtensionEnabled));
   EXPECT_FALSE(
       base::Contains(enable_flags, KeyboardEnableFlag::kPolicyDisabled));
   EXPECT_EQ(enable_flags, test_observer()->enable_flags());
-  EXPECT_TRUE(test_client()->IsKeyboardEnabled());
+  EXPECT_TRUE(ash_keyboard_controller()->IsKeyboardEnabled());
 }
 
 TEST_F(AshKeyboardControllerTest, RebuildKeyboardIfEnabled) {
   EXPECT_EQ(0, test_observer()->destroyed_count());
 
   // Enable the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
   EXPECT_EQ(0, test_observer()->destroyed_count());
 
   // Enable the keyboard again; this should not reload the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
   EXPECT_EQ(0, test_observer()->destroyed_count());
 
   // Rebuild the keyboard. This should destroy the previous keyboard window.
-  test_client()->RebuildKeyboardIfEnabled();
+  ash_keyboard_controller()->RebuildKeyboardIfEnabled();
   EXPECT_EQ(1, test_observer()->destroyed_count());
 
   // Disable the keyboard. The keyboard window should be destroyed.
-  test_client()->ClearEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->ClearEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
   EXPECT_EQ(2, test_observer()->destroyed_count());
 }
 
 TEST_F(AshKeyboardControllerTest, ShowAndHideKeyboard) {
   // Enable the keyboard. This will create the keyboard window but not show it.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
 
   ASSERT_TRUE(keyboard_controller()->GetKeyboardWindow());
   EXPECT_FALSE(keyboard_controller()->GetKeyboardWindow()->IsVisible());
@@ -329,10 +234,10 @@
   // The keyboard needs to be in a loaded state before being shown.
   ASSERT_TRUE(keyboard::test::WaitUntilLoaded());
 
-  test_client()->ShowKeyboard();
+  ash_keyboard_controller()->ShowKeyboard();
   EXPECT_TRUE(keyboard_controller()->GetKeyboardWindow()->IsVisible());
 
-  test_client()->HideKeyboard();
+  ash_keyboard_controller()->HideKeyboard(HideReason::kUser);
   EXPECT_FALSE(keyboard_controller()->GetKeyboardWindow()->IsVisible());
 
   // TODO(stevenjb): Also use TestKeyboardControllerObserver and
@@ -341,14 +246,15 @@
 
 TEST_F(AshKeyboardControllerTest, SetContainerType) {
   // Enable the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
   const auto default_behavior = keyboard::ContainerType::kFullWidth;
   EXPECT_EQ(default_behavior, keyboard_controller()->GetActiveContainerType());
 
   gfx::Rect target_bounds(0, 0, 10, 10);
   // Set the container type to kFloating.
-  EXPECT_TRUE(test_client()->SetContainerType(
-      keyboard::ContainerType::kFloating, target_bounds));
+  EXPECT_TRUE(
+      SetContainerType(keyboard::ContainerType::kFloating, target_bounds));
   EXPECT_EQ(keyboard::ContainerType::kFloating,
             keyboard_controller()->GetActiveContainerType());
   // Ensure that the window size is correct (position is determined by Ash).
@@ -357,23 +263,24 @@
       keyboard_controller()->GetKeyboardWindow()->GetTargetBounds().size());
 
   // Setting the container type to the current type should fail.
-  EXPECT_FALSE(test_client()->SetContainerType(
-      keyboard::ContainerType::kFloating, base::nullopt));
+  EXPECT_FALSE(
+      SetContainerType(keyboard::ContainerType::kFloating, base::nullopt));
   EXPECT_EQ(keyboard::ContainerType::kFloating,
             keyboard_controller()->GetActiveContainerType());
 }
 
 TEST_F(AshKeyboardControllerTest, SetKeyboardLocked) {
   ASSERT_FALSE(keyboard_controller()->keyboard_locked());
-  test_client()->SetKeyboardLocked(true);
+  ash_keyboard_controller()->SetKeyboardLocked(true);
   EXPECT_TRUE(keyboard_controller()->keyboard_locked());
-  test_client()->SetKeyboardLocked(false);
+  ash_keyboard_controller()->SetKeyboardLocked(false);
   EXPECT_FALSE(keyboard_controller()->keyboard_locked());
 }
 
 TEST_F(AshKeyboardControllerTest, SetOccludedBounds) {
   // Enable the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
 
   // Override the container behavior.
   auto scoped_behavior = std::make_unique<TestContainerBehavior>();
@@ -382,23 +289,25 @@
       std::move(scoped_behavior));
 
   gfx::Rect bounds(10, 20, 30, 40);
-  test_client()->SetOccludedBounds({bounds});
+  ash_keyboard_controller()->SetOccludedBounds({bounds});
   EXPECT_EQ(bounds, behavior->occluded_bounds());
 }
 
 TEST_F(AshKeyboardControllerTest, SetHitTestBounds) {
   // Enable the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
   ASSERT_FALSE(keyboard_controller()->GetKeyboardWindow()->targeter());
 
   // Setting the hit test bounds should set a WindowTargeter.
-  test_client()->SetHitTestBounds({gfx::Rect(10, 20, 30, 40)});
+  ash_keyboard_controller()->SetHitTestBounds({gfx::Rect(10, 20, 30, 40)});
   ASSERT_TRUE(keyboard_controller()->GetKeyboardWindow()->targeter());
 }
 
 TEST_F(AshKeyboardControllerTest, SetDraggableArea) {
   // Enable the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
 
   // Override the container behavior.
   auto scoped_behavior = std::make_unique<TestContainerBehavior>();
@@ -407,13 +316,14 @@
       std::move(scoped_behavior));
 
   gfx::Rect bounds(10, 20, 30, 40);
-  test_client()->SetDraggableArea(bounds);
+  ash_keyboard_controller()->SetDraggableArea(bounds);
   EXPECT_EQ(bounds, behavior->draggable_area());
 }
 
 TEST_F(AshKeyboardControllerTest, ChangingSessionRebuildsKeyboard) {
   // Enable the keyboard.
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
 
   // LOGGED_IN_NOT_ACTIVE session state needs to rebuild keyboard for supervised
   // user profile.
@@ -430,7 +340,8 @@
 TEST_F(AshKeyboardControllerTest, VisualBoundsInMultipleDisplays) {
   UpdateDisplay("800x600,1024x768");
 
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
 
   // Show the keyboard in the second display.
   keyboard_controller()->ShowKeyboardInDisplay(
@@ -447,7 +358,8 @@
 TEST_F(AshKeyboardControllerTest, OccludedBoundsInMultipleDisplays) {
   UpdateDisplay("800x600,1024x768");
 
-  test_client()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
+  ash_keyboard_controller()->SetEnableFlag(
+      KeyboardEnableFlag::kExtensionEnabled);
 
   // Show the keyboard in the second display.
   keyboard_controller()->ShowKeyboardInDisplay(
diff --git a/ash/keyboard/ui/BUILD.gn b/ash/keyboard/ui/BUILD.gn
index 9be75ac..457f93d 100644
--- a/ash/keyboard/ui/BUILD.gn
+++ b/ash/keyboard/ui/BUILD.gn
@@ -24,7 +24,6 @@
     "drag_descriptor.h",
     "keyboard_controller.cc",
     "keyboard_controller.h",
-    "keyboard_controller_observer.h",
     "keyboard_event_handler.cc",
     "keyboard_event_handler.h",
     "keyboard_export.h",
@@ -95,6 +94,7 @@
   ]
   deps = [
     ":ui",
+    "//ash/public/cpp",
     "//base",
     "//ui/aura",
     "//ui/aura:test_support",
@@ -149,6 +149,7 @@
     "container_full_width_behavior_unittest.cc",
     "keyboard_controller_unittest.cc",
     "keyboard_event_handler_unittest.cc",
+    "keyboard_ui_model_unittest.cc",
     "keyboard_ukm_recorder_unittest.cc",
     "keyboard_util_unittest.cc",
     "notification_manager_unittest.cc",
diff --git a/ash/keyboard/ui/keyboard_controller.cc b/ash/keyboard/ui/keyboard_controller.cc
index 4fbfb39..4ff5db0 100644
--- a/ash/keyboard/ui/keyboard_controller.cc
+++ b/ash/keyboard/ui/keyboard_controller.cc
@@ -9,7 +9,6 @@
 #include "ash/keyboard/ui/container_floating_behavior.h"
 #include "ash/keyboard/ui/container_full_width_behavior.h"
 #include "ash/keyboard/ui/display_util.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
 #include "ash/keyboard/ui/keyboard_layout_manager.h"
 #include "ash/keyboard/ui/keyboard_ui.h"
 #include "ash/keyboard/ui/keyboard_ui_factory.h"
@@ -18,6 +17,7 @@
 #include "ash/keyboard/ui/queued_container_type.h"
 #include "ash/keyboard/ui/queued_display_change.h"
 #include "ash/keyboard/ui/shaped_window_targeter.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/public/cpp/keyboard/keyboard_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -203,8 +203,7 @@
 
 void KeyboardController::Shutdown() {
   keyboard_enable_flags_.clear();
-  for (KeyboardControllerObserver& observer : observer_list_)
-    observer.OnKeyboardEnableFlagsChanged(keyboard_enable_flags_);
+  EnableFlagsChanged();
 
   DCHECK(!IsKeyboardEnableRequested());
   DisableKeyboard();
@@ -234,7 +233,7 @@
   LoadKeyboardWindowInBackground();
 
   // Notify observers after the keyboard window has a root window.
-  for (KeyboardControllerObserver& observer : observer_list_)
+  for (auto& observer : observer_list_)
     observer.OnKeyboardEnabledChanged(true);
 }
 
@@ -266,7 +265,7 @@
   ui_.reset();
 
   // Notify observers after |ui_| is reset so that IsEnabled() is false.
-  for (KeyboardControllerObserver& observer : observer_list_)
+  for (auto& observer : observer_list_)
     observer.OnKeyboardEnabledChanged(false);
 }
 
@@ -392,16 +391,18 @@
   EnableKeyboard();
 }
 
-void KeyboardController::AddObserver(KeyboardControllerObserver* observer) {
+void KeyboardController::AddObserver(
+    ash::KeyboardControllerObserver* observer) {
   observer_list_.AddObserver(observer);
 }
 
 bool KeyboardController::HasObserver(
-    KeyboardControllerObserver* observer) const {
+    ash::KeyboardControllerObserver* observer) const {
   return observer_list_.HasObserver(observer);
 }
 
-void KeyboardController::RemoveObserver(KeyboardControllerObserver* observer) {
+void KeyboardController::RemoveObserver(
+    ash::KeyboardControllerObserver* observer) {
   observer_list_.RemoveObserver(observer);
 }
 
@@ -435,8 +436,8 @@
     default:
       break;
   }
-  for (KeyboardControllerObserver& observer : observer_list_)
-    observer.OnKeyboardEnableFlagsChanged(keyboard_enable_flags_);
+
+  EnableFlagsChanged();
 
   UpdateKeyboardAsRequestedBy(flag);
 }
@@ -446,8 +447,7 @@
     return;
 
   keyboard_enable_flags_.erase(flag);
-  for (KeyboardControllerObserver& observer : observer_list_)
-    observer.OnKeyboardEnableFlagsChanged(keyboard_enable_flags_);
+  EnableFlagsChanged();
 
   UpdateKeyboardAsRequestedBy(flag);
 }
@@ -589,7 +589,7 @@
       ui_->HideKeyboardWindow();
       ChangeState(KeyboardUIState::kHidden);
 
-      for (KeyboardControllerObserver& observer : observer_list_)
+      for (auto& observer : observer_list_)
         observer.OnKeyboardHidden(reason == HIDE_REASON_SYSTEM_TEMPORARY);
 
       break;
@@ -915,8 +915,8 @@
 }
 
 void KeyboardController::NotifyKeyboardConfigChanged() {
-  for (KeyboardControllerObserver& observer : observer_list_)
-    observer.OnKeyboardConfigChanged();
+  for (auto& observer : observer_list_)
+    observer.OnKeyboardConfigChanged(keyboard_config_);
 }
 
 void KeyboardController::ChangeState(KeyboardUIState state) {
@@ -1122,4 +1122,9 @@
   keyboard_load_time_logged_ = true;
 }
 
+void KeyboardController::EnableFlagsChanged() {
+  for (auto& observer : observer_list_)
+    observer.OnKeyboardEnableFlagsChanged(keyboard_enable_flags_);
+}
+
 }  // namespace keyboard
diff --git a/ash/keyboard/ui/keyboard_controller.h b/ash/keyboard/ui/keyboard_controller.h
index 7d747d8..fcd5e23 100644
--- a/ash/keyboard/ui/keyboard_controller.h
+++ b/ash/keyboard/ui/keyboard_controller.h
@@ -37,6 +37,9 @@
 namespace aura {
 class Window;
 }
+namespace ash {
+class KeyboardControllerObserver;
+}
 namespace ui {
 class InputMethod;
 class TextInputClient;
@@ -45,7 +48,6 @@
 namespace keyboard {
 
 class CallbackAnimationObserver;
-class KeyboardControllerObserver;
 class KeyboardUI;
 class KeyboardUIFactory;
 
@@ -101,9 +103,9 @@
   void RebuildKeyboardIfEnabled();
 
   // Management of the observer list.
-  void AddObserver(KeyboardControllerObserver* observer);
-  bool HasObserver(KeyboardControllerObserver* observer) const;
-  void RemoveObserver(KeyboardControllerObserver* observer);
+  void AddObserver(ash::KeyboardControllerObserver* observer);
+  bool HasObserver(ash::KeyboardControllerObserver* observer) const;
+  void RemoveObserver(ash::KeyboardControllerObserver* observer);
 
   // Updates |keyboard_config_| with |config|. Returns |false| if there is no
   // change, otherwise returns true and notifies observers if this is enabled.
@@ -391,6 +393,9 @@
   // keyboard is loaded.
   void MarkKeyboardLoadFinished();
 
+  // Called when the enable flags change. Notifies observers of the change.
+  void EnableFlagsChanged();
+
   std::unique_ptr<KeyboardUIFactory> ui_factory_;
   std::unique_ptr<KeyboardUI> ui_;
   std::unique_ptr<ui::InputMethodKeyboardController>
@@ -419,7 +424,7 @@
   bool keyboard_locked_ = false;
   KeyboardEventHandler event_handler_;
 
-  base::ObserverList<KeyboardControllerObserver>::Unchecked observer_list_;
+  base::ObserverList<ash::KeyboardControllerObserver>::Unchecked observer_list_;
 
   KeyboardUIModel model_;
 
diff --git a/ash/keyboard/ui/keyboard_controller_observer.h b/ash/keyboard/ui/keyboard_controller_observer.h
deleted file mode 100644
index 58c7e02..0000000
--- a/ash/keyboard/ui/keyboard_controller_observer.h
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2013 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_KEYBOARD_UI_KEYBOARD_CONTROLLER_OBSERVER_H_
-#define ASH_KEYBOARD_UI_KEYBOARD_CONTROLLER_OBSERVER_H_
-
-#include <set>
-
-#include "ash/keyboard/ui/keyboard_export.h"
-#include "ash/public/cpp/keyboard/keyboard_types.h"
-#include "ui/gfx/geometry/rect.h"
-
-namespace keyboard {
-
-// Describes the various attributes of the keyboard's appearance and usability.
-struct KeyboardStateDescriptor {
-  bool is_visible;
-
-  // The bounds of the keyboard window on the screen.
-  gfx::Rect visual_bounds;
-
-  // The bounds of the area on the screen that is considered "blocked" by the
-  // keyboard. For example, the docked keyboard's occluded bounds is the same as
-  // the visual bounds, but the floating keyboard has no occluded bounds (as the
-  // window is small and moveable).
-  gfx::Rect occluded_bounds_in_screen;
-
-  // The bounds of the area on the screen that is considered "unusable" because
-  // it is blocked by the keyboard. This is used by the accessibility keyboard.
-  gfx::Rect displaced_bounds_in_screen;
-};
-
-// Observers to the KeyboardController are notified of significant events that
-// occur with the keyboard, such as the bounds or visibility changing.
-class KEYBOARD_EXPORT KeyboardControllerObserver {
- public:
-  virtual ~KeyboardControllerObserver() {}
-
-  // Called when the keyboard is shown or hidden (e.g. when user focuses and
-  // unfocuses on a textfield).
-  virtual void OnKeyboardVisibilityStateChanged(bool is_visible) {}
-
-  // Called when the keyboard bounds are changing.
-  virtual void OnKeyboardVisibleBoundsChanged(const gfx::Rect& new_bounds) {}
-
-  // Called when the keyboard bounds have changed in a way that should affect
-  // the usable region of the workspace. The user interface should respond to
-  // this event by moving important elements away from |new_bounds_in_screen|
-  // so that they don't overlap. However, drastic visual changes should be
-  // avoided, as the occluded bounds may change frequently.
-  virtual void OnKeyboardWorkspaceOccludedBoundsChanged(
-      const gfx::Rect& new_bounds_in_screen) {}
-
-  // Called when the keyboard bounds have changed in a way that affects how the
-  // workspace should change to not take up the screen space occupied by the
-  // keyboard. The user interface should respond to this event by moving all
-  // elements away from |new_bounds| so that they don't overlap. Large visual
-  // changes are okay, as the displacing bounds do not change frequently.
-  virtual void OnKeyboardWorkspaceDisplacingBoundsChanged(
-      const gfx::Rect& new_bounds) {}
-
-  // Redundant with other various notification methods. Use this if the state of
-  // multiple properties need to be conveyed simultaneously to observer
-  // implementations without the need to track multiple stateful properties.
-  virtual void OnKeyboardAppearanceChanged(
-      const KeyboardStateDescriptor& state) {}
-
-  // Called when an enable flag affecting the requested enabled state changes.
-  virtual void OnKeyboardEnableFlagsChanged(
-      const std::set<KeyboardEnableFlag>& keyboard_enable_flags) {}
-
-  // Called when the keyboard is enabled or disabled. NOTE: This is called
-  // when Enabled() or Disabled() is called, not when the requested enabled
-  // state (IsEnableRequested) changes.
-  virtual void OnKeyboardEnabledChanged(bool is_enabled) {}
-
-  // Called when the keyboard has been hidden and the hiding animation finished
-  // successfully.
-  // When |is_temporary_hide| is true, this hide is immediately followed by a
-  // show (e.g. when changing to floating keyboard)
-  virtual void OnKeyboardHidden(bool is_temporary_hide) {}
-
-  // Called when the virtual keyboard IME config changed.
-  virtual void OnKeyboardConfigChanged() {}
-};
-
-}  // namespace keyboard
-
-#endif  // ASH_KEYBOARD_UI_KEYBOARD_CONTROLLER_OBSERVER_H_
diff --git a/ash/keyboard/ui/keyboard_controller_unittest.cc b/ash/keyboard/ui/keyboard_controller_unittest.cc
index 8e518743..c483ae7 100644
--- a/ash/keyboard/ui/keyboard_controller_unittest.cc
+++ b/ash/keyboard/ui/keyboard_controller_unittest.cc
@@ -7,13 +7,13 @@
 #include <memory>
 
 #include "ash/keyboard/ui/container_full_width_behavior.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
 #include "ash/keyboard/ui/keyboard_layout_manager.h"
 #include "ash/keyboard/ui/keyboard_ui.h"
 #include "ash/keyboard/ui/keyboard_util.h"
 #include "ash/keyboard/ui/test/keyboard_test_util.h"
 #include "ash/keyboard/ui/test/test_keyboard_layout_delegate.h"
 #include "ash/keyboard/ui/test/test_keyboard_ui_factory.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/command_line.h"
@@ -139,7 +139,7 @@
 }  // namespace
 
 class KeyboardControllerTest : public aura::test::AuraTestBase,
-                               public KeyboardControllerObserver {
+                               public ash::KeyboardControllerObserver {
  public:
   KeyboardControllerTest() = default;
   ~KeyboardControllerTest() override = default;
@@ -191,12 +191,11 @@
     visible_bounds_ = new_bounds;
     visible_bounds_number_of_calls_++;
   }
-  void OnKeyboardWorkspaceOccludedBoundsChanged(
-      const gfx::Rect& new_bounds) override {
+  void OnKeyboardOccludedBoundsChanged(const gfx::Rect& new_bounds) override {
     occluding_bounds_ = new_bounds;
     occluding_bounds_number_of_calls_++;
   }
-  void OnKeyboardVisibilityStateChanged(bool is_visible) override {
+  void OnKeyboardVisibilityChanged(bool is_visible) override {
     is_visible_ = is_visible;
     is_visible_number_of_calls_++;
   }
@@ -740,7 +739,7 @@
   EXPECT_TRUE(IsKeyboardDisabled());
 }
 
-class MockKeyboardControllerObserver : public KeyboardControllerObserver {
+class MockKeyboardControllerObserver : public ash::KeyboardControllerObserver {
  public:
   MockKeyboardControllerObserver() = default;
   ~MockKeyboardControllerObserver() override = default;
diff --git a/ash/keyboard/ui/keyboard_ui_model.cc b/ash/keyboard/ui/keyboard_ui_model.cc
index 211c23d..b660514 100644
--- a/ash/keyboard/ui/keyboard_ui_model.cc
+++ b/ash/keyboard/ui/keyboard_ui_model.cc
@@ -4,12 +4,7 @@
 
 #include "ash/keyboard/ui/keyboard_ui_model.h"
 
-#include <set>
-#include <utility>
-
 #include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/no_destructor.h"
 
 namespace keyboard {
 
@@ -18,46 +13,43 @@
 // Returns whether a given state transition is valid.
 // See the design document linked in https://crbug.com/71990.
 bool IsAllowedStateTransition(KeyboardUIState from, KeyboardUIState to) {
-  static const base::NoDestructor<
-      std::set<std::pair<KeyboardUIState, KeyboardUIState>>>
-      kAllowedStateTransition({
-          // The initial ShowKeyboard scenario
-          // INITIAL -> LOADING_EXTENSION -> HIDDEN -> SHOWN.
-          {KeyboardUIState::kInitial, KeyboardUIState::kLoading},
-          {KeyboardUIState::kLoading, KeyboardUIState::kHidden},
-          {KeyboardUIState::kHidden, KeyboardUIState::kShown},
+  using State = KeyboardUIState;
+  switch (GetStateTransitionHash(from, to)) {
+    // The initial ShowKeyboard scenario
+    // INITIAL -> LOADING -> HIDDEN -> SHOWN.
+    case GetStateTransitionHash(State::kInitial, State::kLoading):
+    case GetStateTransitionHash(State::kLoading, State::kHidden):
+    case GetStateTransitionHash(State::kHidden, State::kShown):
 
-          // Hide scenario
-          // SHOWN -> WILL_HIDE -> HIDDEN.
-          {KeyboardUIState::kShown, KeyboardUIState::kWillHide},
-          {KeyboardUIState::kWillHide, KeyboardUIState::kHidden},
+    // Hide scenario
+    // SHOWN -> WILL_HIDE -> HIDDEN.
+    case GetStateTransitionHash(State::kShown, State::kWillHide):
+    case GetStateTransitionHash(State::kWillHide, State::kHidden):
 
-          // Focus transition scenario
-          // SHOWN -> WILL_HIDE -> SHOWN.
-          {KeyboardUIState::kWillHide, KeyboardUIState::kShown},
+    // Focus transition scenario
+    // SHOWN -> WILL_HIDE -> SHOWN.
+    case GetStateTransitionHash(State::kWillHide, State::kShown):
 
-          // HideKeyboard can be called at anytime for example on shutdown.
-          {KeyboardUIState::kShown, KeyboardUIState::kHidden},
+    // HideKeyboard can be called at anytime (for example on shutdown).
+    case GetStateTransitionHash(State::kShown, State::kHidden):
 
-          // Return to INITIAL when keyboard is disabled.
-          {KeyboardUIState::kLoading, KeyboardUIState::kInitial},
-          {KeyboardUIState::kHidden, KeyboardUIState::kInitial},
-      });
-  return kAllowedStateTransition->count(std::make_pair(from, to)) == 1;
+    // Return to INITIAL when keyboard is disabled.
+    case GetStateTransitionHash(State::kLoading, State::kInitial):
+    case GetStateTransitionHash(State::kHidden, State::kInitial):
+      return true;
+    default:
+      return false;
+  }
 }
 
 // Records a state transition for metrics.
 void RecordStateTransition(KeyboardUIState prev, KeyboardUIState next) {
   const bool valid_transition = IsAllowedStateTransition(prev, next);
 
-  // Emit UMA
-  const int transition_record =
-      (valid_transition ? 1 : -1) *
-      (static_cast<int>(prev) * 1000 + static_cast<int>(next));
+  // Use negative hash values to indicate invalid transitions.
+  const int hash = GetStateTransitionHash(prev, next);
   base::UmaHistogramSparse("VirtualKeyboard.ControllerStateTransition",
-                           transition_record);
-  UMA_HISTOGRAM_BOOLEAN("VirtualKeyboard.ControllerStateTransitionIsValid",
-                        valid_transition);
+                           valid_transition ? hash : -hash);
 
   DCHECK(valid_transition) << "State: " << StateToStr(prev) << " -> "
                            << StateToStr(next) << " Unexpected transition";
diff --git a/ash/keyboard/ui/keyboard_ui_model.h b/ash/keyboard/ui/keyboard_ui_model.h
index 84fb147..e9ce0f9 100644
--- a/ash/keyboard/ui/keyboard_ui_model.h
+++ b/ash/keyboard/ui/keyboard_ui_model.h
@@ -38,6 +38,14 @@
 // Returns the string representation of a keyboard UI state.
 std::string StateToStr(KeyboardUIState state);
 
+// Returns a unique hash of a state transition, used for histograms.
+// The hashes correspond to the KeyboardControllerStateTransition entry in
+// tools/metrics/enums.xml.
+constexpr int GetStateTransitionHash(KeyboardUIState prev,
+                                     KeyboardUIState next) {
+  return static_cast<int>(prev) * 1000 + static_cast<int>(next);
+}
+
 // Model for the virtual keyboard UI.
 class KEYBOARD_EXPORT KeyboardUIModel {
  public:
diff --git a/ash/keyboard/ui/keyboard_ui_model_unittest.cc b/ash/keyboard/ui/keyboard_ui_model_unittest.cc
new file mode 100644
index 0000000..86ab381
--- /dev/null
+++ b/ash/keyboard/ui/keyboard_ui_model_unittest.cc
@@ -0,0 +1,43 @@
+// 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/keyboard/ui/keyboard_ui_model.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace keyboard {
+
+TEST(KeyboardUIModelTest, ChangeToValidStateRecordsPositiveHistogram) {
+  base::HistogramTester histogram_tester;
+
+  KeyboardUIModel model;
+  ASSERT_EQ(KeyboardUIState::kInitial, model.state());
+
+  model.ChangeState(KeyboardUIState::kLoading);
+  histogram_tester.ExpectUniqueSample(
+      "VirtualKeyboard.ControllerStateTransition",
+      GetStateTransitionHash(KeyboardUIState::kInitial,
+                             KeyboardUIState::kLoading),
+      1);
+}
+
+// Test fails DCHECK when the state transition is invalid. This is expected.
+#if !DCHECK_IS_ON()
+TEST(KeyboardUIModelTest, ChangeToInvalidStateRecordsNegativeHistogram) {
+  base::HistogramTester histogram_tester;
+
+  KeyboardUIModel model;
+  ASSERT_EQ(KeyboardUIState::kInitial, model.state());
+
+  model.ChangeState(KeyboardUIState::kShown);
+  histogram_tester.ExpectUniqueSample(
+      "VirtualKeyboard.ControllerStateTransition",
+      -GetStateTransitionHash(KeyboardUIState::kInitial,
+                              KeyboardUIState::kShown),
+      1);
+}
+#endif
+
+}  // namespace keyboard
diff --git a/ash/keyboard/ui/notification_manager.cc b/ash/keyboard/ui/notification_manager.cc
index eaf6660..f2fb7d8 100644
--- a/ash/keyboard/ui/notification_manager.cc
+++ b/ash/keyboard/ui/notification_manager.cc
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 #include "ash/keyboard/ui/notification_manager.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/observer_list.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -30,7 +30,7 @@
     bool does_occluded_bounds_affect_layout,
     const gfx::Rect& visual_bounds,
     const gfx::Rect& occluded_bounds,
-    const base::ObserverList<KeyboardControllerObserver>::Unchecked&
+    const base::ObserverList<ash::KeyboardControllerObserver>::Unchecked&
         observers) {
   bool is_visible = !visual_bounds.IsEmpty();
   bool send_visibility_notification =
@@ -48,24 +48,24 @@
       ShouldSendWorkspaceDisplacementBoundsNotification(
           workspace_layout_offset_region);
 
-  KeyboardStateDescriptor state;
+  ash::KeyboardStateDescriptor state;
   state.is_visible = is_visible;
   state.visual_bounds = visual_bounds;
   state.occluded_bounds_in_screen = occluded_bounds;
   state.displaced_bounds_in_screen = workspace_layout_offset_region;
 
-  for (KeyboardControllerObserver& observer : observers) {
+  for (auto& observer : observers) {
     if (send_visibility_notification)
-      observer.OnKeyboardVisibilityStateChanged(is_visible);
+      observer.OnKeyboardVisibilityChanged(is_visible);
 
     if (send_visual_bounds_notification)
       observer.OnKeyboardVisibleBoundsChanged(visual_bounds);
 
     if (send_occluded_bounds_notification)
-      observer.OnKeyboardWorkspaceOccludedBoundsChanged(occluded_bounds);
+      observer.OnKeyboardOccludedBoundsChanged(occluded_bounds);
 
     if (send_displaced_bounds_notification) {
-      observer.OnKeyboardWorkspaceDisplacingBoundsChanged(
+      observer.OnKeyboardDisplacingBoundsChanged(
           workspace_layout_offset_region);
     }
 
diff --git a/ash/keyboard/ui/notification_manager.h b/ash/keyboard/ui/notification_manager.h
index e98d0c7..ee17d3c 100644
--- a/ash/keyboard/ui/notification_manager.h
+++ b/ash/keyboard/ui/notification_manager.h
@@ -9,9 +9,11 @@
 #include "base/observer_list.h"
 #include "ui/gfx/geometry/rect.h"
 
-namespace keyboard {
-
+namespace ash {
 class KeyboardControllerObserver;
+}
+
+namespace keyboard {
 
 template <typename T>
 class ValueNotificationConsolidator {
@@ -41,7 +43,7 @@
       bool does_occluded_bounds_affect_layout,
       const gfx::Rect& visual_bounds,
       const gfx::Rect& occluded_bounds,
-      const base::ObserverList<KeyboardControllerObserver>::Unchecked&
+      const base::ObserverList<ash::KeyboardControllerObserver>::Unchecked&
           observers);
 
   bool ShouldSendVisibilityNotification(bool current_visibility);
diff --git a/ash/keyboard/ui/test/keyboard_test_util.cc b/ash/keyboard/ui/test/keyboard_test_util.cc
index 8b1e48d..8e48c72 100644
--- a/ash/keyboard/ui/test/keyboard_test_util.cc
+++ b/ash/keyboard/ui/test/keyboard_test_util.cc
@@ -5,7 +5,7 @@
 #include "ash/keyboard/ui/test/keyboard_test_util.h"
 
 #include "ash/keyboard/ui/keyboard_controller.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/run_loop.h"
 #include "base/time/time.h"
 #include "ui/display/screen.h"
@@ -14,7 +14,7 @@
 
 namespace {
 
-class KeyboardVisibilityChangeWaiter : public KeyboardControllerObserver {
+class KeyboardVisibilityChangeWaiter : public ash::KeyboardControllerObserver {
  public:
   explicit KeyboardVisibilityChangeWaiter(bool wait_until)
       : wait_until_(wait_until) {
@@ -27,7 +27,7 @@
   void Wait() { run_loop_.Run(); }
 
  private:
-  void OnKeyboardVisibilityStateChanged(const bool is_visible) override {
+  void OnKeyboardVisibilityChanged(const bool is_visible) override {
     if (is_visible == wait_until_)
       run_loop_.QuitWhenIdle();
   }
diff --git a/ash/keyboard/ui/test/test_keyboard_controller_observer.h b/ash/keyboard/ui/test/test_keyboard_controller_observer.h
index fd1645e..877b17d 100644
--- a/ash/keyboard/ui/test/test_keyboard_controller_observer.h
+++ b/ash/keyboard/ui/test/test_keyboard_controller_observer.h
@@ -5,12 +5,12 @@
 #ifndef ASH_KEYBOARD_UI_TEST_TEST_KEYBOARD_CONTROLLER_OBSERVER_H_
 #define ASH_KEYBOARD_UI_TEST_TEST_KEYBOARD_CONTROLLER_OBSERVER_H_
 
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 
 namespace keyboard {
 
 // A KeyboardControllerObserver that counts occurrences of events for testing.
-struct TestKeyboardControllerObserver : public KeyboardControllerObserver {
+struct TestKeyboardControllerObserver : public ash::KeyboardControllerObserver {
   TestKeyboardControllerObserver();
   ~TestKeyboardControllerObserver() override;
 
diff --git a/ash/keyboard/virtual_keyboard_controller.h b/ash/keyboard/virtual_keyboard_controller.h
index f56f614..2c64bc8 100644
--- a/ash/keyboard/virtual_keyboard_controller.h
+++ b/ash/keyboard/virtual_keyboard_controller.h
@@ -9,8 +9,8 @@
 
 #include "ash/ash_export.h"
 #include "ash/bluetooth_devices_observer.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
 #include "ash/keyboard/ui/keyboard_layout_delegate.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/session/session_observer.h"
 #include "ash/wm/tablet_mode/tablet_mode_observer.h"
 #include "base/macros.h"
@@ -27,7 +27,7 @@
     : public TabletModeObserver,
       public ui::InputDeviceEventObserver,
       public keyboard::KeyboardLayoutDelegate,
-      public keyboard::KeyboardControllerObserver,
+      public KeyboardControllerObserver,
       public SessionObserver {
  public:
   VirtualKeyboardController();
@@ -53,7 +53,7 @@
   aura::Window* GetContainerForDisplay(
       const display::Display& display) override;
 
-  // keyboard::KeyboardControllerObserver:
+  // KeyboardControllerObserver:
   void OnKeyboardEnabledChanged(bool is_enabled) override;
   void OnKeyboardHidden(bool is_temporary_hide) override;
 
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 36208324..62d5ddf 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -1149,7 +1149,7 @@
   }
 }
 
-void LockContentsView::OnKeyboardVisibilityStateChanged(bool is_visible) {
+void LockContentsView::OnKeyboardVisibilityChanged(bool is_visible) {
   if (!primary_big_view_ || keyboard_shown_ == is_visible)
     return;
 
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index 6211b18..59ceb575 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -12,13 +12,13 @@
 
 #include "ash/ash_export.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
 #include "ash/login/ui/lock_screen.h"
 #include "ash/login/ui/login_data_dispatcher.h"
 #include "ash/login/ui/login_display_style.h"
 #include "ash/login/ui/login_error_bubble.h"
 #include "ash/login/ui/login_tooltip_view.h"
 #include "ash/login/ui/non_accessible_view.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/public/cpp/login_types.h"
 #include "ash/public/cpp/system_tray_focus_observer.h"
 #include "ash/session/session_observer.h"
@@ -66,7 +66,7 @@
       public display::DisplayObserver,
       public views::StyledLabelListener,
       public SessionObserver,
-      public keyboard::KeyboardControllerObserver,
+      public KeyboardControllerObserver,
       public chromeos::PowerManagerClient::Observer {
  public:
   // TestApi is used for tests to get internal implementation details.
@@ -189,8 +189,8 @@
   // SessionObserver:
   void OnLockStateChanged(bool locked) override;
 
-  // keyboard::KeyboardControllerObserver:
-  void OnKeyboardVisibilityStateChanged(bool is_visible) override;
+  // KeyboardControllerObserver:
+  void OnKeyboardVisibilityChanged(bool is_visible) override;
 
   // chromeos::PowerManagerClient::Observer:
   void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 1b9158e..8dddeeb 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -90,6 +90,7 @@
     "keyboard/keyboard_config.h",
     "keyboard/keyboard_controller.cc",
     "keyboard/keyboard_controller.h",
+    "keyboard/keyboard_controller_observer.h",
     "keyboard/keyboard_switches.cc",
     "keyboard/keyboard_switches.h",
     "keyboard/keyboard_types.h",
diff --git a/ash/public/cpp/keyboard/keyboard_controller.h b/ash/public/cpp/keyboard/keyboard_controller.h
index 79fdf01..6a2de18 100644
--- a/ash/public/cpp/keyboard/keyboard_controller.h
+++ b/ash/public/cpp/keyboard/keyboard_controller.h
@@ -5,6 +5,9 @@
 #ifndef ASH_PUBLIC_CPP_KEYBOARD_KEYBOARD_CONTROLLER_H_
 #define ASH_PUBLIC_CPP_KEYBOARD_KEYBOARD_CONTROLLER_H_
 
+#include <set>
+#include <vector>
+
 #include "ash/public/cpp/ash_public_export.h"
 #include "ash/public/cpp/keyboard/keyboard_config.h"
 #include "ash/public/cpp/keyboard/keyboard_types.h"
@@ -13,6 +16,8 @@
 
 namespace ash {
 
+class KeyboardControllerObserver;
+
 enum class HideReason {
   // Hide requested by an explicit user action.
   kUser,
@@ -22,59 +27,8 @@
   kSystem,
 };
 
-class KeyboardControllerObserver {
- public:
-  virtual ~KeyboardControllerObserver() {}
-
-  // Called when a keyboard enable flag changes.
-  virtual void OnKeyboardEnableFlagsChanged(
-      const std::vector<keyboard::KeyboardEnableFlag>& flags) = 0;
-
-  // Called when the keyboard is enabled or disabled. If ReloadKeyboard() is
-  // called or other code enables the keyboard while already enabled, this will
-  // be called twice, once when the keyboard is disabled and again when it is
-  // re-enabled.
-  virtual void OnKeyboardEnabledChanged(bool is_enabled) = 0;
-
-  // Called when the virtual keyboard configuration changes.
-  virtual void OnKeyboardConfigChanged(
-      const keyboard::KeyboardConfig& config) = 0;
-
-  // Called when the visibility of the virtual keyboard changes, e.g. an input
-  // field is focused or blurred, or the user hides the keyboard.
-  virtual void OnKeyboardVisibilityChanged(bool visible) = 0;
-
-  // Called when the keyboard bounds change. |screen_bounds| is in screen
-  // coordinates.
-  virtual void OnKeyboardVisibleBoundsChanged(
-      const gfx::Rect& screen_bounds) = 0;
-
-  // Called when the keyboard occluded bounds change. |screen_bounds| is in
-  // screen coordinates.
-  virtual void OnKeyboardOccludedBoundsChanged(
-      const gfx::Rect& screen_bounds) = 0;
-
-  // Signals a request to load the keyboard contents. If the contents are
-  // already loaded, requests a reload. Once the contents have loaded,
-  // KeyboardController.KeyboardContentsLoaded is expected to be called by the
-  // client implementation.
-  virtual void OnLoadKeyboardContentsRequested() = 0;
-
-  // Called when the UI has been destroyed so that the client can reset the
-  // embedded contents and handle.
-  virtual void OnKeyboardUIDestroyed() = 0;
-};
-
 class ASH_PUBLIC_EXPORT KeyboardController {
  public:
-  using GetKeyboardConfigCallback =
-      base::OnceCallback<void(const keyboard::KeyboardConfig&)>;
-  using IsKeyboardEnabledCallback = base::OnceCallback<void(bool)>;
-  using GetEnableFlagsCallback = base::OnceCallback<void(
-      const std::vector<keyboard::KeyboardEnableFlag>&)>;
-  using IsKeyboardVisibleCallback = base::OnceCallback<void(bool)>;
-  using SetContainerTypeCallback = base::OnceCallback<void(bool)>;
-
   static KeyboardController* Get();
 
   // Sets the global KeyboardController instance to |this|.
@@ -86,13 +40,13 @@
   virtual void KeyboardContentsLoaded(const gfx::Size& size) = 0;
 
   // Retrieves the current keyboard configuration.
-  virtual void GetKeyboardConfig(GetKeyboardConfigCallback callback) = 0;
+  virtual keyboard::KeyboardConfig GetKeyboardConfig() = 0;
 
   // Sets the current keyboard configuration.
   virtual void SetKeyboardConfig(const keyboard::KeyboardConfig& config) = 0;
 
   // Returns whether the virtual keyboard has been enabled.
-  virtual void IsKeyboardEnabled(IsKeyboardVisibleCallback callback) = 0;
+  virtual bool IsKeyboardEnabled() = 0;
 
   // Sets the provided keyboard enable flag. If the computed enabled state
   // changes, enables or disables the keyboard to match the new state.
@@ -103,7 +57,7 @@
   virtual void ClearEnableFlag(keyboard::KeyboardEnableFlag flag) = 0;
 
   // Gets the current set of keyboard enable flags.
-  virtual void GetEnableFlags(GetEnableFlagsCallback callback) = 0;
+  virtual const std::set<keyboard::KeyboardEnableFlag>& GetEnableFlags() = 0;
 
   // Reloads the virtual keyboard if it is enabled and the URL has changed, e.g.
   // the focus has switched from one type of field to another.
@@ -115,7 +69,7 @@
   virtual void RebuildKeyboardIfEnabled() = 0;
 
   // Returns whether the virtual keyboard is visible.
-  virtual void IsKeyboardVisible(IsKeyboardVisibleCallback callback) = 0;
+  virtual bool IsKeyboardVisible() = 0;
 
   // Shows the virtual keyboard on the current display if it is enabled.
   virtual void ShowKeyboard() = 0;
@@ -126,6 +80,7 @@
   // Sets the keyboard container type. If non empty, |target_bounds| provides
   // the container size. Returns whether the transition succeeded once the
   // container type changes (or fails to change).
+  using SetContainerTypeCallback = base::OnceCallback<void(bool)>;
   virtual void SetContainerType(keyboard::ContainerType container_type,
                                 const base::Optional<gfx::Rect>& target_bounds,
                                 SetContainerTypeCallback callback) = 0;
diff --git a/ash/public/cpp/keyboard/keyboard_controller_observer.h b/ash/public/cpp/keyboard/keyboard_controller_observer.h
new file mode 100644
index 0000000..108dd0c
--- /dev/null
+++ b/ash/public/cpp/keyboard/keyboard_controller_observer.h
@@ -0,0 +1,102 @@
+// 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_KEYBOARD_KEYBOARD_CONTROLLER_OBSERVER_H_
+#define ASH_PUBLIC_CPP_KEYBOARD_KEYBOARD_CONTROLLER_OBSERVER_H_
+
+#include <set>
+
+#include "ash/public/cpp/ash_public_export.h"
+#include "ash/public/cpp/keyboard/keyboard_config.h"
+#include "ash/public/cpp/keyboard/keyboard_types.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace ash {
+
+// Describes the various attributes of the keyboard's appearance and usability.
+struct KeyboardStateDescriptor {
+  bool is_visible;
+
+  // The bounds of the keyboard window on the screen.
+  gfx::Rect visual_bounds;
+
+  // The bounds of the area on the screen that is considered "blocked" by the
+  // keyboard. For example, the docked keyboard's occluded bounds is the same as
+  // the visual bounds, but the floating keyboard has no occluded bounds (as the
+  // window is small and moveable).
+  gfx::Rect occluded_bounds_in_screen;
+
+  // The bounds of the area on the screen that is considered "unusable" because
+  // it is blocked by the keyboard. This is used by the accessibility keyboard.
+  gfx::Rect displaced_bounds_in_screen;
+};
+
+class ASH_PUBLIC_EXPORT KeyboardControllerObserver {
+ public:
+  // Called when a keyboard enable flag changes.
+  virtual void OnKeyboardEnableFlagsChanged(
+      const std::set<keyboard::KeyboardEnableFlag>& flags) {}
+
+  // Called when the keyboard is enabled or disabled. If ReloadKeyboard() is
+  // called or other code enables the keyboard while already enabled, this will
+  // be called twice, once when the keyboard is disabled and again when it is
+  // re-enabled.
+  virtual void OnKeyboardEnabledChanged(bool is_enabled) {}
+
+  // Called when the virtual keyboard configuration changes.
+  virtual void OnKeyboardConfigChanged(const keyboard::KeyboardConfig& config) {
+  }
+
+  // Called when the visibility of the virtual keyboard changes, e.g. an input
+  // field is focused or blurred, or the user hides the keyboard.
+  virtual void OnKeyboardVisibilityChanged(bool visible) {}
+
+  // Called when the keyboard bounds change. |screen_bounds| is in screen
+  // coordinates.
+  virtual void OnKeyboardVisibleBoundsChanged(const gfx::Rect& screen_bounds) {}
+
+  // Called when the keyboard bounds have changed in a way that should affect
+  // the usable region of the workspace. The user interface should respond to
+  // this event by moving important elements away from |new_bounds_in_screen|
+  // so that they don't overlap. However, drastic visual changes should be
+  // avoided, as the occluded bounds may change frequently.
+  virtual void OnKeyboardOccludedBoundsChanged(const gfx::Rect& screen_bounds) {
+  }
+
+  // Called when the keyboard bounds have changed in a way that affects how the
+  // workspace should change to not take up the screen space occupied by the
+  // keyboard. The user interface should respond to this event by moving all
+  // elements away from |new_bounds| so that they don't overlap. Large visual
+  // changes are okay, as the displacing bounds do not change frequently.
+  virtual void OnKeyboardDisplacingBoundsChanged(const gfx::Rect& new_bounds) {}
+
+  // Redundant with other various notification methods. Use this if the state of
+  // multiple properties need to be conveyed simultaneously to observer
+  // implementations without the need to track multiple stateful properties.
+  virtual void OnKeyboardAppearanceChanged(
+      const KeyboardStateDescriptor& state) {}
+
+  // Signals a request to load the keyboard contents. If the contents are
+  // already loaded, requests a reload. Once the contents have loaded,
+  // KeyboardController.KeyboardContentsLoaded is expected to be called by the
+  // client implementation.
+  virtual void OnLoadKeyboardContentsRequested() {}
+
+  // Called when the UI has been destroyed so that the client can reset the
+  // embedded contents and handle.
+  virtual void OnKeyboardUIDestroyed() {}
+
+  // Called when the keyboard has been hidden and the hiding animation finished
+  // successfully.
+  // When |is_temporary_hide| is true, this hide is immediately followed by a
+  // show (e.g. when changing to floating keyboard)
+  virtual void OnKeyboardHidden(bool is_temporary_hide) {}
+
+ protected:
+  virtual ~KeyboardControllerObserver() = default;
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_KEYBOARD_KEYBOARD_CONTROLLER_OBSERVER_H_
diff --git a/ash/public/cpp/test/test_keyboard_controller_observer.cc b/ash/public/cpp/test/test_keyboard_controller_observer.cc
index 906c147..daeac0d 100644
--- a/ash/public/cpp/test/test_keyboard_controller_observer.cc
+++ b/ash/public/cpp/test/test_keyboard_controller_observer.cc
@@ -17,7 +17,7 @@
 TestKeyboardControllerObserver::~TestKeyboardControllerObserver() = default;
 
 void TestKeyboardControllerObserver::OnKeyboardEnableFlagsChanged(
-    const std::vector<keyboard::KeyboardEnableFlag>& flags) {
+    const std::set<keyboard::KeyboardEnableFlag>& flags) {
   enable_flags_ = flags;
 }
 
diff --git a/ash/public/cpp/test/test_keyboard_controller_observer.h b/ash/public/cpp/test/test_keyboard_controller_observer.h
index e089225..55cab7c 100644
--- a/ash/public/cpp/test/test_keyboard_controller_observer.h
+++ b/ash/public/cpp/test/test_keyboard_controller_observer.h
@@ -5,12 +5,15 @@
 #ifndef ASH_PUBLIC_CPP_TEST_TEST_KEYBOARD_CONTROLLER_OBSERVER_H_
 #define ASH_PUBLIC_CPP_TEST_TEST_KEYBOARD_CONTROLLER_OBSERVER_H_
 
+#include <set>
+
 #include "ash/public/cpp/keyboard/keyboard_config.h"
 #include "ash/public/cpp/keyboard/keyboard_controller.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 
 namespace ash {
 
-// :KeyboardControllerObserver implementation for tests. This class
+// KeyboardControllerObserver implementation for tests. This class
 // implements a test client observer for tests running with the Window Service.
 
 class TestKeyboardControllerObserver : public KeyboardControllerObserver {
@@ -20,7 +23,7 @@
 
   // KeyboardControllerObserver:
   void OnKeyboardEnableFlagsChanged(
-      const std::vector<keyboard::KeyboardEnableFlag>& flags) override;
+      const std::set<keyboard::KeyboardEnableFlag>& flags) override;
   void OnKeyboardEnabledChanged(bool enabled) override;
   void OnKeyboardConfigChanged(const keyboard::KeyboardConfig& config) override;
   void OnKeyboardVisibilityChanged(bool visible) override;
@@ -31,14 +34,14 @@
 
   const keyboard::KeyboardConfig& config() const { return config_; }
   void set_config(const keyboard::KeyboardConfig& config) { config_ = config; }
-  const std::vector<keyboard::KeyboardEnableFlag>& enable_flags() const {
+  const std::set<keyboard::KeyboardEnableFlag>& enable_flags() const {
     return enable_flags_;
   }
   int destroyed_count() const { return destroyed_count_; }
 
  private:
   KeyboardController* controller_;
-  std::vector<keyboard::KeyboardEnableFlag> enable_flags_;
+  std::set<keyboard::KeyboardEnableFlag> enable_flags_;
   keyboard::KeyboardConfig config_;
   int destroyed_count_ = 0;
 
diff --git a/ash/shelf/shelf.cc b/ash/shelf/shelf.cc
index 3289c1c..937c7cb 100644
--- a/ash/shelf/shelf.cc
+++ b/ash/shelf/shelf.cc
@@ -8,7 +8,7 @@
 
 #include "ash/animation/animation_change_type.h"
 #include "ash/app_list/app_list_controller_impl.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -330,17 +330,17 @@
 }
 
 void Shelf::SetVirtualKeyboardBoundsForTesting(const gfx::Rect& bounds) {
-  keyboard::KeyboardStateDescriptor state;
+  KeyboardStateDescriptor state;
   state.is_visible = !bounds.IsEmpty();
   state.visual_bounds = bounds;
   state.occluded_bounds_in_screen = bounds;
   state.displaced_bounds_in_screen = gfx::Rect();
   WorkAreaInsets* work_area_insets = GetWorkAreaInsets();
-  work_area_insets->OnKeyboardVisibilityStateChanged(state.is_visible);
+  work_area_insets->OnKeyboardVisibilityChanged(state.is_visible);
   work_area_insets->OnKeyboardVisibleBoundsChanged(state.visual_bounds);
-  work_area_insets->OnKeyboardWorkspaceOccludedBoundsChanged(
+  work_area_insets->OnKeyboardOccludedBoundsChanged(
       state.occluded_bounds_in_screen);
-  work_area_insets->OnKeyboardWorkspaceDisplacingBoundsChanged(
+  work_area_insets->OnKeyboardDisplacingBoundsChanged(
       state.displaced_bounds_in_screen);
   work_area_insets->OnKeyboardAppearanceChanged(state);
 }
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index 6121aed..c2961d3 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -19,11 +19,11 @@
 #include "ash/home_screen/home_screen_controller.h"
 #include "ash/home_screen/home_screen_delegate.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
 #include "ash/keyboard/ui/keyboard_ui.h"
 #include "ash/keyboard/ui/keyboard_util.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/immersive/immersive_fullscreen_controller_test_api.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/public/cpp/shelf_item.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/test/shell_test_api.h"
@@ -3092,13 +3092,13 @@
                               bool is_locked,
                               const gfx::Rect& bounds_in_screen) {
     WorkAreaInsets* work_area_insets = GetPrimaryWorkAreaInsets();
-    keyboard::KeyboardStateDescriptor state;
+    KeyboardStateDescriptor state;
     state.visual_bounds = bounds_in_screen;
     state.occluded_bounds_in_screen = bounds_in_screen;
     state.displaced_bounds_in_screen =
         is_locked ? bounds_in_screen : gfx::Rect();
     state.is_visible = !bounds_in_screen.IsEmpty();
-    work_area_insets->OnKeyboardVisibilityStateChanged(state.is_visible);
+    work_area_insets->OnKeyboardVisibilityChanged(state.is_visible);
     work_area_insets->OnKeyboardAppearanceChanged(state);
   }
 
diff --git a/ash/shell/shell_delegate_impl.cc b/ash/shell/shell_delegate_impl.cc
index 838caeb..8579c51 100644
--- a/ash/shell/shell_delegate_impl.cc
+++ b/ash/shell/shell_delegate_impl.cc
@@ -29,10 +29,5 @@
   return new DefaultAccessibilityDelegate;
 }
 
-ws::InputDeviceControllerClient*
-ShellDelegateImpl::GetInputDeviceControllerClient() {
-  return nullptr;
-}
-
 }  // namespace shell
 }  // namespace ash
diff --git a/ash/shell/shell_delegate_impl.h b/ash/shell/shell_delegate_impl.h
index ebeecb1..d772fd9f 100644
--- a/ash/shell/shell_delegate_impl.h
+++ b/ash/shell/shell_delegate_impl.h
@@ -23,7 +23,6 @@
   bool CanShowWindowForUser(const aura::Window* window) const override;
   std::unique_ptr<ScreenshotDelegate> CreateScreenshotDelegate() override;
   AccessibilityDelegate* CreateAccessibilityDelegate() override;
-  ws::InputDeviceControllerClient* GetInputDeviceControllerClient() override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShellDelegateImpl);
diff --git a/ash/shell_delegate.h b/ash/shell_delegate.h
index 27618b5..17ce0d5 100644
--- a/ash/shell_delegate.h
+++ b/ash/shell_delegate.h
@@ -16,10 +16,6 @@
 class Window;
 }
 
-namespace ws {
-class InputDeviceControllerClient;
-}
-
 namespace ash {
 
 class AccessibilityDelegate;
@@ -42,9 +38,6 @@
   virtual AccessibilityDelegate* CreateAccessibilityDelegate() = 0;
 
   virtual void OpenKeyboardShortcutHelpPage() const {}
-
-  // Creator of Shell owns this; it's assumed this outlives Shell.
-  virtual ws::InputDeviceControllerClient* GetInputDeviceControllerClient() = 0;
 };
 
 }  // namespace ash
diff --git a/ash/system/ime_menu/ime_menu_tray.h b/ash/system/ime_menu/ime_menu_tray.h
index 06d5e1d..4837368 100644
--- a/ash/system/ime_menu/ime_menu_tray.h
+++ b/ash/system/ime_menu/ime_menu_tray.h
@@ -6,7 +6,7 @@
 #define ASH_SYSTEM_IME_MENU_IME_MENU_TRAY_H_
 
 #include "ash/ash_export.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/public/interfaces/ime_controller.mojom.h"
 #include "ash/public/interfaces/ime_info.mojom.h"
 #include "ash/system/ime/ime_observer.h"
@@ -32,7 +32,7 @@
 // for emoji, handwriting, and voice.
 class ASH_EXPORT ImeMenuTray : public TrayBackgroundView,
                                public IMEObserver,
-                               public keyboard::KeyboardControllerObserver,
+                               public KeyboardControllerObserver,
                                public VirtualKeyboardObserver {
  public:
   explicit ImeMenuTray(Shelf* shelf);
@@ -72,7 +72,7 @@
   bool ShouldEnableExtraKeyboardAccessibility() override;
   void HideBubble(const TrayBubbleView* bubble_view) override;
 
-  // keyboard::KeyboardControllerObserver:
+  // KeyboardControllerObserver:
   void OnKeyboardHidden(bool is_temporary_hide) override;
 
   // VirtualKeyboardObserver:
diff --git a/ash/system/toast/toast_overlay.cc b/ash/system/toast/toast_overlay.cc
index 685dbf4..1dd42fb 100644
--- a/ash/system/toast/toast_overlay.cc
+++ b/ash/system/toast/toast_overlay.cc
@@ -302,7 +302,7 @@
     delegate_->OnClosed();
 }
 
-void ToastOverlay::OnKeyboardWorkspaceOccludedBoundsChanged(
+void ToastOverlay::OnKeyboardOccludedBoundsChanged(
     const gfx::Rect& new_bounds_in_screen) {
   // TODO(https://crbug.com/943446): Observe changes in user work area bounds
   // directly instead of listening for keyboard bounds changes.
diff --git a/ash/system/toast/toast_overlay.h b/ash/system/toast/toast_overlay.h
index d26b1e0..dd44ff1 100644
--- a/ash/system/toast/toast_overlay.h
+++ b/ash/system/toast/toast_overlay.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/optional.h"
 #include "base/strings/string16.h"
 #include "ui/compositor/layer_animation_observer.h"
@@ -31,7 +31,7 @@
 class ToastOverlayButton;
 
 class ASH_EXPORT ToastOverlay : public ui::ImplicitAnimationObserver,
-                                public keyboard::KeyboardControllerObserver {
+                                public KeyboardControllerObserver {
  public:
   class ASH_EXPORT Delegate {
    public:
@@ -71,9 +71,8 @@
   void OnImplicitAnimationsScheduled() override;
   void OnImplicitAnimationsCompleted() override;
 
-  // keyboard::KeyboardControllerObserver:
-  void OnKeyboardWorkspaceOccludedBoundsChanged(
-      const gfx::Rect& new_bounds) override;
+  // KeyboardControllerObserver:
+  void OnKeyboardOccludedBoundsChanged(const gfx::Rect& new_bounds) override;
 
   views::Widget* widget_for_testing();
   ToastOverlayButton* dismiss_button_for_testing();
diff --git a/ash/system/virtual_keyboard/virtual_keyboard_tray.cc b/ash/system/virtual_keyboard/virtual_keyboard_tray.cc
index 3266ad0..185cc85 100644
--- a/ash/system/virtual_keyboard/virtual_keyboard_tray.cc
+++ b/ash/system/virtual_keyboard/virtual_keyboard_tray.cc
@@ -95,8 +95,7 @@
   UpdateIconVisibility();
 }
 
-void VirtualKeyboardTray::OnKeyboardVisibilityStateChanged(
-    const bool is_visible) {
+void VirtualKeyboardTray::OnKeyboardVisibilityChanged(const bool is_visible) {
   SetIsActive(is_visible);
 }
 
diff --git a/ash/system/virtual_keyboard/virtual_keyboard_tray.h b/ash/system/virtual_keyboard/virtual_keyboard_tray.h
index 3d0c2c5..4258105 100644
--- a/ash/system/virtual_keyboard/virtual_keyboard_tray.h
+++ b/ash/system/virtual_keyboard/virtual_keyboard_tray.h
@@ -6,7 +6,7 @@
 #define ASH_SYSTEM_VIRTUAL_KEYBOARD_VIRTUAL_KEYBOARD_TRAY_H_
 
 #include "ash/accessibility/accessibility_observer.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/session/session_observer.h"
 #include "ash/shell_observer.h"
 #include "ash/system/tray/tray_background_view.h"
@@ -21,7 +21,7 @@
 // TODO(sky): make this visible on non-chromeos platforms.
 class VirtualKeyboardTray : public TrayBackgroundView,
                             public AccessibilityObserver,
-                            public keyboard::KeyboardControllerObserver,
+                            public KeyboardControllerObserver,
                             public ShellObserver,
                             public SessionObserver {
  public:
@@ -37,8 +37,8 @@
   // AccessibilityObserver:
   void OnAccessibilityStatusChanged() override;
 
-  // keyboard::KeyboardControllerObserver:
-  void OnKeyboardVisibilityStateChanged(bool is_visible) override;
+  // KeyboardControllerObserver:
+  void OnKeyboardVisibilityChanged(bool is_visible) override;
 
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
diff --git a/ash/test_shell_delegate.cc b/ash/test_shell_delegate.cc
index bc1b67f3a..6fc9007 100644
--- a/ash/test_shell_delegate.cc
+++ b/ash/test_shell_delegate.cc
@@ -29,9 +29,4 @@
   return new DefaultAccessibilityDelegate;
 }
 
-ws::InputDeviceControllerClient*
-TestShellDelegate::GetInputDeviceControllerClient() {
-  return nullptr;
-}
-
 }  // namespace ash
diff --git a/ash/test_shell_delegate.h b/ash/test_shell_delegate.h
index f0604f8..c332ec9 100644
--- a/ash/test_shell_delegate.h
+++ b/ash/test_shell_delegate.h
@@ -21,7 +21,6 @@
   bool CanShowWindowForUser(const aura::Window* window) const override;
   std::unique_ptr<ScreenshotDelegate> CreateScreenshotDelegate() override;
   AccessibilityDelegate* CreateAccessibilityDelegate() override;
-  ws::InputDeviceControllerClient* GetInputDeviceControllerClient() override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(TestShellDelegate);
diff --git a/ash/touch/touch_devices_controller.cc b/ash/touch/touch_devices_controller.cc
index 161ca3c..2e1d531 100644
--- a/ash/touch/touch_devices_controller.cc
+++ b/ash/touch/touch_devices_controller.cc
@@ -17,35 +17,14 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
-#include "services/ws/public/cpp/input_devices/input_device_controller_client.h"
+#include "ui/ozone/public/input_controller.h"
+#include "ui/ozone/public/ozone_platform.h"
 #include "ui/wm/core/cursor_manager.h"
 
 namespace ash {
 
 namespace {
 
-void OnSetTouchpadEnabledDone(bool enabled, bool succeeded) {
-  // Don't log here, |succeeded| is only true if there is a touchpad *and* the
-  // value changed. In other words |succeeded| is false when not on device or
-  // the value was already at the value specified. Neither of these are
-  // interesting failures.
-  if (!succeeded)
-    return;
-
-  ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager();
-  if (!cursor_manager)
-    return;
-
-  if (enabled)
-    cursor_manager->ShowCursor();
-  else
-    cursor_manager->HideCursor();
-}
-
-ws::InputDeviceControllerClient* GetInputDeviceControllerClient() {
-  return Shell::Get()->shell_delegate()->GetInputDeviceControllerClient();
-}
-
 PrefService* GetActivePrefService() {
   return Shell::Get()->session_controller()->GetActivePrefService();
 }
@@ -182,30 +161,36 @@
 
   UMA_HISTOGRAM_BOOLEAN("Touchpad.TapDragging.Changed", enabled);
 
-  if (!GetInputDeviceControllerClient())
-    return;  // Happens in tests.
-
-  GetInputDeviceControllerClient()->SetTapDragging(enabled);
+  ui::OzonePlatform::GetInstance()->GetInputController()->SetTapDragging(
+      enabled);
 }
 
 void TouchDevicesController::UpdateTouchpadEnabled() {
-  if (!GetInputDeviceControllerClient())
-    return;  // Happens in tests.
-
   bool enabled = GetTouchpadEnabled(TouchDeviceEnabledSource::GLOBAL) &&
                  GetTouchpadEnabled(TouchDeviceEnabledSource::USER_PREF);
+  ui::InputController* input_controller =
+      ui::OzonePlatform::GetInstance()->GetInputController();
+  const bool old_value = input_controller->IsInternalTouchpadEnabled();
+  input_controller->SetInternalTouchpadEnabled(enabled);
+  if (old_value == input_controller->IsInternalTouchpadEnabled())
+    return;  // Value didn't actually change.
 
-  GetInputDeviceControllerClient()->SetInternalTouchpadEnabled(
-      enabled, base::BindRepeating(&OnSetTouchpadEnabledDone, enabled));
+  ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager();
+  if (!cursor_manager)
+    return;
+
+  if (enabled)
+    cursor_manager->ShowCursor();
+  else
+    cursor_manager->HideCursor();
 }
 
 void TouchDevicesController::UpdateTouchscreenEnabled() {
-  if (!GetInputDeviceControllerClient())
-    return;  // Happens in tests.
-
-  GetInputDeviceControllerClient()->SetTouchscreensEnabled(
-      GetTouchscreenEnabled(TouchDeviceEnabledSource::GLOBAL) &&
-      GetTouchscreenEnabled(TouchDeviceEnabledSource::USER_PREF));
+  ui::OzonePlatform::GetInstance()
+      ->GetInputController()
+      ->SetTouchscreensEnabled(
+          GetTouchscreenEnabled(TouchDeviceEnabledSource::GLOBAL) &&
+          GetTouchscreenEnabled(TouchDeviceEnabledSource::USER_PREF));
 }
 
 }  // namespace ash
diff --git a/ash/wm/always_on_top_controller_unittest.cc b/ash/wm/always_on_top_controller_unittest.cc
index 8714958..8117a9e 100644
--- a/ash/wm/always_on_top_controller_unittest.cc
+++ b/ash/wm/always_on_top_controller_unittest.cc
@@ -45,10 +45,9 @@
 
   ~TestLayoutManager() override = default;
 
-  void OnKeyboardWorkspaceDisplacingBoundsChanged(
-      const gfx::Rect& bounds) override {
+  void OnKeyboardDisplacingBoundsChanged(const gfx::Rect& bounds) override {
     keyboard_bounds_changed_ = true;
-    WorkspaceLayoutManager::OnKeyboardWorkspaceDisplacingBoundsChanged(bounds);
+    WorkspaceLayoutManager::OnKeyboardDisplacingBoundsChanged(bounds);
   }
 
   bool keyboard_bounds_changed() const { return keyboard_bounds_changed_; }
diff --git a/ash/wm/client_controlled_state.cc b/ash/wm/client_controlled_state.cc
index 82f9d54..6bad8bc 100644
--- a/ash/wm/client_controlled_state.cc
+++ b/ash/wm/client_controlled_state.cc
@@ -214,6 +214,14 @@
             break;
         }
         next_bounds_change_animation_type_ = kAnimationNone;
+
+        // For PIP, restore bounds is used to specify the ideal position.
+        // Usually this value is set in completeDrag, but for the initial
+        // position, we need to set it here.
+        if (window_state->IsPip() &&
+            window_state->GetRestoreBoundsInParent().IsEmpty())
+          window_state->SetRestoreBoundsInParent(bounds);
+
       } else if (!window_state->IsPinned()) {
         // TODO(oshima): Define behavior for pinned app.
         bounds_change_animation_duration_ = set_bounds_event->duration();
diff --git a/ash/wm/client_controlled_state_unittest.cc b/ash/wm/client_controlled_state_unittest.cc
index 5c9c1b3..b006a72 100644
--- a/ash/wm/client_controlled_state_unittest.cc
+++ b/ash/wm/client_controlled_state_unittest.cc
@@ -565,5 +565,20 @@
   EXPECT_EQ(gfx::Rect(475, 0, 100, 200), delegate()->requested_bounds());
 }
 
+TEST_F(ClientControlledStateTest, HandleBoundsEventsUpdatesPipRestoreBounds) {
+  state()->EnterNextState(window_state(), ash::WindowStateType::kPip);
+
+  EXPECT_TRUE(window_state()->IsPip());
+
+  state()->set_bounds_locally(true);
+  wm::SetBoundsEvent event(gfx::Rect(0, 0, 50, 50));
+  window_state()->OnWMEvent(&event);
+  state()->set_bounds_locally(false);
+
+  EXPECT_TRUE(window_state()->HasRestoreBounds());
+  EXPECT_EQ(gfx::Rect(0, 0, 50, 50),
+            window_state()->GetRestoreBoundsInParent());
+}
+
 }  // namespace wm
 }  // namespace ash
diff --git a/ash/wm/lock_layout_manager.cc b/ash/wm/lock_layout_manager.cc
index 9dd55075..d2375f2 100644
--- a/ash/wm/lock_layout_manager.cc
+++ b/ash/wm/lock_layout_manager.cc
@@ -114,7 +114,7 @@
   AdjustWindowsForWorkAreaChange(&event);
 }
 
-void LockLayoutManager::OnKeyboardWorkspaceOccludedBoundsChanged(
+void LockLayoutManager::OnKeyboardOccludedBoundsChanged(
     const gfx::Rect& new_bounds_in_screen) {
   OnWindowResized();
 }
diff --git a/ash/wm/lock_layout_manager.h b/ash/wm/lock_layout_manager.h
index 23849da..99512a8 100644
--- a/ash/wm/lock_layout_manager.h
+++ b/ash/wm/lock_layout_manager.h
@@ -7,7 +7,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/shelf/shelf_observer.h"
 #include "ash/shell_observer.h"
 #include "ash/wm/wm_snap_to_pixel_layout_manager.h"
@@ -36,12 +36,11 @@
 // virtual keyboard changes inner workspace of each WebContents.
 // For all windows in LockScreenContainer default wm::WindowState is replaced
 // with LockWindowState.
-class ASH_EXPORT LockLayoutManager
-    : public wm::WmSnapToPixelLayoutManager,
-      public aura::WindowObserver,
-      public ShellObserver,
-      public ShelfObserver,
-      public keyboard::KeyboardControllerObserver {
+class ASH_EXPORT LockLayoutManager : public wm::WmSnapToPixelLayoutManager,
+                                     public aura::WindowObserver,
+                                     public ShellObserver,
+                                     public ShelfObserver,
+                                     public KeyboardControllerObserver {
  public:
   LockLayoutManager(aura::Window* window, Shelf* shelf);
   ~LockLayoutManager() override;
@@ -66,9 +65,8 @@
   // ShelfObserver:
   void WillChangeVisibilityState(ShelfVisibilityState visibility) override;
 
-  // keyboard::KeyboardControllerObserver overrides:
-  void OnKeyboardWorkspaceOccludedBoundsChanged(
-      const gfx::Rect& new_bounds) override;
+  // KeyboardControllerObserver overrides:
+  void OnKeyboardOccludedBoundsChanged(const gfx::Rect& new_bounds) override;
 
  protected:
   // Adjusts the bounds of all managed windows when the display area changes.
diff --git a/ash/wm/system_modal_container_layout_manager.cc b/ash/wm/system_modal_container_layout_manager.cc
index 897223b..c835c0b 100644
--- a/ash/wm/system_modal_container_layout_manager.cc
+++ b/ash/wm/system_modal_container_layout_manager.cc
@@ -134,9 +134,8 @@
 // SystemModalContainerLayoutManager, Keyboard::KeyboardControllerObserver
 // implementation:
 
-void SystemModalContainerLayoutManager::
-    OnKeyboardWorkspaceOccludedBoundsChanged(
-        const gfx::Rect& new_bounds_in_screen) {
+void SystemModalContainerLayoutManager::OnKeyboardOccludedBoundsChanged(
+    const gfx::Rect& new_bounds_in_screen) {
   PositionDialogsAfterWorkAreaResize();
 }
 
diff --git a/ash/wm/system_modal_container_layout_manager.h b/ash/wm/system_modal_container_layout_manager.h
index 7937803..af3c5fe 100644
--- a/ash/wm/system_modal_container_layout_manager.h
+++ b/ash/wm/system_modal_container_layout_manager.h
@@ -10,7 +10,7 @@
 #include <vector>
 
 #include "ash/ash_export.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/wm/wm_snap_to_pixel_layout_manager.h"
 #include "base/macros.h"
 #include "ui/aura/window_observer.h"
@@ -28,7 +28,7 @@
 class ASH_EXPORT SystemModalContainerLayoutManager
     : public wm::WmSnapToPixelLayoutManager,
       public aura::WindowObserver,
-      public keyboard::KeyboardControllerObserver {
+      public KeyboardControllerObserver {
  public:
   explicit SystemModalContainerLayoutManager(aura::Window* container);
   ~SystemModalContainerLayoutManager() override;
@@ -49,9 +49,8 @@
                                const void* key,
                                intptr_t old) override;
 
-  // Overridden from keyboard::KeyboardControllerObserver:
-  void OnKeyboardWorkspaceOccludedBoundsChanged(
-      const gfx::Rect& new_bounds) override;
+  // Overridden from KeyboardControllerObserver:
+  void OnKeyboardOccludedBoundsChanged(const gfx::Rect& new_bounds) override;
 
   // True if the window is either contained by the top most modal window,
   // or contained by its transient children.
diff --git a/ash/wm/tablet_mode/internal_input_devices_event_blocker.cc b/ash/wm/tablet_mode/internal_input_devices_event_blocker.cc
index b0b51a7..c787d75d 100644
--- a/ash/wm/tablet_mode/internal_input_devices_event_blocker.cc
+++ b/ash/wm/tablet_mode/internal_input_devices_event_blocker.cc
@@ -7,21 +7,14 @@
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
 #include "ash/touch/touch_devices_controller.h"
-#include "services/ws/public/cpp/input_devices/input_device_controller_client.h"
 #include "ui/events/devices/input_device.h"
 #include "ui/events/devices/input_device_manager.h"
 #include "ui/events/keycodes/dom/dom_code.h"
+#include "ui/ozone/public/input_controller.h"
+#include "ui/ozone/public/ozone_platform.h"
 
 namespace ash {
 
-namespace {
-
-ws::InputDeviceControllerClient* GetInputDeviceControllerClient() {
-  return Shell::Get()->shell_delegate()->GetInputDeviceControllerClient();
-}
-
-}  // namespace
-
 InternalInputDevicesEventBlocker::InternalInputDevicesEventBlocker() {
   ui::InputDeviceManager::GetInstance()->AddObserver(this);
 }
@@ -86,22 +79,23 @@
   if (should_be_blocked == is_keyboard_blocked_)
     return;
 
-  // Block or unblock the internal keyboard. Note InputDeviceControllerClient
-  // may be null in tests.
-  if (HasInternalKeyboard() && GetInputDeviceControllerClient()) {
-    std::vector<ui::DomCode> allowed_keys;
-    if (should_be_blocked) {
-      // Only allow the acccessible keys present on the side of some devices to
-      // continue working if the internal keyboard events should be blocked.
-      allowed_keys.push_back(ui::DomCode::VOLUME_DOWN);
-      allowed_keys.push_back(ui::DomCode::VOLUME_UP);
-      allowed_keys.push_back(ui::DomCode::POWER);
-    }
-    GetInputDeviceControllerClient()->SetInternalKeyboardFilter(
-        should_be_blocked, allowed_keys);
-    is_keyboard_blocked_ = should_be_blocked;
-    VLOG(1) << "Internal keyboard is blocked: " << is_keyboard_blocked_;
+  if (!HasInternalKeyboard())
+    return;
+
+  // Block or unblock the internal keyboard.
+  ui::InputController* input_controller =
+      ui::OzonePlatform::GetInstance()->GetInputController();
+  std::vector<ui::DomCode> allowed_keys;
+  if (should_be_blocked) {
+    // Only allow the acccessible keys present on the side of some devices to
+    // continue working if the internal keyboard events should be blocked.
+    allowed_keys.push_back(ui::DomCode::VOLUME_DOWN);
+    allowed_keys.push_back(ui::DomCode::VOLUME_UP);
+    allowed_keys.push_back(ui::DomCode::POWER);
   }
+  input_controller->SetInternalKeyboardFilter(should_be_blocked, allowed_keys);
+  is_keyboard_blocked_ = should_be_blocked;
+  VLOG(1) << "Internal keyboard is blocked: " << is_keyboard_blocked_;
 }
 
 }  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index 19fe571..76639c2 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -50,18 +50,18 @@
 namespace {
 
 // The hinge angle at which to enter tablet mode.
-const float kEnterTabletModeAngle = 200.0f;
+constexpr float kEnterTabletModeAngle = 200.0f;
 
 // The angle at which to exit tablet mode, this is specifically less than the
 // angle to enter tablet mode to prevent rapid toggling when near the angle.
-const float kExitTabletModeAngle = 160.0f;
+constexpr float kExitTabletModeAngle = 160.0f;
 
 // Defines a range for which accelerometer readings are considered accurate.
 // When the lid is near open (or near closed) the accelerometer readings may be
 // inaccurate and a lid that is fully open may appear to be near closed (and
 // vice versa).
-const float kMinStableAngle = 20.0f;
-const float kMaxStableAngle = 340.0f;
+constexpr float kMinStableAngle = 20.0f;
+constexpr float kMaxStableAngle = 340.0f;
 
 // The time duration to consider an unstable lid angle to be valid. This is used
 // to prevent entering tablet mode if an erroneous accelerometer reading makes
@@ -78,15 +78,15 @@
 // smoothed over time in order to reduce this noise.
 // This is the minimum acceleration parallel to the hinge under which to begin
 // smoothing in m/s^2.
-const float kHingeVerticalSmoothingStart = 7.0f;
+constexpr float kHingeVerticalSmoothingStart = 7.0f;
 // This is the maximum acceleration parallel to the hinge under which smoothing
 // will incorporate new acceleration values, in m/s^2.
-const float kHingeVerticalSmoothingMaximum = 8.7f;
+constexpr float kHingeVerticalSmoothingMaximum = 8.7f;
 
 // The maximum deviation between the magnitude of the two accelerometers under
 // which to detect hinge angle in m/s^2. These accelerometers are attached to
 // the same physical device and so should be under the same acceleration.
-const float kNoisyMagnitudeDeviation = 1.0f;
+constexpr float kNoisyMagnitudeDeviation = 1.0f;
 
 // Interval between calls to RecordLidAngle().
 constexpr base::TimeDelta kRecordLidAngleInterval =
@@ -203,7 +203,7 @@
 constexpr char TabletModeController::kLidAngleHistogramName[];
 
 TabletModeController::TabletModeController()
-    : event_blocker_(new InternalInputDevicesEventBlocker),
+    : event_blocker_(std::make_unique<InternalInputDevicesEventBlocker>()),
       tablet_mode_usage_interval_start_time_(base::Time::Now()),
       tick_clock_(base::DefaultTickClock::GetInstance()) {
   Shell::Get()->AddShellObserver(this);
@@ -911,7 +911,7 @@
 }
 
 void TabletModeController::FinishInitTabletMode() {
-  tablet_mode_window_manager_.reset(new TabletModeWindowManager());
+  tablet_mode_window_manager_ = std::make_unique<TabletModeWindowManager>();
   tablet_mode_window_manager_->Init();
 
   base::RecordAction(base::UserMetricsAction("Touchview_Enabled"));
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.cc b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
index 4595d50..3d05a3f9 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
@@ -115,6 +115,8 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedObserveWindowAnimation);
 };
 
+TabletModeWindowManager::TabletModeWindowManager() = default;
+
 TabletModeWindowManager::~TabletModeWindowManager() = default;
 
 // static
@@ -195,6 +197,11 @@
     window_state_map_.erase(it);
 }
 
+void TabletModeWindowManager::SetIgnoreWmEventsForExit() {
+  for (auto& pair : window_state_map_)
+    pair.second->set_ignore_wm_events(true);
+}
+
 void TabletModeWindowManager::OnOverviewModeEndingAnimationComplete(
     bool canceled) {
   if (canceled)
@@ -254,7 +261,7 @@
   } else {
     // If a known window gets destroyed we need to remove all knowledge about
     // it.
-    ForgetWindow(window, true /* destroyed */);
+    ForgetWindow(window, /*destroyed=*/true);
   }
 }
 
@@ -416,13 +423,6 @@
   }
 }
 
-void TabletModeWindowManager::SetIgnoreWmEventsForExit() {
-  for (auto& pair : window_state_map_)
-    pair.second->set_ignore_wm_events(true);
-}
-
-TabletModeWindowManager::TabletModeWindowManager() = default;
-
 WindowStateType TabletModeWindowManager::GetDesktopWindowStateType(
     aura::Window* window) const {
   auto iter = window_state_map_.find(window);
@@ -522,13 +522,12 @@
   DCHECK(!IsTrackingWindow(window));
   window->AddObserver(this);
 
-  // We create and remember a tablet mode state which will attach itself to
-  // the provided state object. First set the map to point to a null object
-  // because on creating a TabletModeWindowState will check to see if |window|
-  // is being tracked by |this|, and we want that to return true.
-  window_state_map_.emplace(window, nullptr);
-  window_state_map_[window] = new TabletModeWindowState(
-      window, this, snap, animate_bounds_on_attach, entering_tablet_mode);
+  // Create and remember a tablet mode state which will attach itself to the
+  // provided state object.
+  window_state_map_.emplace(
+      window,
+      new TabletModeWindowState(window, this, snap, animate_bounds_on_attach,
+                                entering_tablet_mode));
 }
 
 void TabletModeWindowManager::ForgetWindow(aura::Window* window,
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.h b/ash/wm/tablet_mode/tablet_mode_window_manager.h
index 780b8bf..42cec9a 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.h
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.h
@@ -47,7 +47,9 @@
                                            public ShellObserver,
                                            public SessionObserver {
  public:
-  // This should only be deleted by the creator (TabletModeController).
+  // This should only be created or deleted by the creator
+  // (TabletModeController).
+  TabletModeWindowManager();
   ~TabletModeWindowManager() override;
 
   static aura::Window* GetTopWindow();
@@ -74,6 +76,9 @@
   // Called from a window state object when it gets destroyed.
   void WindowStateDestroyed(aura::Window* window);
 
+  // Tell all managing windows not to handle WM events.
+  void SetIgnoreWmEventsForExit();
+
   // OverviewObserver:
   void OnOverviewModeEndingAnimationComplete(bool canceled) override;
 
@@ -99,15 +104,6 @@
   // SessionObserver:
   void OnActiveUserSessionChanged(const AccountId& account_id) override;
 
-  // Tell all managing windows not to handle WM events.
-  void SetIgnoreWmEventsForExit();
-
- protected:
-  friend class TabletModeController;
-
-  // The object should only be created by TabletModeController.
-  TabletModeWindowManager();
-
  private:
   using WindowToState = std::map<aura::Window*, TabletModeWindowState*>;
 
diff --git a/ash/wm/work_area_insets.cc b/ash/wm/work_area_insets.cc
index 8fc729f..f8f567f 100644
--- a/ash/wm/work_area_insets.cc
+++ b/ash/wm/work_area_insets.cc
@@ -102,7 +102,7 @@
 }
 
 void WorkAreaInsets::OnKeyboardAppearanceChanged(
-    const keyboard::KeyboardStateDescriptor& state) {
+    const KeyboardStateDescriptor& state) {
   aura::Window* window = root_window_controller_->GetRootWindow();
 
   keyboard_occluded_bounds_ = state.occluded_bounds_in_screen;
@@ -112,7 +112,7 @@
   Shell::Get()->NotifyUserWorkAreaInsetsChanged(window);
 }
 
-void WorkAreaInsets::OnKeyboardVisibilityStateChanged(const bool is_visible) {
+void WorkAreaInsets::OnKeyboardVisibilityChanged(const bool is_visible) {
   // On login screen if keyboard has been just hidden, update bounds just once
   // but ignore work area insets since shelf overlaps with login window.
   if (Shell::Get()->session_controller()->IsUserSessionBlocked() &&
diff --git a/ash/wm/work_area_insets.h b/ash/wm/work_area_insets.h
index ff8ace8..d6563e0 100644
--- a/ash/wm/work_area_insets.h
+++ b/ash/wm/work_area_insets.h
@@ -6,7 +6,7 @@
 #define ASH_WM_WORK_AREA_INSETS_H_
 
 #include "ash/ash_export.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/macros.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
@@ -26,7 +26,7 @@
 // WorkAreaInsets class caches information about persistent system elements to
 // avoid frequent recalculations. It gathers work area related computations and
 // provides single interface to query work area details.
-class ASH_EXPORT WorkAreaInsets : public keyboard::KeyboardControllerObserver {
+class ASH_EXPORT WorkAreaInsets : public KeyboardControllerObserver {
  public:
   // Returns work area parameters associated with the given |window|.
   static WorkAreaInsets* ForWindow(const aura::Window* window);
@@ -79,10 +79,10 @@
   void SetShelfBoundsAndInsets(const gfx::Rect& bounds,
                                const gfx::Insets& insets);
 
-  // keyboard::KeyboardControllerObserver:
+  // KeyboardControllerObserver:
   void OnKeyboardAppearanceChanged(
-      const keyboard::KeyboardStateDescriptor& state) override;
-  void OnKeyboardVisibilityStateChanged(bool is_visible) override;
+      const KeyboardStateDescriptor& state) override;
+  void OnKeyboardVisibilityChanged(bool is_visible) override;
 
  private:
   // Updates cached values of work area bounds and insets.
diff --git a/ash/wm/workspace/workspace_layout_manager.cc b/ash/wm/workspace/workspace_layout_manager.cc
index ac1941a..fa25925 100644
--- a/ash/wm/workspace/workspace_layout_manager.cc
+++ b/ash/wm/workspace/workspace_layout_manager.cc
@@ -10,7 +10,7 @@
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/autoclick/autoclick_controller.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/root_window_controller.h"
@@ -213,7 +213,7 @@
 }
 
 //////////////////////////////////////////////////////////////////////////////
-// WorkspaceLayoutManager, keyboard::KeyboardControllerObserver implementation:
+// WorkspaceLayoutManager, ash::KeyboardControllerObserver implementation:
 
 void WorkspaceLayoutManager::OnKeyboardVisibleBoundsChanged(
     const gfx::Rect& new_bounds) {
@@ -223,7 +223,7 @@
     NotifySystemUiAreaChanged();
 }
 
-void WorkspaceLayoutManager::OnKeyboardWorkspaceDisplacingBoundsChanged(
+void WorkspaceLayoutManager::OnKeyboardDisplacingBoundsChanged(
     const gfx::Rect& new_bounds_in_screen) {
   aura::Window* window = wm::GetActiveWindow();
   if (!window)
diff --git a/ash/wm/workspace/workspace_layout_manager.h b/ash/wm/workspace/workspace_layout_manager.h
index 19292323..540a6b2 100644
--- a/ash/wm/workspace/workspace_layout_manager.h
+++ b/ash/wm/workspace/workspace_layout_manager.h
@@ -9,7 +9,7 @@
 #include <set>
 
 #include "ash/ash_export.h"
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "ash/shelf/shelf_observer.h"
 #include "ash/shell_observer.h"
 #include "ash/wm/window_state_observer.h"
@@ -32,15 +32,14 @@
 }
 
 // LayoutManager used on the window created for a workspace.
-class ASH_EXPORT WorkspaceLayoutManager
-    : public aura::LayoutManager,
-      public aura::WindowObserver,
-      public ::wm::ActivationChangeObserver,
-      public keyboard::KeyboardControllerObserver,
-      public display::DisplayObserver,
-      public ShellObserver,
-      public wm::WindowStateObserver,
-      public ShelfObserver {
+class ASH_EXPORT WorkspaceLayoutManager : public aura::LayoutManager,
+                                          public aura::WindowObserver,
+                                          public ::wm::ActivationChangeObserver,
+                                          public KeyboardControllerObserver,
+                                          public display::DisplayObserver,
+                                          public ShellObserver,
+                                          public wm::WindowStateObserver,
+                                          public ShelfObserver {
  public:
   // |window| is the container for this layout manager.
   explicit WorkspaceLayoutManager(aura::Window* window);
@@ -87,10 +86,9 @@
       aura::Window* gained_active,
       aura::Window* lost_active) override;
 
-  // keyboard::KeyboardControllerObserver:
+  // KeyboardControllerObserver:
   void OnKeyboardVisibleBoundsChanged(const gfx::Rect& new_bounds) override;
-  void OnKeyboardWorkspaceDisplacingBoundsChanged(
-      const gfx::Rect& new_bounds) override;
+  void OnKeyboardDisplacingBoundsChanged(const gfx::Rect& new_bounds) override;
 
   // WindowStateObserver:
   void OnPostWindowStateTypeChange(wm::WindowState* window_state,
diff --git a/ash/wm/workspace/workspace_layout_manager_unittest.cc b/ash/wm/workspace/workspace_layout_manager_unittest.cc
index 75b08b7..232cfd1 100644
--- a/ash/wm/workspace/workspace_layout_manager_unittest.cc
+++ b/ash/wm/workspace/workspace_layout_manager_unittest.cc
@@ -1638,8 +1638,7 @@
   }
 
   void ShowKeyboard() {
-    layout_manager_->OnKeyboardWorkspaceDisplacingBoundsChanged(
-        keyboard_bounds_);
+    layout_manager_->OnKeyboardDisplacingBoundsChanged(keyboard_bounds_);
     restore_work_area_insets_ = GetPrimaryDisplay().GetWorkAreaInsets();
     Shell::Get()->SetDisplayWorkAreaInsets(
         Shell::GetPrimaryRootWindow(),
@@ -1649,7 +1648,7 @@
   void HideKeyboard() {
     Shell::Get()->SetDisplayWorkAreaInsets(Shell::GetPrimaryRootWindow(),
                                            restore_work_area_insets_);
-    layout_manager_->OnKeyboardWorkspaceDisplacingBoundsChanged(gfx::Rect());
+    layout_manager_->OnKeyboardDisplacingBoundsChanged(gfx::Rect());
   }
 
   // Initializes the keyboard bounds using the bottom half of the work area.
diff --git a/chrome/VERSION b/chrome/VERSION
index 55f74ea..cc6cebc 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=77
 MINOR=0
-BUILD=3824
+BUILD=3825
 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 1615b8a..e4254cf 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1557,6 +1557,7 @@
   "java/src/org/chromium/chrome/browser/tabmodel/document/AsyncTabCreationParams.java",
   "java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java",
   "java/src/org/chromium/chrome/browser/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java",
+  "java/src/org/chromium/chrome/browser/tab_activity_glue/TabDelegateFactoryImpl.java",
   "java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java",
   "java/src/org/chromium/chrome/browser/tasks/EngagementTimeUtil.java",
   "java/src/org/chromium/chrome/browser/tasks/JourneyManager.java",
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediator.java
index 94ba7a3..52f6037 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherMediator.java
@@ -357,7 +357,8 @@
 
     @Nullable
     TabListMediator.TabActionListener getCreateGroupButtonOnClickListener(Tab tab) {
-        if (!ableToCreateGroup(tab)) return null;
+        if (!ableToCreateGroup(tab) || FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled())
+            return null;
 
         return tabId -> {
             Tab parentTab = TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
index f70cea2..a12fd4f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
@@ -98,16 +98,15 @@
 
             @Override
             public void didSelectTab(Tab tab, int type, int lastId) {
-                if (type == TabSelectionType.FROM_USER)
+                if (type == TabSelectionType.FROM_USER) {
                     // Cancel the zooming into tab grid card animation.
                     mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, null);
                     mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, false);
+                }
             }
 
             @Override
             public void willCloseTab(Tab tab, boolean animate) {
-                updateDialog();
-                updateGridTabSwitcher();
                 List<Tab> relatedTabs = getRelatedTabs(tab.getId());
                 // If current tab is closed and tab group is not empty, hand over ID of the next
                 // tab in the group to mCurrentTabId.
@@ -115,6 +114,8 @@
                 if (tab.getId() == mCurrentTabId) {
                     mCurrentTabId = relatedTabs.get(0).getId();
                 }
+                updateDialog();
+                updateGridTabSwitcher();
             }
         };
         mTabModelSelector.getTabModelFilterProvider().addTabModelFilterObserver(mTabModelObserver);
@@ -134,9 +135,11 @@
                             .getCurrentTabModelFilter();
             int index = filter.indexOf(
                     TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId));
-            Rect rect = mAnimationOriginProvider.getAnimationOriginRect(index);
+            if (mAnimationOriginProvider != null) {
+                Rect rect = mAnimationOriginProvider.getAnimationOriginRect(index);
+                mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, rect);
+            }
             updateDialog();
-            mModel.set(TabGridSheetProperties.ANIMATION_SOURCE_RECT, rect);
             mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, true);
         } else {
             mModel.set(TabGridSheetProperties.IS_DIALOG_VISIBLE, false);
@@ -154,7 +157,9 @@
     }
 
     private void updateGridTabSwitcher() {
-        if (!mModel.get(TabGridSheetProperties.IS_DIALOG_VISIBLE)) return;
+        if (!mModel.get(TabGridSheetProperties.IS_DIALOG_VISIBLE)
+                || mGridTabSwitcherResetHandler == null)
+            return;
         mGridTabSwitcherResetHandler.resetWithTabList(
                 mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter(), false);
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
index 69f09c7a..a0fc983 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
@@ -46,6 +46,7 @@
     private ScrimView.ScrimParams mScrimParams;
     private View mBlockView;
     private Animator mCurrentAnimator;
+    private ObjectAnimator mBasicFadeIn;
     private ObjectAnimator mBasicFadeOut;
     private AnimatorSet mShowDialogAnimation;
     private AnimatorSet mHideDialogAnimation;
@@ -99,9 +100,14 @@
     }
 
     private void prepareAnimation() {
+        mBasicFadeIn = ObjectAnimator.ofFloat(mDialogContainerView, View.ALPHA, 0f, 1f);
+        mBasicFadeIn.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
+        mBasicFadeIn.setDuration(DIALOG_ANIMATION_DURATION);
+
         mBasicFadeOut = ObjectAnimator.ofFloat(mDialogContainerView, View.ALPHA, 1f, 0f);
         mBasicFadeOut.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
         mBasicFadeOut.setDuration(DIALOG_ANIMATION_DURATION);
+
         mShowDialogAnimationListener = new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -124,6 +130,11 @@
         // mHideDialogAnimation and play basic fade out instead of zooming back to corresponding tab
         // grid card.
         if (rect == null) {
+            mShowDialogAnimation = new AnimatorSet();
+            mShowDialogAnimation.play(mBasicFadeIn);
+            mShowDialogAnimation.removeAllListeners();
+            mShowDialogAnimation.addListener(mShowDialogAnimationListener);
+
             mHideDialogAnimation = new AnimatorSet();
             mHideDialogAnimation.play(mBasicFadeOut);
             mHideDialogAnimation.removeAllListeners();
@@ -159,6 +170,7 @@
                 .with(showMoveYAnimator)
                 .with(showScaleXAnimator)
                 .with(showScaleYAnimator);
+        mShowDialogAnimation.removeAllListeners();
         mShowDialogAnimation.addListener(mShowDialogAnimationListener);
 
         final ObjectAnimator hideMoveYAnimator =
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 a025ade..72d51a7 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
@@ -23,6 +23,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.tabgroup.TabGroupModelFilter;
 import org.chromium.chrome.browser.toolbar.bottom.BottomControlsCoordinator;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -40,6 +41,7 @@
     private final PropertyModel mTabStripToolbarModel;
     private final ThemeColorProvider mThemeColorProvider;
     private TabGridSheetCoordinator mTabGridSheetCoordinator;
+    private TabGridDialogCoordinator mTabGridDialogCoordinator;
     private TabListCoordinator mTabStripCoordinator;
     private TabGroupUiMediator mMediator;
     private TabStripToolbarCoordinator mTabStripToolbarCoordinator;
@@ -79,9 +81,20 @@
                 mTabStripToolbarCoordinator.getTabListContainerView(), null, true,
                 R.layout.tab_list_recycler_view_layout, COMPONENT_NAME);
 
-        mTabGridSheetCoordinator =
-                new TabGridSheetCoordinator(mContext, activity.getBottomSheetController(),
-                        tabModelSelector, tabContentManager, activity, mThemeColorProvider);
+        if (FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) {
+            // TODO(yuezhanggg): find a way to enable interactions between grid tab switcher and the
+            // dialog here.
+            mTabGridSheetCoordinator = null;
+
+            mTabGridDialogCoordinator = new TabGridDialogCoordinator(mContext, tabModelSelector,
+                    tabContentManager, activity, activity.getCompositorViewHolder(), null, null);
+        } else {
+            mTabGridSheetCoordinator =
+                    new TabGridSheetCoordinator(mContext, activity.getBottomSheetController(),
+                            tabModelSelector, tabContentManager, activity, mThemeColorProvider);
+
+            mTabGridDialogCoordinator = null;
+        }
 
         mMediator = new TabGroupUiMediator(visibilityController, this, mTabStripToolbarModel,
                 tabModelSelector, activity,
@@ -92,7 +105,7 @@
 
     /**
      * Handles a reset event originated from {@link TabGroupUiMediator}
-     * when the bottom sheet is collapsed.
+     * when the bottom sheet is collapsed or the dialog is hidden.
      *
      * @param tabs List of Tabs to reset.
      */
@@ -103,13 +116,17 @@
 
     /**
      * Handles a reset event originated from {@link TabGroupUiMediator}
-     * when the bottom sheet is expanded.
+     * when the bottom sheet is expanded or the dialog is shown.
      *
      * @param tabs List of Tabs to reset.
      */
     @Override
-    public void resetSheetWithListOfTabs(List<Tab> tabs) {
-        mTabGridSheetCoordinator.resetWithListOfTabs(tabs);
+    public void resetGridWithListOfTabs(List<Tab> tabs) {
+        if (mTabGridDialogCoordinator == null) {
+            mTabGridSheetCoordinator.resetWithListOfTabs(tabs);
+        } else {
+            mTabGridDialogCoordinator.resetWithListOfTabs(tabs);
+        }
     }
 
     /**
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 dee69c376..19a80b3 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
@@ -39,7 +39,7 @@
     interface ResetHandler {
         /**
          * Handles a reset event originated from {@link TabGroupUiMediator}
-         * when the bottom sheet is collapsed.
+         * when the bottom sheet is collapsed or the dialog is hidden.
          *
          * @param tabs List of Tabs to reset.
          */
@@ -47,11 +47,11 @@
 
         /**
          * Handles a reset event originated from {@link TabGroupUiMediator}
-         * when the bottom sheet is expanded.
+         * when the bottom sheet is expanded or the dialog is shown.
          *
          * @param tabs List of Tabs to reset.
          */
-        void resetSheetWithListOfTabs(List<Tab> tabs);
+        void resetGridWithListOfTabs(List<Tab> tabs);
     }
 
     private final PropertyModel mToolbarPropertyModel;
@@ -176,7 +176,7 @@
         mToolbarPropertyModel.set(TabStripToolbarViewProperties.EXPAND_CLICK_LISTENER, view -> {
             Tab currentTab = mTabModelSelector.getCurrentTab();
             if (currentTab == null) return;
-            mResetHandler.resetSheetWithListOfTabs(getRelatedTabsForId(currentTab.getId()));
+            mResetHandler.resetGridWithListOfTabs(getRelatedTabsForId(currentTab.getId()));
             RecordUserAction.record("TabGroup.ExpandedFromStrip");
         });
         mToolbarPropertyModel.set(TabStripToolbarViewProperties.ADD_CLICK_LISTENER, view -> {
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
index de50cb6..9a6a5c6 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedLoggingBridge.java
@@ -208,6 +208,7 @@
 
     @Override
     public void onPietFrameRenderingEvent(List<Integer> pietErrorCodes) {
+        if (mNativeFeedLoggingBridge == 0) return;
         int[] pietErrorCodesArray = new int[pietErrorCodes.size()];
         for (int i = 0; i < pietErrorCodes.size(); ++i) {
             pietErrorCodesArray[i] = pietErrorCodes.get(i);
@@ -217,12 +218,18 @@
 
     @Override
     public void onVisualElementClicked(ElementLoggingData data, int elementType) {
-        // TODO(https://crbug.com/924739): Implementation.
+        if (mNativeFeedLoggingBridge == 0) return;
+        nativeOnVisualElementClicked(mNativeFeedLoggingBridge, elementType,
+                data.getPositionInStream(),
+                TimeUnit.SECONDS.toMillis(data.getTimeContentBecameAvailable()));
     }
 
     @Override
     public void onVisualElementViewed(ElementLoggingData data, int elementType) {
-        // TODO(https://crbug.com/924739): Implementation.
+        if (mNativeFeedLoggingBridge == 0) return;
+        nativeOnVisualElementViewed(mNativeFeedLoggingBridge, elementType,
+                data.getPositionInStream(),
+                TimeUnit.SECONDS.toMillis(data.getTimeContentBecameAvailable()));
     }
 
     @Override
@@ -393,6 +400,10 @@
             long nativeFeedLoggingBridge, long spinnerShownTimeMs, int spinnerType);
     private native void nativeOnPietFrameRenderingEvent(
             long nativeFeedLoggingBridge, int[] pietErrorCodes);
+    private native void nativeOnVisualElementClicked(long nativeFeedLoggingBridge, int elementType,
+            int position, long timeContentBecameAvailableMs);
+    private native void nativeOnVisualElementViewed(long nativeFeedLoggingBridge, int elementType,
+            int position, long timeContentBecameAvailableMs);
     private native void nativeOnInternalError(long nativeFeedLoggingBridge, int internalError);
     private native void nativeOnTokenCompleted(
             long nativeFeedLoggingBridge, boolean wasSynthetic, int contentCount, int tokenCount);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index ebe13e40..657c16a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -123,6 +123,7 @@
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tab.TabRedirectHandler;
 import org.chromium.chrome.browser.tab.TabStateBrowserControlsVisibilityDelegate;
+import org.chromium.chrome.browser.tab_activity_glue.TabDelegateFactoryImpl;
 import org.chromium.chrome.browser.tabbed_mode.TabbedAppMenuPropertiesDelegate;
 import org.chromium.chrome.browser.tabbed_mode.TabbedRootUiCoordinator;
 import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
@@ -448,7 +449,11 @@
         }
     }
 
-    private class TabbedModeTabDelegateFactory extends TabDelegateFactory {
+    private class TabbedModeTabDelegateFactory extends TabDelegateFactoryImpl {
+        private TabbedModeTabDelegateFactory() {
+            super(ChromeTabbedActivity.this);
+        }
+
         @Override
         public void createBrowserControlsState(Tab tab) {
             TabBrowserControlsState.create(tab,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
index 6e1f760..6f59e20 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tab.TabState;
+import org.chromium.chrome.browser.tab_activity_glue.TabDelegateFactoryImpl;
 import org.chromium.chrome.browser.tabmodel.SingleTabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -114,7 +115,7 @@
      * @return {@link TabDelegateFactory} to be used while creating the associated {@link Tab}.
      */
     protected TabDelegateFactory createTabDelegateFactory() {
-        return new TabDelegateFactory();
+        return new TabDelegateFactoryImpl(this);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
index 2f88199..8aebfd5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
@@ -249,7 +249,7 @@
             mLastOnDownTimeStamp = time;
 
             if (shouldIgnoreTouchInput()) return;
-            if (mNavigationEnabled) mNavigationHandler.onDown();
+            if (mNavigationHandler != null) mNavigationHandler.onDown();
             mStacks.get(getTabStackIndex()).onDown(time);
         }
 
@@ -262,7 +262,7 @@
         public void drag(float x, float y, float dx, float dy, float tx, float ty) {
             if (shouldIgnoreTouchInput()) return;
 
-            if (mNavigationEnabled) {
+            if (mNavigationHandler != null) {
                 mNavigationHandler.onScroll(mLastOnDownX * mDpToPx, -dx * mDpToPx, -dy * mDpToPx,
                         x * mDpToPx, y * mDpToPx);
                 if (mNavigationHandler.isActive()) {
@@ -355,7 +355,7 @@
         private void onUpOrCancel(long time) {
             if (shouldIgnoreTouchInput()) return;
 
-            if (mNavigationEnabled && mNavigationHandler.isActive()) {
+            if (mNavigationHandler != null && mNavigationHandler.isActive()) {
                 mNavigationHandler.onTouchEvent(MotionEvent.ACTION_UP);
             }
             cancelDragTabs(time);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
index ae58803..0bda3b8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
@@ -50,7 +50,7 @@
  * by a {@link CustomTabActivity}.
  */
 @ActivityScope
-public class CustomTabDelegateFactory extends TabDelegateFactory {
+public class CustomTabDelegateFactory implements TabDelegateFactory {
     /**
      * A custom external navigation delegate that forbids the intent picker from showing up.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
index d996efa4..4690468 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
@@ -7,6 +7,7 @@
 import android.app.Activity;
 import android.app.SearchManager;
 import android.content.Intent;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.support.v4.app.ActivityOptionsCompat;
 import android.text.TextUtils;
@@ -22,8 +23,10 @@
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.WebContentsFactory;
 import org.chromium.chrome.browser.WindowDelegate;
+import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
+import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler;
 import org.chromium.chrome.browser.init.AsyncInitializationActivity;
 import org.chromium.chrome.browser.init.SingleWindowKeyboardVisibilityDelegate;
 import org.chromium.chrome.browser.locale.LocaleManager;
@@ -34,10 +37,12 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
+import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.components.url_formatter.UrlFormatter;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate;
 import org.chromium.ui.base.ActivityWindowAndroid;
@@ -171,8 +176,47 @@
                        .setWindow(getWindowAndroid())
                        .setLaunchType(TabLaunchType.FROM_EXTERNAL_APP)
                        .build();
-        mTab.initialize(WebContentsFactory.createWebContents(false, false),
-                new TabDelegateFactory(), false, null, false);
+        TabDelegateFactory factory = new TabDelegateFactory() {
+            @Override
+            public TabWebContentsDelegateAndroid createWebContentsDelegate(Tab tab) {
+                return new TabWebContentsDelegateAndroid(tab) {
+                    @Override
+                    protected boolean shouldResumeRequestsForCreatedWindow() {
+                        return false;
+                    }
+
+                    @Override
+                    protected boolean addNewContents(WebContents sourceWebContents,
+                            WebContents webContents, int disposition, Rect initialPosition,
+                            boolean userGesture) {
+                        return false;
+                    }
+
+                    @Override
+                    protected void setOverlayMode(boolean useOverlayMode) {}
+                };
+            }
+
+            @Override
+            public ExternalNavigationHandler createExternalNavigationHandler(Tab tab) {
+                return null;
+            }
+
+            @Override
+            public ContextMenuPopulator createContextMenuPopulator(Tab tab) {
+                return null;
+            }
+
+            @Override
+            public boolean canShowAppBanners() {
+                return false;
+            }
+
+            @Override
+            public void createBrowserControlsState(Tab tab) {}
+        };
+        mTab.initialize(
+                WebContentsFactory.createWebContents(false, false), factory, false, null, false);
         mTab.loadUrl(new LoadUrlParams(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL));
 
         mSearchBoxDataProvider.onNativeLibraryReady(mTab);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
index 3f83208..fc98d94 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/DEPS
@@ -14,7 +14,4 @@
     "+ui/android/java/src/org/chromium/ui/base",
     "+ui/android/java/src/org/chromium/ui/mojom",
   ],
-  'TabDelegateFactory\.java': [
-    "+chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue",
-  ]
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index 8fa6394..3588d08b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -865,11 +865,11 @@
 
             if (tabState != null) restoreFieldsFromState(tabState);
 
-            mDelegateFactory = delegateFactory;
             initializeNative();
 
             RevenueStats.getInstance().tabCreated(this);
 
+            mDelegateFactory = delegateFactory;
             mDelegateFactory.createBrowserControlsState(this);
 
             // If there is a frozen WebContents state or a pending lazy load, don't create a new
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuPopulator.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuPopulator.java
index 977044b..8329944 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuPopulator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuPopulator.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.tab;
 
 import android.content.Context;
+import android.support.annotation.Nullable;
 import android.util.Pair;
 import android.view.ContextMenu;
 
@@ -20,6 +21,7 @@
  * A simple wrapper around a {@link ContextMenuPopulator} to handle observer notification.
  */
 public class TabContextMenuPopulator implements ContextMenuPopulator {
+    @Nullable
     private final ContextMenuPopulator mPopulator;
     private final Tab mTab;
 
@@ -36,7 +38,9 @@
 
     @Override
     public void onDestroy() {
-        mPopulator.onDestroy();
+        // |mPopulator| can be null for activities that do not use context menu. Following
+        // methods are not called, but |onDestroy| is.
+        if (mPopulator != null) mPopulator.onDestroy();
     }
 
     @Override
@@ -55,4 +59,4 @@
     public boolean onItemSelected(ContextMenuHelper helper, ContextMenuParams params, int itemId) {
         return mPopulator.onItemSelected(helper, params, itemId);
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabDelegateFactory.java
index 72f5f4a..6d81f42 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabDelegateFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabDelegateFactory.java
@@ -4,26 +4,21 @@
 
 package org.chromium.chrome.browser.tab;
 
-import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator;
 import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator;
 import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler;
-import org.chromium.chrome.browser.tab_activity_glue.ActivityTabWebContentsDelegateAndroid;
 import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
 
 /**
- * A factory class to create {@link Tab} related delegates.
- * TODO(jinsukkim): Turn this into an interface.
+ * An interface for factory to create {@link Tab} related delegates.
  */
-public class TabDelegateFactory {
+public interface TabDelegateFactory {
     /**
      * Creates the {@link WebContentsDelegateAndroid} the tab will be initialized with.
      * @param tab The associated {@link Tab}.
      * @return The {@link WebContentsDelegateAndroid} to be used for this tab.
      */
-    public TabWebContentsDelegateAndroid createWebContentsDelegate(Tab tab) {
-        return new ActivityTabWebContentsDelegateAndroid(tab, tab.getActivity());
-    }
+    TabWebContentsDelegateAndroid createWebContentsDelegate(Tab tab);
 
     /**
      * Creates the {@link ExternalNavigationHandler} the tab will use for its
@@ -31,38 +26,24 @@
      * @param tab The associated {@link Tab}.
      * @return The {@link ExternalNavigationHandler} to be used for this tab.
      */
-
-    public ExternalNavigationHandler createExternalNavigationHandler(Tab tab) {
-        return new ExternalNavigationHandler(tab);
-    }
+    ExternalNavigationHandler createExternalNavigationHandler(Tab tab);
 
     /**
      * Creates the {@link ContextMenuPopulator} the tab will be initialized with.
      * @param tab The associated {@link Tab}.
      * @return The {@link ContextMenuPopulator} to be used for this tab.
      */
-    public ContextMenuPopulator createContextMenuPopulator(Tab tab) {
-        return new ChromeContextMenuPopulator(new TabContextMenuItemDelegate(tab),
-                ChromeContextMenuPopulator.ContextMenuMode.NORMAL);
-    }
+    ContextMenuPopulator createContextMenuPopulator(Tab tab);
 
     /**
      * Return true if app banners are to be permitted in this tab. May need to be overridden.
      * @return true if app banners are permitted, and false otherwise.
      */
-    public boolean canShowAppBanners() {
-        return true;
-    }
+    boolean canShowAppBanners();
 
     /**
      * Creates the {@link BrowserControlsVisibilityDelegate} the tab will be initialized with.
      * @param tab The associated {@link Tab}.
      */
-    public void createBrowserControlsState(Tab tab) {
-        TabBrowserControlsState.create(tab, new TabStateBrowserControlsVisibilityDelegate(tab));
-    }
-
-    public TabDelegateFactory createNewTabDelegateFactory() {
-        return new TabDelegateFactory();
-    }
+    void createBrowserControlsState(Tab tab);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
index 48eaf0b..6e3d9fb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
@@ -196,6 +196,16 @@
     @CalledByNative
     protected abstract boolean shouldResumeRequestsForCreatedWindow();
 
+    /**
+     * Creates a new tab with the already-created WebContents. The tab for the added
+     * contents should be reparented correctly when this method returns.
+     * @param sourceWebContents Source WebContents from which the new one is created.
+     * @param webContents Newly created WebContents object.
+     * @param disposition WindowOpenDisposition indicating how the tab should be created.
+     * @param initialPosition Initial position of the content to be created.
+     * @param userGesture {@code true} if opened by user gesture.
+     * @return {@code true} if new tab was created successfully with a give WebContents.
+     */
     @CalledByNative
     protected abstract boolean addNewContents(WebContents sourceWebContents,
             WebContents webContents, int disposition, Rect initialPosition, boolean userGesture);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
index 18d0d0c..0eb3868 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue/ActivityTabWebContentsDelegateAndroid.java
@@ -128,7 +128,7 @@
     }
 
     @Override
-    public boolean addNewContents(WebContents sourceWebContents, WebContents webContents,
+    protected boolean addNewContents(WebContents sourceWebContents, WebContents webContents,
             int disposition, Rect initialPosition, boolean userGesture) {
         assert mWebContentsUrlMapping.containsKey(webContents);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue/TabDelegateFactoryImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue/TabDelegateFactoryImpl.java
new file mode 100644
index 0000000..4c207de
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue/TabDelegateFactoryImpl.java
@@ -0,0 +1,53 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tab_activity_glue;
+
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator;
+import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator;
+import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBrowserControlsState;
+import org.chromium.chrome.browser.tab.TabContextMenuItemDelegate;
+import org.chromium.chrome.browser.tab.TabDelegateFactory;
+import org.chromium.chrome.browser.tab.TabStateBrowserControlsVisibilityDelegate;
+import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
+
+/**
+ * A default implementation of {@link TabDelegateFactory}.
+ */
+public class TabDelegateFactoryImpl implements TabDelegateFactory {
+    private final ChromeActivity mActivity;
+
+    public TabDelegateFactoryImpl(ChromeActivity activity) {
+        mActivity = activity;
+    }
+
+    @Override
+    public TabWebContentsDelegateAndroid createWebContentsDelegate(Tab tab) {
+        return new ActivityTabWebContentsDelegateAndroid(tab, mActivity);
+    }
+
+    @Override
+    public ExternalNavigationHandler createExternalNavigationHandler(Tab tab) {
+        return new ExternalNavigationHandler(tab);
+    }
+
+    @Override
+    public ContextMenuPopulator createContextMenuPopulator(Tab tab) {
+        return new ChromeContextMenuPopulator(new TabContextMenuItemDelegate(tab),
+                ChromeContextMenuPopulator.ContextMenuMode.NORMAL);
+    }
+
+    @Override
+    public boolean canShowAppBanners() {
+        return true;
+    }
+
+    @Override
+    public void createBrowserControlsState(Tab tab) {
+        TabBrowserControlsState.create(tab, new TabStateBrowserControlsVisibilityDelegate(tab));
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
index 99ec101..2b552d0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
@@ -20,6 +20,7 @@
 import org.chromium.chrome.browser.tab.TabParentIntent;
 import org.chromium.chrome.browser.tab.TabRedirectHandler;
 import org.chromium.chrome.browser.tab.TabState;
+import org.chromium.chrome.browser.tab_activity_glue.TabDelegateFactoryImpl;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.components.url_formatter.UrlFormatter;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -359,8 +360,8 @@
     /**
      * @return The default tab delegate factory to be used if creating new tabs w/o parents.
      */
-    public TabDelegateFactory createDefaultTabDelegateFactory() {
-        return new TabDelegateFactory();
+    protected TabDelegateFactory createDefaultTabDelegateFactory() {
+        return new TabDelegateFactoryImpl(mActivity);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDelegateFactory.java
index 2f49768d..241cc89 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDelegateFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDelegateFactory.java
@@ -20,6 +20,7 @@
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
 import org.chromium.chrome.browser.tab_activity_glue.ActivityTabWebContentsDelegateAndroid;
+import org.chromium.chrome.browser.tab_activity_glue.TabDelegateFactoryImpl;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.webapk.lib.client.WebApkNavigationClient;
 
@@ -27,7 +28,7 @@
  * A {@link TabDelegateFactory} class to be used in all {@link Tab} instances owned by a
  * {@link SingleTabActivity}.
  */
-public class WebappDelegateFactory extends TabDelegateFactory {
+public class WebappDelegateFactory extends TabDelegateFactoryImpl {
     private static class WebappWebContentsDelegateAndroid
             extends ActivityTabWebContentsDelegateAndroid {
         private final WebappActivity mActivity;
@@ -92,6 +93,7 @@
     private final WebappActivity mActivity;
 
     public WebappDelegateFactory(WebappActivity activity) {
+        super(activity);
         mActivity = activity;
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
index ba308bc..328a5fd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
@@ -20,6 +20,7 @@
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.tab_activity_glue.TabDelegateFactoryImpl;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.test.ChromeActivityTestRule;
@@ -74,7 +75,8 @@
                                     .setWindow(mActivityTestRule.getActivity().getWindowAndroid())
                                     .setLaunchType(TabLaunchType.FROM_LONGPRESS_BACKGROUND)
                                     .build();
-                bgTab.initialize(null, new TabDelegateFactory(), true, null, false);
+                bgTab.initialize(null, new TabDelegateFactoryImpl(mActivityTestRule.getActivity()),
+                        true, null, false);
                 return bgTab;
             }
         });
@@ -119,7 +121,8 @@
                                     .setWindow(mActivityTestRule.getActivity().getWindowAndroid())
                                     .setLaunchType(TabLaunchType.FROM_LONGPRESS_BACKGROUND)
                                     .build();
-                bgTab.initialize(null, new TabDelegateFactory(), true, null, false);
+                bgTab.initialize(null, new TabDelegateFactoryImpl(mActivityTestRule.getActivity()),
+                        true, null, false);
                 bgTab.loadUrl(new LoadUrlParams(mTestUrl));
                 bgTab.show(TabSelectionType.FROM_USER);
                 return bgTab;
@@ -137,7 +140,8 @@
                                     .setWindow(mActivityTestRule.getActivity().getWindowAndroid())
                                     .setLaunchType(TabLaunchType.FROM_LONGPRESS_BACKGROUND)
                                     .build();
-                bgTab.initialize(null, new TabDelegateFactory(), true, null, false);
+                bgTab.initialize(null, new TabDelegateFactoryImpl(mActivityTestRule.getActivity()),
+                        true, null, false);
                 bgTab.loadUrl(new LoadUrlParams(mTestUrl));
                 // Simulate the renderer being killed by the OS.
                 ChromeTabUtils.simulateRendererKilledForTesting(bgTab, false);
@@ -157,7 +161,8 @@
                                     .setWindow(mActivityTestRule.getActivity().getWindowAndroid())
                                     .setLaunchType(TabLaunchType.FROM_LONGPRESS_BACKGROUND)
                                     .build();
-                bgTab.initialize(null, new TabDelegateFactory(), true, null, false);
+                bgTab.initialize(null, new TabDelegateFactoryImpl(mActivityTestRule.getActivity()),
+                        true, null, false);
                 bgTab.show(TabSelectionType.FROM_USER);
                 return bgTab;
             }
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index 338daf2..83ceb0a 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -626,7 +626,6 @@
       "//chromeos/services/ime/public/cpp:manifest",
       "//chromeos/services/network_config/public/cpp:manifest",
       "//chromeos/services/secure_channel/public/cpp:manifest",
-      "//services/ws/public/mojom/input_devices",
     ]
   }
 
diff --git a/chrome/app/DEPS b/chrome/app/DEPS
index 13bf73a..291ba12 100644
--- a/chrome/app/DEPS
+++ b/chrome/app/DEPS
@@ -37,7 +37,6 @@
   "+sandbox",
   "+services/preferences/public",
   "+services/service_manager/public",
-  "+services/ws/public",
   "+third_party/breakpad/breakpad",
   "+third_party/crashpad/crashpad",
 ]
@@ -68,7 +67,6 @@
     "+services/identity",
     "+services/image_annotation/public",
     "+services/resource_coordinator/public",
-    "+services/ws/common",
     "+third_party/blink/public/mojom",
   ],
   "chrome_content_gpu_overlay_manifest\.cc": [
diff --git a/chrome/app/builtin_service_manifests.cc b/chrome/app/builtin_service_manifests.cc
index 03f0652..170afd5 100644
--- a/chrome/app/builtin_service_manifests.cc
+++ b/chrome/app/builtin_service_manifests.cc
@@ -30,7 +30,6 @@
 #include "chromeos/services/ime/public/cpp/manifest.h"
 #include "chromeos/services/network_config/public/cpp/manifest.h"
 #include "chromeos/services/secure_channel/public/cpp/manifest.h"
-#include "services/ws/public/mojom/input_devices/input_device_controller.mojom.h"
 #endif
 
 #if defined(OS_MACOSX)
@@ -97,12 +96,6 @@
 #endif
                               spellcheck::mojom::SpellCheckHost,
                               startup_metric_utils::mojom::StartupMetricHost>())
-#if defined(OS_CHROMEOS)
-        // Only used in the classic Ash case.
-        .ExposeCapability("input_device_controller",
-                          service_manager::Manifest::InterfaceList<
-                              ws::mojom::InputDeviceController>())
-#endif
         .RequireCapability(chrome::mojom::kRendererServiceName, "browser")
         .Build()
   };
diff --git a/chrome/app/vector_icons/send_tab_to_self.icon b/chrome/app/vector_icons/send_tab_to_self.icon
index a303c736..1c7966a 100644
--- a/chrome/app/vector_icons/send_tab_to_self.icon
+++ b/chrome/app/vector_icons/send_tab_to_self.icon
@@ -3,40 +3,32 @@
 // found in the LICENSE file.
 
 CANVAS_DIMENSIONS, 16,
-MOVE_TO, 0, 0,
-R_H_LINE_TO, 16,
-R_V_LINE_TO, 16,
+MOVE_TO, 2.5f, 4.21f,
+R_H_LINE_TO, 11.25f,
+V_LINE_TO, 3,
+H_LINE_TO, 2.5f,
+R_CUBIC_TO, -0.69f, 0, -1.25f, 0.54f, -1.25f, 1.21f,
+R_V_LINE_TO, 6.63f,
 H_LINE_TO, 0,
+R_V_LINE_TO, 1.81f,
+R_H_LINE_TO, 8.75f,
+R_V_LINE_TO, -1.81f,
+H_LINE_TO, 2.5f,
+R_V_LINE_TO, -6.63f,
 CLOSE,
-MOVE_TO, 3.4f, 4.41f,
-R_H_LINE_TO, 10.8f,
-V_LINE_TO, 3.25f,
-H_LINE_TO, 3.4f,
-R_CUBIC_TO, -0.66f, 0, -1.2f, 0.52f, -1.2f, 1.16f,
-R_V_LINE_TO, 6.36f,
-H_LINE_TO, 1,
-R_V_LINE_TO, 1.74f,
-R_H_LINE_TO, 8.4f,
-R_V_LINE_TO, -1.74f,
-R_H_LINE_TO, -6,
+R_MOVE_TO, 11.88f, 1.21f,
+R_H_LINE_TO, -3.75f,
+R_ARC_TO, 0.62f, 0.62f, 0, 0, 0, -0.62f, 0.6f,
+R_V_LINE_TO, 6.03f,
+R_CUBIC_TO, 0, 0.33f, 0.28f, 0.6f, 0.63f, 0.6f,
+R_H_LINE_TO, 3.75f,
+ARC_TO, 0.62f, 0.62f, 0, 0, 0, 15, 12.04f,
+V_LINE_TO, 6.01f,
+R_ARC_TO, 0.62f, 0.62f, 0, 0, 0, -0.62f, -0.6f,
 CLOSE,
-R_MOVE_TO, 11.4f, 1.16f,
-R_H_LINE_TO, -3.6f,
-R_CUBIC_TO, -0.33f, 0, -0.6f, 0.26f, -0.6f, 0.58f,
-R_V_LINE_TO, 5.79f,
-R_CUBIC_TO, 0, 0.32f, 0.27f, 0.58f, 0.6f, 0.58f,
-R_H_LINE_TO, 3.6f,
-R_CUBIC_TO, 0.33f, 0, 0.6f, -0.26f, 0.6f, -0.58f,
-V_LINE_TO, 6.14f,
-R_ARC_TO, 0.59f, 0.59f, 0, 0, 0, -0.6f, -0.58f,
-CLOSE,
-R_MOVE_TO, -0.6f, 5.21f,
-R_H_LINE_TO, -2.4f,
-R_V_LINE_TO, -4.05f,
-R_H_LINE_TO, 2.4f,
-CLOSE,
-MOVE_TO, 0, 0,
-R_H_LINE_TO, 16,
-R_V_LINE_TO, 16,
-H_LINE_TO, 0,
+R_MOVE_TO, -0.62f, 5.42f,
+R_H_LINE_TO, -2.5f,
+V_LINE_TO, 6.62f,
+R_H_LINE_TO, 2.5f,
+R_V_LINE_TO, 4.22f,
 CLOSE
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index e23e995..acfd23a8 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3521,8 +3521,6 @@
       "//chromeos/strings",
       "//components/services/font:lib",
       "//components/services/font/public/interfaces",
-      "//services/ws/public/cpp/input_devices",
-      "//services/ws/public/cpp/input_devices:input_device_controller",
       "//ui/ozone",
     ]
     allow_circular_includes_from += [ "//chrome/browser/chromeos" ]
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 51703a5..84b5578 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -93,7 +93,6 @@
   "+services/video_capture/public",
   "+services/viz/public",
   "+services/viz/privileged",
-  "+services/ws/public",
   "+skia/ext",
   "+third_party/boringssl/src/include",
   "+third_party/crashpad",
diff --git a/chrome/browser/android/feed/feed_logging_bridge.cc b/chrome/browser/android/feed/feed_logging_bridge.cc
index 2a206f4..b0cda75 100644
--- a/chrome/browser/android/feed/feed_logging_bridge.cc
+++ b/chrome/browser/android/feed/feed_logging_bridge.cc
@@ -171,6 +171,28 @@
   feed_logging_metrics_->OnPietFrameRenderingEvent(std::move(piet_error_codes));
 }
 
+void FeedLoggingBridge::OnVisualElementClicked(
+    JNIEnv* j_env,
+    const base::android::JavaRef<jobject>& j_this,
+    const jint j_element_type,
+    const jint j_position,
+    const jlong j_timeContentBecameAvailableMs) {
+  feed_logging_metrics_->OnVisualElementClicked(
+      j_element_type, j_position,
+      base::Time::FromJavaTime(j_timeContentBecameAvailableMs));
+}
+
+void FeedLoggingBridge::OnVisualElementViewed(
+    JNIEnv* j_env,
+    const base::android::JavaRef<jobject>& j_this,
+    const jint j_element_type,
+    const jint j_position,
+    const jlong j_timeContentBecameAvailableMs) {
+  feed_logging_metrics_->OnVisualElementViewed(
+      j_element_type, j_position,
+      base::Time::FromJavaTime(j_timeContentBecameAvailableMs));
+}
+
 void FeedLoggingBridge::OnInternalError(JNIEnv* j_env,
                                         const JavaRef<jobject>& j_this,
                                         const jint j_internal_error) {
diff --git a/chrome/browser/android/feed/feed_logging_bridge.h b/chrome/browser/android/feed/feed_logging_bridge.h
index cbf8a1bb..abe7bd93 100644
--- a/chrome/browser/android/feed/feed_logging_bridge.h
+++ b/chrome/browser/android/feed/feed_logging_bridge.h
@@ -102,6 +102,18 @@
       const base::android::JavaRef<jobject>& j_this,
       const base::android::JavaRef<jintArray>& j_piet_error_codes);
 
+  void OnVisualElementClicked(JNIEnv* j_env,
+                              const base::android::JavaRef<jobject>& j_this,
+                              const jint j_element_type,
+                              const jint j_position,
+                              const jlong j_timeContentBecameAvailableMs);
+
+  void OnVisualElementViewed(JNIEnv* j_env,
+                             const base::android::JavaRef<jobject>& j_this,
+                             const jint j_element_type,
+                             const jint j_position,
+                             const jlong j_timeContentBecameAvailableMs);
+
   void OnInternalError(JNIEnv* j_env,
                        const base::android::JavaRef<jobject>& j_this,
                        const jint j_internal_error);
diff --git a/chrome/browser/banners/app_banner_manager.cc b/chrome/browser/banners/app_banner_manager.cc
index 0d2301a..9fa0c4b 100644
--- a/chrome/browser/banners/app_banner_manager.cc
+++ b/chrome/browser/banners/app_banner_manager.cc
@@ -210,6 +210,20 @@
   observer_list_.RemoveObserver(observer);
 }
 
+void AppBannerManager::MigrateObserverListForTesting(
+    content::WebContents* web_contents) {
+  AppBannerManager* existing_manager = FromWebContents(web_contents);
+  for (Observer& observer : existing_manager->observer_list_)
+    observer.ObserveAppBannerManager(this);
+  DCHECK(existing_manager->observer_list_.begin() ==
+         existing_manager->observer_list_.end())
+      << "Old observer list must be empty after transfer to test instance.";
+}
+
+bool AppBannerManager::IsPromptAvailableForTesting() const {
+  return binding_.is_bound();
+}
+
 AppBannerManager::AppBannerManager(content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
       SiteEngagementObserver(SiteEngagementService::Get(
@@ -489,16 +503,6 @@
     observer.OnInstallableWebAppStatusUpdated();
 }
 
-void AppBannerManager::MigrateObserverListForTesting(
-    content::WebContents* web_contents) {
-  AppBannerManager* existing_manager = FromWebContents(web_contents);
-  for (Observer& observer : existing_manager->observer_list_)
-    observer.ObserveAppBannerManager(this);
-  DCHECK(existing_manager->observer_list_.begin() ==
-         existing_manager->observer_list_.end())
-      << "Old observer list must be empty after transfer to test instance.";
-}
-
 void AppBannerManager::Stop(InstallableStatusCode code) {
   ReportStatus(code);
 
diff --git a/chrome/browser/banners/app_banner_manager.h b/chrome/browser/banners/app_banner_manager.h
index 38304c0..67a9638 100644
--- a/chrome/browser/banners/app_banner_manager.h
+++ b/chrome/browser/banners/app_banner_manager.h
@@ -152,7 +152,8 @@
   // performs logging related to the app installation. Appinstalled event is
   // redundant for the beforeinstallprompt event's promise being resolved, but
   // is required by the install event spec.
-  void OnInstall(bool is_native, blink::WebDisplayMode display);
+  // This is virtual for testing.
+  virtual void OnInstall(bool is_native, blink::WebDisplayMode display);
 
   // Sends a message to the renderer that the user accepted the banner.
   void SendBannerAccepted();
@@ -163,6 +164,17 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
+  virtual base::WeakPtr<AppBannerManager> GetWeakPtr() = 0;
+
+  // Used by test subclasses that replace the existing AppBannerManager
+  // instance. The observer list must be transferred over to avoid dangling
+  // pointers in the observers.
+  void MigrateObserverListForTesting(content::WebContents* web_contents);
+
+  // Returns whether the site can call "event.prompt()" to prompt the user to
+  // install the site.
+  bool IsPromptAvailableForTesting() const;
+
  protected:
   explicit AppBannerManager(content::WebContents* web_contents);
   ~AppBannerManager() override;
@@ -189,7 +201,6 @@
   // alerting websites that a banner is about to be created.
   virtual std::string GetBannerType();
 
-  virtual base::WeakPtr<AppBannerManager> GetWeakPtr() = 0;
   virtual void InvalidateWeakPtrs() = 0;
 
   // Returns true if |has_sufficient_engagement_| is true or
@@ -296,11 +307,6 @@
   State state() const { return state_; }
   bool IsRunning() const;
 
-  // Used by test subclasses that replace the existing AppBannerManager
-  // instance. The observer list must be transferred over to avoid dangling
-  // pointers in the observers.
-  void MigrateObserverListForTesting(content::WebContents* web_contents);
-
   // The URL for which the banner check is being conducted.
   GURL validated_url_;
 
diff --git a/chrome/browser/banners/app_banner_manager_desktop_browsertest.cc b/chrome/browser/banners/app_banner_manager_desktop_browsertest.cc
index 68c1804..9c4f757 100644
--- a/chrome/browser/banners/app_banner_manager_desktop_browsertest.cc
+++ b/chrome/browser/banners/app_banner_manager_desktop_browsertest.cc
@@ -12,15 +12,20 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/banners/app_banner_manager_browsertest_base.h"
 #include "chrome/browser/banners/app_banner_manager_desktop.h"
 #include "chrome/browser/banners/app_banner_settings_helper.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/page_action/page_action_icon_container.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
@@ -37,11 +42,14 @@
 
   static FakeAppBannerManagerDesktop* CreateForWebContents(
       content::WebContents* web_contents) {
-    web_contents->SetUserData(
-        UserDataKey(),
-        std::make_unique<FakeAppBannerManagerDesktop>(web_contents));
-    return static_cast<FakeAppBannerManagerDesktop*>(
-        web_contents->GetUserData(UserDataKey()));
+    auto banner_manager =
+        std::make_unique<FakeAppBannerManagerDesktop>(web_contents);
+    banner_manager->MigrateObserverListForTesting(web_contents);
+
+    FakeAppBannerManagerDesktop* result = banner_manager.get();
+    web_contents->SetUserData(FakeAppBannerManagerDesktop::UserDataKey(),
+                              std::move(banner_manager));
+    return result;
   }
 
   // Configures a callback to be invoked when the app banner flow finishes.
@@ -49,7 +57,19 @@
 
   State state() { return AppBannerManager::state(); }
 
+  void AwaitAppInstall() {
+    base::RunLoop loop;
+    on_install_ = loop.QuitClosure();
+    loop.Run();
+  }
+
  protected:
+  void OnInstall(bool is_native, blink::WebDisplayMode display) override {
+    AppBannerManager::OnInstall(is_native, display);
+    if (on_install_)
+      std::move(on_install_).Run();
+  }
+
   void OnFinished() {
     if (on_done_) {
       base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
@@ -74,6 +94,7 @@
 
  private:
   base::OnceClosure on_done_;
+  base::OnceClosure on_install_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeAppBannerManagerDesktop);
 };
@@ -224,3 +245,65 @@
     EXPECT_TRUE(callback_called);
   }
 }
+
+IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest,
+                       InstallPromptAfterUserMenuInstall) {
+  // TODO(https://crbug.com/915043): Fix this test for the unified install
+  // codepath. This fails under unified install because the menu install path
+  // relies on extensions::TabHelper to call AppBannerManager::OnInstall which
+  // is no longer the case under unified install. This will be fixed in a follow
+  // up patch when AppBannerManager calls OnInstall by itself via
+  // AppRegistrarObserver::OnWebAppInstalled().
+  if (base::FeatureList::IsEnabled(features::kDesktopPWAsUnifiedInstall))
+    return;
+
+  FakeAppBannerManagerDesktop* manager =
+      FakeAppBannerManagerDesktop::CreateForWebContents(
+          browser()->tab_strip_model()->GetActiveWebContents());
+
+  {
+    base::RunLoop run_loop;
+    manager->PrepareDone(run_loop.QuitClosure());
+
+    ui_test_utils::NavigateToURL(browser(),
+                                 GetBannerURLWithAction("stash_event"));
+    run_loop.Run();
+    EXPECT_EQ(State::PENDING_PROMPT, manager->state());
+  }
+
+  // Install the app via the menu instead of the banner.
+  chrome::SetAutoAcceptPWAInstallConfirmationForTesting(true);
+  browser()->command_controller()->ExecuteCommand(IDC_INSTALL_PWA);
+  manager->AwaitAppInstall();
+  chrome::SetAutoAcceptPWAInstallConfirmationForTesting(false);
+
+  EXPECT_FALSE(manager->IsPromptAvailableForTesting());
+}
+
+IN_PROC_BROWSER_TEST_F(AppBannerManagerDesktopBrowserTest,
+                       InstallPromptAfterUserOmniboxInstall) {
+  FakeAppBannerManagerDesktop* manager =
+      FakeAppBannerManagerDesktop::CreateForWebContents(
+          browser()->tab_strip_model()->GetActiveWebContents());
+
+  {
+    base::RunLoop run_loop;
+    manager->PrepareDone(run_loop.QuitClosure());
+
+    ui_test_utils::NavigateToURL(browser(),
+                                 GetBannerURLWithAction("stash_event"));
+    run_loop.Run();
+    EXPECT_EQ(State::PENDING_PROMPT, manager->state());
+  }
+
+  // Install the app via the menu instead of the banner.
+  chrome::SetAutoAcceptPWAInstallConfirmationForTesting(true);
+  browser()
+      ->window()
+      ->GetOmniboxPageActionIconContainer()
+      ->ExecutePageActionIconForTesting(PageActionIconType::kPwaInstall);
+  manager->AwaitAppInstall();
+  chrome::SetAutoAcceptPWAInstallConfirmationForTesting(false);
+
+  EXPECT_FALSE(manager->IsPromptAvailableForTesting());
+}
diff --git a/chrome/browser/browser_process_platform_part_chromeos.cc b/chrome/browser/browser_process_platform_part_chromeos.cc
index 9c32d6d..c78ed1b 100644
--- a/chrome/browser/browser_process_platform_part_chromeos.cc
+++ b/chrome/browser/browser_process_platform_part_chromeos.cc
@@ -43,8 +43,6 @@
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "services/service_manager/public/cpp/service.h"
-#include "services/ws/public/cpp/input_devices/input_device_controller.h"
-#include "services/ws/public/cpp/input_devices/input_device_controller_client.h"
 
 BrowserProcessPlatformPart::BrowserProcessPlatformPart()
     : created_profile_helper_(false),
@@ -215,17 +213,6 @@
   system_clock_.reset();
 }
 
-ws::InputDeviceControllerClient*
-BrowserProcessPlatformPart::GetInputDeviceControllerClient() {
-  if (!input_device_controller_client_) {
-    input_device_controller_client_ =
-        std::make_unique<ws::InputDeviceControllerClient>(
-            content::ServiceManagerConnection::GetForProcess()->GetConnector(),
-            chromeos::kChromeServiceName);
-  }
-  return input_device_controller_client_.get();
-}
-
 void BrowserProcessPlatformPart::CreateProfileHelper() {
   DCHECK(!created_profile_helper_ && !profile_helper_);
   created_profile_helper_ = true;
diff --git a/chrome/browser/browser_process_platform_part_chromeos.h b/chrome/browser/browser_process_platform_part_chromeos.h
index 5ca39f9..67a4d39 100644
--- a/chrome/browser/browser_process_platform_part_chromeos.h
+++ b/chrome/browser/browser_process_platform_part_chromeos.h
@@ -41,10 +41,6 @@
 class BrowserPolicyConnectorChromeOS;
 }
 
-namespace ws {
-class InputDeviceControllerClient;
-}
-
 class ScopedKeepAlive;
 
 class BrowserProcessPlatformPart : public BrowserProcessPlatformPartBase {
@@ -124,8 +120,6 @@
   chromeos::system::SystemClock* GetSystemClock();
   void DestroySystemClock();
 
-  ws::InputDeviceControllerClient* GetInputDeviceControllerClient();
-
   chromeos::AccountManagerFactory* GetAccountManagerFactory();
 
  private:
@@ -167,11 +161,6 @@
   std::unique_ptr<chromeos::KerberosCredentialsManager>
       kerberos_credentials_manager_;
 
-#if defined(USE_OZONE)
-  std::unique_ptr<ws::InputDeviceControllerClient>
-      input_device_controller_client_;
-#endif
-
   SEQUENCE_CHECKER(sequence_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(BrowserProcessPlatformPart);
diff --git a/chrome/browser/chrome_service.cc b/chrome/browser/chrome_service.cc
index dec6c1e..a1719f0 100644
--- a/chrome/browser/chrome_service.cc
+++ b/chrome/browser/chrome_service.cc
@@ -22,9 +22,6 @@
 #include "services/service_manager/public/cpp/service.h"
 #include "services/service_manager/public/cpp/service_binding.h"
 
-#if defined(OS_CHROMEOS)
-#include "services/ws/public/cpp/input_devices/input_device_controller.h"
-#endif
 #if BUILDFLAG(ENABLE_SPELLCHECK)
 #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h"
 #if BUILDFLAG(HAS_SPELLCHECK_PANEL)
@@ -39,9 +36,6 @@
         base::CreateSingleThreadTaskRunnerWithTraits(
             {content::BrowserThread::UI});
 
-#if defined(OS_CHROMEOS)
-    input_device_controller_.AddInterface(&registry_, ui_task_runner);
-#endif
     registry_.AddInterface(base::BindRepeating(
         &startup_metric_utils::StartupMetricHostImpl::Create));
 #if BUILDFLAG(ENABLE_SPELLCHECK)
@@ -109,10 +103,6 @@
       const service_manager::BindSourceInfo&>
       registry_with_source_info_;
 
-#if defined(OS_CHROMEOS)
-  ws::InputDeviceController input_device_controller_;
-#endif
-
   DISALLOW_COPY_AND_ASSIGN(IOThreadContext);
 };
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 901d057..701de951 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -239,7 +239,6 @@
     "//services/preferences/public/mojom",
     "//services/resource_coordinator/public/cpp:resource_coordinator_cpp",
     "//services/service_manager/public/cpp",
-    "//services/ws/public/cpp/input_devices",
     "//skia",
     "//storage/browser",
     "//storage/common",
diff --git a/chrome/browser/chromeos/DEPS b/chrome/browser/chromeos/DEPS
index 64789cf..d388eba 100644
--- a/chrome/browser/chromeos/DEPS
+++ b/chrome/browser/chromeos/DEPS
@@ -19,10 +19,6 @@
   "+services/network",
   "+services/tracing/public",
   "+services/viz/public/interfaces",
-  # Chromeos should not use ozone directly, it must go through mojo as ozone
-  # does not run in process in mus.
-  "-ui/ozone/public",
-  "+ui/ozone/public/ozone_switches.h",
 
   # keyboard::KeyboardController only exists in ash and should not be accessed
   # directly from src/chrome. Use ChromeKeyboardControllerClient instead.
diff --git a/chrome/browser/chromeos/login/users/multi_profile_user_controller_unittest.cc b/chrome/browser/chromeos/login/users/multi_profile_user_controller_unittest.cc
index 372c4382..2e20692 100644
--- a/chrome/browser/chromeos/login/users/multi_profile_user_controller_unittest.cc
+++ b/chrome/browser/chromeos/login/users/multi_profile_user_controller_unittest.cc
@@ -33,7 +33,6 @@
 #include "net/cert/x509_certificate.h"
 #include "net/test/cert_test_util.h"
 #include "net/test/test_data_directory.h"
-#include "services/network/cert_verifier_with_trust_anchors.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -108,41 +107,12 @@
     },
 };
 
-// Weak ptr to network::CertVerifierWithTrustAnchors - object is freed in test
-// destructor once we've ensured the profile has been shut down.
-network::CertVerifierWithTrustAnchors* g_policy_cert_verifier_for_factory =
-    NULL;
-
 std::unique_ptr<KeyedService> TestPolicyCertServiceFactory(
     content::BrowserContext* context) {
   return policy::PolicyCertService::CreateForTesting(
-      kUsers[0], g_policy_cert_verifier_for_factory,
-      user_manager::UserManager::Get());
+      kUsers[0], user_manager::UserManager::Get());
 }
 
-class MockCertVerifyProc : public net::CertVerifyProc {
- public:
-  MockCertVerifyProc() = default;
-
-  // net::CertVerifyProc implementation
-  bool SupportsAdditionalTrustAnchors() const override { return true; }
-
- protected:
-  ~MockCertVerifyProc() override = default;
-
- private:
-  int VerifyInternal(net::X509Certificate* cert,
-                     const std::string& hostname,
-                     const std::string& ocsp_response,
-                     const std::string& sct_list,
-                     int flags,
-                     net::CRLSet* crl_set,
-                     const net::CertificateList& additional_trust_anchors,
-                     net::CertVerifyResult* result) override {
-    return net::ERR_FAILED;
-  }
-};
-
 }  // namespace
 
 class MultiProfileUserControllerTest
@@ -184,9 +154,6 @@
   }
 
   void TearDown() override {
-    // Clear our cached pointer to the network::CertVerifierWithTrustAnchors.
-    g_policy_cert_verifier_for_factory = NULL;
-
     // We must ensure that the network::CertVerifierWithTrustAnchors outlives
     // the PolicyCertService so shutdown the profile here. Additionally, we need
     // to run the message loop between freeing the PolicyCertService and
@@ -239,7 +206,6 @@
   TestingProfile* profile(int index) { return user_profiles_[index]; }
 
   content::TestBrowserThreadBundle threads_;
-  std::unique_ptr<network::CertVerifierWithTrustAnchors> cert_verifier_;
   std::unique_ptr<TestingProfileManager> profile_manager_;
   FakeChromeUserManager* fake_user_manager_;  // Not owned
   user_manager::ScopedUserManager user_manager_enabler_;
@@ -412,11 +378,6 @@
       test_users_[0].GetUserEmail());
   LoginUser(0);
 
-  cert_verifier_.reset(
-      new network::CertVerifierWithTrustAnchors(base::Closure()));
-  cert_verifier_->InitializeOnIOThread(
-      base::MakeRefCounted<MockCertVerifyProc>());
-  g_policy_cert_verifier_for_factory = cert_verifier_.get();
   ASSERT_TRUE(
       policy::PolicyCertServiceFactory::GetInstance()->SetTestingFactoryAndUse(
           profile(0), base::BindRepeating(&TestPolicyCertServiceFactory)));
@@ -451,11 +412,6 @@
   // changed back to enabled.
   SetPrefBehavior(0, MultiProfileUserController::kBehaviorUnrestricted);
 
-  cert_verifier_.reset(
-      new network::CertVerifierWithTrustAnchors(base::Closure()));
-  cert_verifier_->InitializeOnIOThread(
-      base::MakeRefCounted<MockCertVerifyProc>());
-  g_policy_cert_verifier_for_factory = cert_verifier_.get();
   ASSERT_TRUE(
       policy::PolicyCertServiceFactory::GetInstance()->SetTestingFactoryAndUse(
           profile(0), base::BindRepeating(&TestPolicyCertServiceFactory)));
diff --git a/chrome/browser/chromeos/policy/policy_cert_service.cc b/chrome/browser/chromeos/policy/policy_cert_service.cc
index 08a1669..5330c81 100644
--- a/chrome/browser/chromeos/policy/policy_cert_service.cc
+++ b/chrome/browser/chromeos/policy/policy_cert_service.cc
@@ -17,18 +17,11 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "net/cert/x509_certificate.h"
-#include "services/network/cert_verifier_with_trust_anchors.h"
 #include "services/network/nss_temp_certs_cache_chromeos.h"
-#include "services/network/public/cpp/features.h"
 
 namespace policy {
 
-PolicyCertService::~PolicyCertService() {
-  if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-    DCHECK(cert_verifier_)
-        << "CreatePolicyCertVerifier() must be called after construction.";
-  }
-}
+PolicyCertService::~PolicyCertService() {}
 
 PolicyCertService::PolicyCertService(
     Profile* profile,
@@ -36,7 +29,6 @@
     UserNetworkConfigurationUpdater* net_conf_updater,
     user_manager::UserManager* user_manager)
     : profile_(profile),
-      cert_verifier_(NULL),
       user_id_(user_id),
       net_conf_updater_(net_conf_updater),
       user_manager_(user_manager),
@@ -45,35 +37,14 @@
   DCHECK(user_manager_);
 }
 
-PolicyCertService::PolicyCertService(
-    const std::string& user_id,
-    network::CertVerifierWithTrustAnchors* verifier,
-    user_manager::UserManager* user_manager)
-    : cert_verifier_(verifier),
-      user_id_(user_id),
+PolicyCertService::PolicyCertService(const std::string& user_id,
+                                     user_manager::UserManager* user_manager)
+    : user_id_(user_id),
       net_conf_updater_(NULL),
       user_manager_(user_manager),
       weak_ptr_factory_(this) {}
 
-std::unique_ptr<network::CertVerifierWithTrustAnchors>
-PolicyCertService::CreatePolicyCertVerifier() {
-  DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
-  base::Closure callback = base::Bind(
-      &PolicyCertServiceFactory::SetUsedPolicyCertificates, user_id_);
-  constexpr base::TaskTraits traits = {content::BrowserThread::UI};
-  auto cert_verifier = std::make_unique<network::CertVerifierWithTrustAnchors>(
-      base::Bind(base::IgnoreResult(&base::PostTaskWithTraits), FROM_HERE,
-                 traits, callback));
-  cert_verifier_ = cert_verifier.get();
-  // Certs are forwarded to |cert_verifier_|, thus register here after
-  // |cert_verifier_| is created.
-  StartObservingPolicyCertsInternal(true /* notify */);
-
-  return cert_verifier;
-}
-
 void PolicyCertService::StartObservingPolicyCerts() {
-  DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService));
   // Don't notify the network service since it will get the initial list of
   // trust anchors in NetworkContextParams::initial_trust_anchors.
   StartObservingPolicyCertsInternal(false /* notify */);
@@ -129,24 +100,12 @@
   if (!notify)
     return;
 
-  if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-    ProfileNetworkContextServiceFactory::GetForContext(profile_)
-        ->UpdateAdditionalCertificates(all_server_and_authority_certs_,
-                                       trust_anchors_);
-    return;
+  auto* profile_network_context =
+      ProfileNetworkContextServiceFactory::GetForContext(profile_);
+  if (profile_network_context) {  // null in unit tests.
+    profile_network_context->UpdateAdditionalCertificates(
+        all_server_and_authority_certs_, trust_anchors_);
   }
-
-  DCHECK(cert_verifier_);
-
-  // It's safe to use base::Unretained here, because it's guaranteed that
-  // |cert_verifier_| outlives this object (see description of
-  // CreatePolicyCertVerifier).
-  // Note: ProfileIOData, which owns the CertVerifier is deleted by a
-  // DeleteSoon on IO, i.e. after all pending tasks on IO are finished.
-  base::PostTaskWithTraits(
-      FROM_HERE, {content::BrowserThread::IO},
-      base::BindOnce(&network::CertVerifierWithTrustAnchors::SetTrustAnchors,
-                     base::Unretained(cert_verifier_), trust_anchors_));
 }
 
 bool PolicyCertService::UsedPolicyCertificates() const {
@@ -166,10 +125,8 @@
 // static
 std::unique_ptr<PolicyCertService> PolicyCertService::CreateForTesting(
     const std::string& user_id,
-    network::CertVerifierWithTrustAnchors* verifier,
     user_manager::UserManager* user_manager) {
-  return base::WrapUnique(
-      new PolicyCertService(user_id, verifier, user_manager));
+  return base::WrapUnique(new PolicyCertService(user_id, user_manager));
 }
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/policy_cert_service.h b/chrome/browser/chromeos/policy/policy_cert_service.h
index 28e5eaa..14a20a0 100644
--- a/chrome/browser/chromeos/policy/policy_cert_service.h
+++ b/chrome/browser/chromeos/policy/policy_cert_service.h
@@ -29,7 +29,6 @@
 }
 
 namespace network {
-class CertVerifierWithTrustAnchors;
 class NSSTempCertsCacheChromeOS;
 }
 
@@ -49,12 +48,6 @@
                     user_manager::UserManager* user_manager);
   ~PolicyCertService() override;
 
-  // Creates an associated PolicyCertVerifier. The returned object must only be
-  // used on the IO thread and must outlive this object.
-  // This can only be called if the network service is disabled.
-  std::unique_ptr<network::CertVerifierWithTrustAnchors>
-  CreatePolicyCertVerifier();
-
   // Start listening for trust anchor changes to push to the network service.
   // This only needs to be called with the network service.
   void StartObservingPolicyCerts();
@@ -81,12 +74,10 @@
 
   static std::unique_ptr<PolicyCertService> CreateForTesting(
       const std::string& user_id,
-      network::CertVerifierWithTrustAnchors* verifier,
       user_manager::UserManager* user_manager);
 
  private:
   PolicyCertService(const std::string& user_id,
-                    network::CertVerifierWithTrustAnchors* verifier,
                     user_manager::UserManager* user_manager);
 
   void StartObservingPolicyCertsInternal(bool notify);
@@ -96,7 +87,6 @@
       bool notify);
 
   Profile* profile_ = nullptr;
-  network::CertVerifierWithTrustAnchors* cert_verifier_;
   std::string user_id_;
   UserNetworkConfigurationUpdater* net_conf_updater_;
   user_manager::UserManager* user_manager_;
diff --git a/chrome/browser/chromeos/policy/policy_cert_service_factory.cc b/chrome/browser/chromeos/policy/policy_cert_service_factory.cc
index 9c9c7fcf..b5ccb9c 100644
--- a/chrome/browser/chromeos/policy/policy_cert_service_factory.cc
+++ b/chrome/browser/chromeos/policy/policy_cert_service_factory.cc
@@ -17,7 +17,6 @@
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/user_manager/user_manager.h"
 #include "services/network/cert_verifier_with_trust_anchors.h"
-#include "services/network/public/cpp/features.h"
 
 namespace policy {
 
@@ -28,21 +27,8 @@
 }
 
 // static
-std::unique_ptr<network::CertVerifierWithTrustAnchors>
-PolicyCertServiceFactory::CreateForProfile(Profile* profile) {
-  DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
-  DCHECK(!GetInstance()->GetServiceForBrowserContext(profile, false));
-  PolicyCertService* service = static_cast<PolicyCertService*>(
-      GetInstance()->GetServiceForBrowserContext(profile, true));
-  if (!service)
-    return nullptr;
-  return service->CreatePolicyCertVerifier();
-}
-
-// static
 bool PolicyCertServiceFactory::CreateAndStartObservingForProfile(
     Profile* profile) {
-  DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService));
   // This can be called multiple times if the network process crashes.
   if (GetInstance()->GetServiceForBrowserContext(profile, false))
     return true;
diff --git a/chrome/browser/chromeos/policy/policy_cert_service_factory.h b/chrome/browser/chromeos/policy/policy_cert_service_factory.h
index 1e170ba..2138cef 100644
--- a/chrome/browser/chromeos/policy/policy_cert_service_factory.h
+++ b/chrome/browser/chromeos/policy/policy_cert_service_factory.h
@@ -20,10 +20,6 @@
 class PrefRegistrySimple;
 class Profile;
 
-namespace network {
-class CertVerifierWithTrustAnchors;
-}
-
 namespace policy {
 
 class PolicyCertService;
@@ -35,17 +31,6 @@
   // CreateForProfile.
   static PolicyCertService* GetForProfile(Profile* profile);
 
-  // Creates a new PolicyCertService and returns the associated
-  // PolicyCertVerifier. Returns nullptr if this service isn't allowed for
-  // |profile|, i.e. if NetworkConfigurationUpdater doesn't exist.
-  // This service is created separately for the original profile and the
-  // incognito profile.
-  // Note: NetworkConfigurationUpdater is currently only created for the primary
-  // user's profile.
-  // This should  only be called if the network service is disabled.
-  static std::unique_ptr<network::CertVerifierWithTrustAnchors>
-  CreateForProfile(Profile* profile);
-
   // Creates (if it's not already created) a PolicyCertService and gets it to
   // start listening for trust anchors for the profile. Returns false if this
   // service isn't allowed for |profile|, i.e. if NetworkConfigurationUpdater
diff --git a/chrome/browser/chromeos/system/input_device_settings_impl_ozone.cc b/chrome/browser/chromeos/system/input_device_settings_impl_ozone.cc
index eca3f75..0a64967 100644
--- a/chrome/browser/chromeos/system/input_device_settings_impl_ozone.cc
+++ b/chrome/browser/chromeos/system/input_device_settings_impl_ozone.cc
@@ -11,7 +11,8 @@
 #include "chrome/browser/chromeos/system/fake_input_device_settings.h"
 #include "chromeos/system/devicemode.h"
 #include "content/public/browser/browser_thread.h"
-#include "services/ws/public/cpp/input_devices/input_device_controller_client.h"
+#include "ui/ozone/public/input_controller.h"
+#include "ui/ozone/public/ozone_platform.h"
 
 namespace chromeos {
 namespace system {
@@ -19,9 +20,6 @@
 
 InputDeviceSettings* g_input_device_settings_impl_ozone_instance = nullptr;
 
-// Callback from SetInternalTouchpadEnabled().
-void OnSetInternalTouchpadEnabled(bool result) {}
-
 // InputDeviceSettings for Ozone.
 class InputDeviceSettingsImplOzone : public InputDeviceSettings {
  public:
@@ -50,8 +48,9 @@
   void SetInternalTouchpadEnabled(bool enabled) override;
   void SetTouchscreensEnabled(bool enabled) override;
 
-  // Cached InputDeviceControllerClient. It is owned by BrowserProcess.
-  ws::InputDeviceControllerClient* input_device_controller_client_;
+  ui::InputController* input_controller() {
+    return ui::OzonePlatform::GetInstance()->GetInputController();
+  }
 
   // Respective device setting objects.
   TouchpadSettings current_touchpad_settings_;
@@ -60,17 +59,12 @@
   DISALLOW_COPY_AND_ASSIGN(InputDeviceSettingsImplOzone);
 };
 
-InputDeviceSettingsImplOzone::InputDeviceSettingsImplOzone()
-    : input_device_controller_client_(g_browser_process->platform_part()
-                                          ->GetInputDeviceControllerClient()) {
-  // Make sure the input controller does exist.
-  DCHECK(input_device_controller_client_);
-}
+InputDeviceSettingsImplOzone::InputDeviceSettingsImplOzone() = default;
 
 void InputDeviceSettingsImplOzone::TouchpadExists(
     DeviceExistsCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  input_device_controller_client_->GetHasTouchpad(std::move(callback));
+  std::move(callback).Run(input_controller()->HasTouchpad());
 }
 
 void InputDeviceSettingsImplOzone::UpdateTouchpadSettings(
@@ -82,33 +76,33 @@
 void InputDeviceSettingsImplOzone::SetTouchpadSensitivity(int value) {
   DCHECK(value >= kMinPointerSensitivity && value <= kMaxPointerSensitivity);
   current_touchpad_settings_.SetSensitivity(value);
-  input_device_controller_client_->SetTouchpadSensitivity(value);
+  input_controller()->SetTouchpadSensitivity(value);
 }
 
 void InputDeviceSettingsImplOzone::SetNaturalScroll(bool enabled) {
   current_touchpad_settings_.SetNaturalScroll(enabled);
-  input_device_controller_client_->SetNaturalScroll(enabled);
+  input_controller()->SetNaturalScroll(enabled);
 }
 
 void InputDeviceSettingsImplOzone::SetTapToClick(bool enabled) {
   current_touchpad_settings_.SetTapToClick(enabled);
-  input_device_controller_client_->SetTapToClick(enabled);
+  input_controller()->SetTapToClick(enabled);
 }
 
 void InputDeviceSettingsImplOzone::SetThreeFingerClick(bool enabled) {
   // For Alex/ZGB.
   current_touchpad_settings_.SetThreeFingerClick(enabled);
-  input_device_controller_client_->SetThreeFingerClick(enabled);
+  input_controller()->SetThreeFingerClick(enabled);
 }
 
 void InputDeviceSettingsImplOzone::SetTapDragging(bool enabled) {
   current_touchpad_settings_.SetTapDragging(enabled);
-  input_device_controller_client_->SetTapDragging(enabled);
+  input_controller()->SetTapDragging(enabled);
 }
 
 void InputDeviceSettingsImplOzone::MouseExists(DeviceExistsCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  input_device_controller_client_->GetHasMouse(std::move(callback));
+  std::move(callback).Run(input_controller()->HasMouse());
 }
 
 void InputDeviceSettingsImplOzone::UpdateMouseSettings(
@@ -120,17 +114,17 @@
 void InputDeviceSettingsImplOzone::SetMouseSensitivity(int value) {
   DCHECK(value >= kMinPointerSensitivity && value <= kMaxPointerSensitivity);
   current_mouse_settings_.SetSensitivity(value);
-  input_device_controller_client_->SetMouseSensitivity(value);
+  input_controller()->SetMouseSensitivity(value);
 }
 
 void InputDeviceSettingsImplOzone::SetPrimaryButtonRight(bool right) {
   current_mouse_settings_.SetPrimaryButtonRight(right);
-  input_device_controller_client_->SetPrimaryButtonRight(right);
+  input_controller()->SetPrimaryButtonRight(right);
 }
 
 void InputDeviceSettingsImplOzone::SetMouseReverseScroll(bool enabled) {
   current_mouse_settings_.SetReverseScroll(enabled);
-  input_device_controller_client_->SetMouseReverseScroll(enabled);
+  input_controller()->SetMouseReverseScroll(enabled);
 }
 
 void InputDeviceSettingsImplOzone::ReapplyTouchpadSettings() {
@@ -147,12 +141,11 @@
 }
 
 void InputDeviceSettingsImplOzone::SetInternalTouchpadEnabled(bool enabled) {
-  input_device_controller_client_->SetInternalTouchpadEnabled(
-      enabled, base::BindOnce(&OnSetInternalTouchpadEnabled));
+  input_controller()->SetInternalTouchpadEnabled(enabled);
 }
 
 void InputDeviceSettingsImplOzone::SetTouchscreensEnabled(bool enabled) {
-  input_device_controller_client_->SetTouchscreensEnabled(enabled);
+  input_controller()->SetTouchscreensEnabled(enabled);
 }
 
 }  // namespace
diff --git a/chrome/browser/chromeos/system_logs/touch_log_source.cc b/chrome/browser/chromeos/system_logs/touch_log_source.cc
index c89cfd5..f951e49 100644
--- a/chrome/browser/chromeos/system_logs/touch_log_source.cc
+++ b/chrome/browser/chromeos/system_logs/touch_log_source.cc
@@ -17,11 +17,10 @@
 #include "base/logging.h"
 #include "base/process/launch.h"
 #include "base/task/post_task.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/browser_process_platform_part_chromeos.h"
 #include "content/public/browser/browser_thread.h"
-#include "services/ws/public/cpp/input_devices/input_device_controller_client.h"
 #include "ui/base/ui_base_features.h"
+#include "ui/ozone/public/input_controller.h"
+#include "ui/ozone/public/ozone_platform.h"
 
 using content::BrowserThread;
 
@@ -160,9 +159,9 @@
 
   // Collect touch event logs.
   const base::FilePath kBaseLogPath(kTouchEventLogDir);
-  ws::InputDeviceControllerClient* input_device_controller_client =
-      g_browser_process->platform_part()->GetInputDeviceControllerClient();
-  input_device_controller_client->GetTouchEventLog(
+  ui::InputController* input_controller =
+      ui::OzonePlatform::GetInstance()->GetInputController();
+  input_controller->GetTouchEventLog(
       kBaseLogPath, base::BindOnce(&OnEventLogCollected, std::move(response),
                                    std::move(callback)));
 }
@@ -197,9 +196,9 @@
   CollectTouchHudDebugLog(response.get());
 
   // Collect touch device status logs.
-  ws::InputDeviceControllerClient* input_device_controller_client =
-      g_browser_process->platform_part()->GetInputDeviceControllerClient();
-  input_device_controller_client->GetTouchDeviceStatus(base::BindOnce(
+  ui::InputController* input_controller =
+      ui::OzonePlatform::GetInstance()->GetInputController();
+  input_controller->GetTouchDeviceStatus(base::BindOnce(
       &OnStatusLogCollected, std::move(response), std::move(callback)));
 }
 
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry.cc b/chrome/browser/custom_handlers/protocol_handler_registry.cc
index 801be3a..8c0aff6 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry.cc
@@ -129,71 +129,6 @@
   enabled_ = false;
 }
 
-// JobInterceptorFactory -------------------------------------------------------
-
-// Instances of JobInterceptorFactory are produced for ownership by the IO
-// thread where it handler URL requests. We should never hold
-// any pointers on this class, only produce them in response to
-// requests via |ProtocolHandlerRegistry::CreateJobInterceptorFactory|.
-ProtocolHandlerRegistry::JobInterceptorFactory::JobInterceptorFactory(
-    IOThreadDelegate* io_thread_delegate)
-    : io_thread_delegate_(io_thread_delegate) {
-  DCHECK(io_thread_delegate_.get());
-  DETACH_FROM_THREAD(thread_checker_);
-}
-
-ProtocolHandlerRegistry::JobInterceptorFactory::~JobInterceptorFactory() {
-}
-
-void ProtocolHandlerRegistry::JobInterceptorFactory::Chain(
-    std::unique_ptr<net::URLRequestJobFactory> job_factory) {
-  job_factory_ = std::move(job_factory);
-}
-
-net::URLRequestJob*
-ProtocolHandlerRegistry::JobInterceptorFactory::
-MaybeCreateJobWithProtocolHandler(
-    const std::string& scheme,
-    net::URLRequest* request,
-    net::NetworkDelegate* network_delegate) const {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  net::URLRequestJob* job = io_thread_delegate_->MaybeCreateJob(
-      request, network_delegate);
-  if (job)
-    return job;
-  return job_factory_->MaybeCreateJobWithProtocolHandler(
-      scheme, request, network_delegate);
-}
-
-net::URLRequestJob*
-ProtocolHandlerRegistry::JobInterceptorFactory::MaybeInterceptRedirect(
-    net::URLRequest* request,
-    net::NetworkDelegate* network_delegate,
-    const GURL& location) const {
-  return job_factory_->MaybeInterceptRedirect(
-      request, network_delegate, location);
-}
-
-net::URLRequestJob*
-ProtocolHandlerRegistry::JobInterceptorFactory::MaybeInterceptResponse(
-    net::URLRequest* request,
-    net::NetworkDelegate* network_delegate) const {
-  return job_factory_->MaybeInterceptResponse(request, network_delegate);
-}
-
-bool ProtocolHandlerRegistry::JobInterceptorFactory::IsHandledProtocol(
-    const std::string& scheme) const {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  return io_thread_delegate_->IsHandledProtocol(scheme) ||
-      job_factory_->IsHandledProtocol(scheme);
-}
-
-bool ProtocolHandlerRegistry::JobInterceptorFactory::IsSafeRedirectTarget(
-    const GURL& location) const {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  return job_factory_->IsSafeRedirectTarget(location);
-}
-
 // Delegate --------------------------------------------------------------------
 
 ProtocolHandlerRegistry::Delegate::~Delegate() {}
@@ -913,13 +848,3 @@
       &ProtocolHandlerRegistry::OnSetAsDefaultProtocolClientFinished,
       weak_ptr_factory_.GetWeakPtr(), protocol);
 }
-
-std::unique_ptr<ProtocolHandlerRegistry::JobInterceptorFactory>
-ProtocolHandlerRegistry::CreateJobInterceptorFactory() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  // this is always created on the UI thread (in profile_io's
-  // InitializeOnUIThread. Any method calls must be done
-  // on the IO thread (this is checked).
-  return std::unique_ptr<JobInterceptorFactory>(
-      new JobInterceptorFactory(io_thread_delegate_.get()));
-}
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry.h b/chrome/browser/custom_handlers/protocol_handler_registry.h
index 8c9dce7..cff3ad96 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry.h
+++ b/chrome/browser/custom_handlers/protocol_handler_registry.h
@@ -113,59 +113,10 @@
     DISALLOW_COPY_AND_ASSIGN(IOThreadDelegate);
   };
 
-  // JobInterceptorFactory intercepts URLRequestJob creation for URLRequests the
-  // ProtocolHandlerRegistry is registered to handle.  When no handler is
-  // registered, the URLRequest is passed along to the chained
-  // URLRequestJobFactory (set with |JobInterceptorFactory::Chain|).
-  // JobInterceptorFactory's are created via
-  // |ProtocolHandlerRegistry::CreateJobInterceptorFactory|.
-  class JobInterceptorFactory : public net::URLRequestJobFactory {
-   public:
-    // |io_thread_delegate| is used to perform actual job creation work.
-    explicit JobInterceptorFactory(IOThreadDelegate* io_thread_delegate);
-    ~JobInterceptorFactory() override;
-
-    // |job_factory| is set as the URLRequestJobFactory where requests are
-    // forwarded if JobInterceptorFactory decides to pass on them.
-    void Chain(std::unique_ptr<net::URLRequestJobFactory> job_factory);
-
-    // URLRequestJobFactory implementation.
-    net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
-        const std::string& scheme,
-        net::URLRequest* request,
-        net::NetworkDelegate* network_delegate) const override;
-
-    net::URLRequestJob* MaybeInterceptRedirect(
-        net::URLRequest* request,
-        net::NetworkDelegate* network_delegate,
-        const GURL& location) const override;
-
-    net::URLRequestJob* MaybeInterceptResponse(
-        net::URLRequest* request,
-        net::NetworkDelegate* network_delegate) const override;
-
-    bool IsHandledProtocol(const std::string& scheme) const override;
-    bool IsSafeRedirectTarget(const GURL& location) const override;
-
-   private:
-    // When JobInterceptorFactory decides to pass on particular requests,
-    // they're forwarded to the chained URLRequestJobFactory, |job_factory_|.
-    std::unique_ptr<URLRequestJobFactory> job_factory_;
-    // |io_thread_delegate_| performs the actual job creation decisions by
-    // mirroring the ProtocolHandlerRegistry on the IO thread.
-    scoped_refptr<IOThreadDelegate> io_thread_delegate_;
-
-    DISALLOW_COPY_AND_ASSIGN(JobInterceptorFactory);
-  };
-
   // Creates a new instance. Assumes ownership of |delegate|.
   ProtocolHandlerRegistry(content::BrowserContext* context, Delegate* delegate);
   ~ProtocolHandlerRegistry() override;
 
-  // Returns a net::URLRequestJobFactory suitable for use on the IO thread, but
-  // is initialized on the UI thread.
-  std::unique_ptr<JobInterceptorFactory> CreateJobInterceptorFactory();
-
   // Called when a site tries to register as a protocol handler. If the request
   // can be handled silently by the registry - either to ignore the request
   // or to update an existing handler - the request will succeed. If this
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc b/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
index 42db890..e467fb6 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
@@ -38,27 +38,6 @@
 
 namespace {
 
-void AssertInterceptedIO(
-    const GURL& url,
-    net::URLRequestJobFactory* interceptor) {
-  net::URLRequestContext context;
-  std::unique_ptr<net::URLRequest> request(context.CreateRequest(
-      url, net::DEFAULT_PRIORITY, nullptr, TRAFFIC_ANNOTATION_FOR_TESTS));
-  std::unique_ptr<net::URLRequestJob> job(
-      interceptor->MaybeCreateJobWithProtocolHandler(
-          url.scheme(), request.get(), context.network_delegate()));
-  ASSERT_TRUE(job.get());
-}
-
-void AssertIntercepted(
-    const GURL& url,
-    net::URLRequestJobFactory* interceptor) {
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(AssertInterceptedIO, url, base::Unretained(interceptor)));
-  base::RunLoop().RunUntilIdle();
-}
-
 // FakeURLRequestJobFactory returns NULL for all job creation requests and false
 // for all IsHandledProtocol() requests. FakeURLRequestJobFactory can be chained
 // to ProtocolHandlerRegistry::JobInterceptorFactory so the result of
@@ -94,26 +73,6 @@
   }
 };
 
-void AssertWillHandleIO(
-    const std::string& scheme,
-    bool expected,
-    ProtocolHandlerRegistry::JobInterceptorFactory* interceptor) {
-  interceptor->Chain(std::unique_ptr<net::URLRequestJobFactory>(
-      new FakeURLRequestJobFactory()));
-  ASSERT_EQ(expected, interceptor->IsHandledProtocol(scheme));
-  interceptor->Chain(std::unique_ptr<net::URLRequestJobFactory>());
-}
-
-void AssertWillHandle(
-    const std::string& scheme,
-    bool expected,
-    ProtocolHandlerRegistry::JobInterceptorFactory* interceptor) {
-  base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO},
-                           base::BindOnce(AssertWillHandleIO, scheme, expected,
-                                          base::Unretained(interceptor)));
-  base::RunLoop().RunUntilIdle();
-}
-
 std::unique_ptr<base::DictionaryValue> GetProtocolHandlerValue(
     const std::string& protocol,
     const std::string& url) {
@@ -767,27 +726,6 @@
   ASSERT_EQ(static_cast<size_t>(1), registry()->GetHandlersFor("dont").size());
 }
 
-TEST_F(ProtocolHandlerRegistryTest, TestMaybeCreateTaskWorksFromIOThread) {
-  ProtocolHandler ph1 = CreateProtocolHandler("mailto", "test1");
-  registry()->OnAcceptRegisterProtocolHandler(ph1);
-  GURL url("mailto:someone@something.com");
-
-  std::unique_ptr<net::URLRequestJobFactory> interceptor(
-      registry()->CreateJobInterceptorFactory());
-  AssertIntercepted(url, interceptor.get());
-}
-
-TEST_F(ProtocolHandlerRegistryTest,
-       TestIsHandledProtocolWorksOnIOThread) {
-  std::string scheme("mailto");
-  ProtocolHandler ph1 = CreateProtocolHandler(scheme, "test1");
-  registry()->OnAcceptRegisterProtocolHandler(ph1);
-
-  std::unique_ptr<ProtocolHandlerRegistry::JobInterceptorFactory> interceptor(
-      registry()->CreateJobInterceptorFactory());
-  AssertWillHandle(scheme, true, interceptor.get());
-}
-
 TEST_F(ProtocolHandlerRegistryTest, TestRemovingDefaultFallsBackToOldDefault) {
   ProtocolHandler ph1 = CreateProtocolHandler("mailto", "test1");
   ProtocolHandler ph2 = CreateProtocolHandler("mailto", "test2");
@@ -824,29 +762,6 @@
   ASSERT_EQ(ph1, handlers[1]);
 }
 
-TEST_F(ProtocolHandlerRegistryTest, TestClearDefaultGetsPropagatedToIO) {
-  std::string scheme("mailto");
-  ProtocolHandler ph1 = CreateProtocolHandler(scheme, "test1");
-  registry()->OnAcceptRegisterProtocolHandler(ph1);
-  registry()->ClearDefault(scheme);
-
-  std::unique_ptr<ProtocolHandlerRegistry::JobInterceptorFactory> interceptor(
-      registry()->CreateJobInterceptorFactory());
-  AssertWillHandle(scheme, false, interceptor.get());
-}
-
-TEST_F(ProtocolHandlerRegistryTest, TestLoadEnabledGetsPropogatedToIO) {
-  std::string mailto("mailto");
-  ProtocolHandler ph1 = CreateProtocolHandler(mailto, "MailtoHandler");
-  registry()->OnAcceptRegisterProtocolHandler(ph1);
-
-  std::unique_ptr<ProtocolHandlerRegistry::JobInterceptorFactory> interceptor(
-      registry()->CreateJobInterceptorFactory());
-  AssertWillHandle(mailto, true, interceptor.get());
-  registry()->Disable();
-  AssertWillHandle(mailto, false, interceptor.get());
-}
-
 TEST_F(ProtocolHandlerRegistryTest, TestReplaceHandler) {
   ProtocolHandler ph1 =
       CreateProtocolHandler("mailto", GURL("http://test.com/%s"));
diff --git a/chrome/browser/extensions/bookmark_app_helper_unittest.cc b/chrome/browser/extensions/bookmark_app_helper_unittest.cc
index 3b26e91a..654ddc2 100644
--- a/chrome/browser/extensions/bookmark_app_helper_unittest.cc
+++ b/chrome/browser/extensions/bookmark_app_helper_unittest.cc
@@ -190,6 +190,8 @@
 
 }  // namespace
 
+// Deprecated in favour of InstallManagerBookmarkAppTest.CreateBookmarkApp.
+// TODO(crbug.com/915043): Erase it.
 TEST_F(BookmarkAppHelperExtensionServiceTest, CreateBookmarkApp) {
   WebApplicationInfo web_app_info;
   web_app_info.app_url = GURL(kAppUrl);
@@ -223,6 +225,9 @@
           .is_null());
 }
 
+// Deprecated in favour of
+// InstallManagerBookmarkAppTest.CreateBookmarkAppDefaultApp.
+// TODO(crbug.com/915043): Erase it.
 TEST_F(BookmarkAppHelperExtensionServiceTest, CreateBookmarkAppDefaultApp) {
   WebApplicationInfo web_app_info;
   web_app_info.app_url = GURL(kAppUrl);
@@ -242,6 +247,9 @@
   EXPECT_FALSE(Manifest::IsPolicyLocation(helper.extension()->location()));
 }
 
+// Deprecated in favour of
+// InstallManagerBookmarkAppTest.CreateBookmarkAppPolicyInstalled.
+// TODO(crbug.com/915043): Erase it.
 TEST_F(BookmarkAppHelperExtensionServiceTest,
        CreateBookmarkAppPolicyInstalled) {
   WebApplicationInfo web_app_info;
@@ -273,6 +281,9 @@
       BookmarkAppHelperExtensionServiceInstallableSiteTest);
 };
 
+// Deprecated in favour of
+// InstallManagerBookmarkAppInstallableSiteTest.CreateBookmarkAppWithManifest.
+// TODO(crbug.com/915043): Erase it.
 TEST_P(BookmarkAppHelperExtensionServiceInstallableSiteTest,
        CreateBookmarkAppWithManifest) {
   WebApplicationInfo web_app_info;
@@ -314,6 +325,10 @@
   }
 }
 
+// Deprecated in favour of
+// InstallManagerBookmarkAppInstallableSiteTest
+// .CreateBookmarkAppWithManifestIcons.
+// TODO(crbug.com/915043): Erase it.
 TEST_P(BookmarkAppHelperExtensionServiceInstallableSiteTest,
        CreateBookmarkAppWithManifestIcons) {
   WebApplicationInfo web_app_info;
@@ -362,6 +377,10 @@
   }
 }
 
+// Deprecated in favour of
+// InstallManagerBookmarkAppInstallableSiteTest
+// .CreateBookmarkAppWithManifestNoScope.
+// TODO(crbug.com/915043): Erase it.
 TEST_P(BookmarkAppHelperExtensionServiceInstallableSiteTest,
        CreateBookmarkAppWithManifestNoScope) {
   WebApplicationInfo web_app_info;
@@ -395,6 +414,9 @@
                          ::testing::Values(ForInstallableSite::kNo,
                                            ForInstallableSite::kYes));
 
+// Deprecated in favour of
+// InstallManagerBookmarkAppTest.CreateBookmarkAppDefaultLauncherContainers.
+// TODO(crbug.com/915043): Erase it.
 TEST_F(BookmarkAppHelperExtensionServiceTest,
        CreateBookmarkAppDefaultLauncherContainers) {
   std::map<GURL, std::vector<SkBitmap>> icon_map;
@@ -455,6 +477,9 @@
   }
 }
 
+// Deprecated in favour of
+// InstallManagerBookmarkAppTest.CreateBookmarkAppForcedLauncherContainers.
+// TODO(crbug.com/915043): Erase it.
 TEST_F(BookmarkAppHelperExtensionServiceTest,
        CreateBookmarkAppForcedLauncherContainers) {
   WebApplicationInfo web_app_info;
@@ -498,6 +523,9 @@
   }
 }
 
+// Deprecated in favour of
+// InstallManagerBookmarkAppTest.CreateBookmarkAppWithoutManifest.
+// TODO(crbug.com/915043): Erase it.
 TEST_F(BookmarkAppHelperExtensionServiceTest,
        CreateBookmarkAppWithoutManifest) {
   WebApplicationInfo web_app_info;
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 4178f4b..3040ea5 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -57,6 +57,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/service_worker_task_queue.h"
+#include "extensions/browser/test_extension_registry_observer.h"
 #include "extensions/common/api/test.h"
 #include "extensions/common/value_builder.h"
 #include "extensions/common/verifier_formats.h"
@@ -1683,6 +1684,35 @@
   EXPECT_FALSE(ProcessManager::Get(profile())->HasServiceWorker(*worker_id));
 }
 
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest, UninstallSelf) {
+  constexpr char kManifest[] =
+      R"({
+           "name": "Test Extension",
+           "manifest_version": 2,
+           "version": "0.1",
+           "background": {"service_worker": "script.js"}
+         })";
+
+  // This script uninstalls itself.
+  constexpr char kScript[] =
+      "chrome.management.uninstallSelf({showConfirmDialog: false});";
+
+  TestExtensionDir test_dir;
+
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("script.js"), kScript);
+
+  // Consruct this before loading the extension, since the extension will
+  // immediately uninstall itself when it loads.
+  extensions::TestExtensionRegistryObserver observer(
+      extensions::ExtensionRegistry::Get(browser()->profile()));
+
+  base::FilePath path = test_dir.Pack();
+  scoped_refptr<const Extension> extension = LoadExtension(path);
+
+  EXPECT_EQ(extension, observer.WaitForExtensionUninstalled());
+}
+
 IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
                        PRE_EventsAfterRestart) {
   ExtensionTestMessageListener event_added_listener("ready", false);
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
index bae20ee..0558944 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
@@ -81,7 +81,7 @@
 
   void GetAllNotifications(Notifications* notifications) override {
     DCHECK(notifications);
-
+    notifications->clear();
     for (const auto& pair : notifications_) {
       const auto& notif = pair.second;
       DCHECK(notif);
@@ -94,6 +94,22 @@
     }
   }
 
+  void DeleteNotifications(SchedulerClientType type) override {
+    auto it = notifications_.begin();
+    while (it != notifications_.end()) {
+      const auto& entry = *it->second;
+      ++it;
+      if (entry.type == type) {
+        store_->Delete(
+            entry.guid,
+            base::BindOnce(
+                &ScheduledNotificationManagerImpl::OnNotificationDeleted,
+                weak_ptr_factory_.GetWeakPtr()));
+        notifications_.erase(entry.guid);
+      }
+    }
+  }
+
   void OnStoreInitialized(InitCallback callback,
                           bool success,
                           CollectionStore<NotificationEntry>::Entries entries) {
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
index 6ed3e8b..3437053 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
@@ -59,6 +59,9 @@
   // by creation timestamp.
   virtual void GetAllNotifications(Notifications* notifications) = 0;
 
+  // Deletes all notifications of given SchedulerClientType.
+  virtual void DeleteNotifications(SchedulerClientType type) = 0;
+
   virtual ~ScheduledNotificationManager();
 
  protected:
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
index b5b1c132..0ba4d73 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
@@ -28,6 +28,10 @@
   return NotificationEntry(SchedulerClientType::kUnknown, base::GenerateGUID());
 }
 
+NotificationEntry CreateNotificationEntry(SchedulerClientType type) {
+  return NotificationEntry(type, base::GenerateGUID());
+}
+
 class MockDelegate : public ScheduledNotificationManager::Delegate {
  public:
   MockDelegate() = default;
@@ -225,5 +229,44 @@
   EXPECT_EQ(output2->create_time, entry2.create_time);
 }
 
+// Verify DeleteNotifications API, all notifications with given
+// SchedulerClientType should be deleted.
+TEST_F(ScheduledNotificationManagerTest, DeleteNotifications) {
+  // Type1: entry0
+  // Type2: entry1, entry2
+  // Type3: entry3
+  auto entry0 = CreateNotificationEntry(SchedulerClientType::kTest1);
+  auto entry1 = CreateNotificationEntry(SchedulerClientType::kTest2);
+  auto entry2 = CreateNotificationEntry(SchedulerClientType::kTest2);
+  auto entry3 = CreateNotificationEntry(SchedulerClientType::kTest3);
+
+  InitWithData(
+      std::vector<NotificationEntry>({entry0, entry1, entry2, entry3}));
+  ScheduledNotificationManager::Notifications notifications;
+  manager()->GetAllNotifications(&notifications);
+  EXPECT_EQ(notifications.size(), 3u);
+
+  EXPECT_CALL(*store(), Delete(_, _)).Times(2).RetiresOnSaturation();
+  manager()->DeleteNotifications(SchedulerClientType::kTest2);
+  manager()->GetAllNotifications(&notifications);
+  EXPECT_EQ(notifications.size(), 2u);
+
+  // Ensure deleting non-existing key will not crash, and store will not call
+  // Delete.
+  EXPECT_CALL(*store(), Delete(_, _)).Times(0).RetiresOnSaturation();
+  manager()->DeleteNotifications(SchedulerClientType::kTest2);
+  manager()->GetAllNotifications(&notifications);
+  EXPECT_EQ(notifications.size(), 2u);
+
+  EXPECT_CALL(*store(), Delete(_, _)).RetiresOnSaturation();
+  manager()->DeleteNotifications(SchedulerClientType::kTest1);
+  manager()->GetAllNotifications(&notifications);
+  EXPECT_EQ(notifications.size(), 1u);
+
+  EXPECT_CALL(*store(), Delete(_, _)).RetiresOnSaturation();
+  manager()->DeleteNotifications(SchedulerClientType::kTest3);
+  manager()->GetAllNotifications(&notifications);
+  EXPECT_EQ(notifications.size(), 0u);
+}
 }  // namespace
 }  // namespace notifications
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index 7486d7f..d99a117 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -199,6 +199,7 @@
 #include "content/public/test/download_test_observer.h"
 #include "content/public/test/mock_notification_observer.h"
 #include "content/public/test/network_service_test_helper.h"
+#include "content/public/test/no_renderer_crashes_assertion.h"
 #include "content/public/test/signed_exchange_browser_test_helper.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
@@ -2650,19 +2651,23 @@
   }
 
   // Test policy-installed extensions are reloaded when killed.
-  BackgroundContentsService::
-      SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(0);
-  content::WindowedNotificationObserver extension_crashed_observer(
-      extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
-      content::NotificationService::AllSources());
-  extensions::TestExtensionRegistryObserver extension_loaded_observer(
-      extensions::ExtensionRegistry::Get(browser()->profile()), kGoodCrxId);
-  extensions::ExtensionHost* extension_host =
-      extensions::ProcessManager::Get(browser()->profile())
-          ->GetBackgroundHostForExtension(kGoodCrxId);
-  extension_host->render_process_host()->Shutdown(content::RESULT_CODE_KILLED);
-  extension_crashed_observer.Wait();
-  extension_loaded_observer.WaitForExtensionLoaded();
+  {
+    content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
+    BackgroundContentsService::
+        SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(0);
+    content::WindowedNotificationObserver extension_crashed_observer(
+        extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
+        content::NotificationService::AllSources());
+    extensions::TestExtensionRegistryObserver extension_loaded_observer(
+        extensions::ExtensionRegistry::Get(browser()->profile()), kGoodCrxId);
+    extensions::ExtensionHost* extension_host =
+        extensions::ProcessManager::Get(browser()->profile())
+            ->GetBackgroundHostForExtension(kGoodCrxId);
+    extension_host->render_process_host()->Shutdown(
+        content::RESULT_CODE_KILLED);
+    extension_crashed_observer.Wait();
+    extension_loaded_observer.WaitForExtensionLoaded();
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(PolicyTest,
diff --git a/chrome/browser/previews/previews_content_util.cc b/chrome/browser/previews/previews_content_util.cc
index ec184c7..b2f6363 100644
--- a/chrome/browser/previews/previews_content_util.cc
+++ b/chrome/browser/previews/previews_content_util.cc
@@ -104,6 +104,11 @@
         PreviewsLitePageNavigationThrottle::IneligibleReason::kCookiesBlocked);
   }
 
+  if (!decider->has_drp_headers()) {
+    ineligible_reasons.push_back(PreviewsLitePageNavigationThrottle::
+                                     IneligibleReason::kInvalidProxyHeaders);
+  }
+
   // Record UMA.
   for (PreviewsLitePageNavigationThrottle::IneligibleReason reason :
        ineligible_reasons) {
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index 209bec6..1a706ae 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -1112,6 +1112,31 @@
 
 IN_PROC_BROWSER_TEST_P(
     PreviewsLitePageServerBrowserTest,
+    DISABLE_ON_WIN_MAC_CHROMESOS(LitePagePreviewsNoChromeProxyHeader)) {
+  ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
+  VerifyPreviewLoaded();
+
+  // Mimic a bad proxy header update.
+  net::HttpRequestHeaders empty;
+  PreviewsService* previews_service =
+      PreviewsServiceFactory::GetForProfile(browser()->profile());
+  PreviewsLitePageDecider* decider =
+      previews_service->previews_lite_page_decider();
+  decider->OnProxyRequestHeadersChanged(empty);
+
+  base::HistogramTester histogram_tester;
+  ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
+  VerifyPreviewNotLoaded();
+
+  histogram_tester.ExpectBucketCount(
+      "Previews.ServerLitePage.IneligibleReasons",
+      static_cast<int>(PreviewsLitePageNavigationThrottle::IneligibleReason::
+                           kInvalidProxyHeaders),
+      1);
+}
+
+IN_PROC_BROWSER_TEST_P(
+    PreviewsLitePageServerBrowserTest,
     DISABLE_ON_WIN_MAC_CHROMESOS(CoinFlipHoldbackTriggering)) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeatureWithParameters(
diff --git a/chrome/browser/previews/previews_lite_page_decider.cc b/chrome/browser/previews/previews_lite_page_decider.cc
index 036d3af..58012c5 100644
--- a/chrome/browser/previews/previews_lite_page_decider.cc
+++ b/chrome/browser/previews/previews_lite_page_decider.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_metrics.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
 #include "components/pref_registry/pref_registry_syncable.h"
@@ -138,7 +139,8 @@
       page_id_(base::RandUint64()),
       drp_settings_(nullptr),
       pref_service_(nullptr),
-      host_bypass_blacklist_(std::make_unique<base::DictionaryValue>()) {
+      host_bypass_blacklist_(std::make_unique<base::DictionaryValue>()),
+      drp_headers_valid_(false) {
   if (!browser_context)
     return;
 
@@ -244,6 +246,15 @@
 void PreviewsLitePageDecider::OnProxyRequestHeadersChanged(
     const net::HttpRequestHeaders& headers) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  std::string drp_header;
+  drp_headers_valid_ =
+      headers.GetHeader(data_reduction_proxy::chrome_proxy_header(),
+                        &drp_header) &&
+      (drp_header.find(",s=") != std::string::npos ||
+       drp_header.find(" s=") != std::string::npos ||
+       base::StartsWith(drp_header, "s=", base::CompareCase::SENSITIVE));
+
   // This is done so that successive page ids cannot be used to track users
   // across sessions. These sessions are contained in the chrome-proxy header.
   page_id_ = base::RandUint64();
diff --git a/chrome/browser/previews/previews_lite_page_decider.h b/chrome/browser/previews/previews_lite_page_decider.h
index df594e71..c9c0b18 100644
--- a/chrome/browser/previews/previews_lite_page_decider.h
+++ b/chrome/browser/previews/previews_lite_page_decider.h
@@ -97,12 +97,14 @@
                              base::TimeDelta duration) override;
   bool HostBlacklistedFromBypass(const std::string& host) override;
 
- private:
   // data_reduction_proxy::DataReductionProxySettingsObserver:
   void OnProxyRequestHeadersChanged(
       const net::HttpRequestHeaders& headers) override;
   void OnSettingsInitialized() override;
 
+  bool has_drp_headers() const { return drp_headers_valid_; }
+
+ private:
   // The time after which it is ok to send the server more preview requests.
   base::Optional<base::TimeTicks> retry_at_;
 
@@ -133,6 +135,10 @@
   // after the time value. This is stored persistently in prefs.
   std::unique_ptr<base::DictionaryValue> host_bypass_blacklist_;
 
+  // A bool that tracks if the last call to |OnProxyRequestHeadersChanged| had
+  // what looked like a valid chrome-proxy header.
+  bool drp_headers_valid_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(PreviewsLitePageDecider);
diff --git a/chrome/browser/previews/previews_lite_page_navigation_throttle.h b/chrome/browser/previews/previews_lite_page_navigation_throttle.h
index 593d5c4..1c5c5e62 100644
--- a/chrome/browser/previews/previews_lite_page_navigation_throttle.h
+++ b/chrome/browser/previews/previews_lite_page_navigation_throttle.h
@@ -63,7 +63,8 @@
     kECTUnknown_DEPRECATED = 8,
     kExceededMaxNavigationRestarts = 9,
     kPreviewsState_DEPRECATED = 10,
-    kMaxValue = kPreviewsState_DEPRECATED,
+    kInvalidProxyHeaders = 11,
+    kMaxValue = kInvalidProxyHeaders,
   };
 
   // The response type from the previews server. This enum must
diff --git a/chrome/browser/previews/previews_top_host_provider_impl.cc b/chrome/browser/previews/previews_top_host_provider_impl.cc
index c8f48f8..acdb84d 100644
--- a/chrome/browser/previews/previews_top_host_provider_impl.cc
+++ b/chrome/browser/previews/previews_top_host_provider_impl.cc
@@ -10,8 +10,11 @@
 #include "chrome/browser/engagement/site_engagement_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/previews/content/previews_hints_util.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
 
 namespace {
 
@@ -48,6 +51,38 @@
   UpdateCurrentBlacklistState(HintsFetcherTopHostBlacklistState::kInitialized);
 }
 
+// static
+void PreviewsTopHostProviderImpl::MaybeUpdateTopHostBlacklist(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->GetURL().SchemeIsHTTPOrHTTPS())
+    return;
+
+  PrefService* pref_service =
+      Profile::FromBrowserContext(
+          navigation_handle->GetWebContents()->GetBrowserContext())
+          ->GetPrefs();
+
+  if (pref_service->GetInteger(kHintsFetcherTopHostBlacklistState) !=
+      static_cast<int>(HintsFetcherTopHostBlacklistState::kInitialized)) {
+    return;
+  }
+
+  DictionaryPrefUpdate blacklist_pref(pref_service,
+                                      kHintsFetcherTopHostBlacklist);
+  if (!blacklist_pref->FindKey(previews::HashHostForDictionary(
+          navigation_handle->GetURL().host()))) {
+    return;
+  }
+  blacklist_pref->RemovePath(
+      previews::HashHostForDictionary(navigation_handle->GetURL().host()));
+  if (blacklist_pref->empty()) {
+    blacklist_pref->Clear();
+    pref_service->SetInteger(
+        kHintsFetcherTopHostBlacklistState,
+        static_cast<int>(HintsFetcherTopHostBlacklistState::kEmpty));
+  }
+}
+
 HintsFetcherTopHostBlacklistState
 PreviewsTopHostProviderImpl::GetCurrentBlacklistState() const {
   return static_cast<HintsFetcherTopHostBlacklistState>(
diff --git a/chrome/browser/previews/previews_top_host_provider_impl.h b/chrome/browser/previews/previews_top_host_provider_impl.h
index d3cb3e5..8aac274 100644
--- a/chrome/browser/previews/previews_top_host_provider_impl.h
+++ b/chrome/browser/previews/previews_top_host_provider_impl.h
@@ -18,6 +18,7 @@
 
 namespace content {
 class BrowserContext;
+class NavigationHandle;
 }
 namespace previews {
 
@@ -28,6 +29,13 @@
   explicit PreviewsTopHostProviderImpl(content::BrowserContext* BrowserContext);
   ~PreviewsTopHostProviderImpl() override;
 
+  // Update the HintsFetcherTopHostBlacklist by attempting to remove the host
+  // for the current navigation from the blacklist. A host is removed if it is
+  // currently on the blacklist and the blacklist state is updated if the
+  // blacklist is empty after removing a host.
+  static void MaybeUpdateTopHostBlacklist(
+      content::NavigationHandle* navigation_handle);
+
   std::vector<std::string> GetTopHosts(size_t max_sites) override;
 
  private:
diff --git a/chrome/browser/previews/previews_top_host_provider_impl_unittest.cc b/chrome/browser/previews/previews_top_host_provider_impl_unittest.cc
index 463098f3a..e3cb550 100644
--- a/chrome/browser/previews/previews_top_host_provider_impl_unittest.cc
+++ b/chrome/browser/previews/previews_top_host_provider_impl_unittest.cc
@@ -11,6 +11,7 @@
 #include "components/optimization_guide/optimization_guide_prefs.h"
 #include "components/prefs/pref_service.h"
 #include "components/previews/content/previews_hints_util.h"
+#include "content/public/test/mock_navigation_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace previews {
@@ -23,21 +24,31 @@
 
     top_host_provider_ =
         std::make_unique<PreviewsTopHostProviderImpl>(profile());
+    service_ = SiteEngagementService::Get(profile());
+    pref_service_ = profile()->GetPrefs();
   }
 
   void AddEngagedHosts(size_t num_hosts) {
-    SiteEngagementService* service = SiteEngagementService::Get(profile());
     for (size_t i = 1; i <= num_hosts; i++) {
-      GURL url = GURL(base::StringPrintf("https://domain%zu.com", i));
-      service->AddPointsForTesting(url, int(i));
+      AddEngagedHost(GURL(base::StringPrintf("https://domain%zu.com", i)),
+                     static_cast<int>(i));
     }
   }
 
-  void PopulateTopHostBlacklist(size_t num_hosts) {
-    PrefService* pref_service = profile()->GetPrefs();
+  void AddEngagedHost(GURL url, int num_points) {
+    service_->AddPointsForTesting(url, num_points);
+  }
 
+  bool IsHostBlacklisted(const std::string& host) {
+    const base::DictionaryValue* top_host_blacklist =
+        pref_service_->GetDictionary(
+            optimization_guide::prefs::kHintsFetcherTopHostBlacklist);
+    return top_host_blacklist->FindKey(previews::HashHostForDictionary(host));
+  }
+
+  void PopulateTopHostBlacklist(size_t num_hosts) {
     std::unique_ptr<base::DictionaryValue> top_host_filter =
-        pref_service
+        pref_service_
             ->GetDictionary(
                 optimization_guide::prefs::kHintsFetcherTopHostBlacklist)
             ->CreateDeepCopy();
@@ -46,15 +57,37 @@
       top_host_filter->SetBoolKey(
           HashHostForDictionary(base::StringPrintf("domain%zu.com", i)), true);
     }
-    pref_service->Set(optimization_guide::prefs::kHintsFetcherTopHostBlacklist,
-                      *top_host_filter);
+    pref_service_->Set(optimization_guide::prefs::kHintsFetcherTopHostBlacklist,
+                       *top_host_filter);
+  }
+
+  void AddHostToBlackList(const std::string& host) {
+    std::unique_ptr<base::DictionaryValue> top_host_filter =
+        pref_service_
+            ->GetDictionary(
+                optimization_guide::prefs::kHintsFetcherTopHostBlacklist)
+            ->CreateDeepCopy();
+    top_host_filter->SetBoolKey(previews::HashHostForDictionary(host), true);
+    pref_service_->Set(optimization_guide::prefs::kHintsFetcherTopHostBlacklist,
+                       *top_host_filter);
+  }
+
+  void SimulateUniqueNavigationsToTopHosts(size_t num_hosts) {
+    for (size_t i = 1; i <= num_hosts; i++) {
+      SimulateNavigation(GURL(base::StringPrintf("https://domain%zu.com", i)));
+    }
+  }
+
+  void SimulateNavigation(GURL url) {
+    std::unique_ptr<content::MockNavigationHandle> test_handle_ =
+        std::make_unique<content::MockNavigationHandle>(url, main_rfh());
+    PreviewsTopHostProviderImpl::MaybeUpdateTopHostBlacklist(
+        test_handle_.get());
   }
 
   void RemoveHostsFromBlacklist(size_t num_hosts_navigated) {
-    PrefService* pref_service = profile()->GetPrefs();
-
     std::unique_ptr<base::DictionaryValue> top_host_filter =
-        pref_service
+        pref_service_
             ->GetDictionary(
                 optimization_guide::prefs::kHintsFetcherTopHostBlacklist)
             ->CreateDeepCopy();
@@ -63,8 +96,8 @@
       top_host_filter->RemoveKey(
           HashHostForDictionary(base::StringPrintf("domain%zu.com", i)));
     }
-    pref_service->Set(optimization_guide::prefs::kHintsFetcherTopHostBlacklist,
-                      *top_host_filter);
+    pref_service_->Set(optimization_guide::prefs::kHintsFetcherTopHostBlacklist,
+                       *top_host_filter);
   }
 
   void SetTopHostBlacklistState(
@@ -77,10 +110,9 @@
 
   optimization_guide::prefs::HintsFetcherTopHostBlacklistState
   GetCurrentTopHostBlacklistState() {
-    PrefService* pref_service = profile()->GetPrefs();
     return static_cast<
         optimization_guide::prefs::HintsFetcherTopHostBlacklistState>(
-        pref_service->GetInteger(
+        pref_service_->GetInteger(
             optimization_guide::prefs::kHintsFetcherTopHostBlacklistState));
   }
 
@@ -92,6 +124,8 @@
 
  private:
   std::unique_ptr<PreviewsTopHostProviderImpl> top_host_provider_;
+  SiteEngagementService* service_;
+  PrefService* pref_service_;
 };
 
 TEST_F(PreviewsTopHostProviderImplTest, GetTopHostsMaxSites) {
@@ -195,4 +229,89 @@
       optimization_guide::prefs::HintsFetcherTopHostBlacklistState::kEmpty);
 }
 
+TEST_F(PreviewsTopHostProviderImplTest,
+       MaybeUpdateTopHostBlacklistNavigationsOnBlacklist) {
+  size_t engaged_hosts = 5;
+  size_t max_top_hosts = 5;
+  size_t num_hosts_blacklisted = 5;
+  size_t num_top_hosts = 3;
+  AddEngagedHosts(engaged_hosts);
+  // TODO(mcrouse): Remove once the blacklist is populated on initialization.
+  // The expected behavior will be that all hosts in the engagement service will
+  // be blacklisted on initialization.
+  PopulateTopHostBlacklist(num_hosts_blacklisted);
+
+  // Verify that all engaged hosts are blacklisted.
+  std::vector<std::string> hosts =
+      top_host_provider()->GetTopHosts(max_top_hosts);
+  EXPECT_EQ(hosts.size(), 0u);
+
+  // Navigate to some engaged hosts to trigger their removal from the top host
+  // blacklist.
+  SimulateUniqueNavigationsToTopHosts(num_top_hosts);
+
+  hosts = top_host_provider()->GetTopHosts(max_top_hosts);
+  EXPECT_EQ(hosts.size(), num_top_hosts);
+}
+
+TEST_F(PreviewsTopHostProviderImplTest,
+       MaybeUpdateTopHostBlacklistEmptyBlacklist) {
+  size_t engaged_hosts = 5;
+  size_t max_top_hosts = 5;
+  size_t num_hosts_blacklisted = 5;
+  size_t num_top_hosts = 5;
+  AddEngagedHosts(engaged_hosts);
+  // TODO(mcrouse): Remove once the blacklist is populated on initialization.
+  // The expected behavior will be that all hosts in the engagement service will
+  // be blacklisted on initialization.
+  PopulateTopHostBlacklist(num_hosts_blacklisted);
+
+  std::vector<std::string> hosts =
+      top_host_provider()->GetTopHosts(max_top_hosts);
+  EXPECT_EQ(hosts.size(), 0u);
+
+  SimulateUniqueNavigationsToTopHosts(num_top_hosts);
+
+  EXPECT_EQ(
+      GetCurrentTopHostBlacklistState(),
+      optimization_guide::prefs::HintsFetcherTopHostBlacklistState::kEmpty);
+
+  hosts = top_host_provider()->GetTopHosts(max_top_hosts);
+  EXPECT_EQ(hosts.size(), num_top_hosts);
+}
+
+TEST_F(PreviewsTopHostProviderImplTest,
+       HintsFetcherTopHostBlacklistNonHTTPOrHTTPSHost) {
+  size_t engaged_hosts = 5;
+  size_t max_top_hosts = 5;
+  size_t num_hosts_blacklisted = 5;
+  GURL http_url = GURL("http://anyscheme.com");
+  GURL file_url = GURL("file://anyscheme.com");
+  AddEngagedHosts(engaged_hosts);
+  AddEngagedHost(http_url, 5);
+  // TODO(mcrouse): Remove once the blacklist is populated on initialization.
+  // The expected behavior will be that all hosts in the engagement service will
+  // be blacklisted on initialization.
+  PopulateTopHostBlacklist(num_hosts_blacklisted);
+  AddHostToBlackList(http_url.host());
+
+  SetTopHostBlacklistState(optimization_guide::prefs::
+                               HintsFetcherTopHostBlacklistState::kInitialized);
+
+  // A Non HTTP/HTTPS navigation should not remove a host from the blacklist.
+  SimulateNavigation(file_url);
+  std::vector<std::string> hosts =
+      top_host_provider()->GetTopHosts(max_top_hosts);
+  EXPECT_EQ(hosts.size(), 0u);
+  // The host, anyscheme.com, should still be on the blacklist.
+  EXPECT_TRUE(IsHostBlacklisted(file_url.host()));
+
+  // TopHostProviderImpl prevents HTTP hosts from being returned.
+  SimulateNavigation(http_url);
+  hosts = top_host_provider()->GetTopHosts(max_top_hosts);
+  EXPECT_EQ(hosts.size(), 0u);
+
+  EXPECT_FALSE(IsHostBlacklisted(http_url.host()));
+}
+
 }  // namespace previews
diff --git a/chrome/browser/previews/previews_ui_tab_helper.cc b/chrome/browser/previews/previews_ui_tab_helper.cc
index cbb4d75..f28d555 100644
--- a/chrome/browser/previews/previews_ui_tab_helper.cc
+++ b/chrome/browser/previews/previews_ui_tab_helper.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/previews/previews_content_util.h"
 #include "chrome/browser/previews/previews_service.h"
 #include "chrome/browser/previews/previews_service_factory.h"
+#include "chrome/browser/previews/previews_top_host_provider_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_service.h"
@@ -227,12 +228,10 @@
   is_stale_reload_ = is_reload;
 }
 
-void PreviewsUITabHelper::DidStartNavigation(
+void PreviewsUITabHelper::MaybeRecordPreviewReload(
     content::NavigationHandle* navigation_handle) {
   if (navigation_handle->GetReloadType() == content::ReloadType::NONE)
     return;
-  if (!navigation_handle->IsInMainFrame())
-    return;
   if (!previews_user_data_)
     return;
   if (!previews_user_data_->HasCommittedPreviewsType())
@@ -251,6 +250,17 @@
   }
 }
 
+void PreviewsUITabHelper::DidStartNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame())
+    return;
+
+  MaybeRecordPreviewReload(navigation_handle);
+
+  previews::PreviewsTopHostProviderImpl::MaybeUpdateTopHostBlacklist(
+      navigation_handle);
+}
+
 void PreviewsUITabHelper::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
   // Delete Previews information later, so that other DidFinishNavigation
diff --git a/chrome/browser/previews/previews_ui_tab_helper.h b/chrome/browser/previews/previews_ui_tab_helper.h
index ca19333..e6c137a 100644
--- a/chrome/browser/previews/previews_ui_tab_helper.h
+++ b/chrome/browser/previews/previews_ui_tab_helper.h
@@ -5,10 +5,12 @@
 #ifndef CHROME_BROWSER_PREVIEWS_PREVIEWS_UI_TAB_HELPER_H_
 #define CHROME_BROWSER_PREVIEWS_PREVIEWS_UI_TAB_HELPER_H_
 
-#include <map>
-
 #include <stdint.h>
 
+#include <map>
+#include <memory>
+#include <utility>
+
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -129,6 +131,10 @@
   void DidStartNavigation(
       content::NavigationHandle* navigation_handle) override;
 
+  // Records the time of the navigation if the current navigation is a reload
+  // and a preview was shown.
+  void MaybeRecordPreviewReload(content::NavigationHandle* navigation_handle);
+
   // True if the UI for a preview has been shown for the page.
   bool displayed_preview_ui_ = false;
 
diff --git a/chrome/browser/profiles/profile_io_data.cc b/chrome/browser/profiles/profile_io_data.cc
index cf31bbd..bad56f3 100644
--- a/chrome/browser/profiles/profile_io_data.cc
+++ b/chrome/browser/profiles/profile_io_data.cc
@@ -273,12 +273,6 @@
   protocol_handler_registry_io_thread_delegate_ =
       protocol_handler_registry->io_thread_delegate();
 
-  // The profile instance is only available here in the InitializeOnUIThread
-  // method, so we create the url job factory here, then save it for
-  // later delivery to the job factory in Init().
-  params->protocol_handler_interceptor =
-      protocol_handler_registry->CreateJobInterceptorFactory();
-
 #if defined(OS_CHROMEOS)
   // Enable client certificates for the Chrome OS sign-in frame, if this feature
   // is not disabled by a flag.
diff --git a/chrome/browser/profiles/profile_io_data.h b/chrome/browser/profiles/profile_io_data.h
index 2fdb8e4..6951921 100644
--- a/chrome/browser/profiles/profile_io_data.h
+++ b/chrome/browser/profiles/profile_io_data.h
@@ -34,7 +34,6 @@
 #include "services/network/public/mojom/network_service.mojom.h"
 
 class HostContentSettingsMap;
-class ProtocolHandlerRegistry;
 
 namespace chromeos {
 class CertificateProvider;
@@ -213,17 +212,6 @@
     signin::AccountConsistencyMethod account_consistency =
         signin::AccountConsistencyMethod::kDisabled;
 
-    // This pointer exists only as a means of conveying a url job factory
-    // pointer from the protocol handler registry on the UI thread to the
-    // the URLRequestContext on the IO thread. The consumer MUST take
-    // ownership of the object by calling release() on this pointer.
-    std::unique_ptr<ProtocolHandlerRegistry::JobInterceptorFactory>
-        protocol_handler_interceptor;
-
-    // Holds the URLRequestInterceptor pointer that is created on the UI thread
-    // and then passed to the list of request_interceptors on the IO thread.
-    std::unique_ptr<net::URLRequestInterceptor> new_tab_page_interceptor;
-
 #if defined(OS_CHROMEOS)
     std::string username_hash;
     SystemKeySlotUseType system_key_slot_use_type = SystemKeySlotUseType::kNone;
diff --git a/chrome/browser/resources/chromeos/account_manager_welcome.html b/chrome/browser/resources/chromeos/account_manager_welcome.html
index 54e41f8..c39e14d 100644
--- a/chrome/browser/resources/chromeos/account_manager_welcome.html
+++ b/chrome/browser/resources/chromeos/account_manager_welcome.html
@@ -4,15 +4,14 @@
   <title>$i18n{welcomeTitle}</title>
   <meta charset="utf-8">
   <link rel="import" href="chrome://resources/html/polymer.html">
-  <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
-  <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
+  <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
   <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
 
   <link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="stylesheet" href="account_manager_shared.css">
   <custom-style>
-    <style is="custom-style" include="cr-shared-style paper-button-style">
+    <style is="custom-style" include="cr-shared-style">
     </style>
   </custom-style>
 
@@ -36,9 +35,9 @@
                   account_manager_welcome_2x.png 2x">
     </if>
     <div class="button-container">
-      <paper-button id="ok-button" class="action-button">
+      <cr-button id="ok-button" class="action-button">
         $i18n{okButton}
-      </paper-button>
+      </cr-button>
     </div>
   </div>
 </body>
diff --git a/chrome/browser/resources/chromeos/account_migration_welcome.html b/chrome/browser/resources/chromeos/account_migration_welcome.html
index 952623e..2bde61a 100644
--- a/chrome/browser/resources/chromeos/account_migration_welcome.html
+++ b/chrome/browser/resources/chromeos/account_migration_welcome.html
@@ -4,15 +4,14 @@
   <title>$i18n{welcomePageTitle}</title>
   <meta charset="utf-8">
   <link rel="import" href="chrome://resources/html/polymer.html">
-  <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
-  <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
+  <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
   <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
   <link rel="import" href="account_migration_browser_proxy.html">
 
   <link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
   <link rel="stylesheet" href="account_manager_shared.css">
   <custom-style>
-    <style is="custom-style" include="cr-shared-style paper-button-style">
+    <style is="custom-style" include="cr-shared-style">
     </style>
   </custom-style>
 
@@ -36,12 +35,12 @@
                   account_manager_welcome_2x.png 2x">
     </if>
     <div class="button-container">
-      <paper-button id="cancel-button" class="cancel-button">
+      <cr-button id="cancel-button" class="cancel-button">
         $i18n{cancelButton}
-      </paper-button>
-      <paper-button id="migrate-button" class="action-button">
+      </cr-button>
+      <cr-button id="migrate-button" class="action-button">
         $i18n{migrateButton}
-      </paper-button>
+      </cr-button>
     </div>
   </div>
 </body>
diff --git a/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js b/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
index ee5c97e..6540ed4 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/models/filesystem.js
@@ -294,6 +294,9 @@
 cca.models.FileSystem.createThumbnail_ = function(isVideo, url) {
   const thumbnailWidth = 480;
   var element = document.createElement(isVideo ? 'video' : 'img');
+  if (isVideo) {
+    element.preload = 'auto';
+  }
   return new Promise((resolve, reject) => {
     element.addEventListener(isVideo ? 'canplay' : 'load', resolve);
     element.addEventListener('error', reject);
diff --git a/chrome/browser/resources/chromeos/emulator/audio_settings.html b/chrome/browser/resources/chromeos/emulator/audio_settings.html
index 871744a..96aab81 100644
--- a/chrome/browser/resources/chromeos/emulator/audio_settings.html
+++ b/chrome/browser/resources/chromeos/emulator/audio_settings.html
@@ -1,5 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
@@ -8,7 +9,6 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
 <link rel="import" href="icons.html">
 <link rel="import" href="shared_styles.html">
 
@@ -54,9 +54,9 @@
         </form>
       </div>
       <div slot="button-container">
-        <paper-button class="action-button" on-click="insertEditedAudioNode">
+        <cr-button class="action-button" on-click="insertEditedAudioNode">
           Done
-        </paper-button>
+        </cr-button>
       </div>
     </cr-dialog>
 
@@ -99,9 +99,7 @@
         </tbody>
       </table>
       <div class="add-device-container">
-        <paper-button on-click="appendNewNode">
-          Add Node
-        </paper-button>
+        <cr-button on-click="appendNewNode">Add Node</cr-button>
       </div>
     </div>
   </template>
diff --git a/chrome/browser/resources/chromeos/emulator/battery_settings.html b/chrome/browser/resources/chromeos/emulator/battery_settings.html
index 2e08c751a..7067464 100644
--- a/chrome/browser/resources/chromeos/emulator/battery_settings.html
+++ b/chrome/browser/resources/chromeos/emulator/battery_settings.html
@@ -1,5 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
@@ -66,13 +67,14 @@
                 <cr-checkbox checked="{{item.connected}}"></cr-checkbox>
               </td>
               <td class="control-cell">
-                <paper-button on-tap="onSetAsSourceTap"
+                <cr-button on-click="onSetAsSourceClick"
+                    class$="[[cssClassForSetAsSource_(item,
+                        selectedPowerSourceId)]]"
                     hidden$="[[!isDualRole(item)]]"
-                    raised="[[!isSelectedSource(item, selectedPowerSourceId)]]"
                     disabled="[[!canBecomeSource(
                         item, selectedPowerSourceId, powerSourceOptions.*)]]">
                   Set as source
-                </paper-button>
+                </cr-button>
                 <div hidden$="[[isDualRole(item)]]">Source</div>
               </td>
               <td class="control-cell">
diff --git a/chrome/browser/resources/chromeos/emulator/battery_settings.js b/chrome/browser/resources/chromeos/emulator/battery_settings.js
index 65801e7..c913283 100644
--- a/chrome/browser/resources/chromeos/emulator/battery_settings.js
+++ b/chrome/browser/resources/chromeos/emulator/battery_settings.js
@@ -136,7 +136,11 @@
       chrome.send('updateBatteryPercent', [this.percent]);
   },
 
-  onSetAsSourceTap: function(e) {
+  /**
+   * @param {!{model: {item: {id: string}}}} e
+   * @private
+   */
+  onSetAsSourceClick_: function(e) {
     chrome.send('updatePowerSourceId', [e.model.item.id]);
   },
 
@@ -189,8 +193,13 @@
     return source.type == 'DualRoleUSB';
   },
 
-  isSelectedSource: function(source) {
-    return source.id == this.selectedPowerSourceId;
+  /**
+   * @param {!{id: string}} source
+   * @return {string}
+   * @private
+   */
+  cssClassForSetAsSource_: function(source) {
+    return source.id == this.selectedPowerSourceId ? '' : 'action-button';
   },
 
   canAmpsChange: function(type) {
diff --git a/chrome/browser/resources/chromeos/emulator/bluetooth_settings.html b/chrome/browser/resources/chromeos/emulator/bluetooth_settings.html
index 59142a2..00e1c009 100644
--- a/chrome/browser/resources/chromeos/emulator/bluetooth_settings.html
+++ b/chrome/browser/resources/chromeos/emulator/bluetooth_settings.html
@@ -1,5 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
@@ -8,7 +9,6 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
 <link rel="import" href="icons.html">
 <link rel="import" href="shared_styles.html">
 
@@ -94,9 +94,9 @@
         </form>
       </div>
       <div slot="button-container">
-        <paper-button class="action-button" on-tap="onCloseTap_">
+        <cr-button class="action-button" on-click="onCloseClick_">
           Close
-        </paper-button>
+        </cr-button>
       </div>
     </cr-dialog>
 
@@ -167,9 +167,9 @@
         </tbody>
       </table>
       <div class="add-device-container">
-        <paper-button on-click="appendNewDevice">
+        <cr-button on-click="appendNewDevice">
           Add Device
-        </paper-button>
+        </cr-button>
       </div>
     </div>
   </template>
diff --git a/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js b/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
index 4bcda58..1c36a83 100644
--- a/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
+++ b/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
@@ -475,7 +475,7 @@
   },
 
   /** @private */
-  onCloseTap_: function() {
+  onCloseClick_: function() {
     this.$.editDialog.close();
   },
 
diff --git a/chrome/browser/resources/chromeos/emulator/device_emulator_pages.html b/chrome/browser/resources/chromeos/emulator/device_emulator_pages.html
index 7727008..9b0123d 100644
--- a/chrome/browser/resources/chromeos/emulator/device_emulator_pages.html
+++ b/chrome/browser/resources/chromeos/emulator/device_emulator_pages.html
@@ -69,7 +69,7 @@
     <cr-toolbar page-name="Device Emulator"
         clear-label="clear"
         search-prompt="Search not working..."
-        on-cr-toolbar-menu-tap="onMenuButtonTap_"
+        on-cr-toolbar-menu-tap="onMenuButtonClick_"
         menu-label="Device Emulator"
         role="banner"
         show-menu>
diff --git a/chrome/browser/resources/chromeos/emulator/device_emulator_pages.js b/chrome/browser/resources/chromeos/emulator/device_emulator_pages.js
index 1411f5b..64fad29 100644
--- a/chrome/browser/resources/chromeos/emulator/device_emulator_pages.js
+++ b/chrome/browser/resources/chromeos/emulator/device_emulator_pages.js
@@ -28,7 +28,7 @@
   },
 
   /** @private */
-  onMenuButtonTap_: function() {
+  onMenuButtonClick_: function() {
     this.$.drawer.toggle();
   },
 
diff --git a/chrome/browser/resources/chromeos/emulator/shared_styles.html b/chrome/browser/resources/chromeos/emulator/shared_styles.html
index fcbec934..e49712f 100644
--- a/chrome/browser/resources/chromeos/emulator/shared_styles.html
+++ b/chrome/browser/resources/chromeos/emulator/shared_styles.html
@@ -107,7 +107,7 @@
         text-align: end;
       }
 
-      .add-device-container paper-button {
+      .add-device-container cr-button {
         color: rgb(82, 101, 162);
       }
     </style>
diff --git a/chrome/browser/resources/chromeos/zip_archiver/html/passphrase-dialog.html b/chrome/browser/resources/chromeos/zip_archiver/html/passphrase-dialog.html
index f697e84..93928ae4 100644
--- a/chrome/browser/resources/chromeos/zip_archiver/html/passphrase-dialog.html
+++ b/chrome/browser/resources/chromeos/zip_archiver/html/passphrase-dialog.html
@@ -1,14 +1,13 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
 
 <dom-module id="passphrase-dialog">
   <template>
-    <style include="paper-button-style">
+    <style>
       cr-input,
-      paper-button {
+      cr-button {
         -webkit-app-region: no-drag;
       }
 
@@ -42,7 +41,7 @@
         text-overflow: ellipsis;
       }
 
-      paper-button + paper-button {
+      cr-button + cr-button {
         margin-inline-start: 8px;
       }
     </style>
@@ -53,15 +52,12 @@
               i18n-values="placeholder:ZIP_ARCHIVER_PASSPHRASE_INPUT_LABEL;aria-label:ZIP_ARCHIVER_PASSPHRASE_INPUT_LABEL">
     </cr-input>
     <div id="buttons">
-      <paper-button on-click="cancel"
-                    id="cancelButton"
-                    i18n-content="ZIP_ARCHIVER_PASSPHRASE_CANCEL">
-      </paper-button>
-      <paper-button class="action-button"
-                    on-click="accept"
-                    id="acceptButton"
-                    i18n-content="ZIP_ARCHIVER_PASSPHRASE_ACCEPT">
-      </paper-button>
+      <cr-button on-click="cancel" id="cancelButton"
+          i18n-content="ZIP_ARCHIVER_PASSPHRASE_CANCEL">
+      </cr-button>
+      <cr-button class="action-button" on-click="accept" id="acceptButton"
+          i18n-content="ZIP_ARCHIVER_PASSPHRASE_ACCEPT">
+      </cr-button>
     </div>
   </template>
 </dom-module>
diff --git a/chrome/browser/resources/local_ntp/customize.js b/chrome/browser/resources/local_ntp/customize.js
index 8104e15..be6258e 100644
--- a/chrome/browser/resources/local_ntp/customize.js
+++ b/chrome/browser/resources/local_ntp/customize.js
@@ -82,6 +82,7 @@
   BACKGROUNDS_IMAGE_MENU: 'backgrounds-image-menu',
   BACKGROUNDS_MENU: 'backgrounds-menu',
   BACKGROUNDS_UPLOAD: 'backgrounds-upload',
+  BACKGROUNDS_UPLOAD_WRAPPER: 'backgrounds-upload-wrapper',
   CANCEL: 'bg-sel-footer-cancel',
   COLORS_BUTTON: 'colors-button',
   COLORS_MENU: 'colors-menu',
@@ -457,13 +458,48 @@
   return 3;
 };
 
+/**
+ * @param {number} deltaX Change in the x direction.
+ * @param {number} deltaY Change in the y direction.
+ * @param {Element} current The current tile.
+ */
+customize.richerPicker_getNextTile = function(deltaX, deltaY, current) {
+  const menu = $(customize.IDS.CUSTOMIZATION_MENU);
+  const container = menu.classList.contains(customize.CLASSES.ON_IMAGE_MENU) ?
+      $(customize.IDS.BACKGROUNDS_IMAGE_MENU) :
+      $(customize.IDS.BACKGROUNDS_MENU);
+  const tiles = Array.from(
+      container.getElementsByClassName(customize.CLASSES.COLLECTION_TILE));
+  const curIndex = tiles.indexOf(current);
+  if (deltaX != 0) {
+    return tiles[curIndex + deltaX];
+  } else if (deltaY != 0) {
+    let nextIndex = tiles.indexOf(current);
+    const startingTop = current.getBoundingClientRect().top;
+    const startingLeft = current.getBoundingClientRect().left;
+
+    // Search until a tile in a different row and the same column is found.
+    while (tiles[nextIndex] &&
+           (tiles[nextIndex].getBoundingClientRect().top == startingTop ||
+            tiles[nextIndex].getBoundingClientRect().left != startingLeft)) {
+      nextIndex += deltaY;
+    }
+    return tiles[nextIndex];
+  }
+  return null;
+};
+
 /* Get the next tile when the arrow keys are used to navigate the grid.
  * Returns null if the tile doesn't exist.
  * @param {number} deltaX Change in the x direction.
  * @param {number} deltaY Change in the y direction.
- * @param {string} current Number of the current tile.
+ * @param {Element} currentElem The current tile.
  */
-customize.getNextTile = function(deltaX, deltaY, current) {
+customize.getNextTile = function(deltaX, deltaY, currentElem) {
+  if (configData.richerPicker) {
+    return customize.richerPicker_getNextTile(deltaX, deltaY, currentElem);
+  }
+  const current = currentElem.dataset.tileNum;
   let idPrefix = 'coll_tile_';
   if ($(customize.IDS.MENU)
           .classList.contains(customize.CLASSES.IMAGE_DIALOG)) {
@@ -576,6 +612,10 @@
     if (event.keyCode === customize.KEYCODES.ENTER) {
       event.preventDefault();
       event.stopPropagation();
+      if (event.currentTarget.onClickOverride) {
+        event.currentTarget.onClickOverride(event);
+        return;
+      }
       tileOnClickInteraction(event);
     } else if (
         event.keyCode === customize.KEYCODES.LEFT ||
@@ -590,17 +630,15 @@
       if (event.keyCode === customize.KEYCODES.LEFT) {
         target = customize.getNextTile(
             document.documentElement.classList.contains('rtl') ? 1 : -1, 0,
-            event.currentTarget.dataset.tileNum);
+            event.currentTarget);
       } else if (event.keyCode === customize.KEYCODES.UP) {
-        target =
-            customize.getNextTile(0, -1, event.currentTarget.dataset.tileNum);
+        target = customize.getNextTile(0, -1, event.currentTarget);
       } else if (event.keyCode === customize.KEYCODES.RIGHT) {
         target = customize.getNextTile(
             document.documentElement.classList.contains('rtl') ? -1 : 1, 0,
-            event.currentTarget.dataset.tileNum);
+            event.currentTarget);
       } else if (event.keyCode === customize.KEYCODES.DOWN) {
-        target =
-            customize.getNextTile(0, 1, event.currentTarget.dataset.tileNum);
+        target = customize.getNextTile(0, 1, event.currentTarget);
       }
       if (target) {
         target.focus();
@@ -623,6 +661,16 @@
     tileContainer.appendChild(tile);
   }
 
+  // Attach event listeners for upload and default tiles
+  $(customize.IDS.BACKGROUNDS_UPLOAD_WRAPPER).onkeydown =
+      tileOnKeyDownInteraction;
+  $(customize.IDS.BACKGROUNDS_DEFAULT_ICON).onkeydown =
+      tileOnKeyDownInteraction;
+  $(customize.IDS.BACKGROUNDS_UPLOAD_WRAPPER).onClickOverride =
+      $(customize.IDS.BACKGROUNDS_UPLOAD).onkeydown;
+  $(customize.IDS.BACKGROUNDS_DEFAULT_ICON).onClickOverride =
+      $(customize.IDS.BACKGROUNDS_DEFAULT).onkeydown;
+
   $(customize.IDS.TILES).focus();
 };
 
@@ -810,17 +858,15 @@
       if (event.keyCode == customize.KEYCODES.LEFT) {
         target = customize.getNextTile(
             document.documentElement.classList.contains('rtl') ? 1 : -1, 0,
-            event.currentTarget.dataset.tileNum);
+            event.currentTarget);
       } else if (event.keyCode == customize.KEYCODES.UP) {
-        target =
-            customize.getNextTile(0, -1, event.currentTarget.dataset.tileNum);
+        target = customize.getNextTile(0, -1, event.currentTarget);
       } else if (event.keyCode == customize.KEYCODES.RIGHT) {
         target = customize.getNextTile(
             document.documentElement.classList.contains('rtl') ? -1 : 1, 0,
-            event.currentTarget.dataset.tileNum);
+            event.currentTarget);
       } else if (event.keyCode == customize.KEYCODES.DOWN) {
-        target =
-            customize.getNextTile(0, 1, event.currentTarget.dataset.tileNum);
+        target = customize.getNextTile(0, 1, event.currentTarget);
       }
       if (target) {
         target.focus();
@@ -1382,6 +1428,12 @@
       backInteraction(event);
     }
   };
+  $(customize.IDS.MENU_BACK_CIRCLE).onkeyup = function(event) {
+    if (event.keyCode === customize.KEYCODES.ENTER ||
+        event.keyCode === customize.KEYCODES.SPACE) {
+      backInteraction(event);
+    }
+  };
   // Pressing Spacebar on the back arrow shouldn't scroll the dialog.
   $(customize.IDS.BACK_CIRCLE).onkeydown = function(event) {
     if (event.keyCode === customize.KEYCODES.SPACE) {
@@ -1441,6 +1493,24 @@
     }
   };
 
+  $(customize.IDS.BACKGROUNDS_MENU).onkeydown = function(event) {
+    if (event.keyCode === customize.KEYCODES.LEFT ||
+        event.keyCode === customize.KEYCODES.UP ||
+        event.keyCode === customize.KEYCODES.RIGHT ||
+        event.keyCode === customize.KEYCODES.DOWN) {
+      $(customize.IDS.BACKGROUNDS_UPLOAD_WRAPPER).focus();
+    }
+  };
+
+  $(customize.IDS.BACKGROUNDS_IMAGE_MENU).onkeydown = function(event) {
+    if (event.keyCode === customize.KEYCODES.LEFT ||
+        event.keyCode === customize.KEYCODES.UP ||
+        event.keyCode === customize.KEYCODES.RIGHT ||
+        event.keyCode === customize.KEYCODES.DOWN) {
+      $('img_tile_0').focus();
+    }
+  };
+
   $(customize.IDS.BACKGROUNDS_UPLOAD).onclick = uploadImageInteraction;
   $(customize.IDS.BACKGROUNDS_UPLOAD).onkeydown = function(event) {
     if (event.keyCode === customize.KEYCODES.ENTER ||
@@ -1449,7 +1519,7 @@
     }
   };
 
-  $(customize.IDS.BACKGROUNDS_DEFAULT).onclick = function() {
+  $(customize.IDS.BACKGROUNDS_DEFAULT).onclick = function(event) {
     const tile = $(customize.IDS.BACKGROUNDS_DEFAULT_ICON);
     tile.dataset.url = '';
     tile.dataset.attributionLine1 = '';
@@ -1457,6 +1527,12 @@
     tile.dataset.attributionActionUrl = '';
     customize.richerPicker_selectTile(tile);
   };
+  $(customize.IDS.BACKGROUNDS_DEFAULT).onkeydown = function(event) {
+    if (event.keyCode === customize.KEYCODES.ENTER ||
+        event.keyCode === customize.KEYCODES.SPACE) {
+      $(customize.IDS.BACKGROUNDS_DEFAULT).onclick(event);
+    }
+  };
 
   const richerPickerOpenBackgrounds = function() {
     customize.richerPicker_resetCustomizationMenu();
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index 82b5da2..dbbd9eb 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -1030,11 +1030,16 @@
   width: 144px;
 }
 
+#backgrounds-menu .bg-sel-tile-bg.selected .bg-sel-tile:focus,
 #backgrounds-image-menu .bg-sel-tile-bg.selected .bg-sel-tile:focus,
 #colors-menu .bg-sel-tile-bg.selected .bg-sel-tile:focus {
   outline: none;
 }
 
+.using-mouse-nav .bg-sel-tile:focus {
+  outline: none;
+}
+
 #backgrounds-menu .bg-sel-tile,
 #backgrounds-image-menu .bg-sel-tile,
 #colors-menu .bg-sel-tile {
@@ -1142,6 +1147,14 @@
   transform: scaleX(-1);
 }
 
+#backgrounds-upload-wrapper {
+  display: inline-block;
+  left: 0;
+  opacity: 1;
+  position: relative;
+  top: 0;
+}
+
 #backgrounds-upload-icon {
   -webkit-mask-image: url(icons/upload.svg);
   -webkit-mask-repeat: no-repeat;
diff --git a/chrome/browser/resources/local_ntp/local_ntp.html b/chrome/browser/resources/local_ntp/local_ntp.html
index 3f67324..adb3f36 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.html
+++ b/chrome/browser/resources/local_ntp/local_ntp.html
@@ -172,7 +172,7 @@
   <dialog id="customization-menu" class="customize-dialog">
     <div id="menu-header">
       <div id="menu-back-wrapper">
-        <div id="menu-back-circle" tabindex="0">
+        <div id="menu-back-circle" tabindex="0" role="button">
           <div id="menu-back"></div>
         </div>
       </div>
@@ -201,22 +201,24 @@
       </div>
     </div>
     <div id="menu-contents">
-      <div id="backgrounds-menu" class="menu-panel">
+      <div id="backgrounds-menu" class="menu-panel" tabindex="0">
         <div id="backgrounds-upload" class="bg-sel-tile-bg">
-          <div id="backgrounds-upload-icon"></div>
-          <div id="backgrounds-upload-text">$i18n{uploadImage}</div>
+          <div id="backgrounds-upload-wrapper" class="bg-sel-tile" tabindex="-1">
+            <div id="backgrounds-upload-icon"></div>
+            <div id="backgrounds-upload-text">$i18n{uploadImage}</div>
+          </div>
         </div>
         <div id="backgrounds-default" class="bg-sel-tile-bg">
-          <div id="backgrounds-default-icon" class="bg-sel-tile">
+          <div id="backgrounds-default-icon" class="bg-sel-tile" tabindex="-1">
             <div class="mini-page">
               <div class="mini-header"></div>
               <div class="mini-shortcuts"></div>
             </div>
           </div>
           <div class="bg-sel-tile-title">$i18n{noBackground}</div>
-        </div>
+          </div>
       </div>
-      <div id="backgrounds-image-menu" class="menu-panel"></div>
+      <div id="backgrounds-image-menu" class="menu-panel" tabindex="0"></div>
       <div id="shortcuts-menu" class="menu-panel">
         <div id="sh-options">
           <div class="sh-option">
diff --git a/chrome/browser/resources/ntp4/images/2x/disclosure_triangle_mask.png b/chrome/browser/resources/ntp4/images/2x/disclosure_triangle_mask.png
deleted file mode 100644
index 72b4f2b..0000000
--- a/chrome/browser/resources/ntp4/images/2x/disclosure_triangle_mask.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/ntp4/images/disclosure_triangle_mask.png b/chrome/browser/resources/ntp4/images/disclosure_triangle_mask.png
deleted file mode 100644
index 7b70a3e..0000000
--- a/chrome/browser/resources/ntp4/images/disclosure_triangle_mask.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
index afda4e4..b6d133f 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service_browsertest.cc
@@ -85,6 +85,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/no_renderer_crashes_assertion.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "crypto/sha2.h"
 #include "net/cookies/cookie_util.h"
@@ -1267,6 +1268,12 @@
   V4SafeBrowsingServiceMetadataTest() {}
 
  private:
+#if defined(ADDRESS_SANITIZER)
+  // TODO(lukasza): https://crbug.com/971820: Disallow renderer crashes once the
+  // bug is fixed.
+  content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes_;
+#endif
+
   DISALLOW_COPY_AND_ASSIGN(V4SafeBrowsingServiceMetadataTest);
 };
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 067e1db..376c5c6 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3080,7 +3080,6 @@
       "//components/payments/core",
       "//components/ui_devtools/views",
       "//device/vr/buildflags:buildflags",
-      "//services/ws/public/cpp/input_devices",
       "//ui/views:buildflags",
     ]
 
@@ -3173,6 +3172,7 @@
         "views/relaunch_notification/relaunch_notification_controller_platform_impl_chromeos.cc",
         "views/relaunch_notification/relaunch_notification_controller_platform_impl_chromeos.h",
       ]
+      deps += [ "//ui/ozone" ]
     } else {
       sources += [
         "views/frame/opaque_browser_frame_view.cc",
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc
index f108f5b..4771a2d 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.cc
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
-#include "services/ws/public/cpp/input_devices/input_device_controller_client.h"
 #include "ui/aura/window.h"
 #include "url/gurl.h"
 
@@ -62,8 +61,3 @@
 ChromeShellDelegate::CreateScreenshotDelegate() {
   return std::make_unique<ChromeScreenshotGrabber>();
 }
-
-ws::InputDeviceControllerClient*
-ChromeShellDelegate::GetInputDeviceControllerClient() {
-  return g_browser_process->platform_part()->GetInputDeviceControllerClient();
-}
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.h b/chrome/browser/ui/ash/chrome_shell_delegate.h
index 6f0f743..75df60f 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.h
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.h
@@ -18,7 +18,6 @@
   std::unique_ptr<ash::ScreenshotDelegate> CreateScreenshotDelegate() override;
   ash::AccessibilityDelegate* CreateAccessibilityDelegate() override;
   void OpenKeyboardShortcutHelpPage() const override;
-  ws::InputDeviceControllerClient* GetInputDeviceControllerClient() override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ChromeShellDelegate);
diff --git a/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc b/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc
index 616c0c54..3bf14a9 100644
--- a/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc
+++ b/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc
@@ -88,24 +88,16 @@
   keyboard_controller_->AddObserver(this);
 
   // Request the initial enabled state.
-  keyboard_controller_->IsKeyboardEnabled(
-      base::BindOnce(&ChromeKeyboardControllerClient::OnKeyboardEnabledChanged,
-                     weak_ptr_factory_.GetWeakPtr()));
+  OnKeyboardEnabledChanged(keyboard_controller_->IsKeyboardEnabled());
 
   // Request the initial set of enable flags.
-  keyboard_controller_->GetEnableFlags(base::BindOnce(
-      &ChromeKeyboardControllerClient::OnKeyboardEnableFlagsChanged,
-      weak_ptr_factory_.GetWeakPtr()));
+  OnKeyboardEnableFlagsChanged(keyboard_controller_->GetEnableFlags());
 
   // Request the initial visible state.
-  keyboard_controller_->IsKeyboardVisible(base::BindOnce(
-      &ChromeKeyboardControllerClient::OnKeyboardVisibilityChanged,
-      weak_ptr_factory_.GetWeakPtr()));
+  OnKeyboardVisibilityChanged(keyboard_controller_->IsKeyboardVisible());
 
   // Request the configuration.
-  keyboard_controller_->GetKeyboardConfig(
-      base::BindOnce(&ChromeKeyboardControllerClient::OnKeyboardConfigChanged,
-                     weak_ptr_factory_.GetWeakPtr()));
+  OnKeyboardConfigChanged(keyboard_controller_->GetKeyboardConfig());
 }
 
 ChromeKeyboardControllerClient::~ChromeKeyboardControllerClient() {
@@ -164,9 +156,8 @@
   keyboard_controller_->SetKeyboardConfig(config);
 }
 
-void ChromeKeyboardControllerClient::GetKeyboardEnabled(
-    base::OnceCallback<void(bool)> callback) {
-  keyboard_controller_->IsKeyboardEnabled(std::move(callback));
+bool ChromeKeyboardControllerClient::GetKeyboardEnabled() {
+  return keyboard_controller_->IsKeyboardEnabled();
 }
 
 void ChromeKeyboardControllerClient::SetEnableFlag(
@@ -264,9 +255,8 @@
 }
 
 void ChromeKeyboardControllerClient::OnKeyboardEnableFlagsChanged(
-    const std::vector<keyboard::KeyboardEnableFlag>& flags) {
-  keyboard_enable_flags_ =
-      std::set<keyboard::KeyboardEnableFlag>(flags.begin(), flags.end());
+    const std::set<keyboard::KeyboardEnableFlag>& flags) {
+  keyboard_enable_flags_ = flags;
 }
 
 void ChromeKeyboardControllerClient::OnKeyboardEnabledChanged(bool enabled) {
diff --git a/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h b/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h
index 49be1af..0f5200e 100644
--- a/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h
+++ b/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h
@@ -11,6 +11,7 @@
 
 #include "ash/public/cpp/keyboard/keyboard_config.h"
 #include "ash/public/cpp/keyboard/keyboard_controller.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -92,9 +93,9 @@
   // Sets the new keyboard configuration and updates the cached config.
   void SetKeyboardConfig(const keyboard::KeyboardConfig& config);
 
-  // Invokes |callback| with the current enabled state. Call this after
-  // Set/ClearEnableFlag to get the updated enabled state.
-  void GetKeyboardEnabled(base::OnceCallback<void(bool)> callback);
+  // Returns the current enabled state. Call this after Set/ClearEnableFlag to
+  // get the updated enabled state.
+  bool GetKeyboardEnabled();
 
   // Sets/clears the privided keyboard enable state.
   void SetEnableFlag(const keyboard::KeyboardEnableFlag& flag);
@@ -143,7 +144,7 @@
 
   // ash::KeyboardControllerObserver:
   void OnKeyboardEnableFlagsChanged(
-      const std::vector<keyboard::KeyboardEnableFlag>& flags) override;
+      const std::set<keyboard::KeyboardEnableFlag>& flags) override;
   void OnKeyboardEnabledChanged(bool enabled) override;
   void OnKeyboardConfigChanged(const keyboard::KeyboardConfig& config) override;
   void OnKeyboardVisibilityChanged(bool visible) override;
diff --git a/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.cc b/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.cc
index 0d0a0130..eb48b68 100644
--- a/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.cc
+++ b/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client_test_helper.h"
 
 #include <set>
+#include <utility>
+#include <vector>
 
 #include "ash/keyboard/ash_keyboard_controller.h"
 #include "ash/public/cpp/keyboard/keyboard_controller.h"
@@ -23,30 +25,26 @@
 
   // ash::KeyboardController:
   void KeyboardContentsLoaded(const gfx::Size& size) override {}
-  void GetKeyboardConfig(GetKeyboardConfigCallback callback) override {
-    std::move(callback).Run(keyboard_config_);
+  keyboard::KeyboardConfig GetKeyboardConfig() override {
+    return keyboard_config_;
   }
   void SetKeyboardConfig(
       const keyboard::KeyboardConfig& keyboard_config) override {
     keyboard_config_ = keyboard_config;
   }
-  void IsKeyboardEnabled(IsKeyboardEnabledCallback callback) override {
-    std::move(callback).Run(enabled_);
-  }
+  bool IsKeyboardEnabled() override { return enabled_; }
   void SetEnableFlag(keyboard::KeyboardEnableFlag flag) override {
     keyboard_enable_flags_.insert(flag);
   }
   void ClearEnableFlag(keyboard::KeyboardEnableFlag flag) override {
     keyboard_enable_flags_.erase(flag);
   }
-  void GetEnableFlags(GetEnableFlagsCallback callback) override {
-    std::move(callback).Run(std::vector<keyboard::KeyboardEnableFlag>());
+  const std::set<keyboard::KeyboardEnableFlag>& GetEnableFlags() override {
+    return keyboard_enable_flags_;
   }
   void ReloadKeyboardIfNeeded() override {}
   void RebuildKeyboardIfEnabled() override {}
-  void IsKeyboardVisible(IsKeyboardVisibleCallback callback) override {
-    std::move(callback).Run(visible_);
-  }
+  bool IsKeyboardVisible() override { return visible_; }
   void ShowKeyboard() override { visible_ = true; }
   void HideKeyboard(ash::HideReason reason) override { visible_ = false; }
   void SetContainerType(keyboard::ContainerType container_type,
diff --git a/chrome/browser/ui/ash/session_controller_client_impl_unittest.cc b/chrome/browser/ui/ash/session_controller_client_impl_unittest.cc
index 123708c..f4fccdd 100644
--- a/chrome/browser/ui/ash/session_controller_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/session_controller_client_impl_unittest.cc
@@ -38,7 +38,6 @@
 #include "net/cert/x509_certificate.h"
 #include "net/test/cert_test_util.h"
 #include "net/test/test_data_directory.h"
-#include "services/network/cert_verifier_with_trust_anchors.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using chromeos::FakeChromeUserManager;
@@ -49,16 +48,10 @@
 constexpr char kUser[] = "user@test.com";
 constexpr char kUserGaiaId[] = "0123456789";
 
-// Weak ptr to network::CertVerifierWithTrustAnchors - object is freed in test
-// destructor once we've ensured the profile has been shut down.
-network::CertVerifierWithTrustAnchors* g_policy_cert_verifier_for_factory =
-    nullptr;
-
 std::unique_ptr<KeyedService> CreateTestPolicyCertService(
     content::BrowserContext* context) {
   return policy::PolicyCertService::CreateForTesting(
-      kUser, g_policy_cert_verifier_for_factory,
-      user_manager::UserManager::Get());
+      kUser, user_manager::UserManager::Get());
 }
 
 // A user manager that does not set profiles as loaded and notifies observers
@@ -130,8 +123,6 @@
   void TearDown() override {
     user_manager_enabler_.reset();
     user_manager_ = nullptr;
-    // Clear our cached pointer to the network::CertVerifierWithTrustAnchors.
-    g_policy_cert_verifier_for_factory = nullptr;
     profile_manager_.reset();
 
     // We must ensure that the network::CertVerifierWithTrustAnchors outlives
@@ -191,7 +182,6 @@
 
   content::TestBrowserThreadBundle threads_;
   content::TestServiceManagerContext context_;
-  std::unique_ptr<network::CertVerifierWithTrustAnchors> cert_verifier_;
   std::unique_ptr<TestingProfileManager> profile_manager_;
   session_manager::SessionManager session_manager_;
 
@@ -314,9 +304,6 @@
   user_manager()->LoginUser(account_id);
   EXPECT_EQ(ash::AddUserSessionPolicy::ALLOWED,
             SessionControllerClientImpl::GetAddUserSessionPolicy());
-  cert_verifier_.reset(
-      new network::CertVerifierWithTrustAnchors(base::Closure()));
-  g_policy_cert_verifier_for_factory = cert_verifier_.get();
   ASSERT_TRUE(
       policy::PolicyCertServiceFactory::GetInstance()->SetTestingFactoryAndUse(
           user_profile, base::BindRepeating(&CreateTestPolicyCertService)));
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index c9813f6..18db3e5 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -2044,6 +2044,10 @@
       }
     }
   }
+
+  // If an extension page was active, the toolbar may need to be updated to hide
+  // the extension name in the location icon.
+  UpdateToolbar(/*should_restore_state=*/false);
 }
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 55e9bf8..6a5dec3 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -1331,7 +1331,8 @@
 void CreateBookmarkAppFromCurrentWebContents(Browser* browser,
                                              bool force_shortcut_app) {
   base::RecordAction(UserMetricsAction("CreateHostedApp"));
-  web_app::CreateWebAppFromCurrentWebContents(browser, force_shortcut_app);
+  web_app::CreateWebAppFromCurrentWebContents(browser, force_shortcut_app,
+                                              base::DoNothing());
 }
 
 bool CanCreateBookmarkApp(const Browser* browser) {
diff --git a/chrome/browser/ui/cocoa/color_chooser_mac.h b/chrome/browser/ui/cocoa/color_chooser_mac.h
index 9dbcc9f..64da207 100644
--- a/chrome/browser/ui/cocoa/color_chooser_mac.h
+++ b/chrome/browser/ui/cocoa/color_chooser_mac.h
@@ -8,35 +8,13 @@
 #import <Cocoa/Cocoa.h>
 
 #import "base/mac/scoped_nsobject.h"
+#include "components/remote_cocoa/common/color_panel.mojom.h"
 #include "content/public/browser/color_chooser.h"
 #include "content/public/browser/web_contents.h"
+#include "mojo/public/cpp/bindings/binding.h"
 
-class ColorChooserMac;
-
-// A Listener class to act as a event target for NSColorPanel and send
-// the results to the C++ class, ColorChooserMac.
-@interface ColorPanelCocoa : NSObject {
- @protected
-  // We don't call DidChooseColor if the change wasn't caused by the user
-  // interacting with the panel.
-  BOOL nonUserChange_;
- @private
-  ColorChooserMac* chooser_;  // weak, owns this
-}
-
-- (id)initWithChooser:(ColorChooserMac*)chooser;
-
-- (void)windowWillClose:(NSNotification*)notification;
-
-// Called from NSColorPanel.
-- (void)didChooseColor:(NSColorPanel*)panel;
-
-// Sets color to the NSColorPanel as a non user change.
-- (void)setColor:(NSColor*)color;
-
-@end
-
-class ColorChooserMac : public content::ColorChooser {
+class ColorChooserMac : public content::ColorChooser,
+                        public remote_cocoa::mojom::ColorPanelHost {
  public:
   // Returns a ColorChooserMac instance owned by the ColorChooserMac class -
   // call End() when done to free it. Each call to Open() returns a new
@@ -45,28 +23,25 @@
   static ColorChooserMac* Open(content::WebContents* web_contents,
                                SkColor initial_color);
 
-  // Called from ColorPanelCocoa.
-  void DidChooseColorInColorPanel(SkColor color);
-  void DidCloseColorPanel();
-
-  // Set the color programmatically.
+  // content::ColorChooser.
   void SetSelectedColor(SkColor color) override;
-
-  // Call when done with the ColorChooserMac.
   void End() override;
 
  private:
+  // remote_cocoa::mojom::ColorPanelHost.
+  void DidChooseColorInColorPanel(SkColor color) override;
+  void DidCloseColorPanel() override;
+
   ColorChooserMac(content::WebContents* tab, SkColor initial_color);
 
   ~ColorChooserMac() override;
 
-  static ColorChooserMac* current_color_chooser_;
-
   // The web contents invoking the color chooser.  No ownership because it will
   // outlive this class.
   content::WebContents* web_contents_;
-  base::scoped_nsobject<ColorPanelCocoa> panel_;
 
+  remote_cocoa::mojom::ColorPanelPtr mojo_panel_ptr_;
+  mojo::Binding<remote_cocoa::mojom::ColorPanelHost> mojo_host_binding_;
   DISALLOW_COPY_AND_ASSIGN(ColorChooserMac);
 };
 
diff --git a/chrome/browser/ui/cocoa/color_chooser_mac.mm b/chrome/browser/ui/cocoa/color_chooser_mac.mm
index e7cdab7..fca16f9 100644
--- a/chrome/browser/ui/cocoa/color_chooser_mac.mm
+++ b/chrome/browser/ui/cocoa/color_chooser_mac.mm
@@ -6,150 +6,79 @@
 
 #include "base/logging.h"
 #include "chrome/browser/ui/color_chooser.h"
+#include "components/remote_cocoa/app_shim/color_panel_bridge.h"
+#include "components/remote_cocoa/browser/application_host.h"
+#include "components/remote_cocoa/browser/window.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
 #include "skia/ext/skia_utils_mac.h"
 
-ColorChooserMac* ColorChooserMac::current_color_chooser_ = NULL;
+namespace {
+// The currently active color chooser.
+ColorChooserMac* g_current_color_chooser = nullptr;
+}  // namespace
 
 // static
 ColorChooserMac* ColorChooserMac::Open(content::WebContents* web_contents,
                                        SkColor initial_color) {
-  if (current_color_chooser_)
-    current_color_chooser_->End();
-  DCHECK(!current_color_chooser_);
-  current_color_chooser_ =
-      new ColorChooserMac(web_contents, initial_color);
-  return current_color_chooser_;
+  if (g_current_color_chooser)
+    g_current_color_chooser->End();
+  DCHECK(!g_current_color_chooser);
+  // Note that WebContentsImpl::ColorChooser ultimately takes ownership (and
+  // deletes) the returned pointer.
+  g_current_color_chooser = new ColorChooserMac(web_contents, initial_color);
+  return g_current_color_chooser;
 }
 
 ColorChooserMac::ColorChooserMac(content::WebContents* web_contents,
                                  SkColor initial_color)
-    : web_contents_(web_contents) {
-  panel_.reset([[ColorPanelCocoa alloc] initWithChooser:this]);
-  [panel_ setColor:skia::SkColorToDeviceNSColor(initial_color)];
-  [[NSColorPanel sharedColorPanel] makeKeyAndOrderFront:nil];
+    : web_contents_(web_contents), mojo_host_binding_(this) {
+  remote_cocoa::mojom::ColorPanelHostPtr mojo_host_ptr;
+  mojo_host_binding_.Bind(mojo::MakeRequest(&mojo_host_ptr));
+  auto* application_host = remote_cocoa::ApplicationHost::GetForNativeView(
+      web_contents ? web_contents->GetNativeView() : gfx::NativeView());
+  if (application_host) {
+    application_host->GetApplication()->ShowColorPanel(
+        mojo::MakeRequest(&mojo_panel_ptr_), std::move(mojo_host_ptr));
+  } else {
+    mojo::MakeStrongBinding(std::make_unique<remote_cocoa::ColorPanelBridge>(
+                                std::move(mojo_host_ptr)),
+                            mojo::MakeRequest(&mojo_panel_ptr_));
+  }
+  mojo_panel_ptr_->Show(initial_color);
 }
 
 ColorChooserMac::~ColorChooserMac() {
   // Always call End() before destroying.
-  DCHECK(!panel_);
+  DCHECK_NE(g_current_color_chooser, this);
 }
 
 void ColorChooserMac::DidChooseColorInColorPanel(SkColor color) {
+  DCHECK_EQ(g_current_color_chooser, this);
   if (web_contents_)
     web_contents_->DidChooseColorInColorChooser(color);
 }
 
 void ColorChooserMac::DidCloseColorPanel() {
+  DCHECK_EQ(g_current_color_chooser, this);
   End();
 }
 
 void ColorChooserMac::End() {
-  if (panel_) {
-    panel_.reset();
-    DCHECK(current_color_chooser_ == this);
-    current_color_chooser_ = NULL;
+  if (g_current_color_chooser == this) {
+    g_current_color_chooser = nullptr;
     if (web_contents_)
       web_contents_->DidEndColorChooser();
   }
 }
 
 void ColorChooserMac::SetSelectedColor(SkColor color) {
-  [panel_ setColor:skia::SkColorToDeviceNSColor(color)];
-}
-
-@interface NSColorPanel (Private)
-// Private method returning the NSColorPanel's target.
-- (id)__target;
-@end
-
-@implementation ColorPanelCocoa
-
-- (id)initWithChooser:(ColorChooserMac*)chooser {
-  if ((self = [super init])) {
-    chooser_ = chooser;
-    NSColorPanel* panel = [NSColorPanel sharedColorPanel];
-    [panel setShowsAlpha:NO];
-    [panel setTarget:self];
-    [panel setAction:@selector(didChooseColor:)];
-
-    [[NSNotificationCenter defaultCenter]
-        addObserver:self
-           selector:@selector(windowWillClose:)
-               name:NSWindowWillCloseNotification
-             object:panel];
-  }
-  return self;
-}
-
-- (void)dealloc {
-  NSColorPanel* panel = [NSColorPanel sharedColorPanel];
-
-  // On macOS 10.13 the NSColorPanel delegate can apparently get reset to nil
-  // with the target left unchanged. Use the private __target method to see if
-  // the ColorPanelCocoa is still the target.
-  BOOL respondsToPrivateTargetMethod =
-      [panel respondsToSelector:@selector(__target)];
-  if (respondsToPrivateTargetMethod && [panel __target] == self) {
-    [panel setTarget:nil];
-    [panel setAction:nullptr];
-  }
-
-  [[NSNotificationCenter defaultCenter]
-      removeObserver:self
-                name:NSWindowWillCloseNotification
-              object:panel];
-
-  [super dealloc];
-}
-
-- (void)windowWillClose:(NSNotification*)notification {
-  chooser_->DidCloseColorPanel();
-  nonUserChange_ = NO;
-}
-
-- (void)didChooseColor:(NSColorPanel*)panel {
-  if (nonUserChange_) {
-    nonUserChange_ = NO;
-    return;
-  }
-  nonUserChange_ = NO;
-  NSColor* color = [panel color];
-  if ([[color colorSpaceName] isEqualToString:NSNamedColorSpace]) {
-    color = [color colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
-    // Some colors in "Developer" palette in "Color Palettes" tab can't be
-    // converted to RGB. We just ignore such colors.
-    // TODO(tkent): We should notice the rejection to users.
-    if (!color)
-      return;
-  }
-  if ([color colorSpace] == [NSColorSpace genericRGBColorSpace]) {
-    // genericRGB -> deviceRGB conversion isn't ignorable.  We'd like to use RGB
-    // values shown in NSColorPanel UI.
-    CGFloat red, green, blue, alpha;
-    [color getRed:&red green:&green blue:&blue alpha:&alpha];
-    SkColor skColor = SkColorSetARGB(SkScalarRoundToInt(255.0 * alpha),
-                                     SkScalarRoundToInt(255.0 * red),
-                                     SkScalarRoundToInt(255.0 * green),
-                                     SkScalarRoundToInt(255.0 * blue));
-    chooser_->DidChooseColorInColorPanel(skColor);
-  } else {
-    chooser_->DidChooseColorInColorPanel(skia::NSDeviceColorToSkColor(
-        [[panel color] colorUsingColorSpaceName:NSDeviceRGBColorSpace]));
-  }
-}
-
-- (void)setColor:(NSColor*)color {
-  nonUserChange_ = YES;
-  [[NSColorPanel sharedColorPanel] setColor:color];
+  mojo_panel_ptr_->SetSelectedColor(color);
 }
 
 namespace chrome {
-
 content::ColorChooser* ShowColorChooser(content::WebContents* web_contents,
                                         SkColor initial_color) {
   return ColorChooserMac::Open(web_contents, initial_color);
 }
-
 }  // namepace chrome
-
-@end
diff --git a/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm b/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
index 823365f..6169e16 100644
--- a/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
+++ b/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
@@ -4,6 +4,8 @@
 
 #import "chrome/browser/ui/cocoa/color_chooser_mac.h"
 
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
 #import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
 #include "skia/ext/skia_utils_mac.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -26,6 +28,7 @@
     [[NSColorPanel sharedColorPanel] makeKeyAndOrderFront:nil];
     Init();
   }
+  base::test::ScopedTaskEnvironment task_environment_;
 };
 
 TEST_F(ColorPanelCocoaTest, ClearTargetOnEnd) {
@@ -36,6 +39,7 @@
     // Create a ColorPanelCocoa.
     ColorChooserMac* color_chooser_mac =
         ColorChooserMac::Open(nullptr, SK_ColorBLACK);
+    base::RunLoop().RunUntilIdle();
 
     // Confirm the NSColorPanel's configuration by the ColorChooserMac's
     // ColorPanelCocoa.
@@ -44,8 +48,6 @@
     // Release the ColorPanelCocoa.
     color_chooser_mac->End();
   }
-  // Confirm the ColorPanelCocoa is no longer the NSColorPanel's target
-  EXPECT_EQ([nscolor_panel __target], nil);
 }
 
 TEST_F(ColorPanelCocoaTest, SetColor) {
@@ -60,6 +62,7 @@
   SkColor initial_color = SK_ColorBLACK;
   ColorChooserMac* color_chooser_mac =
       ColorChooserMac::Open(nullptr, SK_ColorBLACK);
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_NSEQ([nscolor_panel color],
               skia::SkColorToDeviceNSColor(initial_color));
@@ -67,6 +70,7 @@
   // Confirm that -[ColorPanelCocoa setColor:] sets the NSColorPanel's color.
   SkColor test_color = SK_ColorRED;
   color_chooser_mac->SetSelectedColor(test_color);
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_NSEQ([nscolor_panel color], skia::SkColorToDeviceNSColor(test_color));
 
diff --git a/chrome/browser/ui/cocoa/javascript_app_modal_dialog_cocoa.mm b/chrome/browser/ui/cocoa/javascript_app_modal_dialog_cocoa.mm
index 6e54b33..2357156 100644
--- a/chrome/browser/ui/cocoa/javascript_app_modal_dialog_cocoa.mm
+++ b/chrome/browser/ui/cocoa/javascript_app_modal_dialog_cocoa.mm
@@ -26,7 +26,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/strings/grit/ui_strings.h"
-#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
 
 using remote_cocoa::mojom::AlertDisposition;
 
@@ -144,13 +143,8 @@
   // remote_cocoa::ApplicationHost for that window to create the alert.
   // Otherwise create an AlertBridge in-process (but still communicate with it
   // over mojo).
-  auto* bridged_native_widget_host =
-      views::NativeWidgetMacNSWindowHost::GetFromNativeView(
-          dialog_->web_contents()->GetNativeView());
-  remote_cocoa::ApplicationHost* application_host =
-      bridged_native_widget_host
-          ? bridged_native_widget_host->application_host()
-          : nullptr;
+  auto* application_host = remote_cocoa::ApplicationHost::GetForNativeView(
+      dialog_->web_contents()->GetNativeView());
   if (application_host)
     application_host->GetApplication()->CreateAlert(std::move(bridge_request));
   else
diff --git a/chrome/browser/ui/page_action/page_action_icon_container.h b/chrome/browser/ui/page_action/page_action_icon_container.h
index f09ce77..e2a76c4 100644
--- a/chrome/browser/ui/page_action/page_action_icon_container.h
+++ b/chrome/browser/ui/page_action/page_action_icon_container.h
@@ -26,6 +26,8 @@
   // Signals a page action icon to update its visual state if it is present in
   // the browser window.
   virtual void UpdatePageActionIcon(PageActionIconType type) = 0;
+
+  virtual void ExecutePageActionIconForTesting(PageActionIconType type) = 0;
 };
 
 #endif  // CHROME_BROWSER_UI_PAGE_ACTION_PAGE_ACTION_ICON_CONTAINER_H_
diff --git a/chrome/browser/ui/views/DEPS b/chrome/browser/ui/views/DEPS
index fb12613..0301e1c 100644
--- a/chrome/browser/ui/views/DEPS
+++ b/chrome/browser/ui/views/DEPS
@@ -1,7 +1,6 @@
 include_rules = [
  "+chrome/browser/ui/views",
  "+chrome/services/app_service/public",
- "+services/ws/public/cpp",
  "+third_party/libaddressinput",
 ]
 
diff --git a/chrome/browser/ui/views/extensions/extension_popup.cc b/chrome/browser/ui/views/extensions/extension_popup.cc
index a97884a..e4c2301 100644
--- a/chrome/browser/ui/views/extensions/extension_popup.cc
+++ b/chrome/browser/ui/views/extensions/extension_popup.cc
@@ -33,7 +33,7 @@
     views::BubbleBorder::Arrow arrow,
     ShowAction show_action) {
   auto* popup =
-      new ExtensionPopup(host.release(), anchor_view, arrow, show_action);
+      new ExtensionPopup(std::move(host), anchor_view, arrow, show_action);
   views::BubbleDialogDelegateView::CreateBubble(popup);
 
 #if defined(USE_AURA)
@@ -168,14 +168,15 @@
     show_action_ = SHOW;
 }
 
-ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host,
-                               views::View* anchor_view,
-                               views::BubbleBorder::Arrow arrow,
-                               ShowAction show_action)
+ExtensionPopup::ExtensionPopup(
+    std::unique_ptr<extensions::ExtensionViewHost> host,
+    views::View* anchor_view,
+    views::BubbleBorder::Arrow arrow,
+    ShowAction show_action)
     : BubbleDialogDelegateView(anchor_view,
                                arrow,
                                views::BubbleBorder::SMALL_SHADOW),
-      host_(host),
+      host_(std::move(host)),
       show_action_(show_action) {
   set_margins(gfx::Insets());
   SetLayoutManager(std::make_unique<views::FillLayout>());
@@ -188,7 +189,7 @@
   // Listen for the containing view calling window.close();
   registrar_.Add(
       this, extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
-      content::Source<content::BrowserContext>(host->browser_context()));
+      content::Source<content::BrowserContext>(host_->browser_context()));
   content::DevToolsAgentHost::AddObserver(this);
   observer_.Add(GetExtensionView()->GetBrowser()->tab_strip_model());
 
diff --git a/chrome/browser/ui/views/extensions/extension_popup.h b/chrome/browser/ui/views/extensions/extension_popup.h
index 342f067..4a3ea77 100644
--- a/chrome/browser/ui/views/extensions/extension_popup.h
+++ b/chrome/browser/ui/views/extensions/extension_popup.h
@@ -108,7 +108,7 @@
       content::DevToolsAgentHost* agent_host) override;
 
  private:
-  ExtensionPopup(extensions::ExtensionViewHost* host,
+  ExtensionPopup(std::unique_ptr<extensions::ExtensionViewHost> host,
                  views::View* anchor_view,
                  views::BubbleBorder::Arrow arrow,
                  ShowAction show_action);
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index ae49232..c90d55c 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -1399,17 +1399,12 @@
   }
 
   LocationBarView* location_bar = GetLocationBarView();
-  PageActionIconView* icon_view =
-      (PageActionIconView*)location_bar->send_tab_to_self_icon_view();
   views::View* anchor_view = location_bar;
 
   send_tab_to_self::SendTabToSelfBubbleViewImpl* bubble =
       new send_tab_to_self::SendTabToSelfBubbleViewImpl(
           anchor_view, gfx::Point(), web_contents, controller);
 
-  if (icon_view) {
-    bubble->SetHighlightedButton(icon_view);
-  }
   views::BubbleDialogDelegateView::CreateBubble(bubble);
   bubble->Show(send_tab_to_self::SendTabToSelfBubbleViewImpl::USER_GESTURE);
 
diff --git a/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.cc b/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.cc
index a1072f3..cf458b3 100644
--- a/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.cc
+++ b/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.cc
@@ -134,6 +134,13 @@
     icon->Update();
 }
 
+void OmniboxPageActionIconContainerView::ExecutePageActionIconForTesting(
+    PageActionIconType type) {
+  PageActionIconView* icon = GetPageActionIconView(type);
+  if (icon)
+    icon->ExecuteForTesting();
+}
+
 bool OmniboxPageActionIconContainerView::
     ActivateFirstInactiveBubbleForAccessibility() {
   for (PageActionIconView* icon : page_action_icons_) {
diff --git a/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h b/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h
index c588322..0149652 100644
--- a/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h
+++ b/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h
@@ -69,6 +69,7 @@
 
   // PageActionIconContainer:
   void UpdatePageActionIcon(PageActionIconType type) override;
+  void ExecutePageActionIconForTesting(PageActionIconType type) override;
 
  private:
   // views::View:
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view.cc b/chrome/browser/ui/views/page_action/pwa_install_view.cc
index bdaeae7..412fbcf 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view.cc
+++ b/chrome/browser/ui/views/page_action/pwa_install_view.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/banners/app_banner_manager.h"
 #include "chrome/browser/installable/installable_metrics.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
+#include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_tab_helper_base.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/omnibox/browser/vector_icons.h"
@@ -54,9 +55,20 @@
 
 void PwaInstallView::OnExecuting(PageActionIconView::ExecuteSource source) {
   base::RecordAction(base::UserMetricsAction("PWAInstallIcon"));
-  web_app::CreateWebAppFromManifest(GetWebContents(),
-                                    WebappInstallSource::OMNIBOX_INSTALL_ICON,
-                                    base::DoNothing());
+  content::WebContents* web_contents = GetWebContents();
+  // TODO(https://crbug.com/956810): Make AppBannerManager listen for
+  // installations instead of having to notify it from every installation UI
+  // surface.
+  auto* manager = banners::AppBannerManager::FromWebContents(web_contents);
+  web_app::CreateWebAppFromManifest(
+      web_contents, WebappInstallSource::OMNIBOX_INSTALL_ICON,
+      base::BindOnce(
+          [](base::WeakPtr<banners::AppBannerManager> manager,
+             const web_app::AppId& app_id, web_app::InstallResultCode code) {
+            if (manager && code == web_app::InstallResultCode::kSuccess)
+              manager->OnInstall(false, blink::kWebDisplayModeStandalone);
+          },
+          manager ? manager->GetWeakPtr() : nullptr));
 }
 
 views::BubbleDialogDelegateView* PwaInstallView::GetBubble() const {
diff --git a/chrome/browser/ui/views/payments/editor_view_controller.cc b/chrome/browser/ui/views/payments/editor_view_controller.cc
index bd56d9ff..0ff3199 100644
--- a/chrome/browser/ui/views/payments/editor_view_controller.cc
+++ b/chrome/browser/ui/views/payments/editor_view_controller.cc
@@ -314,6 +314,17 @@
                      kFieldExtraViewHorizontalPadding - long_extra_view_width;
   columns_long->AddPaddingColumn(views::GridLayout::kFixedSize, long_padding);
 
+  // This column set is used for the error label in CreateInputField().
+  views::ColumnSet* columns_error = editor_layout->AddColumnSet(2);
+  columns_error->AddColumn(
+      views::GridLayout::LEADING, views::GridLayout::CENTER,
+      views::GridLayout::kFixedSize, views::GridLayout::FIXED, kLabelWidth, 0);
+  columns_error->AddPaddingColumn(views::GridLayout::kFixedSize,
+                                  kLabelInputFieldHorizontalPadding);
+  columns_error->AddColumn(views::GridLayout::LEADING,
+                           views::GridLayout::CENTER, 1.0,
+                           views::GridLayout::USE_PREF, 0, 0);
+
   views::View* first_field = nullptr;
   for (const auto& field : GetFieldDefinitions()) {
     bool valid = false;
@@ -331,11 +342,12 @@
   // Validate all fields and disable the primary (Done) button if necessary.
   primary_button()->SetEnabled(ValidateInputFields());
 
-  views::ColumnSet* required_field_columns = editor_layout->AddColumnSet(2);
+  views::ColumnSet* required_field_columns = editor_layout->AddColumnSet(3);
   required_field_columns->AddColumn(views::GridLayout::LEADING,
                                     views::GridLayout::CENTER, 1.0,
                                     views::GridLayout::USE_PREF, 0, 0);
-  editor_layout->StartRow(views::GridLayout::kFixedSize, 2);
+  editor_layout->StartRow(views::GridLayout::kFixedSize, 3);
+
   // Adds the "* indicates a required field" label in "hint" grey text.
   editor_layout->AddView(
       CreateHintLabel(
@@ -447,7 +459,7 @@
   if (extra_view)
     layout->AddView(extra_view.release());
 
-  layout->StartRow(views::GridLayout::kFixedSize, column_set);
+  layout->StartRow(views::GridLayout::kFixedSize, 2);
   layout->SkipColumns(1);
   std::unique_ptr<views::View> error_label_view =
       std::make_unique<views::View>();
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc
index 32c5b34..d0fd7a9 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc
@@ -90,13 +90,16 @@
 
 void SendTabToSelfBubbleViewImpl::Show(DisplayReason reason) {
   ShowForReason(reason);
-  // Keeps the send tab to self icon in omnibox while showing the bubble.
-  BrowserView::GetBrowserViewForBrowser(
-      chrome::FindBrowserWithWebContents(web_contents_))
-      ->toolbar_button_provider()
-      ->GetOmniboxPageActionIconContainerView()
-      ->GetPageActionIconView(PageActionIconType::kSendTabToSelf)
-      ->SetVisible(true);
+  // Keeps the send tab to self icon in omnibox and be highlighted while
+  // showing the bubble.
+  views::Button* highlight_button =
+      BrowserView::GetBrowserViewForBrowser(
+          chrome::FindBrowserWithWebContents(web_contents_))
+          ->toolbar_button_provider()
+          ->GetOmniboxPageActionIconContainerView()
+          ->GetPageActionIconView(PageActionIconType::kSendTabToSelf);
+  highlight_button->SetVisible(true);
+  SetHighlightedButton(highlight_button);
 }
 
 const std::vector<std::unique_ptr<SendTabToSelfBubbleDeviceButton>>&
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index 3ad4e5f..a306da76 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -148,9 +148,9 @@
     run_loop_.QuitWhenIdle();
   }
 
-  void Wait() {
-    run_loop_.Run();
-  }
+  // The observer should be constructed prior to initiating the drag. To prevent
+  // misuse via constructing a temporary object, Wait is marked lvalue-only.
+  void Wait() & { run_loop_.Run(); }
 
  private:
   content::NotificationRegistrar registrar_;
@@ -583,6 +583,7 @@
                         base::OnceClosure task,
                         int tab = 0,
                         int drag_x_offset = 0) {
+    test::QuitDraggingObserver observer;
     // Move to the tab and drag it enough so that it detaches.
     const gfx::Point tab_0_center =
         GetCenterInScreenCoordinates(tab_strip->tab_at(tab));
@@ -590,7 +591,7 @@
     ASSERT_TRUE(DragInputToNotifyWhenDone(
         tab_0_center + gfx::Vector2d(drag_x_offset, GetDetachY(tab_strip)),
         std::move(task)));
-    test::QuitDraggingObserver().Wait();
+    observer.Wait();
   }
 
   Browser* browser() const { return InProcessBrowserTest::browser(); }
@@ -1244,7 +1245,6 @@
 
 }  // namespace
 
-// This is disabled until NativeViewHost::Detach really detaches.
 // Detaches a tab and while detached presses escape to revert the drag.
 IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
                        PressEscapeWhileDetached) {
@@ -1608,6 +1608,7 @@
 void DragWindowAndVerifyOffset(DetachToBrowserTabDragControllerTest* test,
                                TabStrip* tab_strip,
                                int tab_index) {
+  test::QuitDraggingObserver observer;
   // Move to the tab and drag it enough so that it detaches.
   const gfx::Point tab_center =
       GetCenterInScreenCoordinates(tab_strip->tab_at(tab_index));
@@ -1640,7 +1641,7 @@
               ASSERT_TRUE(test->ReleaseInput());
             })));
       })));
-  test::QuitDraggingObserver().Wait();
+  observer.Wait();
 }
 
 }  // namespace
@@ -3032,6 +3033,7 @@
   // minimizing the window. See https://crbug.com/902897 for the details.
   ui::GestureConfiguration::GetInstance()->set_min_fling_velocity(1);
 
+  test::QuitDraggingObserver observer;
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   const gfx::Point tab_0_center =
       GetCenterInScreenCoordinates(tab_strip->tab_at(0));
@@ -3051,7 +3053,7 @@
               ASSERT_TRUE(ReleaseInput());
             })));
       })));
-  test::QuitDraggingObserver().Wait();
+  observer.Wait();
 
   ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
   ASSERT_FALSE(TabDragController::IsActive());
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 4be7369..38b857f 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -2146,7 +2146,10 @@
 void TabStrip::AnimateToIdealBounds() {
   // bounds_animator_ and animator_ should not run concurrently.
   // bounds_animator_ takes precedence, and can finish what animator_ started.
-  animator_->CompleteAnimationsWithoutDestroyingTabs();
+  if (animator_->IsAnimating()) {
+    LayoutToCurrentBounds();
+    animator_->CompleteAnimationsWithoutDestroyingTabs();
+  }
 
   for (int i = 0; i < tab_count(); ++i) {
     // If the tab is being dragged manually, skip it.
diff --git a/chrome/browser/ui/views/toolbar/toolbar_page_action_icon_container_view.cc b/chrome/browser/ui/views/toolbar/toolbar_page_action_icon_container_view.cc
index da46d67..3fcf042 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_page_action_icon_container_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_page_action_icon_container_view.cc
@@ -106,6 +106,13 @@
     icon->Update();
 }
 
+void ToolbarPageActionIconContainerView::ExecutePageActionIconForTesting(
+    PageActionIconType type) {
+  PageActionIconView* icon = GetIconView(type);
+  if (icon)
+    icon->ExecuteForTesting();
+}
+
 SkColor ToolbarPageActionIconContainerView::GetPageActionInkDropColor() const {
   return GetToolbarInkDropBaseColor(this);
 }
diff --git a/chrome/browser/ui/views/toolbar/toolbar_page_action_icon_container_view.h b/chrome/browser/ui/views/toolbar/toolbar_page_action_icon_container_view.h
index 0558126..d525bc7 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_page_action_icon_container_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_page_action_icon_container_view.h
@@ -39,6 +39,7 @@
 
   // PageActionIconContainer:
   void UpdatePageActionIcon(PageActionIconType icon_type) override;
+  void ExecutePageActionIconForTesting(PageActionIconType icon_type) override;
 
   // PageActionIconView::Delegate:
   SkColor GetPageActionInkDropColor() const override;
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_utils.cc b/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
index 16d61236..2d69f63 100644
--- a/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
+++ b/chrome/browser/ui/web_applications/web_app_dialog_utils.cc
@@ -80,8 +80,10 @@
   return provider->install_manager().CanInstallWebApp(web_contents);
 }
 
-void CreateWebAppFromCurrentWebContents(Browser* browser,
-                                        bool force_shortcut_app) {
+void CreateWebAppFromCurrentWebContents(
+    Browser* browser,
+    bool force_shortcut_app,
+    WebAppInstalledCallback installed_callback) {
   DCHECK(CanCreateWebApp(browser));
 
   content::WebContents* web_contents =
@@ -89,8 +91,6 @@
   auto* provider = WebAppProvider::GetForWebContents(web_contents);
   DCHECK(provider);
 
-  WebAppInstalledCallback installed_callback = base::DoNothing();
-
   WebappInstallSource install_source =
       InstallableMetrics::GetInstallSource(web_contents, InstallTrigger::MENU);
 
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_utils.h b/chrome/browser/ui/web_applications/web_app_dialog_utils.h
index 0c91f59..41159af 100644
--- a/chrome/browser/ui/web_applications/web_app_dialog_utils.h
+++ b/chrome/browser/ui/web_applications/web_app_dialog_utils.h
@@ -25,13 +25,15 @@
 // Returns true if a WebApp installation is allowed for the current page.
 bool CanCreateWebApp(const Browser* browser);
 
-// Initiates install of a WebApp for the current page.
-void CreateWebAppFromCurrentWebContents(Browser* browser,
-                                        bool force_shortcut_app);
-
 using WebAppInstalledCallback =
     base::OnceCallback<void(const AppId& app_id, InstallResultCode code)>;
 
+// Initiates install of a WebApp for the current page.
+void CreateWebAppFromCurrentWebContents(
+    Browser* browser,
+    bool force_shortcut_app,
+    WebAppInstalledCallback installed_callback);
+
 // Starts install of a WebApp for a given |web_contents|, initiated from
 // a promotional banner or omnibox install icon.
 // Returns false if WebApps are disabled for the profile behind |web_contents|.
diff --git a/chrome/browser/web_applications/components/web_app_shortcut.cc b/chrome/browser/web_applications/components/web_app_shortcut.cc
index 095752d..0116b96 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut.cc
+++ b/chrome/browser/web_applications/components/web_app_shortcut.cc
@@ -31,10 +31,10 @@
 
 namespace web_app {
 
-ShortcutInfo::ShortcutInfo() {}
+ShortcutInfo::ShortcutInfo() = default;
 
 ShortcutInfo::~ShortcutInfo() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
 ShortcutLocations::ShortcutLocations()
diff --git a/chrome/browser/web_applications/components/web_app_shortcut.h b/chrome/browser/web_applications/components/web_app_shortcut.h
index 5e29fb6..afbce360 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut.h
+++ b/chrome/browser/web_applications/components/web_app_shortcut.h
@@ -7,6 +7,7 @@
 
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/sequence_checker.h"
 #include "base/strings/string16.h"
 #include "ui/gfx/image/image_family.h"
 #include "url/gurl.h"
@@ -39,10 +40,10 @@
   std::string version_for_display;
 
  private:
-  // ShortcutInfo must not be copied; generally it is passed around via
-  // unique_ptr. Since ImageFamily has a non-thread-safe reference count in
-  // its member and is bound to UI thread, destroy ShortcutInfo instance
-  // on UI thread.
+  // Since gfx::ImageFamily |favicon| has a non-thread-safe reference count in
+  // its member and is bound to current thread, always destroy ShortcutInfo
+  // instance on the same thread.
+  SEQUENCE_CHECKER(sequence_checker_);
   DISALLOW_COPY_AND_ASSIGN(ShortcutInfo);
 };
 
diff --git a/chrome/browser/web_applications/components/web_app_shortcut_mac_unittest.mm b/chrome/browser/web_applications/components/web_app_shortcut_mac_unittest.mm
index 2f9a548..7f57fc8 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut_mac_unittest.mm
+++ b/chrome/browser/web_applications/components/web_app_shortcut_mac_unittest.mm
@@ -415,8 +415,7 @@
   EXPECT_EQ(product_logo.Height(), [image size].height);
 }
 
-// Disabled, sometimes crashes on "Mac10.10 tests". https://crbug.com/741642
-TEST_F(WebAppShortcutCreatorTest, DISABLED_RevealAppShimInFinder) {
+TEST_F(WebAppShortcutCreatorTest, RevealAppShimInFinder) {
   WebAppShortcutCreatorMock shortcut_creator(app_data_dir_, info_.get());
   EXPECT_CALL(shortcut_creator, GetApplicationsDirname())
       .WillRepeatedly(Return(destination_dir_));
diff --git a/chrome/browser/web_applications/extensions/install_manager_bookmark_app_unittest.cc b/chrome/browser/web_applications/extensions/install_manager_bookmark_app_unittest.cc
index 7b306d3..8c813c0 100644
--- a/chrome/browser/web_applications/extensions/install_manager_bookmark_app_unittest.cc
+++ b/chrome/browser/web_applications/extensions/install_manager_bookmark_app_unittest.cc
@@ -8,21 +8,29 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/banners/app_banner_settings_helper.h"
 #include "chrome/browser/extensions/convert_web_app.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
+#include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/installable/installable_metrics.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/components/app_registrar.h"
+#include "chrome/browser/web_applications/bookmark_apps/test_web_app_provider.h"
 #include "chrome/browser/web_applications/components/install_manager.h"
+#include "chrome/browser/web_applications/components/install_options.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
+#include "chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h"
+#include "chrome/browser/web_applications/extensions/bookmark_app_registrar.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_util.h"
+#include "chrome/browser/web_applications/test/test_app_registrar.h"
+#include "chrome/browser/web_applications/test/test_data_retriever.h"
 #include "chrome/browser/web_applications/test/test_web_app_url_loader.h"
 #include "chrome/browser/web_applications/web_app_install_manager.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
+#include "chrome/common/extensions/manifest_handlers/app_theme_color_info.h"
 #include "chrome/common/web_application_info.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/browser/browser_context.h"
@@ -45,9 +53,12 @@
 const GURL kAppUrl("https://www.chromium.org/index.html");
 const GURL kAppScope("https://www.chromium.org/");
 const char kAppAlternativeScope[] = "http://www.chromium.org/new/";
+const char kAppDefaultScope[] = "http://www.chromium.org/";
 const char kAppTitle[] = "Test title";
 const char kAlternativeAppTitle[] = "Different test title";
 const char kAppDescription[] = "Test description";
+const char kAppIconURL1[] = "http://foo.com/1.png";
+const char kAppIconURL2[] = "http://foo.com/2.png";
 
 const int kIconSizeTiny = extension_misc::EXTENSION_ICON_BITTY;
 const int kIconSizeSmall = extension_misc::EXTENSION_ICON_SMALL;
@@ -69,6 +80,40 @@
   return icon_info;
 }
 
+void TestAcceptDialogCallback(
+    content::WebContents* initiator_web_contents,
+    std::unique_ptr<WebApplicationInfo> web_app_info,
+    web_app::ForInstallableSite for_installable_site,
+    web_app::InstallManager::WebAppInstallationAcceptanceCallback
+        acceptance_callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(acceptance_callback), true /*accept*/,
+                                std::move(web_app_info)));
+}
+
+// Use only real BookmarkAppInstallFinalizer::FinalizeInstall and mock any other
+// finalization steps as a no-operation.
+class BookmarkAppInstallFinalizerInstallOnly
+    : public BookmarkAppInstallFinalizer {
+ public:
+  using BookmarkAppInstallFinalizer::BookmarkAppInstallFinalizer;
+  ~BookmarkAppInstallFinalizerInstallOnly() override = default;
+
+  // InstallFinalizer:
+  void CreateOsShortcuts(const web_app::AppId& app_id,
+                         bool add_to_desktop,
+                         CreateOsShortcutsCallback callback) override {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), true /*shortcuts_created*/
+                       ));
+  }
+  void PinAppToShelf(const web_app::AppId& app_id) override {}
+  void ReparentTab(const web_app::AppId& app_id,
+                   content::WebContents* web_contents) override {}
+  void RevealAppShim(const web_app::AppId& app_id) override {}
+};
+
 }  // namespace
 
 class InstallManagerBookmarkAppTest : public ExtensionServiceTestBase {
@@ -88,9 +133,36 @@
     web_contents_ =
         content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
 
+    DCHECK(profile()->AsTestingProfile());
+    auto* provider = static_cast<web_app::TestWebAppProvider*>(
+        web_app::WebAppProvider::Get(profile()));
+
+    auto registrar = std::make_unique<BookmarkAppRegistrar>(profile());
+    registrar_ = registrar.get();
+
+    auto install_finalizer =
+        std::make_unique<BookmarkAppInstallFinalizerInstallOnly>(profile());
+    install_finalizer_ = install_finalizer.get();
+
+    auto install_manager = std::make_unique<web_app::WebAppInstallManager>(
+        profile(), registrar.get(), install_finalizer.get());
+
+    install_manager->SetDataRetrieverFactoryForTesting(
+        base::BindLambdaForTesting([this]() {
+          // This factory requires a prepared DataRetriever. A test should
+          // create one with CreateDefaultDataToRetrieve, for example.
+          DCHECK(prepared_data_retriever_);
+          return std::unique_ptr<web_app::WebAppDataRetriever>(
+              std::move(prepared_data_retriever_));
+        }));
+
     auto test_url_loader = std::make_unique<web_app::TestWebAppUrlLoader>();
     test_url_loader_ = test_url_loader.get();
-    install_manager().SetUrlLoaderForTesting(std::move(test_url_loader));
+    install_manager->SetUrlLoaderForTesting(std::move(test_url_loader));
+
+    provider->SetRegistrar(std::move(registrar));
+    provider->SetInstallManager(std::move(install_manager));
+    provider->SetInstallFinalizer(std::move(install_finalizer));
   }
 
   void TearDown() override {
@@ -118,15 +190,404 @@
     return *test_url_loader_;
   }
 
+  web_app::TestDataRetriever* data_retriever() {
+    DCHECK(prepared_data_retriever_);
+    return prepared_data_retriever_.get();
+  }
+
+  void CreateEmptyDataRetriever() {
+    DCHECK(!prepared_data_retriever_);
+    prepared_data_retriever_ = std::make_unique<web_app::TestDataRetriever>();
+  }
+
+  void CreateDataRetrieverWithManifest(
+      std::unique_ptr<blink::Manifest> manifest,
+      bool is_installable) {
+    CreateEmptyDataRetriever();
+    data_retriever()->SetRendererWebApplicationInfo(
+        std::make_unique<WebApplicationInfo>());
+    data_retriever()->SetManifest(std::move(manifest), is_installable);
+  }
+
+  void CreateDataRetrieverWithRendererWebAppInfo(
+      std::unique_ptr<WebApplicationInfo> web_app_info,
+      bool is_installable) {
+    CreateEmptyDataRetriever();
+
+    data_retriever()->SetRendererWebApplicationInfo(std::move(web_app_info));
+
+    auto manifest = std::make_unique<blink::Manifest>();
+    data_retriever()->SetManifest(std::move(manifest), is_installable);
+
+    web_app::IconsMap icons_map;
+    icons_map[GURL(kAppUrl)].push_back(
+        CreateSquareBitmapWithColor(kIconSizeSmall, SK_ColorRED));
+    data_retriever()->SetIcons(std::move(icons_map));
+  }
+
+  void CreateDataRetrieverWithLaunchContainer(const GURL& app_url,
+                                              bool open_as_window,
+                                              bool is_installable) {
+    CreateEmptyDataRetriever();
+
+    auto web_app_info = std::make_unique<WebApplicationInfo>();
+    web_app_info->open_as_window = open_as_window;
+    data_retriever()->SetRendererWebApplicationInfo(std::move(web_app_info));
+
+    auto manifest = std::make_unique<blink::Manifest>();
+    manifest->start_url = app_url;
+    manifest->name =
+        base::NullableString16(base::UTF8ToUTF16(kAppTitle), false);
+    manifest->scope = GURL(kAppScope);
+    data_retriever()->SetManifest(std::move(manifest), is_installable);
+
+    data_retriever()->SetIcons(web_app::IconsMap{});
+  }
+
+  const Extension* InstallWebAppFromManifestWithFallback() {
+    base::RunLoop run_loop;
+    web_app::AppId app_id;
+
+    auto* provider = web_app::WebAppProviderBase::GetProviderBase(profile());
+
+    provider->install_manager().InstallWebAppFromManifestWithFallback(
+        web_contents(),
+        /*force_shortcut_app=*/false, WebappInstallSource::MENU_BROWSER_TAB,
+        base::BindOnce(TestAcceptDialogCallback),
+        base::BindLambdaForTesting([&](const web_app::AppId& installed_app_id,
+                                       web_app::InstallResultCode code) {
+          EXPECT_EQ(web_app::InstallResultCode::kSuccess, code);
+          app_id = installed_app_id;
+          run_loop.Quit();
+        }));
+
+    run_loop.Run();
+
+    const Extension* extension = service_->GetInstalledExtension(app_id);
+    DCHECK(extension);
+    return extension;
+  }
+
+  const Extension* InstallWebAppWithOptions(
+      const web_app::InstallOptions& install_options) {
+    base::RunLoop run_loop;
+    web_app::AppId app_id;
+
+    auto* provider = web_app::WebAppProviderBase::GetProviderBase(profile());
+
+    provider->install_manager().InstallWebAppWithOptions(
+        web_contents(), install_options,
+        base::BindLambdaForTesting([&](const web_app::AppId& installed_app_id,
+                                       web_app::InstallResultCode code) {
+          EXPECT_EQ(web_app::InstallResultCode::kSuccess, code);
+          app_id = installed_app_id;
+          run_loop.Quit();
+        }));
+
+    run_loop.Run();
+
+    const Extension* extension = service_->GetInstalledExtension(app_id);
+    DCHECK(extension);
+    return extension;
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<content::WebContents> web_contents_;
+
+  BookmarkAppRegistrar* registrar_ = nullptr;
+  BookmarkAppInstallFinalizerInstallOnly* install_finalizer_ = nullptr;
+
   web_app::TestWebAppUrlLoader* test_url_loader_ = nullptr;
+  std::unique_ptr<web_app::TestDataRetriever> prepared_data_retriever_;
 
   DISALLOW_COPY_AND_ASSIGN(InstallManagerBookmarkAppTest);
 };
 
+TEST_F(InstallManagerBookmarkAppTest, CreateBookmarkApp) {
+  auto web_app_info = std::make_unique<WebApplicationInfo>();
+  const GURL app_url(kAppUrl);
+  web_app_info->app_url = app_url;
+  web_app_info->title = base::UTF8ToUTF16(kAppTitle);
+  web_app_info->description = base::UTF8ToUTF16(kAppDescription);
+  CreateDataRetrieverWithRendererWebAppInfo(std::move(web_app_info),
+                                            /*is_installable=*/false);
+
+  const Extension* extension = InstallWebAppFromManifestWithFallback();
+
+  EXPECT_EQ(1u, registry()->enabled_extensions().size());
+  EXPECT_TRUE(extension->from_bookmark());
+  EXPECT_FALSE(extension->was_installed_by_default());
+  EXPECT_FALSE(Manifest::IsPolicyLocation(extension->location()));
+
+  EXPECT_EQ(kAppTitle, extension->name());
+  EXPECT_EQ(kAppDescription, extension->description());
+  EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension));
+  EXPECT_FALSE(IconsInfo::GetIconResource(extension, kIconSizeSmall,
+                                          ExtensionIconSet::MATCH_EXACTLY)
+                   .empty());
+  EXPECT_FALSE(
+      AppBannerSettingsHelper::GetSingleBannerEvent(
+          web_contents(), app_url, app_url.spec(),
+          AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN)
+          .is_null());
+}
+
+TEST_F(InstallManagerBookmarkAppTest, CreateBookmarkAppDefaultApp) {
+  auto web_app_info = std::make_unique<WebApplicationInfo>();
+  const GURL app_url(kAppUrl);
+  web_app_info->app_url = app_url;
+  web_app_info->title = base::UTF8ToUTF16(kAppTitle);
+  web_app_info->description = base::UTF8ToUTF16(kAppDescription);
+  CreateDataRetrieverWithRendererWebAppInfo(std::move(web_app_info),
+                                            /*is_installable=*/false);
+
+  web_app::InstallOptions install_options;
+  install_options.install_source = web_app::InstallSource::kExternalDefault;
+
+  const Extension* extension = InstallWebAppWithOptions(install_options);
+
+  EXPECT_TRUE(extension->from_bookmark());
+  EXPECT_TRUE(extension->was_installed_by_default());
+  EXPECT_EQ(Manifest::EXTERNAL_PREF_DOWNLOAD, extension->location());
+  EXPECT_FALSE(Manifest::IsPolicyLocation(extension->location()));
+}
+
+TEST_F(InstallManagerBookmarkAppTest, CreateBookmarkAppPolicyInstalled) {
+  auto web_app_info = std::make_unique<WebApplicationInfo>();
+  const GURL app_url(kAppUrl);
+  web_app_info->app_url = app_url;
+  web_app_info->title = base::UTF8ToUTF16(kAppTitle);
+  web_app_info->description = base::UTF8ToUTF16(kAppDescription);
+  CreateDataRetrieverWithRendererWebAppInfo(std::move(web_app_info),
+                                            /*is_installable=*/false);
+
+  web_app::InstallOptions install_options;
+  install_options.install_source = web_app::InstallSource::kExternalPolicy;
+
+  const Extension* extension = InstallWebAppWithOptions(install_options);
+
+  EXPECT_TRUE(extension->from_bookmark());
+  EXPECT_FALSE(extension->was_installed_by_default());
+  EXPECT_TRUE(Manifest::IsPolicyLocation(extension->location()));
+}
+
+class InstallManagerBookmarkAppInstallableSiteTest
+    : public InstallManagerBookmarkAppTest,
+      public ::testing::WithParamInterface<web_app::ForInstallableSite> {
+ public:
+  InstallManagerBookmarkAppInstallableSiteTest() {}
+  ~InstallManagerBookmarkAppInstallableSiteTest() override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InstallManagerBookmarkAppInstallableSiteTest);
+};
+
+TEST_P(InstallManagerBookmarkAppInstallableSiteTest,
+       CreateBookmarkAppWithManifest) {
+  const GURL app_url(kAppUrl);
+
+  auto manifest = std::make_unique<blink::Manifest>();
+  manifest->start_url = app_url;
+  manifest->name = base::NullableString16(base::UTF8ToUTF16(kAppTitle), false);
+  manifest->scope = GURL(kAppScope);
+  manifest->theme_color = SK_ColorBLUE;
+
+  const bool is_installable = GetParam() == web_app::ForInstallableSite::kYes;
+  CreateDataRetrieverWithManifest(std::move(manifest), is_installable);
+
+  const Extension* extension = InstallWebAppFromManifestWithFallback();
+
+  EXPECT_EQ(1u, registry()->enabled_extensions().size());
+  EXPECT_TRUE(extension->from_bookmark());
+  EXPECT_EQ(kAppTitle, extension->name());
+  EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension));
+  EXPECT_EQ(SK_ColorBLUE, AppThemeColorInfo::GetThemeColor(extension).value());
+  EXPECT_FALSE(
+      AppBannerSettingsHelper::GetSingleBannerEvent(
+          web_contents(), app_url, app_url.spec(),
+          AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN)
+          .is_null());
+
+  if (GetParam() == web_app::ForInstallableSite::kYes) {
+    EXPECT_EQ(GURL(kAppScope), GetScopeURLFromBookmarkApp(extension));
+  } else {
+    EXPECT_EQ(GURL(), GetScopeURLFromBookmarkApp(extension));
+  }
+}
+
+TEST_P(InstallManagerBookmarkAppInstallableSiteTest,
+       CreateBookmarkAppWithManifestIcons) {
+  const GURL app_url(kAppUrl);
+
+  auto manifest = std::make_unique<blink::Manifest>();
+  manifest->start_url = app_url;
+  manifest->name = base::NullableString16(base::UTF8ToUTF16(kAppTitle), false);
+  manifest->scope = GURL(kAppScope);
+
+  blink::Manifest::ImageResource icon;
+  icon.src = GURL(kAppIconURL1);
+  icon.purpose = {blink::Manifest::ImageResource::Purpose::ANY};
+  manifest->icons.push_back(icon);
+  icon.src = GURL(kAppIconURL2);
+  manifest->icons.push_back(icon);
+
+  const bool is_installable = GetParam() == web_app::ForInstallableSite::kYes;
+
+  CreateDataRetrieverWithManifest(std::move(manifest), is_installable);
+
+  // In the legacy system Favicon URLs were ignored by WebAppIconDownloader
+  // because the site had a manifest with icons: Only 1 icon had to be
+  // downloaded since the other was provided by the InstallableManager. In the
+  // new extension-independent system we prefer to redownload all the icons: 2
+  // icons have to be downloaded.
+  data_retriever()->SetGetIconsDelegate(base::BindLambdaForTesting(
+      [&](content::WebContents* web_contents,
+          const std::vector<GURL>& icon_urls, bool skip_page_favicons) {
+        // Instructs the downloader to not query the page for favicons.
+        EXPECT_TRUE(skip_page_favicons);
+
+        const std::vector<GURL> expected_icon_urls{GURL(kAppIconURL1),
+                                                   GURL(kAppIconURL2)};
+        EXPECT_EQ(expected_icon_urls, icon_urls);
+
+        web_app::IconsMap icons_map;
+        icons_map[GURL(kAppIconURL1)].push_back(
+            CreateSquareBitmapWithColor(kIconSizeTiny, SK_ColorBLUE));
+        icons_map[GURL(kAppIconURL2)].push_back(
+            CreateSquareBitmapWithColor(kIconSizeSmall, SK_ColorRED));
+        return icons_map;
+      }));
+
+  const Extension* extension = InstallWebAppFromManifestWithFallback();
+
+  EXPECT_EQ(1u, registry()->enabled_extensions().size());
+  EXPECT_TRUE(extension->from_bookmark());
+  EXPECT_EQ(kAppTitle, extension->name());
+  EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension));
+
+  if (GetParam() == web_app::ForInstallableSite::kYes) {
+    EXPECT_EQ(GURL(kAppScope), GetScopeURLFromBookmarkApp(extension));
+  } else {
+    EXPECT_EQ(GURL(), GetScopeURLFromBookmarkApp(extension));
+  }
+}
+
+TEST_P(InstallManagerBookmarkAppInstallableSiteTest,
+       CreateBookmarkAppWithManifestNoScope) {
+  const GURL app_url(kAppUrl);
+
+  auto manifest = std::make_unique<blink::Manifest>();
+  manifest->start_url = app_url;
+  manifest->scope = GURL(kAppDefaultScope);
+  manifest->name = base::NullableString16(base::UTF8ToUTF16(kAppTitle), false);
+
+  const bool is_installable = GetParam() == web_app::ForInstallableSite::kYes;
+  CreateDataRetrieverWithManifest(std::move(manifest), is_installable);
+
+  const Extension* extension = InstallWebAppFromManifestWithFallback();
+
+  if (GetParam() == web_app::ForInstallableSite::kYes) {
+    EXPECT_EQ(GURL(kAppDefaultScope), GetScopeURLFromBookmarkApp(extension));
+  } else {
+    EXPECT_EQ(GURL(), GetScopeURLFromBookmarkApp(extension));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(/* no prefix */,
+                         InstallManagerBookmarkAppInstallableSiteTest,
+                         ::testing::Values(web_app::ForInstallableSite::kNo,
+                                           web_app::ForInstallableSite::kYes));
+
+TEST_F(InstallManagerBookmarkAppTest,
+       CreateBookmarkAppDefaultLauncherContainers) {
+  {
+    CreateDataRetrieverWithLaunchContainer(
+        GURL(kAppUrl), /*open_as_window=*/true, /*is_installable=*/true);
+
+    const Extension* extension = InstallWebAppFromManifestWithFallback();
+
+    EXPECT_EQ(LAUNCH_CONTAINER_WINDOW,
+              GetLaunchContainer(ExtensionPrefs::Get(profile()), extension));
+
+    // Mark the app as not locally installed and check that it now opens in a
+    // tab.
+    SetBookmarkAppIsLocallyInstalled(profile(), extension, false);
+    EXPECT_EQ(LAUNCH_CONTAINER_TAB,
+              GetLaunchContainer(ExtensionPrefs::Get(profile()), extension));
+
+    // Mark the app as locally installed and check that it now opens in a
+    // window.
+    SetBookmarkAppIsLocallyInstalled(profile(), extension, true);
+    EXPECT_EQ(LAUNCH_CONTAINER_WINDOW,
+              GetLaunchContainer(ExtensionPrefs::Get(profile()), extension));
+  }
+  {
+    CreateDataRetrieverWithLaunchContainer(GURL("https://www.example.org/"),
+                                           /*open_as_window=*/false,
+                                           /*is_installable=*/false);
+
+    const Extension* extension = InstallWebAppFromManifestWithFallback();
+
+    EXPECT_EQ(LAUNCH_CONTAINER_TAB,
+              GetLaunchContainer(ExtensionPrefs::Get(profile()), extension));
+  }
+}
+
+TEST_F(InstallManagerBookmarkAppTest,
+       CreateBookmarkAppForcedLauncherContainers) {
+  {
+    CreateDataRetrieverWithLaunchContainer(GURL("https://www.example.org/"),
+                                           /*open_as_window=*/true,
+                                           /*is_installable=*/true);
+
+    web_app::InstallOptions install_options;
+    install_options.launch_container = web_app::LaunchContainer::kTab;
+
+    const Extension* extension = InstallWebAppWithOptions(install_options);
+
+    EXPECT_EQ(LAUNCH_CONTAINER_TAB,
+              GetLaunchContainer(ExtensionPrefs::Get(profile()), extension));
+  }
+  {
+    CreateDataRetrieverWithLaunchContainer(
+        GURL(kAppUrl), /*open_as_window=*/false, /*is_installable=*/false);
+
+    web_app::InstallOptions install_options;
+    install_options.launch_container = web_app::LaunchContainer::kWindow;
+
+    const Extension* extension = InstallWebAppWithOptions(install_options);
+
+    EXPECT_EQ(LAUNCH_CONTAINER_WINDOW,
+              GetLaunchContainer(ExtensionPrefs::Get(profile()), extension));
+  }
+}
+
+TEST_F(InstallManagerBookmarkAppTest, CreateBookmarkAppWithoutManifest) {
+  auto web_app_info = std::make_unique<WebApplicationInfo>();
+  const GURL app_url(kAppUrl);
+  web_app_info->app_url = app_url;
+  web_app_info->title = base::UTF8ToUTF16(kAppTitle);
+  web_app_info->description = base::UTF8ToUTF16(kAppDescription);
+
+  CreateEmptyDataRetriever();
+  data_retriever()->SetRendererWebApplicationInfo(std::move(web_app_info));
+  auto manifest = std::make_unique<blink::Manifest>();
+  data_retriever()->SetManifest(std::move(manifest), /*is_installable=*/false);
+  data_retriever()->SetIcons(web_app::IconsMap{});
+
+  const Extension* extension = InstallWebAppFromManifestWithFallback();
+
+  EXPECT_EQ(kAppTitle, extension->name());
+  EXPECT_EQ(kAppDescription, extension->description());
+  EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension));
+  EXPECT_EQ(GURL(), GetScopeURLFromBookmarkApp(extension));
+  EXPECT_FALSE(AppThemeColorInfo::GetThemeColor(extension));
+}
+
 TEST_F(InstallManagerBookmarkAppTest, CreateWebAppFromInfo) {
+  CreateEmptyDataRetriever();
+
   auto web_app_info = std::make_unique<WebApplicationInfo>();
   web_app_info->app_url = kAppUrl;
   web_app_info->title = base::UTF8ToUTF16(kAppTitle);
@@ -153,7 +614,7 @@
   run_loop.Run();
 
   const Extension* extension = service_->GetInstalledExtension(app_id);
-  EXPECT_TRUE(extension);
+  ASSERT_TRUE(extension);
 
   EXPECT_EQ(1u, registry()->enabled_extensions().size());
   EXPECT_TRUE(extension->from_bookmark());
@@ -179,6 +640,8 @@
 }
 
 TEST_F(InstallManagerBookmarkAppTest, InstallOrUpdateWebAppFromSync) {
+  CreateEmptyDataRetriever();
+
   EXPECT_EQ(0u, registry()->enabled_extensions().size());
 
   auto web_app_info = std::make_unique<WebApplicationInfo>();
@@ -246,6 +709,7 @@
   // On ChromeOS, it always behaves as if app is installed.
   EXPECT_TRUE(provider->registrar().IsInstalled(app_id));
 
+  CreateEmptyDataRetriever();
   {
     base::RunLoop run_loop;
 
@@ -281,6 +745,4 @@
   }
 }
 
-// TODO(loyso): Convert more tests from bookmark_app_helper_unittest.cc
-
 }  // namespace extensions
diff --git a/chrome/test/base/test_browser_window.h b/chrome/test/base/test_browser_window.h
index fb7da23..919853e 100644
--- a/chrome/test/base/test_browser_window.h
+++ b/chrome/test/base/test_browser_window.h
@@ -226,6 +226,7 @@
 
     // PageActionIconContainer:
     void UpdatePageActionIcon(PageActionIconType type) override {}
+    void ExecutePageActionIconForTesting(PageActionIconType type) override {}
 
    private:
     DISALLOW_COPY_AND_ASSIGN(TestOmniboxPageActionIconContainer);
diff --git a/chrome/test/data/local_ntp/local_ntp_browsertest.html b/chrome/test/data/local_ntp/local_ntp_browsertest.html
index 3517f42..3c4ea02 100644
--- a/chrome/test/data/local_ntp/local_ntp_browsertest.html
+++ b/chrome/test/data/local_ntp/local_ntp_browsertest.html
@@ -158,7 +158,7 @@
     <dialog id="customization-menu" class="customize-dialog">
       <div id="menu-header">
         <div id="menu-back-wrapper">
-          <div id="menu-back-circle" tabindex="0">
+          <div id="menu-back-circle" tabindex="0" role="button">
             <div id="menu-back"></div>
           </div>
         </div>
@@ -187,13 +187,15 @@
         </div>
       </div>
       <div id="menu-contents">
-        <div id="backgrounds-menu" class="menu-panel">
+        <div id="backgrounds-menu" class="menu-panel" tabindex="0">
           <div id="backgrounds-upload" class="bg-sel-tile-bg">
-            <div id="backgrounds-upload-icon"></div>
-            <div id="backgrounds-upload-text">$i18n{uploadImage}</div>
+            <div id="backgrounds-upload-wrapper" class="bg-sel-tile" tabindex="-1">
+              <div id="backgrounds-upload-icon"></div>
+              <div id="backgrounds-upload-text">$i18n{uploadImage}</div>
+            </div>
           </div>
           <div id="backgrounds-default" class="bg-sel-tile-bg">
-            <div id="backgrounds-default-icon" class="bg-sel-tile">
+            <div id="backgrounds-default-icon" class="bg-sel-tile" tabindex="-1">
               <div class="mini-page">
                 <div class="mini-header"></div>
                 <div class="mini-shortcuts"></div>
@@ -202,7 +204,7 @@
             <div class="bg-sel-tile-title">$i18n{noBackground}</div>
           </div>
         </div>
-        <div id="backgrounds-image-menu" class="menu-panel"></div>
+        <div id="backgrounds-image-menu" class="menu-panel" tabindex="-1"></div>
         <div id="shortcuts-menu" class="menu-panel">
           <div id="sh-options">
             <div class="sh-option">
diff --git a/chromecast/media/audio/cast_audio_output_stream_unittest.cc b/chromecast/media/audio/cast_audio_output_stream_unittest.cc
index 31a9f05..9960ad1 100644
--- a/chromecast/media/audio/cast_audio_output_stream_unittest.cc
+++ b/chromecast/media/audio/cast_audio_output_stream_unittest.cc
@@ -369,6 +369,7 @@
   RunThreadsUntilIdle();
 
   stream->Close();
+  RunThreadsUntilIdle();
 }
 
 TEST_F(CastAudioOutputStreamTest, CloseCancelsOpen) {
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 4630256..016432c 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -20,7 +20,7 @@
 
 // Enables or disables Crostini Backup.
 const base::Feature kCrostiniBackup{"CrostiniBackup",
-                                    base::FEATURE_DISABLED_BY_DEFAULT};
+                                    base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables or disables Crostini GPU support.
 const base::Feature kCrostiniGpuSupport{"CrostiniGpuSupport",
diff --git a/components/arc/ime/arc_ime_service.cc b/components/arc/ime/arc_ime_service.cc
index 037682b9..bdafd0b0 100644
--- a/components/arc/ime/arc_ime_service.cc
+++ b/components/arc/ime/arc_ime_service.cc
@@ -93,6 +93,13 @@
     return window->GetHost()->GetInputMethod();
   }
 
+  bool IsImeBlocked(aura::Window* window) const override {
+    // WMHelper is not created in tests.
+    if (!exo::WMHelper::HasInstance())
+      return false;
+    return exo::WMHelper::GetInstance()->IsImeBlocked(window);
+  }
+
  private:
   ArcImeService* const ime_service_;
 
@@ -215,15 +222,39 @@
   // This shouldn't be reached on production, since the window lost the focus
   // and called OnWindowFocused() before destroying.
   // But we handle this case for testing.
-  DCHECK_EQ(window, focused_arc_window_);
-  OnWindowFocused(nullptr, focused_arc_window_);
+  if (window == focused_arc_window_)
+    OnWindowFocused(nullptr, focused_arc_window_);
 }
 
 void ArcImeService::OnWindowRemovingFromRootWindow(aura::Window* window,
                                                    aura::Window* new_root) {
-  DCHECK_EQ(window, focused_arc_window_);
   // IMEs are associated with root windows, hence we may need to detach/attach.
-  ReattachInputMethod(focused_arc_window_, new_root);
+  if (window == focused_arc_window_)
+    ReattachInputMethod(focused_arc_window_, new_root);
+}
+
+void ArcImeService::OnWindowPropertyChanged(aura::Window* window,
+                                            const void* key,
+                                            intptr_t old) {
+  if (window == focused_arc_window_)
+    return;
+
+  bool ime_blocked = arc_window_delegate_->IsImeBlocked(focused_arc_window_);
+  if (last_ime_blocked_ == ime_blocked)
+    return;
+  last_ime_blocked_ = ime_blocked;
+
+  // IME blocking has changed.
+  ui::InputMethod* const input_method = GetInputMethod();
+  if (input_method) {
+    if (has_composition_text_) {
+      // If it has composition text, clear both ARC's current composition text
+      // and Chrome IME's one.
+      ClearCompositionText();
+      input_method->CancelComposition(this);
+    }
+    input_method->OnTextInputTypeChanged(this);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -238,6 +269,10 @@
   const bool attach = arc_window_delegate_->IsInArcAppWindow(gained_focus);
 
   if (detach) {
+    // The focused window and the toplevel window are different in production,
+    // but in tests they can be the same, so avoid adding the observer twice.
+    if (focused_arc_window_ != focused_arc_window_->GetToplevelWindow())
+      focused_arc_window_->GetToplevelWindow()->RemoveObserver(this);
     focused_arc_window_->RemoveObserver(this);
     focused_arc_window_ = nullptr;
   }
@@ -245,6 +280,10 @@
     DCHECK_EQ(nullptr, focused_arc_window_);
     focused_arc_window_ = gained_focus;
     focused_arc_window_->AddObserver(this);
+    // The focused window and the toplevel window are different in production,
+    // but in tests they can be the same, so avoid adding the observer twice.
+    if (focused_arc_window_ != focused_arc_window_->GetToplevelWindow())
+      focused_arc_window_->GetToplevelWindow()->AddObserver(this);
   }
 
   ReattachInputMethod(detach ? lost_focus : nullptr, focused_arc_window_);
@@ -330,9 +369,9 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Overridden from keyboard::KeyboardControllerObserver
+// Overridden from ash::KeyboardControllerObserver
 void ArcImeService::OnKeyboardAppearanceChanged(
-    const keyboard::KeyboardStateDescriptor& state) {
+    const ash::KeyboardStateDescriptor& state) {
   gfx::Rect new_bounds = state.occluded_bounds_in_screen;
   // Multiply by the scale factor. To convert from DIP to physical pixels.
   // The default scale factor is always used in Android side regardless of
@@ -378,6 +417,10 @@
 }
 
 void ArcImeService::InsertChar(const ui::KeyEvent& event) {
+  // When IME is blocked for the window, let Exo handle the event.
+  if (arc_window_delegate_->IsImeBlocked(focused_arc_window_))
+    return;
+
   // According to the document in text_input_client.h, InsertChar() is called
   // even when the text input type is NONE. We ignore such events, since for
   // ARC we are only interested in the event as a method of text input.
@@ -419,6 +462,8 @@
 }
 
 ui::TextInputType ArcImeService::GetTextInputType() const {
+  if (arc_window_delegate_->IsImeBlocked(focused_arc_window_))
+    return ui::TEXT_INPUT_TYPE_NONE;
   return ime_type_;
 }
 
diff --git a/components/arc/ime/arc_ime_service.h b/components/arc/ime/arc_ime_service.h
index 53eb8b5..2b3276e 100644
--- a/components/arc/ime/arc_ime_service.h
+++ b/components/arc/ime/arc_ime_service.h
@@ -7,7 +7,7 @@
 
 #include <memory>
 
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/macros.h"
 #include "base/optional.h"
 #include "components/arc/ime/arc_ime_bridge.h"
@@ -42,7 +42,7 @@
                       public aura::EnvObserver,
                       public aura::WindowObserver,
                       public aura::client::FocusChangeObserver,
-                      public keyboard::KeyboardControllerObserver,
+                      public ash::KeyboardControllerObserver,
                       public ui::TextInputClient {
  public:
   // Returns singleton instance for the given BrowserContext,
@@ -64,6 +64,7 @@
     virtual void UnregisterFocusObserver() = 0;
     virtual ui::InputMethod* GetInputMethodForWindow(
         aura::Window* window) const = 0;
+    virtual bool IsImeBlocked(aura::Window* window) const = 0;
   };
 
   // Injects the custom IPC bridge object for testing purpose only.
@@ -80,6 +81,9 @@
   void OnWindowDestroying(aura::Window* window) override;
   void OnWindowRemovingFromRootWindow(aura::Window* window,
                                       aura::Window* new_root) override;
+  void OnWindowPropertyChanged(aura::Window* window,
+                               const void* key,
+                               intptr_t old) override;
 
   // Overridden from aura::client::FocusChangeObserver:
   void OnWindowFocused(aura::Window* gained_focus,
@@ -101,9 +105,9 @@
       bool is_screen_coordinates) override;
   void RequestHideIme() override;
 
-  // Overridden from keyboard::KeyboardControllerObserver.
+  // Overridden from ash::KeyboardControllerObserver.
   void OnKeyboardAppearanceChanged(
-      const keyboard::KeyboardStateDescriptor& state) override;
+      const ash::KeyboardStateDescriptor& state) override;
 
   // Overridden from ui::TextInputClient:
   void SetCompositionText(const ui::CompositionText& composition) override;
@@ -178,6 +182,10 @@
   base::string16 text_in_range_;
   gfx::Range selection_range_;
 
+  // Return value of IsImeBlocked() last time OnWindowPropertyChanged() is
+  // called. It might not be the latest blocking state.
+  bool last_ime_blocked_ = false;
+
   aura::Window* focused_arc_window_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(ArcImeService);
diff --git a/components/arc/ime/arc_ime_service_unittest.cc b/components/arc/ime/arc_ime_service_unittest.cc
index b29a822..3a878ac 100644
--- a/components/arc/ime/arc_ime_service_unittest.cc
+++ b/components/arc/ime/arc_ime_service_unittest.cc
@@ -141,6 +141,10 @@
     return window ? test_input_method_ : nullptr;
   }
 
+  bool IsImeBlocked(aura::Window* window) const override {
+    return ime_blocked_;
+  }
+
   std::unique_ptr<aura::Window> CreateFakeArcWindow() {
     const int id = next_id_++;
     arc_window_id_.insert(id);
@@ -154,11 +158,14 @@
         &dummy_delegate_, id, gfx::Rect(), nullptr));
   }
 
+  void set_ime_blocked(bool ime_blocked) { ime_blocked_ = ime_blocked; }
+
  private:
   aura::test::TestWindowDelegate dummy_delegate_;
   int next_id_;
   std::set<int> arc_window_id_;
   ui::InputMethod* test_input_method_;
+  bool ime_blocked_ = false;
 };
 
 }  // namespace
@@ -272,6 +279,11 @@
                                     mojom::TEXT_INPUT_FLAG_NONE);
   instance_->InsertChar(ui::KeyEvent('a', ui::VKEY_A, ui::DomCode::NONE, 0));
   EXPECT_EQ(1, fake_arc_ime_bridge_->count_send_insert_text());
+
+  // When IME is blocked, the event is not forwarded.
+  fake_window_delegate_->set_ime_blocked(true);
+  instance_->InsertChar(ui::KeyEvent('a', ui::VKEY_A, ui::DomCode::NONE, 0));
+  EXPECT_EQ(1, fake_arc_ime_bridge_->count_send_insert_text());
 }
 
 TEST_F(ArcImeServiceTest, WindowFocusTracking) {
@@ -358,8 +370,8 @@
   EXPECT_FALSE(fake_arc_ime_bridge_->last_keyboard_availability());
 
   const gfx::Rect keyboard_bounds(0, 480, 1200, 320);
-  keyboard::KeyboardStateDescriptor desc{true, keyboard_bounds, keyboard_bounds,
-                                         keyboard_bounds};
+  ash::KeyboardStateDescriptor desc{true, keyboard_bounds, keyboard_bounds,
+                                    keyboard_bounds};
   instance_->OnKeyboardAppearanceChanged(desc);
   EXPECT_EQ(keyboard_bounds, fake_arc_ime_bridge_->last_keyboard_bounds());
   EXPECT_TRUE(fake_arc_ime_bridge_->last_keyboard_availability());
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 7f37047..5860082 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -401,6 +401,17 @@
                                           always_on_top);
 }
 
+void ClientControlledShellSurface::SetImeBlocked(bool ime_blocked) {
+  TRACE_EVENT1("exo", "ClientControlledShellSurface::SetImeBlocked",
+               "ime_blocked", ime_blocked);
+
+  if (!widget_)
+    CreateShellSurfaceWidget(ui::SHOW_STATE_NORMAL);
+
+  WMHelper::GetInstance()->SetImeBlocked(widget_->GetNativeWindow(),
+                                         ime_blocked);
+}
+
 void ClientControlledShellSurface::SetOrientation(Orientation orientation) {
   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetOrientation",
                "orientation",
diff --git a/components/exo/client_controlled_shell_surface.h b/components/exo/client_controlled_shell_surface.h
index e60ce6f..c30fa22 100644
--- a/components/exo/client_controlled_shell_surface.h
+++ b/components/exo/client_controlled_shell_surface.h
@@ -130,6 +130,9 @@
   // Sets the surface to be on top of all other windows.
   void SetAlwaysOnTop(bool always_on_top);
 
+  // Sets the IME to be blocked so that all events are forwarded by Exo.
+  void SetImeBlocked(bool ime_blocked);
+
   // Controls the visibility of the system UI when this surface is active.
   void SetSystemUiVisibility(bool autohide);
 
diff --git a/components/exo/keyboard.cc b/components/exo/keyboard.cc
index 35528cb..8676a34 100644
--- a/components/exo/keyboard.cc
+++ b/components/exo/keyboard.cc
@@ -58,6 +58,10 @@
 }
 
 bool ConsumedByIme(Surface* focus, const ui::KeyEvent* event) {
+  // When IME is blocked, Exo can handle any key events.
+  if (WMHelper::GetInstance()->IsImeBlocked(focus->window()))
+    return false;
+
   // Check if IME consumed the event, to avoid it to be doubly processed.
   // First let us see whether IME is active and is in text input mode.
   views::Widget* widget =
@@ -341,7 +345,7 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// keyboard::KeyboardControllerObserver overrides:
+// ash::KeyboardControllerObserver overrides:
 
 void Keyboard::OnKeyboardEnabledChanged(bool enabled) {
   if (device_configuration_delegate_) {
diff --git a/components/exo/keyboard.h b/components/exo/keyboard.h
index 12b089b..c371a97 100644
--- a/components/exo/keyboard.h
+++ b/components/exo/keyboard.h
@@ -7,7 +7,7 @@
 
 #include <vector>
 
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/macros.h"
@@ -34,7 +34,7 @@
 class Keyboard : public ui::EventHandler,
                  public SurfaceObserver,
                  public SeatObserver,
-                 public keyboard::KeyboardControllerObserver {
+                 public ash::KeyboardControllerObserver {
  public:
   Keyboard(KeyboardDelegate* delegate, Seat* seat);
   ~Keyboard() override;
@@ -65,7 +65,7 @@
   void OnSurfaceFocusing(Surface* gaining_focus) override;
   void OnSurfaceFocused(Surface* gained_focus) override;
 
-  // Overridden from keyboard::KeyboardControllerObserver
+  // Overridden from ash::KeyboardControllerObserver
   void OnKeyboardEnabledChanged(bool is_enabled) override;
 
  private:
diff --git a/components/exo/test/exo_test_base_views.cc b/components/exo/test/exo_test_base_views.cc
index b2843ca..368a2ae5 100644
--- a/components/exo/test/exo_test_base_views.cc
+++ b/components/exo/test/exo_test_base_views.cc
@@ -64,6 +64,8 @@
   void RemovePostTargetHandler(ui::EventHandler* handler) override {}
   bool IsTabletModeWindowManagerEnabled() const override { return false; }
   double GetDefaultDeviceScaleFactor() const override { return 1.0; }
+  void SetImeBlocked(aura::Window* window, bool ime_blocked) override {}
+  bool IsImeBlocked(aura::Window* window) const override { return false; }
 
   LifetimeManager* GetLifetimeManager() override { return &lifetime_manager_; }
   aura::client::CaptureClient* GetCaptureClient() override { return nullptr; }
diff --git a/components/exo/text_input.cc b/components/exo/text_input.cc
index 55a08df..1124aa1 100644
--- a/components/exo/text_input.cc
+++ b/components/exo/text_input.cc
@@ -319,7 +319,7 @@
   return false;
 }
 
-void TextInput::OnKeyboardVisibilityStateChanged(bool is_visible) {
+void TextInput::OnKeyboardVisibilityChanged(bool is_visible) {
   delegate_->OnVirtualKeyboardVisibilityChanged(is_visible);
 }
 
diff --git a/components/exo/text_input.h b/components/exo/text_input.h
index c71e477..da70882 100644
--- a/components/exo/text_input.h
+++ b/components/exo/text_input.h
@@ -5,7 +5,7 @@
 #ifndef COMPONENTS_EXO_TEXT_INPUT_H_
 #define COMPONENTS_EXO_TEXT_INPUT_H_
 
-#include "ash/keyboard/ui/keyboard_controller_observer.h"
+#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
 #include "base/optional.h"
 #include "ui/base/ime/text_input_client.h"
 #include "ui/base/ime/text_input_flags.h"
@@ -29,7 +29,7 @@
 
 // This class bridges the ChromeOS input method and a text-input context.
 class TextInput : public ui::TextInputClient,
-                  public keyboard::KeyboardControllerObserver {
+                  public ash::KeyboardControllerObserver {
  public:
   class Delegate {
    public:
@@ -139,8 +139,8 @@
       const gfx::Range& range,
       const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) override;
 
-  // keyboard::KeyboardControllerObserver:
-  void OnKeyboardVisibilityStateChanged(bool is_visible) override;
+  // ash::KeyboardControllerObserver:
+  void OnKeyboardVisibilityChanged(bool is_visible) override;
 
  private:
   void AttachInputMethod();
diff --git a/components/exo/text_input_unittest.cc b/components/exo/text_input_unittest.cc
index ca0d5bf..f46e0ac 100644
--- a/components/exo/text_input_unittest.cc
+++ b/components/exo/text_input_unittest.cc
@@ -165,7 +165,7 @@
 
   EXPECT_CALL(observer, OnShowVirtualKeyboardIfEnabled)
       .WillOnce(testing::Invoke(
-          [this]() { text_input()->OnKeyboardVisibilityStateChanged(true); }));
+          [this]() { text_input()->OnKeyboardVisibilityChanged(true); }));
   EXPECT_CALL(*delegate(), OnVirtualKeyboardVisibilityChanged(true)).Times(1);
   text_input()->ShowVirtualKeyboardIfEnabled();
 
@@ -182,7 +182,7 @@
   EXPECT_CALL(observer, OnTextInputStateChanged(text_input())).Times(1);
   EXPECT_CALL(observer, OnShowVirtualKeyboardIfEnabled)
       .WillOnce(testing::Invoke(
-          [this]() { text_input()->OnKeyboardVisibilityStateChanged(true); }));
+          [this]() { text_input()->OnKeyboardVisibilityChanged(true); }));
   EXPECT_CALL(*delegate(), Activated).Times(1);
   EXPECT_CALL(*delegate(), OnVirtualKeyboardVisibilityChanged(true)).Times(1);
   text_input()->Activate(surface());
diff --git a/components/exo/wayland/zcr_remote_shell.cc b/components/exo/wayland/zcr_remote_shell.cc
index b0ef96f..82155b5 100644
--- a/components/exo/wayland/zcr_remote_shell.cc
+++ b/components/exo/wayland/zcr_remote_shell.cc
@@ -525,6 +525,14 @@
       gfx::Rect(x, y, width, height));
 }
 
+void remote_surface_block_ime(wl_client* client, wl_resource* resource) {
+  GetUserDataAs<ClientControlledShellSurface>(resource)->SetImeBlocked(true);
+}
+
+void remote_surface_unblock_ime(wl_client* client, wl_resource* resource) {
+  GetUserDataAs<ClientControlledShellSurface>(resource)->SetImeBlocked(false);
+}
+
 const struct zcr_remote_surface_v1_interface remote_surface_implementation = {
     remote_surface_destroy,
     remote_surface_set_app_id,
@@ -568,7 +576,9 @@
     remote_surface_set_orientation_lock,
     remote_surface_pip,
     remote_surface_set_bounds,
-    remote_surface_set_aspect_ratio};
+    remote_surface_set_aspect_ratio,
+    remote_surface_block_ime,
+    remote_surface_unblock_ime};
 
 ////////////////////////////////////////////////////////////////////////////////
 // notification_surface_interface:
diff --git a/components/exo/wm_helper.h b/components/exo/wm_helper.h
index 2629279..ab96ad2 100644
--- a/components/exo/wm_helper.h
+++ b/components/exo/wm_helper.h
@@ -119,6 +119,8 @@
   virtual void RemovePostTargetHandler(ui::EventHandler* handler) = 0;
   virtual bool IsTabletModeWindowManagerEnabled() const = 0;
   virtual double GetDefaultDeviceScaleFactor() const = 0;
+  virtual void SetImeBlocked(aura::Window* window, bool ime_blocked) = 0;
+  virtual bool IsImeBlocked(aura::Window* window) const = 0;
 
   virtual LifetimeManager* GetLifetimeManager() = 0;
   virtual aura::client::CaptureClient* GetCaptureClient() = 0;
diff --git a/components/exo/wm_helper_chromeos.cc b/components/exo/wm_helper_chromeos.cc
index 269d1f1..c79e89a 100644
--- a/components/exo/wm_helper_chromeos.cc
+++ b/components/exo/wm_helper_chromeos.cc
@@ -25,6 +25,9 @@
   return ash::Shell::Get()->GetPrimaryRootWindow();
 }
 
+// A property key to store whether IME should be blocked for the surface.
+DEFINE_UI_CLASS_PROPERTY_KEY(bool, kImeBlockedKey, false)
+
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -212,6 +215,15 @@
   return display_info.display_modes()[0].device_scale_factor();
 }
 
+void WMHelperChromeOS::SetImeBlocked(aura::Window* window, bool ime_blocked) {
+  DCHECK_EQ(window, window->GetToplevelWindow());
+  window->SetProperty(kImeBlockedKey, ime_blocked);
+}
+
+bool WMHelperChromeOS::IsImeBlocked(aura::Window* window) const {
+  return window && window->GetToplevelWindow()->GetProperty(kImeBlockedKey);
+}
+
 WMHelper::LifetimeManager* WMHelperChromeOS::GetLifetimeManager() {
   return &lifetime_manager_;
 }
diff --git a/components/exo/wm_helper_chromeos.h b/components/exo/wm_helper_chromeos.h
index 856d49f..45338cb 100644
--- a/components/exo/wm_helper_chromeos.h
+++ b/components/exo/wm_helper_chromeos.h
@@ -90,6 +90,8 @@
   void RemovePostTargetHandler(ui::EventHandler* handler) override;
   bool IsTabletModeWindowManagerEnabled() const override;
   double GetDefaultDeviceScaleFactor() const override;
+  void SetImeBlocked(aura::Window* window, bool ime_blocked) override;
+  bool IsImeBlocked(aura::Window* window) const override;
 
   LifetimeManager* GetLifetimeManager() override;
   aura::client::CaptureClient* GetCaptureClient() override;
diff --git a/components/feed/core/feed_logging_metrics.cc b/components/feed/core/feed_logging_metrics.cc
index f42e042..dd745c9 100644
--- a/components/feed/core/feed_logging_metrics.cc
+++ b/components/feed/core/feed_logging_metrics.cc
@@ -38,6 +38,11 @@
       base::StringPrintf("%s.%s", histogram_base.c_str(), "TaskTime"),     \
       task_time);
 
+#define AGE_CUSTOM_UMA_HISTOGRAM_TIMES(histogram_name, time)  \
+  UMA_HISTOGRAM_CUSTOM_TIMES(histogram_name, time,            \
+                             base::TimeDelta::FromSeconds(1), \
+                             base::TimeDelta::FromDays(7), 100);
+
 // The constant integers(bucket sizes) and strings(UMA names) in this file need
 // matching with Zine's in the file
 // components/ntp_snippets/content_suggestions_metrics.cc. The purpose to have
@@ -110,8 +115,21 @@
   kMaxValue = KUploadAllActionsForURL
 };
 
+// Values correspond to
+// third_party/feed/src/main/proto/search/now/ui/action/feed_action.proto.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class ElementType {
+  KUnknownElementType = 0,
+  KCardLargeImage = 1,
+  KCardSmallImage = 2,
+  KInterestHeader = 3,
+  KTooltip = 4,
+  kMaxValue = KTooltip
+};
+
 // Each suffix here should correspond to an entry under histogram suffix
-// ContentSuggestionCategory in histograms.xml.
+// FeedSpinnerType in histograms.xml.
 std::string GetSpinnerTypeSuffix(SpinnerType spinner_type) {
   switch (spinner_type) {
     case SpinnerType::KInitialLoad:
@@ -133,6 +151,26 @@
 }
 
 // Each suffix here should correspond to an entry under histogram suffix
+// FeedElementType in histograms.xml.
+std::string GetElementTypeSuffix(ElementType element_type) {
+  switch (element_type) {
+    case ElementType::KUnknownElementType:
+      return "UnknownElementType";
+    case ElementType::KCardLargeImage:
+      return "CardLargeImage";
+    case ElementType::KCardSmallImage:
+      return "CardSmallImage";
+    case ElementType::KInterestHeader:
+      return "InterestHeader";
+    case ElementType::KTooltip:
+      return "Tooltip";
+  }
+
+  NOTREACHED();
+  return std::string();
+}
+
+// Each suffix here should correspond to an entry under histogram suffix
 // FeedTaskType in histograms.xml.
 void ReportTaskTime(TaskType task_type, int delay_time_ms, int task_time_ms) {
   switch (task_type) {
@@ -398,7 +436,7 @@
 }
 
 void RecordSpinnerTimeUMA(const char* base_name,
-                          base::TimeDelta time,
+                          const base::TimeDelta& time,
                           int spinner_type) {
   SpinnerType type = static_cast<SpinnerType>(spinner_type);
   std::string suffix = GetSpinnerTypeSuffix(type);
@@ -408,6 +446,32 @@
   base::UmaHistogramTimes(base_name, time);
 }
 
+void RecordElementPositionUMA(const char* base_name,
+                              int position,
+                              int element_type) {
+  ElementType type = static_cast<ElementType>(element_type);
+  std::string suffix = GetElementTypeSuffix(type);
+  std::string histogram_name(
+      base::StringPrintf("%s.%s", base_name, suffix.c_str()));
+  base::UmaHistogramExactLinear(histogram_name, position, kMaxSuggestionsTotal);
+  base::UmaHistogramExactLinear(base_name, position, kMaxSuggestionsTotal);
+}
+
+void RecordElementTimeUMA(const char* base_name,
+                          const base::TimeDelta& time,
+                          int element_type) {
+  ElementType type = static_cast<ElementType>(element_type);
+  std::string suffix = GetElementTypeSuffix(type);
+  std::string histogram_name(
+      base::StringPrintf("%s.%s", base_name, suffix.c_str()));
+  base::UmaHistogramCustomTimes(histogram_name, time,
+                                base::TimeDelta::FromSeconds(1),
+                                base::TimeDelta::FromDays(7), 100);
+  base::UmaHistogramCustomTimes(base_name, time,
+                                base::TimeDelta::FromSeconds(1),
+                                base::TimeDelta::FromDays(7), 100);
+}
+
 }  // namespace
 
 FeedLoggingMetrics::FeedLoggingMetrics(
@@ -438,20 +502,17 @@
                              kMaxSuggestionsTotal);
 
   base::TimeDelta age = clock_->Now() - publish_date;
-  UMA_HISTOGRAM_CUSTOM_TIMES("NewTabPage.ContentSuggestions.ShownAge.Articles",
-                             age, base::TimeDelta::FromSeconds(1),
-                             base::TimeDelta::FromDays(7), 100);
+  AGE_CUSTOM_UMA_HISTOGRAM_TIMES(
+      "NewTabPage.ContentSuggestions.ShownAge.Articles", age);
 
   UMA_HISTOGRAM_EXACT_LINEAR(
       "NewTabPage.ContentSuggestions.ShownScoreNormalized.Articles",
       ToUMAScore(score), 11);
 
   // Records the time since the fetch time of the displayed snippet.
-  UMA_HISTOGRAM_CUSTOM_TIMES(
-      "NewTabPage.ContentSuggestions.TimeSinceSuggestionFetched",
-      clock_->Now() - fetch_date, base::TimeDelta::FromSeconds(1),
-      base::TimeDelta::FromDays(7),
-      /*bucket_count=*/100);
+  base::TimeDelta fetch_age = clock_->Now() - fetch_date;
+  AGE_CUSTOM_UMA_HISTOGRAM_TIMES(
+      "NewTabPage.ContentSuggestions.TimeSinceSuggestionFetched", fetch_age);
 
   // When the first of the articles suggestions is shown, then we count this as
   // a single usage of content suggestions.
@@ -467,9 +528,8 @@
                              kMaxSuggestionsTotal);
 
   base::TimeDelta age = clock_->Now() - publish_date;
-  UMA_HISTOGRAM_CUSTOM_TIMES("NewTabPage.ContentSuggestions.OpenedAge.Articles",
-                             age, base::TimeDelta::FromSeconds(1),
-                             base::TimeDelta::FromDays(7), 100);
+  AGE_CUSTOM_UMA_HISTOGRAM_TIMES(
+      "NewTabPage.ContentSuggestions.OpenedAge.Articles", age);
 
   UMA_HISTOGRAM_EXACT_LINEAR(
       "NewTabPage.ContentSuggestions.OpenedScoreNormalized.Articles",
@@ -501,9 +561,8 @@
                              position, kMaxSuggestionsTotal);
 
   base::TimeDelta age = clock_->Now() - publish_date;
-  UMA_HISTOGRAM_CUSTOM_TIMES(
-      "NewTabPage.ContentSuggestions.MenuOpenedAge.Articles", age,
-      base::TimeDelta::FromSeconds(1), base::TimeDelta::FromDays(7), 100);
+  AGE_CUSTOM_UMA_HISTOGRAM_TIMES(
+      "NewTabPage.ContentSuggestions.MenuOpenedAge.Articles", age);
 
   UMA_HISTOGRAM_EXACT_LINEAR(
       "NewTabPage.ContentSuggestions.MenuOpenedScoreNormalized.Articles",
@@ -598,6 +657,29 @@
   }
 }
 
+void FeedLoggingMetrics::OnVisualElementClicked(int element_type,
+                                                int position,
+                                                base::Time fetch_date) {
+  RecordElementPositionUMA("ContentSuggestions.Feed.VisualElement.Clicked",
+                           position, element_type);
+
+  RecordElementTimeUMA(
+      "ContentSuggestions.Feed.VisualElement.Clicked."
+      "TimeSinceElementFetched",
+      clock_->Now() - fetch_date, element_type);
+}
+
+void FeedLoggingMetrics::OnVisualElementViewed(int element_type,
+                                               int position,
+                                               base::Time fetch_date) {
+  RecordElementPositionUMA("ContentSuggestions.Feed.VisualElement.Viewed",
+                           position, element_type);
+
+  RecordElementTimeUMA(
+      "ContentSuggestions.Feed.VisualElement.Viewed.TimeSinceElementFetched",
+      clock_->Now() - fetch_date, element_type);
+}
+
 void FeedLoggingMetrics::OnInternalError(int internal_error) {
   // TODO(https://crbug.com/935602): The max value here is fragile, figure out
   // some way to test the @IntDef size.
diff --git a/components/feed/core/feed_logging_metrics.h b/components/feed/core/feed_logging_metrics.h
index c203e08..d73a39d 100644
--- a/components/feed/core/feed_logging_metrics.h
+++ b/components/feed/core/feed_logging_metrics.h
@@ -89,6 +89,14 @@
 
   void OnPietFrameRenderingEvent(std::vector<int> piet_error_codes);
 
+  void OnVisualElementClicked(int element_type,
+                              int position,
+                              base::Time fetch_date);
+
+  void OnVisualElementViewed(int element_type,
+                             int position,
+                             base::Time fetch_date);
+
   void OnInternalError(int internal_error);
 
   void OnTokenCompleted(bool was_synthetic, int content_count, int token_count);
diff --git a/components/nacl/broker/nacl_broker_manifest.cc b/components/nacl/broker/nacl_broker_manifest.cc
index 0cec707..9cdf544 100644
--- a/components/nacl/broker/nacl_broker_manifest.cc
+++ b/components/nacl/broker/nacl_broker_manifest.cc
@@ -27,7 +27,6 @@
                                 "content.mojom.ChildControl",
                                 "content.mojom.ChildHistogramFetcherFactory",
                                 "content.mojom.ResourceUsageReporter",
-                                "tracing.mojom.BackgroundTracingAgent",
                             })
 
           .Build()};
diff --git a/components/nacl/loader/nacl_loader_manifest.cc b/components/nacl/loader/nacl_loader_manifest.cc
index a3ca039..2f250a2 100644
--- a/components/nacl/loader/nacl_loader_manifest.cc
+++ b/components/nacl/loader/nacl_loader_manifest.cc
@@ -26,7 +26,6 @@
                                 "content.mojom.ChildControl",
                                 "content.mojom.ChildHistogramFetcherFactory",
                                 "content.mojom.ResourceUsageReporter",
-                                "tracing.mojom.BackgroundTracingAgent",
                             })
           .Build()};
   return *manifest;
diff --git a/components/previews/content/hint_cache_store.cc b/components/previews/content/hint_cache_store.cc
index 4ef0a37..74d1f08 100644
--- a/components/previews/content/hint_cache_store.cc
+++ b/components/previews/content/hint_cache_store.cc
@@ -812,6 +812,21 @@
     return;
   }
 
+  if (entry->has_expiry_time_secs() &&
+      entry->expiry_time_secs() <=
+          base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds()) {
+    // An expired hint should be loaded rarely if the user is regularly fetching
+    // and storing fresh hints. Expired fetched hints are removed each time
+    // fresh hints are fetched and placed into the store. In the future, the
+    // expired hints could be asynchronously removed if necessary.
+    // An empty hint is returned instead of the expired one.
+    UMA_HISTOGRAM_BOOLEAN(
+        "Previews.HintCacheStore.OnLoadHint.FetchedHintExpired", true);
+    std::unique_ptr<optimization_guide::proto::Hint> loaded_hint(nullptr);
+    std::move(callback).Run(entry_key, std::move(loaded_hint));
+    return;
+  }
+
   // Release the Hint into a Hint unique_ptr. This eliminates the need for any
   // copies of the entry's hint.
   std::unique_ptr<optimization_guide::proto::Hint> loaded_hint(
diff --git a/components/previews/content/hint_cache_store_unittest.cc b/components/previews/content/hint_cache_store_unittest.cc
index 5c26000..cce4eca 100644
--- a/components/previews/content/hint_cache_store_unittest.cc
+++ b/components/previews/content/hint_cache_store_unittest.cc
@@ -1557,4 +1557,67 @@
   EXPECT_TRUE(hint_loaded_counts_pref()->empty());
 }
 
+TEST_F(HintCacheStoreTest, FetchedHintsLoadExpiredHint) {
+  base::HistogramTester histogram_tester;
+  MetadataSchemaState schema_state = MetadataSchemaState::kValid;
+  size_t initial_hint_count = 10;
+  base::Time update_time = base::Time().Now();
+  SeedInitialData(schema_state, initial_hint_count);
+  CreateDatabase();
+  InitializeStore(schema_state);
+
+  base::Version version("2.0.0");
+  std::unique_ptr<HintUpdateData> update_data =
+      hint_store()->MaybeCreateUpdateDataForComponentHints(
+          base::Version(kUpdateComponentVersion));
+  ASSERT_TRUE(update_data);
+
+  optimization_guide::proto::Hint hint1;
+  hint1.set_key("domain1.org");
+  hint1.set_key_representation(optimization_guide::proto::HOST_SUFFIX);
+  update_data->MoveHintIntoUpdateData(std::move(hint1));
+  optimization_guide::proto::Hint hint2;
+  hint2.set_key("host.domain2.org");
+  hint2.set_key_representation(optimization_guide::proto::HOST_SUFFIX);
+  update_data->MoveHintIntoUpdateData(std::move(hint2));
+
+  UpdateComponentHints(std::move(update_data));
+
+  // Add fetched hints to the store that expired.
+  update_data = hint_store()->CreateUpdateDataForFetchedHints(
+      update_time, update_time - base::TimeDelta().FromDays(10));
+
+  optimization_guide::proto::Hint fetched_hint1;
+  fetched_hint1.set_key("domain2.org");
+  fetched_hint1.set_key_representation(optimization_guide::proto::HOST_SUFFIX);
+  update_data->MoveHintIntoUpdateData(std::move(fetched_hint1));
+  optimization_guide::proto::Hint fetched_hint2;
+  fetched_hint2.set_key("domain3.org");
+  fetched_hint2.set_key_representation(optimization_guide::proto::HOST_SUFFIX);
+  update_data->MoveHintIntoUpdateData(std::move(fetched_hint2));
+
+  UpdateFetchedHints(std::move(update_data));
+
+  // Hint for host.domain2.org should be a fetched hint ("3_" prefix)
+  // as fetched hints take priority.
+  std::string host_suffix = "host.domain2.org";
+  HintCacheStore::EntryKey hint_entry_key;
+  if (!hint_store()->FindHintEntryKey(host_suffix, &hint_entry_key)) {
+    FAIL() << "Hint entry not found for host suffix: " << host_suffix;
+  }
+  EXPECT_EQ(hint_entry_key, "3_domain2.org");
+  hint_store()->LoadHint(hint_entry_key,
+                         base::BindOnce(&HintCacheStoreTest::OnHintLoaded,
+                                        base::Unretained(this)));
+
+  // OnLoadHint callback
+  db()->GetCallback(true);
+
+  // |hint_entry_key| will be a fetched hint but the entry will be empty.
+  EXPECT_EQ(last_loaded_hint_entry_key(), hint_entry_key);
+  EXPECT_FALSE(last_loaded_hint());
+  histogram_tester.ExpectBucketCount(
+      "Previews.HintCacheStore.OnLoadHint.FetchedHintExpired", true, 1);
+}
+
 }  // namespace previews
diff --git a/components/remote_cocoa/DEPS b/components/remote_cocoa/DEPS
index 646c0f5..755476f 100644
--- a/components/remote_cocoa/DEPS
+++ b/components/remote_cocoa/DEPS
@@ -3,6 +3,7 @@
   "+components/crash/core/common/crash_key.h",
   "+components/viz/common",
   "+mojo/public/cpp/bindings",
+  "+skia/ext",
   "+ui/accelerated_widget_mac",
   "+ui/base",
   "+ui/compositor",
diff --git a/components/remote_cocoa/app_shim/BUILD.gn b/components/remote_cocoa/app_shim/BUILD.gn
index 912d967..e795b80 100644
--- a/components/remote_cocoa/app_shim/BUILD.gn
+++ b/components/remote_cocoa/app_shim/BUILD.gn
@@ -26,6 +26,8 @@
     "bridged_content_view_touch_bar.mm",
     "browser_native_widget_window_mac.h",
     "browser_native_widget_window_mac.mm",
+    "color_panel_bridge.h",
+    "color_panel_bridge.mm",
     "drag_drop_client.h",
     "mouse_capture.h",
     "mouse_capture.mm",
diff --git a/components/remote_cocoa/app_shim/application_bridge.h b/components/remote_cocoa/app_shim/application_bridge.h
index 8c5c703..f19c3a1 100644
--- a/components/remote_cocoa/app_shim/application_bridge.h
+++ b/components/remote_cocoa/app_shim/application_bridge.h
@@ -41,6 +41,8 @@
 
   // mojom::Application:
   void CreateAlert(mojom::AlertBridgeRequest bridge_request) override;
+  void ShowColorPanel(mojom::ColorPanelRequest request,
+                      mojom::ColorPanelHostPtr host) override;
   void CreateNativeWidgetNSWindow(
       uint64_t bridge_id,
       mojom::NativeWidgetNSWindowAssociatedRequest bridge_request,
diff --git a/components/remote_cocoa/app_shim/application_bridge.mm b/components/remote_cocoa/app_shim/application_bridge.mm
index b810fd9..628196e 100644
--- a/components/remote_cocoa/app_shim/application_bridge.mm
+++ b/components/remote_cocoa/app_shim/application_bridge.mm
@@ -7,8 +7,10 @@
 #include "base/bind.h"
 #include "base/no_destructor.h"
 #include "components/remote_cocoa/app_shim/alert.h"
+#include "components/remote_cocoa/app_shim/color_panel_bridge.h"
 #include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
 #include "components/remote_cocoa/app_shim/native_widget_ns_window_host_helper.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
 #include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
 #include "ui/base/cocoa/remote_accessibility_api.h"
 
@@ -119,6 +121,12 @@
   ignore_result(new AlertBridge(std::move(bridge_request)));
 }
 
+void ApplicationBridge::ShowColorPanel(mojom::ColorPanelRequest request,
+                                       mojom::ColorPanelHostPtr host) {
+  mojo::MakeStrongBinding(std::make_unique<ColorPanelBridge>(std::move(host)),
+                          std::move(request));
+}
+
 void ApplicationBridge::CreateNativeWidgetNSWindow(
     uint64_t bridge_id,
     mojom::NativeWidgetNSWindowAssociatedRequest bridge_request,
diff --git a/components/remote_cocoa/app_shim/color_panel_bridge.h b/components/remote_cocoa/app_shim/color_panel_bridge.h
new file mode 100644
index 0000000..0c767b4
--- /dev/null
+++ b/components/remote_cocoa/app_shim/color_panel_bridge.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 COMPONENTS_REMOTE_COCOA_APP_SHIM_COLOR_PANEL_BRIDGE_H_
+#define COMPONENTS_REMOTE_COCOA_APP_SHIM_COLOR_PANEL_BRIDGE_H_
+
+#include "components/remote_cocoa/app_shim/remote_cocoa_app_shim_export.h"
+#include "components/remote_cocoa/common/color_panel.mojom.h"
+
+namespace remote_cocoa {
+
+// A bridge between the mojo ColorPanel interface and the Objective C
+// ColorPanelListener.
+class REMOTE_COCOA_APP_SHIM_EXPORT ColorPanelBridge
+    : public remote_cocoa::mojom::ColorPanel {
+ public:
+  ColorPanelBridge(mojom::ColorPanelHostPtr host);
+  ~ColorPanelBridge() override;
+  mojom::ColorPanelHost* host() { return host_.get(); }
+
+  // mojom::ColorPanel.
+  void Show(uint32_t initial_color) override;
+  void SetSelectedColor(uint32_t color) override;
+
+ private:
+  mojom::ColorPanelHostPtr host_;
+};
+
+}  // namespace remote_cocoa
+
+#endif  // COMPONENTS_REMOTE_COCOA_APP_SHIM_COLOR_PANEL_BRIDGE_H_
diff --git a/components/remote_cocoa/app_shim/color_panel_bridge.mm b/components/remote_cocoa/app_shim/color_panel_bridge.mm
new file mode 100644
index 0000000..12eaee1
--- /dev/null
+++ b/components/remote_cocoa/app_shim/color_panel_bridge.mm
@@ -0,0 +1,140 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/remote_cocoa/app_shim/color_panel_bridge.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "skia/ext/skia_utils_mac.h"
+
+namespace {
+// The currently active bridge, to which the ColorPanelListener will forward
+// its observations.
+remote_cocoa::ColorPanelBridge* g_current_panel_bridge = nullptr;
+}  // namespace
+
+// A singleton listener class to act as a event target for NSColorPanel and
+// send the results to the C++ class, ColorPanelBridge.
+@interface ColorPanelListener : NSObject {
+ @protected
+  // We don't call DidChooseColor if the change wasn't caused by the user
+  // interacting with the panel.
+  BOOL nonUserChange_;
+}
+// Called from NSNotificationCenter.
+- (void)windowWillClose:(NSNotification*)notification;
+
+// Called from NSColorPanel.
+- (void)didChooseColor:(NSColorPanel*)panel;
+
+// The singleton instance.
++ (ColorPanelListener*)instance;
+
+// Show the NSColorPanel.
+- (void)showColorPanel;
+
+// Sets color to the NSColorPanel as a non user change.
+- (void)setColor:(NSColor*)color;
+@end
+
+@implementation ColorPanelListener
+- (id)init {
+  if ((self = [super init])) {
+    NSColorPanel* panel = [NSColorPanel sharedColorPanel];
+    [[NSNotificationCenter defaultCenter]
+        addObserver:self
+           selector:@selector(windowWillClose:)
+               name:NSWindowWillCloseNotification
+             object:panel];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  // This object is never freed.
+  NOTREACHED();
+  [super dealloc];
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+  if (g_current_panel_bridge)
+    g_current_panel_bridge->host()->DidCloseColorPanel();
+  nonUserChange_ = NO;
+}
+
+- (void)didChooseColor:(NSColorPanel*)panel {
+  if (nonUserChange_) {
+    nonUserChange_ = NO;
+    return;
+  }
+  nonUserChange_ = NO;
+  NSColor* color = [panel color];
+  if ([[color colorSpaceName] isEqualToString:NSNamedColorSpace]) {
+    color = [color colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+    // Some colors in "Developer" palette in "Color Palettes" tab can't be
+    // converted to RGB. We just ignore such colors.
+    // TODO(tkent): We should notice the rejection to users.
+    if (!color)
+      return;
+  }
+  SkColor skColor = 0;
+  if ([color colorSpace] == [NSColorSpace genericRGBColorSpace]) {
+    // genericRGB -> deviceRGB conversion isn't ignorable.  We'd like to use RGB
+    // values shown in NSColorPanel UI.
+    CGFloat red, green, blue, alpha;
+    [color getRed:&red green:&green blue:&blue alpha:&alpha];
+    skColor = SkColorSetARGB(
+        SkScalarRoundToInt(255.0 * alpha), SkScalarRoundToInt(255.0 * red),
+        SkScalarRoundToInt(255.0 * green), SkScalarRoundToInt(255.0 * blue));
+  } else {
+    skColor = skia::NSDeviceColorToSkColor(
+        [[panel color] colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
+  }
+  if (g_current_panel_bridge)
+    g_current_panel_bridge->host()->DidChooseColorInColorPanel(skColor);
+}
+
++ (ColorPanelListener*)instance {
+  static ColorPanelListener* listener = [[ColorPanelListener alloc] init];
+  return listener;
+}
+
+- (void)showColorPanel {
+  NSColorPanel* panel = [NSColorPanel sharedColorPanel];
+  [panel setShowsAlpha:NO];
+  [panel setTarget:self];
+  [panel setAction:@selector(didChooseColor:)];
+  [panel makeKeyAndOrderFront:nil];
+}
+
+- (void)setColor:(NSColor*)color {
+  nonUserChange_ = YES;
+  [[NSColorPanel sharedColorPanel] setColor:color];
+}
+@end
+
+namespace remote_cocoa {
+
+ColorPanelBridge::ColorPanelBridge(mojom::ColorPanelHostPtr host)
+    : host_(std::move(host)) {
+  g_current_panel_bridge = this;
+}
+
+ColorPanelBridge::~ColorPanelBridge() {
+  if (g_current_panel_bridge == this)
+    g_current_panel_bridge = nullptr;
+}
+
+void ColorPanelBridge::Show(uint32_t initial_color) {
+  ColorPanelListener* listener = [ColorPanelListener instance];
+  [listener setColor:skia::SkColorToDeviceNSColor(initial_color)];
+  [listener showColorPanel];
+}
+
+void ColorPanelBridge::SetSelectedColor(uint32_t color) {
+  ColorPanelListener* listener = [ColorPanelListener instance];
+  [listener setColor:skia::SkColorToDeviceNSColor(color)];
+}
+
+}  // namespace remote_cocoa
diff --git a/components/remote_cocoa/browser/application_host.h b/components/remote_cocoa/browser/application_host.h
index 233aa35e4..c83371c 100644
--- a/components/remote_cocoa/browser/application_host.h
+++ b/components/remote_cocoa/browser/application_host.h
@@ -9,6 +9,7 @@
 #include "base/observer_list_types.h"
 #include "components/remote_cocoa/browser/remote_cocoa_browser_export.h"
 #include "components/remote_cocoa/common/application.mojom.h"
+#include "ui/gfx/native_widget_types.h"
 
 namespace remote_cocoa {
 
@@ -33,6 +34,8 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(const Observer* observer);
 
+  static ApplicationHost* GetForNativeView(gfx::NativeView view);
+
  private:
   mojom::ApplicationAssociatedPtr application_ptr_;
   base::ObserverList<Observer> observers_;
diff --git a/components/remote_cocoa/browser/application_host.mm b/components/remote_cocoa/browser/application_host.mm
index ae14569..1d77bac 100644
--- a/components/remote_cocoa/browser/application_host.mm
+++ b/components/remote_cocoa/browser/application_host.mm
@@ -4,6 +4,9 @@
 
 #include "components/remote_cocoa/browser/application_host.h"
 
+#import <Cocoa/Cocoa.h>
+
+#include "components/remote_cocoa/browser/window.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 
 namespace remote_cocoa {
@@ -29,4 +32,10 @@
   observers_.RemoveObserver(observer);
 }
 
+// static
+ApplicationHost* ApplicationHost::GetForNativeView(gfx::NativeView view) {
+  gfx::NativeWindow window([view.GetNativeNSView() window]);
+  return GetWindowApplicationHost(window);
+}
+
 }  // namespace remote_cocoa
diff --git a/components/remote_cocoa/common/BUILD.gn b/components/remote_cocoa/common/BUILD.gn
index a8da71b..ae88645 100644
--- a/components/remote_cocoa/common/BUILD.gn
+++ b/components/remote_cocoa/common/BUILD.gn
@@ -10,6 +10,7 @@
   sources = [
     "alert.mojom",
     "application.mojom",
+    "color_panel.mojom",
     "native_widget_ns_window.mojom",
     "native_widget_ns_window_host.mojom",
     "select_file_dialog.mojom",
diff --git a/components/remote_cocoa/common/application.mojom b/components/remote_cocoa/common/application.mojom
index 9f41b8f..786e5f9 100644
--- a/components/remote_cocoa/common/application.mojom
+++ b/components/remote_cocoa/common/application.mojom
@@ -5,6 +5,7 @@
 module remote_cocoa.mojom;
 
 import "components/remote_cocoa/common/alert.mojom";
+import "components/remote_cocoa/common/color_panel.mojom";
 import "components/remote_cocoa/common/native_widget_ns_window.mojom";
 import "components/remote_cocoa/common/native_widget_ns_window_host.mojom";
 import "components/remote_cocoa/common/text_input_host.mojom";
@@ -27,6 +28,9 @@
   // Create a bridge for an NSAlert. The resulting object owns its own lifetime.
   CreateAlert(AlertBridge& alert_bridge_request);
 
+  // Show the NSColorPanel in this application.
+  ShowColorPanel(ColorPanel& request, ColorPanelHost host);
+
   // Create a window for a native widget. The resulting object will be owned by
   // the connection for |host|. Closing that connection will result in deleting
   // the bridge.
diff --git a/components/remote_cocoa/common/color_panel.mojom b/components/remote_cocoa/common/color_panel.mojom
new file mode 100644
index 0000000..b1519b9
--- /dev/null
+++ b/components/remote_cocoa/common/color_panel.mojom
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module remote_cocoa.mojom;
+
+// Interface to the NSColorPanel.
+interface ColorPanel {
+  // Show the panel with initial color |initial_sk_color|.
+  Show(uint32 initial_sk_color);
+
+  // Programmatically set the selected color to |sk_color|.
+  SetSelectedColor(uint32 sk_color);
+};
+
+// NSColorPanel's interface to the browser.
+interface ColorPanelHost {
+  // Specify that the user color selection has changed.
+  DidChooseColorInColorPanel(uint32 sk_color);
+
+  // Sepecify that the user has closed the panel.
+  DidCloseColorPanel();
+};
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_http_popup.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_http_popup.Pixel_XL-25.png.sha1
index d27a97a..f959db7b 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_http_popup.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_http_popup.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-d227fb8426ac42742dc2edb1947be83db684617a
\ No newline at end of file
+05bdbeb07592d88f819fde004996d6dd1f9ae4c5
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_http_popup.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_http_popup.Pixel_XL-26.png.sha1
index 5f6b506..f20eda82 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_http_popup.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_http_popup.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-15c8f8f4a2cafd01d203390758d38e8b2460e093
\ No newline at end of file
+5beb3cf51a29d5999ef0f08a74769354c30d4e48
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_bad_cert_popup.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_bad_cert_popup.Pixel_XL-25.png.sha1
index 6f889f2..489567f 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_bad_cert_popup.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_bad_cert_popup.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-1cb9e7b549ee40a26d20bc8b9e28f5a7a9248440
\ No newline at end of file
+bab5f5c368b80d75db2f8150d17352e87d120f4e
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_bad_cert_popup.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_bad_cert_popup.Pixel_XL-26.png.sha1
index 1657c26..da749cc 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_bad_cert_popup.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_bad_cert_popup.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-b02278e520c41cdd3ccf52d66c13d8eb1a90ca56
\ No newline at end of file
+ed37b6fd37d9d95fcac3421a41092a9d4b22610c
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_popup.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_popup.Pixel_XL-25.png.sha1
index 9220787..a286705 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_popup.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_popup.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-cb341266df169a673ea23f202a2ef4051b08994d
\ No newline at end of file
+e41e7e2b2e4165de37c047b02cb5b82e7586ee38
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_popup.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_popup.Pixel_XL-26.png.sha1
index e070c14..4599113 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_popup.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.security_token_https_popup.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-f8946d5df09e9bb9c7344427f21965deeba9451a
\ No newline at end of file
+306a8db476ec0e3bf17eae89bd2a99876f9a8225
\ No newline at end of file
diff --git a/components/tracing/BUILD.gn b/components/tracing/BUILD.gn
index 470d686..f0c2452 100644
--- a/components/tracing/BUILD.gn
+++ b/components/tracing/BUILD.gn
@@ -8,6 +8,8 @@
   sources = [
     "child/background_tracing_agent_impl.cc",
     "child/background_tracing_agent_impl.h",
+    "child/background_tracing_agent_provider_impl.cc",
+    "child/background_tracing_agent_provider_impl.h",
     "common/graphics_memory_dump_provider_android.cc",
     "common/graphics_memory_dump_provider_android.h",
     "tracing_export.h",
diff --git a/components/tracing/child/background_tracing_agent_impl.cc b/components/tracing/child/background_tracing_agent_impl.cc
index 3bffae0..25e0d23 100644
--- a/components/tracing/child/background_tracing_agent_impl.cc
+++ b/components/tracing/child/background_tracing_agent_impl.cc
@@ -8,15 +8,10 @@
 
 #include "base/metrics/statistics_recorder.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "base/trace_event/memory_dump_manager.h"
 #include "base/trace_event/trace_event.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 
-using base::trace_event::MemoryDumpManager;
-using base::trace_event::TraceLog;
-
 namespace tracing {
-
 namespace {
 
 constexpr base::TimeDelta kMinTimeBetweenHistogramChanges =
@@ -24,31 +19,14 @@
 
 }  // namespace
 
-// static
-void BackgroundTracingAgentImpl::Create(
-    mojo::PendingReceiver<mojom::BackgroundTracingAgent> receiver) {
-  mojo::MakeSelfOwnedReceiver(std::make_unique<BackgroundTracingAgentImpl>(),
-                              std::move(receiver));
-}
-
-void BackgroundTracingAgentImpl::CreateFromRequest(
-    mojo::InterfaceRequest<mojom::BackgroundTracingAgent> request) {
-  Create(std::move(request));
-}
-
-BackgroundTracingAgentImpl::BackgroundTracingAgentImpl() = default;
-
-BackgroundTracingAgentImpl::~BackgroundTracingAgentImpl() = default;
-
-void BackgroundTracingAgentImpl::Initialize(
-    uint64_t tracing_process_id,
-    mojo::PendingRemote<mojom::BackgroundTracingAgentClient> client) {
-  MemoryDumpManager::GetInstance()->set_tracing_process_id(tracing_process_id);
-
-  client_.Bind(std::move(client));
+BackgroundTracingAgentImpl::BackgroundTracingAgentImpl(
+    mojo::PendingRemote<mojom::BackgroundTracingAgentClient> client)
+    : client_(std::move(client)) {
   client_->OnInitialized();
 }
 
+BackgroundTracingAgentImpl::~BackgroundTracingAgentImpl() = default;
+
 void BackgroundTracingAgentImpl::SetUMACallback(
     const std::string& histogram_name,
     int32_t histogram_lower_value,
diff --git a/components/tracing/child/background_tracing_agent_impl.h b/components/tracing/child/background_tracing_agent_impl.h
index 500e378..dca6f4d 100644
--- a/components/tracing/child/background_tracing_agent_impl.h
+++ b/components/tracing/child/background_tracing_agent_impl.h
@@ -25,20 +25,11 @@
 class TRACING_EXPORT BackgroundTracingAgentImpl
     : public mojom::BackgroundTracingAgent {
  public:
-  static void Create(
-      mojo::PendingReceiver<mojom::BackgroundTracingAgent> receiver);
-
-  // For backwards compat.
-  static void CreateFromRequest(
-      mojo::InterfaceRequest<mojom::BackgroundTracingAgent> request);
-
-  BackgroundTracingAgentImpl();
+  explicit BackgroundTracingAgentImpl(
+      mojo::PendingRemote<mojom::BackgroundTracingAgentClient> client);
   ~BackgroundTracingAgentImpl() override;
 
   // mojom::BackgroundTracingAgent methods:
-  void Initialize(uint64_t tracing_process_id,
-                  mojo::PendingRemote<mojom::BackgroundTracingAgentClient>
-                      pending_client) override;
   void SetUMACallback(const std::string& histogram_name,
                       int32_t histogram_lower_value,
                       int32_t histogram_upper_value,
diff --git a/components/tracing/child/background_tracing_agent_impl_unittest.cc b/components/tracing/child/background_tracing_agent_impl_unittest.cc
index 743e789..a32cb57 100644
--- a/components/tracing/child/background_tracing_agent_impl_unittest.cc
+++ b/components/tracing/child/background_tracing_agent_impl_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/test/scoped_task_environment.h"
+#include "components/tracing/child/background_tracing_agent_provider_impl.h"
 #include "mojo/public/cpp/bindings/unique_receiver_set.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -47,8 +48,9 @@
 class BackgroundTracingAgentImplTest : public testing::Test {
  public:
   BackgroundTracingAgentImplTest() {
-    agent_set_.Add(std::make_unique<tracing::BackgroundTracingAgentImpl>(),
-                   agent_.BindNewPipeAndPassReceiver());
+    provider_set_.Add(
+        std::make_unique<tracing::BackgroundTracingAgentProviderImpl>(),
+        provider_.BindNewPipeAndPassReceiver());
 
     auto recorder = std::make_unique<BackgroundTracingAgentClientRecorder>();
     recorder_ = recorder.get();
@@ -57,7 +59,8 @@
     client_set_.Add(std::move(recorder),
                     client.InitWithNewPipeAndPassReceiver());
 
-    agent_->Initialize(0, std::move(client));
+    provider_->Create(0, std::move(client),
+                      agent_.BindNewPipeAndPassReceiver());
   }
 
   tracing::mojom::BackgroundTracingAgent* agent() { return agent_.get(); }
@@ -66,8 +69,10 @@
 
  private:
   base::test::ScopedTaskEnvironment task_environment_;
+  mojo::Remote<tracing::mojom::BackgroundTracingAgentProvider> provider_;
   mojo::Remote<tracing::mojom::BackgroundTracingAgent> agent_;
-  mojo::UniqueReceiverSet<tracing::mojom::BackgroundTracingAgent> agent_set_;
+  mojo::UniqueReceiverSet<tracing::mojom::BackgroundTracingAgentProvider>
+      provider_set_;
   mojo::UniqueReceiverSet<tracing::mojom::BackgroundTracingAgentClient>
       client_set_;
   BackgroundTracingAgentClientRecorder* recorder_ = nullptr;
diff --git a/components/tracing/child/background_tracing_agent_provider_impl.cc b/components/tracing/child/background_tracing_agent_provider_impl.cc
new file mode 100644
index 0000000..5f1f411
--- /dev/null
+++ b/components/tracing/child/background_tracing_agent_provider_impl.cc
@@ -0,0 +1,41 @@
+// 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/tracing/child/background_tracing_agent_provider_impl.h"
+
+#include <memory>
+
+#include "base/metrics/statistics_recorder.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/trace_event.h"
+#include "components/tracing/child/background_tracing_agent_impl.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+
+namespace tracing {
+
+BackgroundTracingAgentProviderImpl::BackgroundTracingAgentProviderImpl() =
+    default;
+
+BackgroundTracingAgentProviderImpl::~BackgroundTracingAgentProviderImpl() =
+    default;
+
+void BackgroundTracingAgentProviderImpl::AddBinding(
+    mojo::PendingReceiver<mojom::BackgroundTracingAgentProvider> provider) {
+  self_receiver_set_.Add(this, std::move(provider));
+}
+
+void BackgroundTracingAgentProviderImpl::Create(
+    uint64_t tracing_process_id,
+    mojo::PendingRemote<mojom::BackgroundTracingAgentClient> client,
+    mojo::PendingReceiver<mojom::BackgroundTracingAgent> agent) {
+  base::trace_event::MemoryDumpManager::GetInstance()->set_tracing_process_id(
+      tracing_process_id);
+
+  agent_receiver_set_.Add(
+      std::make_unique<BackgroundTracingAgentImpl>(std::move(client)),
+      std::move(agent));
+}
+
+}  // namespace tracing
diff --git a/components/tracing/child/background_tracing_agent_provider_impl.h b/components/tracing/child/background_tracing_agent_provider_impl.h
new file mode 100644
index 0000000..4ac0e44
--- /dev/null
+++ b/components/tracing/child/background_tracing_agent_provider_impl.h
@@ -0,0 +1,40 @@
+// 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_TRACING_CHILD_BACKGROUND_TRACING_AGENT_PROVIDER_IMPL_H_
+#define COMPONENTS_TRACING_CHILD_BACKGROUND_TRACING_AGENT_PROVIDER_IMPL_H_
+
+#include "base/macros.h"
+#include "components/tracing/common/background_tracing_agent.mojom.h"
+#include "components/tracing/tracing_export.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/unique_receiver_set.h"
+
+namespace tracing {
+
+class TRACING_EXPORT BackgroundTracingAgentProviderImpl
+    : public mojom::BackgroundTracingAgentProvider {
+ public:
+  BackgroundTracingAgentProviderImpl();
+  ~BackgroundTracingAgentProviderImpl() override;
+
+  void AddBinding(
+      mojo::PendingReceiver<mojom::BackgroundTracingAgentProvider> provider);
+
+  // mojom::BackgroundTracingAgentProvider methods:
+  void Create(
+      uint64_t tracing_process_id,
+      mojo::PendingRemote<mojom::BackgroundTracingAgentClient> client,
+      mojo::PendingReceiver<mojom::BackgroundTracingAgent> agent) override;
+
+ private:
+  mojo::ReceiverSet<mojom::BackgroundTracingAgentProvider> self_receiver_set_;
+  mojo::UniqueReceiverSet<mojom::BackgroundTracingAgent> agent_receiver_set_;
+
+  DISALLOW_COPY_AND_ASSIGN(BackgroundTracingAgentProviderImpl);
+};
+
+}  // namespace tracing
+
+#endif  // COMPONENTS_TRACING_CHILD_BACKGROUND_TRACING_AGENT_PROVIDER_IMPL_H_
diff --git a/components/tracing/common/background_tracing_agent.mojom b/components/tracing/common/background_tracing_agent.mojom
index 9f502c8..6260b65 100644
--- a/components/tracing/common/background_tracing_agent.mojom
+++ b/components/tracing/common/background_tracing_agent.mojom
@@ -15,10 +15,6 @@
 // specific metrics being hit and control when to start/stop tracing in
 // response. How metrics are communicated b/w processes is not covered here.
 interface BackgroundTracingAgent {
-  // Call this method first. Results in an OnInitialized callback.
-  Initialize(uint64 tracing_process_id,
-             pending_remote<BackgroundTracingAgentClient> client);
-
   // Call this method to begin reporting metrics corresponding to the named
   // histogram. Lower and upper bound values constrain what data is reported.
   // This results in OnTriggerBackgroundTrace callbacks (multiple if |repeat|
@@ -33,3 +29,11 @@
   // histogram.
   ClearUMACallback(string histogram_name);
 };
+
+// This interface is used to construct a BackgroundTracingAgent.
+interface BackgroundTracingAgentProvider {
+  // Results in an OnInitialized callback to |client|.
+  Create(uint64 tracing_process_id,
+         pending_remote<BackgroundTracingAgentClient> client,
+         pending_receiver<BackgroundTracingAgent> agent);
+};
diff --git a/components/viz/common/gpu/vulkan_in_process_context_provider.cc b/components/viz/common/gpu/vulkan_in_process_context_provider.cc
index 3e59e9d..1e685b2 100644
--- a/components/viz/common/gpu/vulkan_in_process_context_provider.cc
+++ b/components/viz/common/gpu/vulkan_in_process_context_provider.cc
@@ -87,8 +87,8 @@
                      instance_extensions.size(), instance_extensions.data(),
                      device_extensions.size(), device_extensions.data());
   backend_context.fVkExtensions = &gr_extensions;
-
-  backend_context.fDeviceFeatures = &device_queue_->enabled_device_features();
+  backend_context.fDeviceFeatures2 =
+      &device_queue_->enabled_device_features_2();
   backend_context.fGetProc = get_proc;
 
   gr_context_ = GrContext::MakeVulkan(backend_context);
diff --git a/content/browser/browser_child_process_host_impl.cc b/content/browser/browser_child_process_host_impl.cc
index 481c063..f62d97f 100644
--- a/content/browser/browser_child_process_host_impl.cc
+++ b/content/browser/browser_child_process_host_impl.cc
@@ -336,7 +336,10 @@
     // Tracing adds too much overhead to the profiling service.
     if (service_manager::SandboxTypeFromCommandLine(*cmd_line) !=
         service_manager::SANDBOX_TYPE_PROFILING) {
-      BackgroundTracingManagerImpl::ActivateForProcess(this);
+      BackgroundTracingManagerImpl::ActivateForProcess(
+          data_.id,
+          static_cast<ChildProcessHostImpl*>(child_process_host_.get())
+              ->child_control());
     }
   }
 
diff --git a/content/browser/compositor/DEPS b/content/browser/compositor/DEPS
index 9140430..e032424 100644
--- a/content/browser/compositor/DEPS
+++ b/content/browser/compositor/DEPS
@@ -2,7 +2,6 @@
  "+components/viz/common",
  "+components/viz/host",
  "+components/viz/service",
- "+services/ws/public/cpp",
  "+ui/platform_window",
 ]
 
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 50522f0..aa0d62c 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -1336,7 +1336,7 @@
             cookie->GetDomain(""), cookie->GetPath(""),
             cookie->GetSecure(false), cookie->GetHttpOnly(false),
             cookie->GetSameSite(""), cookie->GetExpires(-1));
-    if (!cookie) {
+    if (!net_cookie) {
       callback->sendFailure(Response::InvalidParams("Invalid cookie fields"));
       return;
     }
diff --git a/content/browser/renderer_host/DEPS b/content/browser/renderer_host/DEPS
index 83f75617..76bb5ac 100644
--- a/content/browser/renderer_host/DEPS
+++ b/content/browser/renderer_host/DEPS
@@ -3,7 +3,6 @@
   "+components/viz/common",
   "+components/viz/host",
   "+components/viz/service",
-  "+services/ws/public",
   "+third_party/blink/public/platform/web_gesture_curve.h",
   "+third_party/zlib",
   "+ui/events/gestures/blink/web_gesture_curve_impl.h",
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index b0fffeb..574ba1a 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -1743,7 +1743,8 @@
 
     // In single process mode, browser-side tracing and memory will cover the
     // whole process including renderers.
-    BackgroundTracingManagerImpl::ActivateForProcess(this);
+    BackgroundTracingManagerImpl::ActivateForProcess(
+        GetID(), child_control_interface_.get());
 
     fast_shutdown_started_ = false;
   }
diff --git a/content/browser/tracing/background_tracing_agent_client_impl.cc b/content/browser/tracing/background_tracing_agent_client_impl.cc
index 422043a..3822ff3 100644
--- a/content/browser/tracing/background_tracing_agent_client_impl.cc
+++ b/content/browser/tracing/background_tracing_agent_client_impl.cc
@@ -16,23 +16,24 @@
 // static
 void BackgroundTracingAgentClientImpl::Create(
     int child_process_id,
-    mojo::PendingRemote<tracing::mojom::BackgroundTracingAgent> pending_agent) {
-  uint64_t tracing_process_id =
-      ChildProcessHostImpl::ChildProcessUniqueIdToTracingProcessId(
-          child_process_id);
-
+    mojo::PendingRemote<tracing::mojom::BackgroundTracingAgentProvider>
+        pending_provider) {
   mojo::PendingRemote<tracing::mojom::BackgroundTracingAgentClient> client;
-  auto receiver = client.InitWithNewPipeAndPassReceiver();
+  auto client_receiver = client.InitWithNewPipeAndPassReceiver();
 
-  mojo::Remote<tracing::mojom::BackgroundTracingAgent> agent(
-      std::move(pending_agent));
-  agent->Initialize(tracing_process_id, std::move(client));
+  mojo::Remote<tracing::mojom::BackgroundTracingAgent> agent;
+
+  mojo::Remote<tracing::mojom::BackgroundTracingAgentProvider> provider(
+      std::move(pending_provider));
+  provider->Create(ChildProcessHostImpl::ChildProcessUniqueIdToTracingProcessId(
+                       child_process_id),
+                   std::move(client), agent.BindNewPipeAndPassReceiver());
 
   // Lifetime bound to the agent, which means it is bound to the lifetime of
   // the child process. Will be cleaned up when the process exits.
   mojo::MakeSelfOwnedReceiver(
       base::WrapUnique(new BackgroundTracingAgentClientImpl(std::move(agent))),
-      std::move(receiver));
+      std::move(client_receiver));
 }
 
 BackgroundTracingAgentClientImpl::~BackgroundTracingAgentClientImpl() {
diff --git a/content/browser/tracing/background_tracing_agent_client_impl.h b/content/browser/tracing/background_tracing_agent_client_impl.h
index d7ce9ec..97de4ba 100644
--- a/content/browser/tracing/background_tracing_agent_client_impl.h
+++ b/content/browser/tracing/background_tracing_agent_client_impl.h
@@ -17,9 +17,10 @@
 class BackgroundTracingAgentClientImpl
     : public tracing::mojom::BackgroundTracingAgentClient {
  public:
-  static void Create(int child_process_id,
-                     mojo::PendingRemote<tracing::mojom::BackgroundTracingAgent>
-                         pending_agent);
+  static void Create(
+      int child_process_id,
+      mojo::PendingRemote<tracing::mojom::BackgroundTracingAgentProvider>
+          pending_provider);
 
   ~BackgroundTracingAgentClientImpl() override;
 
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index 1dc755c..0a964ce 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -25,6 +25,7 @@
 #include "content/browser/tracing/background_tracing_agent_client_impl.h"
 #include "content/browser/tracing/background_tracing_rule.h"
 #include "content/browser/tracing/tracing_controller_impl.h"
+#include "content/common/child_control.mojom.h"
 #include "content/public/browser/browser_child_process_host.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -63,31 +64,22 @@
 
 // static
 void BackgroundTracingManagerImpl::ActivateForProcess(
-    BrowserChildProcessHost* host) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    int child_process_id,
+    mojom::ChildControl* child_control) {
+  mojo::PendingRemote<tracing::mojom::BackgroundTracingAgentProvider>
+      pending_provider;
+  child_control->GetBackgroundTracingAgentProvider(
+      pending_provider.InitWithNewPipeAndPassReceiver());
 
-  tracing::mojom::BackgroundTracingAgentPtr agent;
-  content::BindInterface(host->GetHost(), &agent);
-
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::UI},
-      base::BindOnce(
-          [](int child_process_id,
-             tracing::mojom::BackgroundTracingAgentPtrInfo info) {
-            BackgroundTracingAgentClientImpl::Create(child_process_id,
-                                                     std::move(info));
-          },
-          host->GetData().id, agent.PassInterface()));
-}
-
-// static
-void BackgroundTracingManagerImpl::ActivateForProcess(RenderProcessHost* host) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  tracing::mojom::BackgroundTracingAgentPtr agent;
-  content::BindInterface(host, &agent);
-  BackgroundTracingAgentClientImpl::Create(host->GetID(),
-                                           agent.PassInterface());
+  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI},
+        base::BindOnce(&BackgroundTracingAgentClientImpl::Create,
+                       child_process_id, std::move(pending_provider)));
+    return;
+  }
+  BackgroundTracingAgentClientImpl::Create(child_process_id,
+                                           std::move(pending_provider));
 }
 
 BackgroundTracingManagerImpl::BackgroundTracingManagerImpl()
diff --git a/content/browser/tracing/background_tracing_manager_impl.h b/content/browser/tracing/background_tracing_manager_impl.h
index f620a91..982aa09 100644
--- a/content/browser/tracing/background_tracing_manager_impl.h
+++ b/content/browser/tracing/background_tracing_manager_impl.h
@@ -27,11 +27,12 @@
 }  // namespace tracing
 
 namespace content {
+namespace mojom {
+class ChildControl;
+}  // namespace mojom
 
 class BackgroundTracingRule;
 class BackgroundTracingActiveScenario;
-class BrowserChildProcessHost;
-class RenderProcessHost;
 class TracingDelegate;
 
 class BackgroundTracingManagerImpl : public BackgroundTracingManager {
@@ -85,8 +86,8 @@
 
   CONTENT_EXPORT static BackgroundTracingManagerImpl* GetInstance();
 
-  static void ActivateForProcess(BrowserChildProcessHost* host);
-  static void ActivateForProcess(RenderProcessHost* host);
+  static void ActivateForProcess(int child_process_id,
+                                 mojom::ChildControl* child_control);
 
   bool SetActiveScenario(std::unique_ptr<BackgroundTracingConfig>,
                          ReceiveCallback,
diff --git a/content/browser/worker_host/shared_worker_service_impl_unittest.cc b/content/browser/worker_host/shared_worker_service_impl_unittest.cc
index 84513ca..39b5705 100644
--- a/content/browser/worker_host/shared_worker_service_impl_unittest.cc
+++ b/content/browser/worker_host/shared_worker_service_impl_unittest.cc
@@ -5,11 +5,12 @@
 #include "content/browser/worker_host/shared_worker_service_impl.h"
 
 #include <memory>
-#include <queue>
 #include <set>
 #include <string>
 
 #include "base/bind.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/queue.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "content/browser/site_instance_impl.h"
@@ -38,6 +39,43 @@
 
 namespace content {
 
+namespace {
+
+void ConnectToSharedWorker(blink::mojom::SharedWorkerConnectorPtr connector,
+                           const GURL& url,
+                           const std::string& name,
+                           MockSharedWorkerClient* client,
+                           MessagePortChannel* local_port) {
+  blink::mojom::SharedWorkerInfoPtr info(blink::mojom::SharedWorkerInfo::New(
+      url, name, std::string(),
+      blink::mojom::ContentSecurityPolicyType::kReport,
+      blink::mojom::IPAddressSpace::kPublic));
+
+  mojo::MessagePipe message_pipe;
+  *local_port = MessagePortChannel(std::move(message_pipe.handle0));
+
+  blink::mojom::SharedWorkerClientPtr client_proxy;
+  client->Bind(mojo::MakeRequest(&client_proxy));
+
+  connector->Connect(std::move(info), std::move(client_proxy),
+                     blink::mojom::SharedWorkerCreationContextType::kSecure,
+                     std::move(message_pipe.handle1), nullptr);
+}
+
+// Helper to delete the given WebContents and shut down its process. This is
+// useful because if FastShutdownIfPossible() is called without deleting the
+// WebContents first, shutdown does not actually start.
+void KillProcess(std::unique_ptr<WebContents> web_contents) {
+  RenderFrameHost* frame_host = web_contents->GetMainFrame();
+  RenderProcessHost* process_host = frame_host->GetProcess();
+  web_contents.reset();
+  process_host->FastShutdownIfPossible(/*page_count=*/0,
+                                       /*skip_unload_handlers=*/true);
+  ASSERT_TRUE(process_host->FastShutdownStarted());
+}
+
+}  // namespace
+
 class SharedWorkerServiceImplTest : public RenderViewHostImplTestHarness {
  public:
   blink::mojom::SharedWorkerConnectorPtr MakeSharedWorkerConnector(
@@ -49,26 +87,51 @@
     return connector;
   }
 
-  static blink::mojom::SharedWorkerFactoryRequest WaitForFactoryRequest() {
-    if (s_factory_request_received_.empty()) {
+  // Waits until a SharedWorkerFactoryRequest from the given process is
+  // received. kInvalidUniqueID means any process.
+  blink::mojom::SharedWorkerFactoryRequest WaitForFactoryRequest(
+      int process_id) {
+    if (CheckNotReceivedFactoryRequest(process_id)) {
       base::RunLoop run_loop;
-      s_factory_request_callback_ = run_loop.QuitClosure();
+      factory_request_callback_ = run_loop.QuitClosure();
+      factory_request_callback_process_id_ = process_id;
       run_loop.Run();
     }
-    auto rv = std::move(s_factory_request_received_.front());
-    s_factory_request_received_.pop();
+    auto iter = (process_id == ChildProcessHost::kInvalidUniqueID)
+                    ? received_factory_requests_.begin()
+                    : received_factory_requests_.find(process_id);
+    DCHECK(iter != received_factory_requests_.end());
+    auto& queue = iter->second;
+    DCHECK(!queue.empty());
+    auto rv = std::move(queue.front());
+    queue.pop();
+    if (queue.empty())
+      received_factory_requests_.erase(iter);
     return rv;
   }
 
-  static bool CheckNotReceivedFactoryRequest() {
-    return s_factory_request_received_.empty();
+  bool CheckNotReceivedFactoryRequest(int process_id) {
+    if (process_id == ChildProcessHost::kInvalidUniqueID)
+      return received_factory_requests_.empty();
+    return !base::Contains(received_factory_requests_, process_id);
   }
 
-  static void BindSharedWorkerFactory(mojo::ScopedMessagePipeHandle handle) {
-    if (s_factory_request_callback_)
-      std::move(s_factory_request_callback_).Run();
-    s_factory_request_received_.push(
-        blink::mojom::SharedWorkerFactoryRequest(std::move(handle)));
+  // Receives a SharedWorkerFactoryRequest.
+  void BindSharedWorkerFactory(int process_id,
+                               mojo::ScopedMessagePipeHandle handle) {
+    if (factory_request_callback_ &&
+        (factory_request_callback_process_id_ == process_id ||
+         factory_request_callback_process_id_ ==
+             ChildProcessHost::kInvalidUniqueID)) {
+      factory_request_callback_process_id_ = ChildProcessHost::kInvalidUniqueID;
+      std::move(factory_request_callback_).Run();
+    }
+
+    if (!base::Contains(received_factory_requests_, process_id)) {
+      received_factory_requests_.emplace(
+          process_id, base::queue<blink::mojom::SharedWorkerFactoryRequest>());
+    }
+    received_factory_requests_[process_id].emplace(std::move(handle));
   }
 
   std::unique_ptr<TestWebContents> CreateWebContents(const GURL& url) {
@@ -113,9 +176,17 @@
   }
 
   std::unique_ptr<TestBrowserContext> browser_context_;
-  static std::queue<blink::mojom::SharedWorkerFactoryRequest>
-      s_factory_request_received_;
-  static base::OnceClosure s_factory_request_callback_;
+
+  // Holds received SharedWorkerFactoryRequests for each process.
+  base::flat_map<int /* process_id */,
+                 base::queue<blink::mojom::SharedWorkerFactoryRequest>>
+      received_factory_requests_;
+
+  // The callback is called when a SharedWorkerFactoryRequest for the specified
+  // process is received. kInvalidUniqueID means any process.
+  base::OnceClosure factory_request_callback_;
+  int factory_request_callback_process_id_ = ChildProcessHost::kInvalidUniqueID;
+
   std::unique_ptr<MockRenderProcessHostFactory> render_process_host_factory_;
   std::unique_ptr<NotImplementedNetworkURLLoaderFactory> url_loader_factory_;
 
@@ -127,46 +198,16 @@
   DISALLOW_COPY_AND_ASSIGN(SharedWorkerServiceImplTest);
 };
 
-// static
-std::queue<blink::mojom::SharedWorkerFactoryRequest>
-    SharedWorkerServiceImplTest::s_factory_request_received_;
-
-// static
-base::OnceClosure SharedWorkerServiceImplTest::s_factory_request_callback_;
-
-namespace {
-
-void ConnectToSharedWorker(blink::mojom::SharedWorkerConnectorPtr connector,
-                           const GURL& url,
-                           const std::string& name,
-                           MockSharedWorkerClient* client,
-                           MessagePortChannel* local_port) {
-  blink::mojom::SharedWorkerInfoPtr info(blink::mojom::SharedWorkerInfo::New(
-      url, name, std::string(),
-      blink::mojom::ContentSecurityPolicyType::kReport,
-      blink::mojom::IPAddressSpace::kPublic));
-
-  mojo::MessagePipe message_pipe;
-  *local_port = MessagePortChannel(std::move(message_pipe.handle0));
-
-  blink::mojom::SharedWorkerClientPtr client_proxy;
-  client->Bind(mojo::MakeRequest(&client_proxy));
-
-  connector->Connect(std::move(info), std::move(client_proxy),
-                     blink::mojom::SharedWorkerCreationContextType::kSecure,
-                     std::move(message_pipe.handle1), nullptr);
-}
-
-}  // namespace
-
 TEST_F(SharedWorkerServiceImplTest, BasicTest) {
   std::unique_ptr<TestWebContents> web_contents =
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host = web_contents->GetMainFrame();
   MockRenderProcessHost* renderer_host = render_frame_host->GetProcess();
+  const int process_id = renderer_host->GetID();
   renderer_host->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -178,7 +219,7 @@
                         kUrl, "name", &client, &local_port);
 
   blink::mojom::SharedWorkerFactoryRequest factory_request =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id);
   MockSharedWorkerFactory factory(std::move(factory_request));
   base::RunLoop().RunUntilIdle();
 
@@ -239,9 +280,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -255,7 +298,7 @@
   base::RunLoop().RunUntilIdle();
 
   blink::mojom::SharedWorkerFactoryRequest factory_request =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id0);
   MockSharedWorkerFactory factory(std::move(factory_request));
   base::RunLoop().RunUntilIdle();
 
@@ -309,9 +352,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -324,7 +369,7 @@
   base::RunLoop().RunUntilIdle();
 
   // Should not have tried to create a new shared worker.
-  EXPECT_TRUE(CheckNotReceivedFactoryRequest());
+  EXPECT_TRUE(CheckNotReceivedFactoryRequest(process_id1));
 
   int connection_request_id1;
   MessagePortChannel port1;
@@ -372,9 +417,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -383,9 +430,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
 
   // First client, creates worker.
 
@@ -397,7 +446,7 @@
   base::RunLoop().RunUntilIdle();
 
   blink::mojom::SharedWorkerFactoryRequest factory_request =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id0);
   MockSharedWorkerFactory factory(std::move(factory_request));
   base::RunLoop().RunUntilIdle();
 
@@ -421,7 +470,7 @@
                         kUrl, kName, &client1, &local_port1);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(CheckNotReceivedFactoryRequest());
+  EXPECT_TRUE(CheckNotReceivedFactoryRequest(process_id1));
 
   EXPECT_TRUE(worker.CheckReceivedConnect(nullptr, nullptr));
   EXPECT_TRUE(client1.CheckReceivedOnCreated());
@@ -445,9 +494,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -456,9 +507,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -472,7 +525,7 @@
   base::RunLoop().RunUntilIdle();
 
   blink::mojom::SharedWorkerFactoryRequest factory_request0 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id0);
   MockSharedWorkerFactory factory0(std::move(factory_request0));
   base::RunLoop().RunUntilIdle();
 
@@ -497,7 +550,7 @@
   base::RunLoop().RunUntilIdle();
 
   blink::mojom::SharedWorkerFactoryRequest factory_request1 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id1);
   MockSharedWorkerFactory factory1(std::move(factory_request1));
   base::RunLoop().RunUntilIdle();
 
@@ -532,9 +585,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -543,9 +598,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -559,7 +616,7 @@
   base::RunLoop().RunUntilIdle();
 
   blink::mojom::SharedWorkerFactoryRequest factory_request0 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id0);
   MockSharedWorkerFactory factory0(std::move(factory_request0));
   base::RunLoop().RunUntilIdle();
 
@@ -584,7 +641,7 @@
   base::RunLoop().RunUntilIdle();
 
   blink::mojom::SharedWorkerFactoryRequest factory_request1 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id1);
   MockSharedWorkerFactory factory1(std::move(factory_request1));
   base::RunLoop().RunUntilIdle();
 
@@ -618,9 +675,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -629,9 +688,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -651,10 +712,11 @@
 
   base::RunLoop().RunUntilIdle();
 
-  // Check that the worker was created.
+  // Check that the worker was created. The request could come from either
+  // process.
 
   blink::mojom::SharedWorkerFactoryRequest factory_request =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(ChildProcessHost::kInvalidUniqueID);
   MockSharedWorkerFactory factory(std::move(factory_request));
 
   base::RunLoop().RunUntilIdle();
@@ -685,9 +747,7 @@
   EXPECT_TRUE(worker.CheckReceivedTerminate());
 }
 
-// TODO(https://crbug.com/968971): Flaky fails on all bots.
-TEST_F(SharedWorkerServiceImplTest,
-       DISABLED_CreateWorkerTest_PendingCase_URLMismatch) {
+TEST_F(SharedWorkerServiceImplTest, CreateWorkerTest_PendingCase_URLMismatch) {
   const GURL kUrl0("http://example.com/w0.js");
   const GURL kUrl1("http://example.com/w1.js");
   const char kName[] = "name";
@@ -699,7 +759,8 @@
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), renderer_host0->GetID()));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -710,7 +771,8 @@
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), renderer_host1->GetID()));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -733,11 +795,11 @@
   // Check that both workers were created.
 
   blink::mojom::SharedWorkerFactoryRequest factory_request0 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(renderer_host0->GetID());
   MockSharedWorkerFactory factory0(std::move(factory_request0));
 
   blink::mojom::SharedWorkerFactoryRequest factory_request1 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(renderer_host1->GetID());
   MockSharedWorkerFactory factory1(std::move(factory_request1));
 
   base::RunLoop().RunUntilIdle();
@@ -778,9 +840,7 @@
   EXPECT_TRUE(worker1.CheckReceivedTerminate());
 }
 
-// TODO(https://crbug.com/968971): Flaky fails on all bots.
-TEST_F(SharedWorkerServiceImplTest,
-       DISABLED_CreateWorkerTest_PendingCase_NameMismatch) {
+TEST_F(SharedWorkerServiceImplTest, CreateWorkerTest_PendingCase_NameMismatch) {
   const GURL kUrl("http://example.com/w.js");
   const char kName0[] = "name0";
   const char kName1[] = "name1";
@@ -790,9 +850,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -801,9 +863,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -826,11 +890,11 @@
   // Check that both workers were created.
 
   blink::mojom::SharedWorkerFactoryRequest factory_request0 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id0);
   MockSharedWorkerFactory factory0(std::move(factory_request0));
 
   blink::mojom::SharedWorkerFactoryRequest factory_request1 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id1);
   MockSharedWorkerFactory factory1(std::move(factory_request1));
 
   base::RunLoop().RunUntilIdle();
@@ -881,9 +945,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -891,9 +957,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -901,9 +969,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host2 = web_contents2->GetMainFrame();
   MockRenderProcessHost* renderer_host2 = render_frame_host2->GetProcess();
+  const int process_id2 = renderer_host2->GetID();
   renderer_host2->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id2));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host2->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -918,7 +988,7 @@
   // Starts a worker.
 
   blink::mojom::SharedWorkerFactoryRequest factory_request0 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id0);
   MockSharedWorkerFactory factory0(std::move(factory_request0));
 
   base::RunLoop().RunUntilIdle();
@@ -936,9 +1006,7 @@
   EXPECT_TRUE(client0.CheckReceivedOnCreated());
 
   // Kill this process, which should make worker0 unavailable.
-  web_contents0.reset();
-  renderer_host0->FastShutdownIfPossible(0, true);
-  ASSERT_TRUE(renderer_host0->FastShutdownStarted());
+  KillProcess(std::move(web_contents0));
 
   // Start a new client, attemping to connect to the same worker.
   MockSharedWorkerClient client1;
@@ -952,7 +1020,7 @@
   // The previous worker is unavailable, so a new worker is created.
 
   blink::mojom::SharedWorkerFactoryRequest factory_request1 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id1);
   MockSharedWorkerFactory factory1(std::move(factory_request1));
 
   base::RunLoop().RunUntilIdle();
@@ -979,7 +1047,7 @@
 
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(CheckNotReceivedFactoryRequest());
+  EXPECT_TRUE(CheckNotReceivedFactoryRequest(process_id2));
 
   EXPECT_TRUE(worker1.CheckReceivedConnect(nullptr, nullptr));
   EXPECT_TRUE(client2.CheckReceivedOnCreated());
@@ -995,9 +1063,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -1005,9 +1075,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -1015,9 +1087,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host2 = web_contents2->GetMainFrame();
   MockRenderProcessHost* renderer_host2 = render_frame_host2->GetProcess();
+  const int process_id2 = renderer_host2->GetID();
   renderer_host2->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::Bind(&SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id2));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host2->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -1027,10 +1101,14 @@
                             renderer_host0, render_frame_host0->GetRoutingID()),
                         kUrl, kName, &client0, &local_port0);
 
-  // Kill this process, which should make worker0 unavailable.
-  renderer_host0->FastShutdownIfPossible(0, true);
+  blink::mojom::SharedWorkerFactoryRequest factory_request0 =
+      WaitForFactoryRequest(process_id0);
+  MockSharedWorkerFactory factory0(std::move(factory_request0));
 
-  // Start a new client, attemping to connect to the same worker.
+  // Kill this process, which should make worker0 unavailable.
+  KillProcess(std::move(web_contents0));
+
+  // Start a new client, attempting to connect to the same worker.
   MockSharedWorkerClient client1;
   MessagePortChannel local_port1;
   ConnectToSharedWorker(MakeSharedWorkerConnector(
@@ -1042,10 +1120,11 @@
   // The previous worker is unavailable, so a new worker is created.
 
   blink::mojom::SharedWorkerFactoryRequest factory_request1 =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(process_id1);
   MockSharedWorkerFactory factory1(std::move(factory_request1));
 
-  EXPECT_TRUE(CheckNotReceivedFactoryRequest());
+  EXPECT_TRUE(
+      CheckNotReceivedFactoryRequest(ChildProcessHost::kInvalidUniqueID));
 
   base::RunLoop().RunUntilIdle();
 
@@ -1070,7 +1149,8 @@
 
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(CheckNotReceivedFactoryRequest());
+  EXPECT_TRUE(
+      CheckNotReceivedFactoryRequest(ChildProcessHost::kInvalidUniqueID));
 
   EXPECT_TRUE(worker1.CheckReceivedConnect(nullptr, nullptr));
   EXPECT_TRUE(client2.CheckReceivedOnCreated());
@@ -1085,10 +1165,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host0 = web_contents0->GetMainFrame();
   MockRenderProcessHost* renderer_host0 = render_frame_host0->GetProcess();
+  const int process_id0 = renderer_host0->GetID();
   renderer_host0->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::BindRepeating(
-          &SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id0));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host0->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -1097,10 +1178,11 @@
       CreateWebContents(GURL("http://example.com/"));
   TestRenderFrameHost* render_frame_host1 = web_contents1->GetMainFrame();
   MockRenderProcessHost* renderer_host1 = render_frame_host1->GetProcess();
+  const int process_id1 = renderer_host1->GetID();
   renderer_host1->OverrideBinderForTesting(
       blink::mojom::SharedWorkerFactory::Name_,
-      base::BindRepeating(
-          &SharedWorkerServiceImplTest::BindSharedWorkerFactory));
+      base::BindRepeating(&SharedWorkerServiceImplTest::BindSharedWorkerFactory,
+                          base::Unretained(this), process_id1));
   if (!base::FeatureList::IsEnabled(network::features::kNetworkService))
     renderer_host1->OverrideURLLoaderFactory(url_loader_factory_.get());
 
@@ -1119,9 +1201,9 @@
                         kURL, kName, &client1, &local_port1);
   base::RunLoop().RunUntilIdle();
 
-  // Expect a factory request.
+  // Expect a factory request. It can come from either process.
   blink::mojom::SharedWorkerFactoryRequest factory_request =
-      WaitForFactoryRequest();
+      WaitForFactoryRequest(ChildProcessHost::kInvalidUniqueID);
   MockSharedWorkerFactory factory(std::move(factory_request));
   base::RunLoop().RunUntilIdle();
 
diff --git a/content/child/child_thread_impl.cc b/content/child/child_thread_impl.cc
index 2c058a2..cab79ad 100644
--- a/content/child/child_thread_impl.cc
+++ b/content/child/child_thread_impl.cc
@@ -38,6 +38,7 @@
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "components/tracing/child/background_tracing_agent_impl.h"
+#include "components/tracing/child/background_tracing_agent_provider_impl.h"
 #include "content/child/child_histogram_fetcher_impl.h"
 #include "content/child/child_process.h"
 #include "content/child/thread_safe_sender.h"
@@ -446,9 +447,6 @@
   registry->AddInterface(base::Bind(&ChildThreadImpl::OnChildControlRequest,
                                     base::Unretained(this)),
                          base::ThreadTaskRunnerHandle::Get());
-  registry->AddInterface(
-      base::Bind(&tracing::BackgroundTracingAgentImpl::CreateFromRequest),
-      base::ThreadTaskRunnerHandle::Get());
   GetServiceManagerConnection()->AddConnectionFilter(
       std::make_unique<SimpleConnectionFilter>(std::move(registry)));
 
@@ -704,6 +702,16 @@
 }
 #endif  //  IPC_MESSAGE_LOG_ENABLED
 
+void ChildThreadImpl::GetBackgroundTracingAgentProvider(
+    mojo::PendingReceiver<tracing::mojom::BackgroundTracingAgentProvider>
+        receiver) {
+  if (!background_tracing_agent_provider_) {
+    background_tracing_agent_provider_ =
+        std::make_unique<tracing::BackgroundTracingAgentProviderImpl>();
+  }
+  background_tracing_agent_provider_->AddBinding(std::move(receiver));
+}
+
 void ChildThreadImpl::RunService(
     const std::string& service_name,
     mojo::PendingReceiver<service_manager::mojom::Service> receiver) {
diff --git a/content/child/child_thread_impl.h b/content/child/child_thread_impl.h
index 3245d77..cf8c92e 100644
--- a/content/child/child_thread_impl.h
+++ b/content/child/child_thread_impl.h
@@ -49,6 +49,10 @@
 }  // namespace core
 }  // namespace mojo
 
+namespace tracing {
+class BackgroundTracingAgentProviderImpl;
+}  // namespace tracing
+
 namespace content {
 class InProcessChildThreadParams;
 class ThreadSafeSender;
@@ -145,6 +149,9 @@
 #if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
   void SetIPCLoggingEnabled(bool enable) override;
 #endif
+  void GetBackgroundTracingAgentProvider(
+      mojo::PendingReceiver<tracing::mojom::BackgroundTracingAgentProvider>
+          receiver) override;
   void RunService(
       const std::string& service_name,
       mojo::PendingReceiver<service_manager::mojom::Service> receiver) override;
@@ -236,6 +243,9 @@
 
   std::unique_ptr<base::PowerMonitor> power_monitor_;
 
+  std::unique_ptr<tracing::BackgroundTracingAgentProviderImpl>
+      background_tracing_agent_provider_;
+
   scoped_refptr<base::SingleThreadTaskRunner> browser_process_io_runner_;
 
   std::unique_ptr<variations::ChildProcessFieldTrialSyncer> field_trial_syncer_;
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index 3387bbf..25d40e6 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -503,6 +503,7 @@
 
   public_deps = [
     "//components/services/leveldb/public/interfaces",
+    "//components/tracing/common:interfaces",
     "//content/public/common:interfaces",
     "//content/public/common:resource_type_bindings",
     "//ipc:mojom_constants",
diff --git a/content/common/child_control.mojom b/content/common/child_control.mojom
index c840289..1bac2fd6 100644
--- a/content/common/child_control.mojom
+++ b/content/common/child_control.mojom
@@ -4,6 +4,7 @@
 
 module content.mojom;
 
+import "components/tracing/common/background_tracing_agent.mojom";
 import "services/service_manager/public/mojom/service.mojom";
 
 interface ChildControl {
@@ -14,6 +15,10 @@
   [EnableIf=ipc_logging]
   SetIPCLoggingEnabled(bool on);
 
+  // Used to configure triggering for background tracing of child processes.
+  GetBackgroundTracingAgentProvider(
+      pending_receiver<tracing.mojom.BackgroundTracingAgentProvider> receiver);
+
   // Tells the child process to run an instance of a service named
   // |service_name|, binding it to |receiver|. This is used by the browser to
   // support launching of packaged services within Utility or GPU processes.
diff --git a/content/common/child_process_host_impl.h b/content/common/child_process_host_impl.h
index 7ed3a4ce..e299a9e 100644
--- a/content/common/child_process_host_impl.h
+++ b/content/common/child_process_host_impl.h
@@ -75,6 +75,7 @@
       mojo::PendingReceiver<service_manager::mojom::Service> receiver) override;
 
   base::Process& peer_process() { return peer_process_; }
+  mojom::ChildControl* child_control() { return child_control_.get(); }
 
  private:
   friend class ChildProcessHost;
diff --git a/content/public/app/content_gpu_manifest.cc b/content/public/app/content_gpu_manifest.cc
index 586eaed..06b9be3 100644
--- a/content/public/app/content_gpu_manifest.cc
+++ b/content/public/app/content_gpu_manifest.cc
@@ -23,7 +23,6 @@
                                 "content.mojom.ChildHistogramFetcherFactory",
                                 "content.mojom.ResourceUsageReporter",
                                 "IPC.mojom.ChannelBootstrap",
-                                "tracing.mojom.BackgroundTracingAgent",
                                 "ui.ozone.mojom.DeviceCursor",
                                 "ui.ozone.mojom.DrmDevice",
                                 "ui.ozone.mojom.WaylandBufferManagerGpu",
diff --git a/content/public/app/content_plugin_manifest.cc b/content/public/app/content_plugin_manifest.cc
index c3dd3fb..6d17755 100644
--- a/content/public/app/content_plugin_manifest.cc
+++ b/content/public/app/content_plugin_manifest.cc
@@ -23,7 +23,6 @@
                                 "content.mojom.ChildHistogramFetcherFactory",
                                 "content.mojom.ResourceUsageReporter",
                                 "IPC.mojom.ChannelBootstrap",
-                                "tracing.mojom.BackgroundTracingAgent",
                             })
           .RequireCapability("device", "device:power_monitor")
           .RequireCapability(mojom::kSystemServiceName, "dwrite_font_proxy")
diff --git a/content/public/app/content_renderer_manifest.cc b/content/public/app/content_renderer_manifest.cc
index e6e5264..27ffa5c 100644
--- a/content/public/app/content_renderer_manifest.cc
+++ b/content/public/app/content_renderer_manifest.cc
@@ -34,7 +34,6 @@
                   "content.mojom.RenderWidgetWindowTreeClientFactory",
                   "content.mojom.ResourceUsageReporter",
                   "IPC.mojom.ChannelBootstrap",
-                  "tracing.mojom.BackgroundTracingAgent",
                   "visitedlink.mojom.VisitedLinkNotificationSink",
                   "web_cache.mojom.WebCache",
               })
diff --git a/content/public/app/content_utility_manifest.cc b/content/public/app/content_utility_manifest.cc
index 575a2a3..e3eafb7 100644
--- a/content/public/app/content_utility_manifest.cc
+++ b/content/public/app/content_utility_manifest.cc
@@ -24,7 +24,6 @@
                                 "content.mojom.ChildHistogramFetcherFactory",
                                 "content.mojom.ResourceUsageReporter",
                                 "IPC.mojom.ChannelBootstrap",
-                                "tracing.mojom.BackgroundTracingAgent",
                                 "printing.mojom.PdfToEmfConverterFactory",
                                 "printing.mojom.PdfToPwgRasterConverter",
                             })
diff --git a/content/public/browser/DEPS b/content/public/browser/DEPS
index c77149d..08ef71e 100644
--- a/content/public/browser/DEPS
+++ b/content/public/browser/DEPS
@@ -11,7 +11,6 @@
   "+services/service_manager/sandbox",
   "+services/video_capture/public/mojom",
   "+services/viz/public/interfaces",
-  "+services/ws/public/mojom",
 ]
 
 specific_include_rules = {
diff --git a/content/public/common/cursor_info.cc b/content/public/common/cursor_info.cc
index e4e1aff..f3e50e1 100644
--- a/content/public/common/cursor_info.cc
+++ b/content/public/common/cursor_info.cc
@@ -19,9 +19,7 @@
 bool CursorInfo::operator==(const CursorInfo& other) const {
   return type == other.type && hotspot == other.hotspot &&
          image_scale_factor == other.image_scale_factor &&
-         (custom_image.getGenerationID() ==
-              other.custom_image.getGenerationID() ||
-          gfx::BitmapsAreEqual(custom_image, other.custom_image));
+         gfx::BitmapsAreEqual(custom_image, other.custom_image);
 }
 
 blink::WebCursorInfo CursorInfo::GetWebCursorInfo() const {
diff --git a/content/shell/browser/DEPS b/content/shell/browser/DEPS
index 0e08b66..2d807b6 100644
--- a/content/shell/browser/DEPS
+++ b/content/shell/browser/DEPS
@@ -6,7 +6,6 @@
   "+services/network/public",
   "+services/service_manager/public/cpp",
   "+services/service_manager/sandbox",
-  "+services/ws/public",
   "+ui/ozone/public",
 ]
 
diff --git a/docs/security/android-ipc.md b/docs/security/android-ipc.md
new file mode 100644
index 0000000..b9131e6
--- /dev/null
+++ b/docs/security/android-ipc.md
@@ -0,0 +1,139 @@
+# Android IPC Security Considerations
+
+Generally Chrome communicates between its processes using the
+[Mojo](../../mojo/README.md) [inter-process communication (IPC)
+mechanism](mojo.md). For most features, this is the preferred IPC mechanism to
+use. However, as an Android application, there are certain interactions with
+other applications and the Android OS that necessitate using different IPC
+mechanisms to communicate. This document covers security concerns related to
+those Android-specific IPC mechanisms.
+
+The Chrome browser process is typically the only process type that will interact
+with these different IPC mechanisms.
+
+## Intents
+
+[Intents](https://developer.android.com/guide/components/intents-filters) are
+the most common type of inter-process communication mechanism on Android. They
+are most commonly used to start Activities and they internally carry data
+associated with that Activity (e.g. using the `ACTION_SEND` Intent to share a
+piece of content and including either text or image data in the Intent body).
+
+### Inbound Intents
+
+Because any application can dispatch Intents with Chrome as the receiver, when
+receiving an inbound Intent, you should never fully trust the data contained
+within. Data sent from other applications could be malicious or malformed, and
+so you must validate or sanitze the data before passing it to other trusted
+components of the browser process. Intents are handled in Java though, so
+following the [Rule of 2](rule-of-2.md) is generally easy. (Though take note
+that certain Android classes are just Java wrappers around native code, which
+would not be considered safe by that rule.)
+
+It is **fundamentally impossible** to determine the sender of an Intent, so the
+security model of your feature cannot depend on authenticating the sender. Do
+not trust `Intent.EXTRA_REFERRER`. See also the discussion below about
+[capability tokens](#capability-tokens).
+
+One way to authorize Intents is to use the system's
+[`android:permission`](https://developer.android.com/guide/topics/permissions/overview#permission_enforcement)
+attribute on a component's (e.g. Activity, Service, etc.) manifest declaration.
+You can [define a custom permission](https://developer.android.com/guide/topics/permissions/defining) and
+set the `android:protectionLevel` of the permission to `"signature"` or
+`"signatureOrSystem"` to restrict access to just components signed by the same
+certificate (or trusted system components).
+
+## Outbound Intents {#outbound-intents}
+
+There are [two types of Intents](https://developer.android.com/guide/components/intents-filters?hl=en#Types):
+implicit and explicit. With implicit Intents, the receiving application is not
+specified by the sender and the system uses a resolution process to find the
+most suitable component to handle it. An implicit Intent can sometimes result in
+a chooser being shown to the user when multiple applications could handle it.
+Explicit Intents specify either the package name or a fully qualified
+`ComponentName`, so the recipient is known at the time it is dispatched.
+Implicit Intents can result in an unexpected (and maybe malicious) application
+receiving user data. If it is possible to know the target application when
+sending an Intent, always prefer using an explicit Intent.
+
+## PendingIntents
+
+A [PendingIntent](https://developer.android.com/reference/android/app/PendingIntent)
+is created by one application and vended to another. The object allows the
+receiving application to start the component (i.e. Activity, Service, Broadcast)
+_as if the creating application started it_. Similar to a [setuid binary](https://en.wikipedia.org/wiki/Setuid),
+you must use this with care, as it can even be used to start non-exported
+components of the creating application.
+
+It is possible to retrieve information about the creator package of the
+PendingIntent using the [`getCreatorPackage()`](https://developer.android.com/reference/android/app/PendingIntent.html#getCreatorPackage())
+method. This is the identity under which the Intent, which the PendingIntent
+represents, will be started. Note that you cannot retrieve specific information
+about the Intent (e.g. its target and extras). And as discussed above with
+Intents, it is not possible to determine the application that called
+`PendingIntent.send()`.
+
+## Binder
+
+[Binder](https://developer.android.com/reference/android/os/Binder) is the low
+level IPC mechanism on Android, and it is what Intents and other Framework-level
+primitives are built upon.
+
+### Bound Services
+
+To communicate between components using Binder, you declare a `<service>` in
+your manifest and connect to it using [`Context.bindService()`](https://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent,%2520android.content.ServiceConnection,%2520int)).
+This is referred to a as a [bound service](https://developer.android.com/guide/components/bound-services).
+
+One of the powerful properties of a bound service is that you can determine the
+identity of your communicating peer. This can only be done during a Binder
+transaction (e.g. in an [AIDL](https://developer.android.com/guide/components/aidl)
+method implementation or a [`Handler.Callback`](https://developer.android.com/reference/android/os/Handler.Callback.html))
+that is **not** marked [`FLAG_ONEWAY`](https://developer.android.com/reference/android/os/IBinder).
+During the transaction use [`Binder.getCallingUid()`](https://developer.android.com/reference/android/os/Binder.html#getCallingUid())
+to retrieve the package's UID.
+
+In Android, every installed application is given a unique user ID (UID). This
+can be used as a key to query the [PackageManager](https://developer.android.com/reference/android/content/pm/PackageManager),
+to retrieve the [PackageInfo](https://developer.android.com/reference/android/content/pm/PackageInfo)
+for the application. With the PackageInfo, information about the applications
+code signing certificates can be retrieved and cryptographically authenticated.
+This is a strong authentication check and it is the **only** reliable mechanism
+by which you can authenticate your peer.
+
+In Chrome, the helper functions
+[`ExternalAuthUtils.isCallerValid()`](https://cs.chromium.org/chromium/src/chrome/android/java/src/org/chromium/chrome/browser/externalauth/ExternalAuthUtils.java?l=157&rcl=fa790f69ce80bf2e192d710ea08b8343cad93fbb)
+and `isCallerValidForPackage()` can perform these checks for you.
+
+## Capability Tokens {#capability-tokens}
+
+We define a **capability token** to be an unforgeable object that the holder may
+present to another application as authentication to access a specific
+capability. Binder objects are backed by the kernel (i.e. are unforgeable), are
+transferable, and are comparable using `isEqual()`, so Binders can be used as
+capability tokens.
+
+One security factor to bear in mind is that because capability tokens are
+transferable, they do not strongly authenticate a caller's identity. One
+application may deliberately or accidentally transfer a capability token to
+another application, or a token could be exfiltrated via an application logic
+vulnerability. Therefore, only use capability tokens for access control, not
+identity authentication.
+
+While noting the above factor, capability tokens can be useful for
+authenticating Intents. If two applications have established a Binder
+connection, they can use the channel to exchange a capability token. One
+application constructs a generic Binder (using the
+[`Binder(String)`](https://developer.android.com/reference/android/os/Binder.html#Binder(java.lang.String))
+constructor) and sends the object over that `ServiceConnection` to the other
+application, while retaining a reference to it.
+
+The generic Binder object can then be transmitted as an Intent extra when
+sending Intents between the two applications. By comparing the object with
+`Binder.isEqual()`, you can validate the capability token. Be sure to use an
+[explicit Intent](#outbound-intents) when sending such an Intent.
+
+This same approach can also be done with using a PendingIntent to a non-exported
+component as a capability token. Internally PendingIntents use a Binder token
+approach, so the only significant difference is the additional capability
+conferred by the PendingIntent to start a component.
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index e76fde4..cd99f5b 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -280,7 +280,7 @@
   },
   "management": [{
     "dependencies": ["permission:management"],
-    "contexts": ["blessed_extension"],
+    "contexts": ["blessed_extension", "extension_service_worker"],
     "default_parent": true
   }, {
     "channel": "stable",
diff --git a/extensions/renderer/worker_thread_dispatcher.cc b/extensions/renderer/worker_thread_dispatcher.cc
index 47b79e0f..6b94e6f 100644
--- a/extensions/renderer/worker_thread_dispatcher.cc
+++ b/extensions/renderer/worker_thread_dispatcher.cc
@@ -100,6 +100,10 @@
     if (worker_thread_id == kMainThreadId)
       return false;
     base::TaskRunner* runner = GetTaskRunnerFor(worker_thread_id);
+    // The TaskRunner may have been destroyed, for example, if the extension
+    // was unloaded, so check before trying to post a task.
+    if (runner == nullptr)
+      return false;
     bool task_posted = runner->PostTask(
         FROM_HERE, base::BindOnce(&WorkerThreadDispatcher::ForwardIPC,
                                   worker_thread_id, message));
@@ -130,7 +134,8 @@
 base::TaskRunner* WorkerThreadDispatcher::GetTaskRunnerFor(
     int worker_thread_id) {
   base::AutoLock lock(task_runner_map_lock_);
-  return task_runner_map_[worker_thread_id];
+  auto it = task_runner_map_.find(worker_thread_id);
+  return it == task_runner_map_.end() ? nullptr : it->second;
 }
 
 bool WorkerThreadDispatcher::Send(IPC::Message* message) {
diff --git a/fuchsia/engine/BUILD.gn b/fuchsia/engine/BUILD.gn
index 0a4486f..a175283 100644
--- a/fuchsia/engine/BUILD.gn
+++ b/fuchsia/engine/BUILD.gn
@@ -168,12 +168,6 @@
     "lib/libswiftshader_libEGL.so",
     "lib/libswiftshader_libGLESv2.so",
   ]
-  if (!is_debug) {
-    excluded_files += [
-      "lib/libVkLayer_core_validation.so",
-      "lib/libVkLayer_parameter_validation.so",
-    ]
-  }
 }
 
 fuchsia_package_runner("web_engine_runner") {
diff --git a/fuchsia/runners/cast/not_implemented_api_bindings.js b/fuchsia/runners/cast/not_implemented_api_bindings.js
index c6150dc..1c1f5ef 100644
--- a/fuchsia/runners/cast/not_implemented_api_bindings.js
+++ b/fuchsia/runners/cast/not_implemented_api_bindings.js
@@ -59,183 +59,203 @@
   };
 
 
-  cast.__platform__.canDisplayType =
-      cast.__platform__._notImplemented('canDisplayType', true);
+  if (!cast.__platform__.canDisplayType) {
+    cast.__platform__.canDisplayType =
+        cast.__platform__._notImplemented('canDisplayType', true);
+  }
 
-  cast.__platform__.setAssistantMessageHandler =
-      cast.__platform__._notImplemented('setAssistantMessageHandler');
+  if (!cast.__platform__.setAssistantMessageHandler) {
+    cast.__platform__.setAssistantMessageHandler =
+        cast.__platform__._notImplemented('setAssistantMessageHandler');
+  }
 
-  cast.__platform__.sendAssistantRequest =
-      cast.__platform__._notImplemented('sendAssistantRequest');
+  if (!cast.__platform__.sendAssistantRequest) {
+    cast.__platform__.sendAssistantRequest =
+        cast.__platform__._notImplemented('sendAssistantRequest');
+  }
 
-  cast.__platform__.setWindowRequestHandler =
-      cast.__platform__._notImplemented('setWindowRequestHandler');
+  if (!cast.__platform__.setWindowRequestHandler) {
+    cast.__platform__.setWindowRequestHandler =
+        cast.__platform__._notImplemented('setWindowRequestHandler');
+  }
 
-  cast.__platform__.takeScreenshot =
-      cast.__platform__._notImplemented('takeScreenshot');
+  if (!cast.__platform__.takeScreenshot) {
+    cast.__platform__.takeScreenshot =
+        cast.__platform__._notImplemented('takeScreenshot');
+  }
 
 
-  cast.__platform__.crypto = {};
+  if (!cast.__platform__.crypto) {
+    cast.__platform__.crypto = {};
 
-  cast.__platform__.crypto.encrypt =
-      cast.__platform__._notImplemented(
-          'crypto.encrypt',
-          new ArrayBuffer(0),
-          cast.__platform__.ReturnType.PROMISE_RESOLVED);
+    cast.__platform__.crypto.encrypt =
+        cast.__platform__._notImplemented(
+            'crypto.encrypt',
+            new ArrayBuffer(0),
+            cast.__platform__.ReturnType.PROMISE_RESOLVED);
 
-  cast.__platform__.crypto.decrypt =
-      cast.__platform__._notImplemented(
-          'crypto.decrypt',
-          new ArrayBuffer(0),
-          cast.__platform__.ReturnType.PROMISE_RESOLVED);
+    cast.__platform__.crypto.decrypt =
+        cast.__platform__._notImplemented(
+            'crypto.decrypt',
+            new ArrayBuffer(0),
+            cast.__platform__.ReturnType.PROMISE_RESOLVED);
 
-  cast.__platform__.crypto.sign =
-      cast.__platform__._notImplemented(
-          'crypto.sign',
-          new ArrayBuffer(0),
-          cast.__platform__.ReturnType.PROMISE_RESOLVED);
+    cast.__platform__.crypto.sign =
+        cast.__platform__._notImplemented(
+            'crypto.sign',
+            new ArrayBuffer(0),
+            cast.__platform__.ReturnType.PROMISE_RESOLVED);
 
-  cast.__platform__.crypto.unwrapKey =
-      cast.__platform__._notImplemented(
-          'crypto.unwrapKey',
-          undefined,
-          cast.__platform__.ReturnType.PROMISE_REJECTED);
+    cast.__platform__.crypto.unwrapKey =
+        cast.__platform__._notImplemented(
+            'crypto.unwrapKey',
+            undefined,
+            cast.__platform__.ReturnType.PROMISE_REJECTED);
 
-  cast.__platform__.crypto.verify =
-      cast.__platform__._notImplemented(
-          'crypto.verify',
-          true,
-          cast.__platform__.ReturnType.PROMISE_RESOLVED);
+    cast.__platform__.crypto.verify =
+        cast.__platform__._notImplemented(
+            'crypto.verify',
+            true,
+            cast.__platform__.ReturnType.PROMISE_RESOLVED);
 
-  cast.__platform__.crypto.wrapKey =
-      cast.__platform__._notImplemented(
-          'crypto.wrapKey',
-          undefined,
-          cast.__platform__.ReturnType.PROMISE_REJECTED);
+    cast.__platform__.crypto.wrapKey =
+        cast.__platform__._notImplemented(
+            'crypto.wrapKey',
+            undefined,
+            cast.__platform__.ReturnType.PROMISE_REJECTED);
+  }
 
 
-  cast.__platform__.cryptokeys = {};
+  if (!cast.__platform__.cryptokeys) {
+    cast.__platform__.cryptokeys = {};
 
-  cast.__platform__.cryptokeys.getKeyByName =
-      cast.__platform__._notImplemented(
-          'cryptokeys.getKeyByName',
-          '',
-          cast.__platform__.ReturnType.PROMISE_REJECTED);
+    cast.__platform__.cryptokeys.getKeyByName =
+        cast.__platform__._notImplemented(
+            'cryptokeys.getKeyByName',
+            '',
+            cast.__platform__.ReturnType.PROMISE_REJECTED);
+  }
 
 
-  cast.__platform__.display = {};
+  if (!cast.__platform__.display) {
+    cast.__platform__.display = {};
 
-  cast.__platform__.display.updateOutputMode =
-      cast.__platform__._notImplemented(
-          'display.updateOutputMode',
-          Promise.resolve(),
-          cast.__platform__.ReturnType.PROMISE_RESOLVED);
+    cast.__platform__.display.updateOutputMode =
+        cast.__platform__._notImplemented(
+            'display.updateOutputMode',
+            Promise.resolve(),
+            cast.__platform__.ReturnType.PROMISE_RESOLVED);
 
-  cast.__platform__.display.getHdcpVersion =
-      cast.__platform__._notImplemented(
-          'display.getHdcpVersion',
-          '0',
-          cast.__platform__.ReturnType.PROMISE_RESOLVED);
+    cast.__platform__.display.getHdcpVersion =
+        cast.__platform__._notImplemented(
+            'display.getHdcpVersion',
+            '0',
+            cast.__platform__.ReturnType.PROMISE_RESOLVED);
+  }
 
 
-  cast.__platform__.metrics = {};
+  if (!cast.__platform__.metrics) {
+    cast.__platform__.metrics = {};
 
-  cast.__platform__.metrics.logBoolToUma =
-      cast.__platform__._notImplemented(
-          'metrics.logBoolToUma');
+    cast.__platform__.metrics.logBoolToUma =
+        cast.__platform__._notImplemented(
+            'metrics.logBoolToUma');
 
-  cast.__platform__.metrics.logIntToUma =
-      cast.__platform__._notImplemented(
-          'metrics.logIntToUma');
+    cast.__platform__.metrics.logIntToUma =
+        cast.__platform__._notImplemented(
+            'metrics.logIntToUma');
 
-  cast.__platform__.metrics.logEventToUma =
-      cast.__platform__._notImplemented(
-          'metrics.logEventToUma');
+    cast.__platform__.metrics.logEventToUma =
+        cast.__platform__._notImplemented(
+            'metrics.logEventToUma');
 
-  cast.__platform__.metrics.logHistogramValueToUma =
-      cast.__platform__._notImplemented(
-          'metrics.logHistogramValueToUma');
+    cast.__platform__.metrics.logHistogramValueToUma =
+        cast.__platform__._notImplemented(
+            'metrics.logHistogramValueToUma');
 
-  cast.__platform__.metrics.setMplVersion =
-      cast.__platform__._notImplemented('metrics.setMplVersion');
+    cast.__platform__.metrics.setMplVersion =
+        cast.__platform__._notImplemented('metrics.setMplVersion');
+  }
 
 
+  if (!cast.__platform__.accessibility) {
+    cast.__platform__.accessibility = {};
+
+    cast.__platform__.accessibility.getAccessibilitySettings =
+        cast.__platform__._notImplemented(
+            'accessibility.getAccessibilitySettings',
+            {isColorInversionEnabled: false, isScreenReaderEnabled: false},
+            cast.__platform__.ReturnType.PROMISE_RESOLVED);
+
+    cast.__platform__.accessibility.setColorInversion =
+        cast.__platform__._notImplemented(
+            'accessibility.setColorInversion');
+
+    cast.__platform__.accessibility.setMagnificationGesture =
+        cast.__platform__._notImplemented(
+            'accessibility.setMagnificationGesture');
+  }
 
 
-  cast.__platform__.accessibility = {};
+  if (!cast.__platform__.windowManager) {
+    cast.__platform__.windowManager = {};
 
-  cast.__platform__.accessibility.getAccessibilitySettings =
-      cast.__platform__._notImplemented(
-          'accessibility.getAccessibilitySettings',
-          {isColorInversionEnabled: false, isScreenReaderEnabled: false},
-          cast.__platform__.ReturnType.PROMISE_RESOLVED);
+    cast.__platform__.windowManager.onBackGesture =
+        cast.__platform__._notImplemented('windowManager.onBackGesture',
+            undefined,
+            cast.__platform__.ReturnType.CALLBACK);
 
-  cast.__platform__.accessibility.setColorInversion =
-      cast.__platform__._notImplemented(
-          'accessibility.setColorInversion');
+    cast.__platform__.windowManager.onBackGestureProgress =
+        cast.__platform__._notImplemented('windowManager.onBackGestureProgress',
+            [0],
+            cast.__platform__.ReturnType.CALLBACK);
 
-  cast.__platform__.accessibility.setMagnificationGesture =
-      cast.__platform__._notImplemented(
-          'accessibility.setMagnificationGesture');
+    cast.__platform__.windowManager.onBackGestureCancel =
+        cast.__platform__._notImplemented('windowManager.onBackGestureCancel',
+            undefined,
+            cast.__platform__.ReturnType.CALLBACK);
 
+    cast.__platform__.windowManager.onTopDragGestureDone =
+        cast.__platform__._notImplemented('windowManager.onTopDragGestureDone',
+            undefined,
+            cast.__platform__.ReturnType.CALLBACK);
 
-  cast.__platform__.windowManager = {};
+    cast.__platform__.windowManager.onTopDragGestureProgress =
+        cast.__platform__._notImplemented(
+            'windowManager.onTopDragGestureProgress',
+            [0],
+            cast.__platform__.ReturnType.CALLBACK);
 
-  cast.__platform__.windowManager.onBackGesture =
-      cast.__platform__._notImplemented('windowManager.onBackGesture',
-          undefined,
-          cast.__platform__.ReturnType.CALLBACK);
+    cast.__platform__.windowManager.onTapGesture =
+        cast.__platform__._notImplemented('windowManager.onTapGesture',
+            undefined,
+            cast.__platform__.ReturnType.CALLBACK);
 
-  cast.__platform__.windowManager.onBackGestureProgress =
-      cast.__platform__._notImplemented('windowManager.onBackGestureProgress',
-          [0],
-          cast.__platform__.ReturnType.CALLBACK);
+    cast.__platform__.windowManager.onTapDownGesture =
+        cast.__platform__._notImplemented('windowManager.onTapDownGesture',
+            undefined,
+            cast.__platform__.ReturnType.CALLBACK);
 
-  cast.__platform__.windowManager.onBackGestureCancel =
-      cast.__platform__._notImplemented('windowManager.onBackGestureCancel',
-          undefined,
-          cast.__platform__.ReturnType.CALLBACK);
+    cast.__platform__.windowManager.canTopDrag =
+        cast.__platform__._notImplemented('windowManager.canTopDrag', false);
 
-  cast.__platform__.windowManager.onTopDragGestureDone =
-      cast.__platform__._notImplemented('windowManager.onTopDragGestureDone',
-          undefined,
-          cast.__platform__.ReturnType.CALLBACK);
+    cast.__platform__.windowManager.canGoBack =
+        cast.__platform__._notImplemented('windowManager.canGoBack', false);
 
-  cast.__platform__.windowManager.onTopDragGestureProgress =
-      cast.__platform__._notImplemented(
-          'windowManager.onTopDragGestureProgress',
-          [0],
-          cast.__platform__.ReturnType.CALLBACK);
+    cast.__platform__.windowManager.onRightDragGestureDone =
+        cast.__platform__._notImplemented(
+            'windowManager.onRightDragGestureDone',
+            undefined,
+            cast.__platform__.ReturnType.CALLBACK);
 
-  cast.__platform__.windowManager.onTapGesture =
-      cast.__platform__._notImplemented('windowManager.onTapGesture',
-          undefined,
-          cast.__platform__.ReturnType.CALLBACK);
+    cast.__platform__.windowManager.onRightDragGestureProgress =
+        cast.__platform__._notImplemented(
+            'windowManager.onRightDragGestureProgress',
+            [0, 0],
+            cast.__platform__.ReturnType.CALLBACK);
 
-  cast.__platform__.windowManager.onTapDownGesture =
-      cast.__platform__._notImplemented('windowManager.onTapDownGesture',
-          undefined,
-          cast.__platform__.ReturnType.CALLBACK);
-
-  cast.__platform__.windowManager.canTopDrag =
-      cast.__platform__._notImplemented('windowManager.canTopDrag', false);
-
-  cast.__platform__.windowManager.canGoBack =
-      cast.__platform__._notImplemented('windowManager.canGoBack', false);
-
-  cast.__platform__.windowManager.onRightDragGestureDone =
-      cast.__platform__._notImplemented('windowManager.onRightDragGestureDone',
-          undefined,
-          cast.__platform__.ReturnType.CALLBACK);
-
-  cast.__platform__.windowManager.onRightDragGestureProgress =
-      cast.__platform__._notImplemented(
-          'windowManager.onRightDragGestureProgress',
-          [0, 0],
-          cast.__platform__.ReturnType.CALLBACK);
-
-  cast.__platform__.windowManager.canRightDrag =
-      cast.__platform__._notImplemented(
-          'windowManager.canRightDrag', false);
-
+    cast.__platform__.windowManager.canRightDrag =
+        cast.__platform__._notImplemented(
+            'windowManager.canRightDrag', false);
+  }
 }
diff --git a/gpu/command_buffer/service/gr_cache_controller.cc b/gpu/command_buffer/service/gr_cache_controller.cc
index 8e26fe334..84bbf65 100644
--- a/gpu/command_buffer/service/gr_cache_controller.cc
+++ b/gpu/command_buffer/service/gr_cache_controller.cc
@@ -7,6 +7,7 @@
 #include <chrono>
 
 #include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "ui/gl/gl_context.h"
 
@@ -31,6 +32,9 @@
   if (!purge_gr_cache_cb_.IsCancelled())
     return;
 
+  // Record memory usage periodically.
+  RecordGrContextMemory();
+
   constexpr int kOldResourceCleanupDelaySeconds = 5;
   // Here we ask GrContext to free any resources that haven't been used in
   // a long while even if it is under budget. Below we set a call back to
@@ -68,5 +72,13 @@
   context_state_->gr_context()->freeGpuResources();
 }
 
+void GrCacheController::RecordGrContextMemory() const {
+  int resource_count = 0;
+  size_t resource_bytes = 0;
+  context_state_->gr_context()->getResourceCacheUsage(&resource_count,
+                                                      &resource_bytes);
+  UMA_HISTOGRAM_MEMORY_KB("GPU.GrContextMemoryKb", resource_bytes / 1000);
+}
+
 }  // namespace raster
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/gr_cache_controller.h b/gpu/command_buffer/service/gr_cache_controller.h
index d207a923..8e578d7 100644
--- a/gpu/command_buffer/service/gr_cache_controller.h
+++ b/gpu/command_buffer/service/gr_cache_controller.h
@@ -30,6 +30,7 @@
 
  private:
   void PurgeGrCache(uint64_t idle_id);
+  void RecordGrContextMemory() const;
 
   // The |current_idle_id_| is used to avoid continuously posting tasks to clear
   // the GrContext. Each time the context is used this id is incremented and
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 80ef980..591a064 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3276,14 +3276,6 @@
       ]
     },
     {
-      "id": 305,
-      "description": "Disable MESA_framebuffer_flip_y until passing WebGL 2.0 Conformance tests",
-      "cr_bugs": [964010],
-      "disabled_extensions": [
-        "GL_MESA_framebuffer_flip_y"
-      ]
-    },
-    {
       "id": 306,
       "description": "Program binaries don't contain transform feedback varyings on Mali GPUs",
       "cr_bugs": [961950],
diff --git a/gpu/vulkan/android/vulkan_implementation_android.cc b/gpu/vulkan/android/vulkan_implementation_android.cc
index e3b427e..7ac90d7 100644
--- a/gpu/vulkan/android/vulkan_implementation_android.cc
+++ b/gpu/vulkan/android/vulkan_implementation_android.cc
@@ -143,7 +143,8 @@
           VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
           VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
           VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
-          VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME};
+          VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
+          VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME};
 }
 
 VkFence VulkanImplementationAndroid::CreateVkFenceForGpuFence(
diff --git a/gpu/vulkan/generate_bindings.py b/gpu/vulkan/generate_bindings.py
index 8706466..89f31fd 100755
--- a/gpu/vulkan/generate_bindings.py
+++ b/gpu/vulkan/generate_bindings.py
@@ -92,6 +92,10 @@
 { 'name': 'vkGetAndroidHardwareBufferPropertiesANDROID' },
 ]
 
+VULKAN_PHYSICAL_DEVICE_FUNCTIONS_ANDROID = [
+{ 'name': 'vkGetPhysicalDeviceFeatures2' },
+]
+
 VULKAN_DEVICE_FUNCTIONS_LINUX_OR_ANDROID = [
 { 'name': 'vkGetSemaphoreFdKHR' },
 { 'name': 'vkImportSemaphoreFdKHR' },
@@ -164,6 +168,7 @@
 def GenerateHeaderFile(file, unassociated_functions, instance_functions,
                        physical_device_functions, device_functions,
                        device_functions_android,
+                       physical_device_functions_android,
                        device_functions_linux_or_android,
                        device_functions_linux, device_functions_fuchsia,
                        queue_functions, command_buffer_functions,
@@ -264,13 +269,20 @@
 
   file.write("""\
 
-  // Android only device functions.
 #if defined(OS_ANDROID)
+  // Android only device functions.
 """)
 
   WriteFunctionDeclarations(file, device_functions_android)
 
   file.write("""\
+
+  // Android only physical device functions.
+""")
+
+  WriteFunctionDeclarations(file, physical_device_functions_android)
+
+  file.write("""\
 #endif
 """)
 
@@ -371,6 +383,16 @@
   ])
   file.write("#endif\n")
 
+  file.write("""\
+
+#if defined(OS_ANDROID)
+""")
+
+  WriteMacros(file, physical_device_functions_android)
+
+  file.write("""\
+#endif
+""")
 
   file.write("""\
 
@@ -485,6 +507,7 @@
 def GenerateSourceFile(file, unassociated_functions, instance_functions,
                        physical_device_functions, device_functions,
                        device_functions_android,
+                       physical_device_functions_android,
                        device_functions_linux_or_android,
                        device_functions_linux, device_functions_fuchsia,
                        queue_functions, command_buffer_functions,
@@ -550,6 +573,17 @@
   WriteInstanceFunctionPointerInitialization(file, physical_device_functions)
 
   file.write("""\
+#if defined(OS_ANDROID)
+""")
+
+  WriteInstanceFunctionPointerInitialization(file,
+                                             physical_device_functions_android)
+
+  file.write("""\
+#endif
+""")
+
+  file.write("""\
 
   return true;
 }
@@ -679,6 +713,7 @@
                      VULKAN_INSTANCE_FUNCTIONS,
                      VULKAN_PHYSICAL_DEVICE_FUNCTIONS, VULKAN_DEVICE_FUNCTIONS,
                      VULKAN_DEVICE_FUNCTIONS_ANDROID,
+                     VULKAN_PHYSICAL_DEVICE_FUNCTIONS_ANDROID,
                      VULKAN_DEVICE_FUNCTIONS_LINUX_OR_ANDROID,
                      VULKAN_DEVICE_FUNCTIONS_LINUX,
                      VULKAN_DEVICE_FUNCTIONS_FUCHSIA,
@@ -694,6 +729,7 @@
                      VULKAN_INSTANCE_FUNCTIONS,
                      VULKAN_PHYSICAL_DEVICE_FUNCTIONS, VULKAN_DEVICE_FUNCTIONS,
                      VULKAN_DEVICE_FUNCTIONS_ANDROID,
+                     VULKAN_PHYSICAL_DEVICE_FUNCTIONS_ANDROID,
                      VULKAN_DEVICE_FUNCTIONS_LINUX_OR_ANDROID,
                      VULKAN_DEVICE_FUNCTIONS_LINUX,
                      VULKAN_DEVICE_FUNCTIONS_FUCHSIA,
diff --git a/gpu/vulkan/vulkan_device_queue.cc b/gpu/vulkan/vulkan_device_queue.cc
index c6f08fd..441940cb 100644
--- a/gpu/vulkan/vulkan_device_queue.cc
+++ b/gpu/vulkan/vulkan_device_queue.cc
@@ -134,15 +134,40 @@
                             std::begin(required_extensions),
                             std::end(required_extensions));
 
+#if defined(OS_ANDROID)
+  // Query if VkPhysicalDeviceSamplerYcbcrConversionFeatures is supported by
+  // the implementation. This extension must be supported for android.
+  // Note that vulkan is only turned on in chrome for android P+ and android P+
+  // requires vulkan apiVersion 1.1.
+  sampler_ycbcr_conversion_features_.sType =
+      VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
+  sampler_ycbcr_conversion_features_.pNext = nullptr;
+
+  // Add VkPhysicalDeviceSamplerYcbcrConversionFeatures struct to pNext chain
+  // of VkPhysicalDeviceFeatures2.
+  enabled_device_features_2_.pNext = &sampler_ycbcr_conversion_features_;
+  vkGetPhysicalDeviceFeatures2(vk_physical_device_,
+                               &enabled_device_features_2_);
+  if (!sampler_ycbcr_conversion_features_.samplerYcbcrConversion) {
+    LOG(ERROR) << "samplerYcbcrConversion is not supported";
+    return false;
+  }
+
+  // Disable all physical device features by default.
+  memset(&enabled_device_features_2_.features, 0,
+         sizeof(enabled_device_features_2_.features));
+#endif
+
   VkDeviceCreateInfo device_create_info = {};
   device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+  device_create_info.pNext = enabled_device_features_2_.pNext;
   device_create_info.queueCreateInfoCount = 1;
   device_create_info.pQueueCreateInfos = &queue_create_info;
   device_create_info.enabledLayerCount = enabled_layer_names.size();
   device_create_info.ppEnabledLayerNames = enabled_layer_names.data();
   device_create_info.enabledExtensionCount = enabled_extensions.size();
   device_create_info.ppEnabledExtensionNames = enabled_extensions.data();
-  device_create_info.pEnabledFeatures = &enabled_device_features_;
+  device_create_info.pEnabledFeatures = &enabled_device_features_2_.features;
 
   result = vkCreateDevice(vk_physical_device_, &device_create_info, nullptr,
                           &owned_vk_device_);
diff --git a/gpu/vulkan/vulkan_device_queue.h b/gpu/vulkan/vulkan_device_queue.h
index f026fd2..df3a0b0 100644
--- a/gpu/vulkan/vulkan_device_queue.h
+++ b/gpu/vulkan/vulkan_device_queue.h
@@ -12,6 +12,7 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "build/build_config.h"
 #include "gpu/vulkan/vulkan_export.h"
 #include "ui/gfx/extension_set.h"
 
@@ -80,8 +81,8 @@
 
   VulkanFenceHelper* GetFenceHelper() const { return cleanup_helper_.get(); }
 
-  const VkPhysicalDeviceFeatures& enabled_device_features() const {
-    return enabled_device_features_;
+  const VkPhysicalDeviceFeatures2& enabled_device_features_2() const {
+    return enabled_device_features_2_;
   }
 
  private:
@@ -94,7 +95,12 @@
   uint32_t vk_queue_index_ = 0;
   const VkInstance vk_instance_;
   std::unique_ptr<VulkanFenceHelper> cleanup_helper_;
-  VkPhysicalDeviceFeatures enabled_device_features_ = {};
+  VkPhysicalDeviceFeatures2 enabled_device_features_2_ = {};
+
+#if defined(OS_ANDROID)
+  VkPhysicalDeviceSamplerYcbcrConversionFeatures
+      sampler_ycbcr_conversion_features_ = {};
+#endif
 
   DISALLOW_COPY_AND_ASSIGN(VulkanDeviceQueue);
 };
diff --git a/gpu/vulkan/vulkan_function_pointers.cc b/gpu/vulkan/vulkan_function_pointers.cc
index ddd920e8..6fc83aa 100644
--- a/gpu/vulkan/vulkan_function_pointers.cc
+++ b/gpu/vulkan/vulkan_function_pointers.cc
@@ -157,6 +157,18 @@
     return false;
   }
 
+#if defined(OS_ANDROID)
+  vkGetPhysicalDeviceFeatures2Fn =
+      reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2>(
+          vkGetInstanceProcAddrFn(vk_instance, "vkGetPhysicalDeviceFeatures2"));
+  if (!vkGetPhysicalDeviceFeatures2Fn) {
+    DLOG(WARNING) << "Failed to bind vulkan entrypoint: "
+                  << "vkGetPhysicalDeviceFeatures2";
+    return false;
+  }
+
+#endif
+
   return true;
 }
 
diff --git a/gpu/vulkan/vulkan_function_pointers.h b/gpu/vulkan/vulkan_function_pointers.h
index 5bcb24d..0d9f8f6 100644
--- a/gpu/vulkan/vulkan_function_pointers.h
+++ b/gpu/vulkan/vulkan_function_pointers.h
@@ -139,10 +139,13 @@
   PFN_vkUpdateDescriptorSets vkUpdateDescriptorSetsFn = nullptr;
   PFN_vkWaitForFences vkWaitForFencesFn = nullptr;
 
-  // Android only device functions.
 #if defined(OS_ANDROID)
+  // Android only device functions.
   PFN_vkGetAndroidHardwareBufferPropertiesANDROID
       vkGetAndroidHardwareBufferPropertiesANDROIDFn = nullptr;
+
+  // Android only physical device functions.
+  PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2Fn = nullptr;
 #endif
 
   // Device functions shared between Linux and Android.
@@ -243,6 +246,11 @@
       ->vkGetPhysicalDeviceXlibPresentationSupportKHRFn
 #endif
 
+#if defined(OS_ANDROID)
+#define vkGetPhysicalDeviceFeatures2 \
+  gpu::GetVulkanFunctionPointers()->vkGetPhysicalDeviceFeatures2Fn
+#endif
+
 // Device functions
 #define vkAllocateCommandBuffers \
   gpu::GetVulkanFunctionPointers()->vkAllocateCommandBuffersFn
diff --git a/headless/lib/headless_browser_browsertest.cc b/headless/lib/headless_browser_browsertest.cc
index 49c74d7..7c7b9db 100644
--- a/headless/lib/headless_browser_browsertest.cc
+++ b/headless/lib/headless_browser_browsertest.cc
@@ -21,6 +21,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/no_renderer_crashes_assertion.h"
 #include "headless/lib/browser/headless_browser_context_impl.h"
 #include "headless/lib/browser/headless_web_contents_impl.h"
 #include "headless/lib/headless_macros.h"
@@ -470,6 +471,8 @@
 #define MAYBE_GenerateMinidump DISABLED_GenerateMinidump
 #endif  // defined(HEADLESS_USE_BREAKPAD) || defined(OS_MACOSX)
 IN_PROC_BROWSER_TEST_F(CrashReporterTest, MAYBE_GenerateMinidump) {
+  content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
+
   // Navigates a tab to chrome://crash and checks that a minidump is generated.
   // Note that we only test renderer crashes here -- browser crashes need to be
   // tested with a separate harness.
diff --git a/headless/lib/headless_devtools_client_browsertest.cc b/headless/lib/headless_devtools_client_browsertest.cc
index 7519a75e..58f4c449 100644
--- a/headless/lib/headless_devtools_client_browsertest.cc
+++ b/headless/lib/headless_devtools_client_browsertest.cc
@@ -18,6 +18,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/no_renderer_crashes_assertion.h"
 #include "headless/lib/browser/headless_web_contents_impl.h"
 #include "headless/public/devtools/domains/browser.h"
 #include "headless/public/devtools/domains/dom.h"
@@ -473,6 +474,9 @@
     EXPECT_EQ(base::TERMINATION_STATUS_ABNORMAL_TERMINATION, status);
 #endif
   }
+
+ private:
+  content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes_;
 };
 
 HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessCrashObserverTest);
diff --git a/infra/config/cr-buildbucket-dev.cfg b/infra/config/cr-buildbucket-dev.cfg
index 2da7dfc..b8918a0 100644
--- a/infra/config/cr-buildbucket-dev.cfg
+++ b/infra/config/cr-buildbucket-dev.cfg
@@ -94,6 +94,7 @@
       category: "Chromium"
       execution_timeout_secs: 10800  # 3h
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
+      swarming_tags: "vpython:native-python-wrapper"
       build_numbers: YES
       recipe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index 3e4c64a..a02ac3d 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -961,6 +961,7 @@
     builders {
       name: "Android FYI 32 dEQP Vk Release (Pixel 2)"
       mixins: "android-gpu-fyi-ci"
+      mixins: "builderless"
       mixins: "goma-rbe-prod"
     }
 
@@ -973,6 +974,7 @@
     builders {
       name: "Android FYI 32 Vk Release (Pixel 2)"
       mixins: "android-gpu-fyi-ci"
+      mixins: "builderless"
       mixins: "goma-rbe-prod"
     }
 
@@ -985,6 +987,7 @@
     builders {
       name: "Android FYI 64 dEQP Vk Release (Pixel 2)"
       mixins: "android-gpu-fyi-ci"
+      mixins: "builderless"
       mixins: "goma-rbe-prod"
     }
 
@@ -997,6 +1000,7 @@
     builders {
       name: "Android FYI 64 Vk Release (Pixel 2)"
       mixins: "android-gpu-fyi-ci"
+      mixins: "builderless"
       mixins: "goma-rbe-prod"
     }
 
@@ -1051,6 +1055,7 @@
     builders {
       name: "Android FYI Release (Pixel 2)"
       mixins: "android-gpu-fyi-ci"
+      mixins: "builderless"
       mixins: "goma-rbe-prod"
     }
 
@@ -2733,13 +2738,6 @@
       mixins: "fyi-ci"
     }
     builders {
-      name: "Chromium Mac 10.13"
-      dimensions: "os:Mac-10.13"
-      dimensions: "cores:4"
-      mixins: "fyi-ci"
-      mixins: "goma-rbe-prod"
-    }
-    builders {
       name: "linux-archive-dbg"
       dimensions: "os:Ubuntu-14.04"
       # Bump to 32 if needed.
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index 1dd0538..d11bead 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -2079,10 +2079,6 @@
     short_name: "jmb"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/Chromium Mac 10.13"
-    category: "default"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/Mac deterministic"
     category: "deterministic"
   }
@@ -2801,11 +2797,6 @@
     short_name: "bld"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/Chromium Mac 10.13"
-    category: "week2c|mac"
-    short_name: "10.13"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/Mac ASAN Release"
     category: "week2c|mac|asan"
   }
diff --git a/infra/config/luci-scheduler.cfg b/infra/config/luci-scheduler.cfg
index e4b96d2..f763f7a 100644
--- a/infra/config/luci-scheduler.cfg
+++ b/infra/config/luci-scheduler.cfg
@@ -116,7 +116,6 @@
   triggers: "Chromium Linux Goma RBE ToT"
   triggers: "Chromium Linux Goma RBE ToT (ATS)"
   triggers: "Chromium Linux Goma Staging"
-  triggers: "Chromium Mac 10.13"
   triggers: "Chromium Mac Goma RBE Prod"
   triggers: "Chromium Mac Goma RBE Staging"
   triggers: "Chromium Mac Goma RBE Staging (clobber)"
@@ -378,7 +377,6 @@
   triggers: "win-archive-dbg"
   triggers: "win-jumbo-rel"
   triggers: "win-archive-rel"
-  triggers: "win-celab-builder-rel"
   triggers: "win-pixel-builder-rel"
   triggers: "win32-arm64-rel"
   triggers: "win32-archive-dbg"
@@ -3352,16 +3350,6 @@
 }
 
 job {
-  id: "Chromium Mac 10.13"
-  acl_sets: "default"
-  buildbucket: {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "Chromium Mac 10.13"
-  }
-}
-
-job {
   id: "ChromiumOS ASAN Release"
   acl_sets: "default"
   buildbucket: {
diff --git a/ios/chrome/browser/ui/settings/language/language_settings_egtest.mm b/ios/chrome/browser/ui/settings/language/language_settings_egtest.mm
index a8db591..391fdcc 100644
--- a/ios/chrome/browser/ui/settings/language/language_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/language/language_settings_egtest.mm
@@ -44,31 +44,27 @@
 // Labels used to identify language entries.
 NSString* const kLanguageEntryThreeLabelsTemplate = @"%@, %@, %@";
 NSString* const kLanguageEntryTwoLabelsTemplate = @"%@, %@";
-NSString* const kEnglishLabel = @"English (United States)";
+NSString* const kEnglishLabel = @"English";
 NSString* const kTurkishLabel = @"Turkish";
 NSString* const kTurkishNativeLabel = @"Türkçe";
+NSString* const kAragoneseLabel = @"Aragonese";
 NSString* const kNeverTranslateLabel = @"Never Translate";
 NSString* const kOfferToTranslateLabel = @"Offer to Translate";
 
 // Matcher for the Language Settings's main page table view.
 id<GREYMatcher> LanguageSettingsTableView() {
-  return grey_allOf(
-      grey_accessibilityID(kLanguageSettingsTableViewAccessibilityIdentifier),
-      grey_sufficientlyVisible(), nil);
+  return grey_accessibilityID(
+      kLanguageSettingsTableViewAccessibilityIdentifier);
 }
 
 // Matcher for the Language Settings's Add Language page table view.
 id<GREYMatcher> AddLanguageTableView() {
-  return grey_allOf(
-      grey_accessibilityID(kAddLanguageTableViewAccessibilityIdentifier),
-      grey_sufficientlyVisible(), nil);
+  return grey_accessibilityID(kAddLanguageTableViewAccessibilityIdentifier);
 }
 
 // Matcher for the Language Settings's Language Details page table view.
 id<GREYMatcher> LanguageDetailsTableView() {
-  return grey_allOf(
-      grey_accessibilityID(kLanguageDetailsTableViewAccessibilityIdentifier),
-      grey_sufficientlyVisible(), nil);
+  return grey_accessibilityID(kLanguageDetailsTableViewAccessibilityIdentifier);
 }
 
 // Matcher for the Language Settings's general Settings menu entry.
@@ -85,15 +81,6 @@
                     grey_sufficientlyVisible(), nil);
 }
 
-// Matcher for the nav bar's back button to the Language Settings's main page.
-id<GREYMatcher> NavigationBarLanguageSettingsButton() {
-  return grey_allOf(
-      ButtonWithAccessibilityLabelId(IDS_IOS_LANGUAGE_SETTINGS_TITLE),
-      grey_kindOfClass([UIButton class]),
-      grey_ancestor(grey_kindOfClass([UINavigationBar class])),
-      grey_sufficientlyVisible(), nil);
-}
-
 // Matcher for the search bar.
 id<GREYMatcher> SearchBar() {
   return grey_allOf(
@@ -144,6 +131,22 @@
              : grey_not(grey_accessibilityTrait(UIAccessibilityTraitSelected));
 }
 
+// Matcher for the delete button for a language entry in the Language Settings's
+// main page.
+id<GREYMatcher> LanguageEntryDeleteButton() {
+  return grey_allOf(grey_accessibilityLabel(@"Delete"),
+                    grey_sufficientlyVisible(), nil);
+}
+
+// Matcher for the nav bar's edit button.
+id<GREYMatcher> NavigationBarEditButton() {
+  return grey_allOf(
+      ButtonWithAccessibilityLabelId(IDS_IOS_NAVIGATION_BAR_EDIT_BUTTON),
+      grey_kindOfClass([UIButton class]),
+      grey_ancestor(grey_kindOfClass([UINavigationBar class])),
+      grey_sufficientlyVisible(), nil);
+}
+
 }  // namespace
 
 @interface LanguageSettingsTestCase : ChromeTestCase
@@ -169,19 +172,32 @@
   // Make sure Translate is enabled.
   SetBooleanUserPref(browserState, prefs::kOfferTranslateEnabled, YES);
 
-  // Make sure "en-US" is the only accept language.
+  // Make sure "en" is the only accept language.
   std::vector<std::string> languages;
   translatePrefs_->GetLanguageList(&languages);
   for (const auto& language : languages) {
     translatePrefs_->RemoveFromLanguageList(language);
   }
-  translatePrefs_->AddToLanguageList("en-US", /*force_blocked=*/false);
+  translatePrefs_->AddToLanguageList("en", /*force_blocked=*/false);
 }
 
 - (void)tearDown {
-  // Go back to the general Settings menu and close it.
+  // Keep navigating back while a Language Settings subpage is displaying.
+  NSError* error = nil;
   [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
-      performAction:grey_tap()];
+      assertWithMatcher:grey_notNil()
+                  error:&error];
+  while (!error) {
+    [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+        performAction:grey_tap()];
+
+    error = nil;
+    [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
+        assertWithMatcher:grey_notNil()
+                    error:&error];
+  }
+
+  // Close the general Settings menu.
   [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
       performAction:grey_tap()];
 
@@ -208,7 +224,7 @@
   VerifyAccessibilityForCurrentScreen();
 
   // Navigate back.
-  [[EarlGrey selectElementWithMatcher:NavigationBarLanguageSettingsButton()]
+  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
       performAction:grey_tap()];
 
   // Test accessibility on the Language Details page.
@@ -220,10 +236,6 @@
   [[EarlGrey selectElementWithMatcher:LanguageDetailsTableView()]
       assertWithMatcher:grey_notNil()];
   VerifyAccessibilityForCurrentScreen();
-
-  // Navigate back.
-  [[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()]
-      performAction:grey_tap()];
 }
 
 // Tests that the Translate Switch enables/disables Translate and the UI gets
@@ -346,7 +358,7 @@
 
   // Verify the prefs are up-to-date.
   ios::ChromeBrowserState* browserState = GetOriginalBrowserState();
-  GREYAssertEqual("en-US,tr",
+  GREYAssertEqual("en,tr",
                   browserState->GetPrefs()->GetString(kAcceptLanguages),
                   @"Unexpected value for kAcceptLanguages pref");
 }
@@ -373,10 +385,11 @@
 
   // Verify both options are enabled and "Never Translate" is selected.
   [[EarlGrey selectElementWithMatcher:NeverTranslateButton()]
-      assertWithMatcher:grey_allOf(grey_enabled(), ElementIsSelected(YES),
-                                   nil)];
+      assertWithMatcher:grey_allOf(grey_userInteractionEnabled(),
+                                   ElementIsSelected(YES), nil)];
   [[EarlGrey selectElementWithMatcher:OfferToTranslateButton()]
-      assertWithMatcher:grey_allOf(grey_enabled(), ElementIsSelected(NO), nil)];
+      assertWithMatcher:grey_allOf(grey_userInteractionEnabled(),
+                                   ElementIsSelected(NO), nil)];
 
   // Tap the "Offer to Translate" button.
   [[EarlGrey selectElementWithMatcher:OfferToTranslateButton()]
@@ -403,10 +416,11 @@
 
   // Verify both options are enabled and "Offer to Translate" is selected.
   [[EarlGrey selectElementWithMatcher:NeverTranslateButton()]
-      assertWithMatcher:grey_allOf(grey_enabled(), ElementIsSelected(NO), nil)];
+      assertWithMatcher:grey_allOf(grey_userInteractionEnabled(),
+                                   ElementIsSelected(NO), nil)];
   [[EarlGrey selectElementWithMatcher:OfferToTranslateButton()]
-      assertWithMatcher:grey_allOf(grey_enabled(), ElementIsSelected(YES),
-                                   nil)];
+      assertWithMatcher:grey_allOf(grey_userInteractionEnabled(),
+                                   ElementIsSelected(YES), nil)];
 
   // Tap the "Never Translate" button.
   [[EarlGrey selectElementWithMatcher:NeverTranslateButton()]
@@ -416,7 +430,7 @@
   [[EarlGrey selectElementWithMatcher:LanguageSettingsTableView()]
       assertWithMatcher:grey_notNil()];
 
-  // Verify "Turkish" is blocked.
+  // Verify "Turkish" is Translate-blocked.
   languageEntryLabel = [NSString
       stringWithFormat:kLanguageEntryThreeLabelsTemplate, kTurkishLabel,
                        kTurkishNativeLabel, kNeverTranslateLabel];
@@ -428,4 +442,172 @@
                  @"Turkish is expected to be Translate-blocked");
 }
 
+// Tests that the target language cannot be unblocked.
+- (void)testUnblockTargetLanguage {
+  [ChromeEarlGreyUI openSettingsMenu];
+
+  // Add "Turkish" to the list of accept languages.
+  translatePrefs_->AddToLanguageList("tr", /*force_blocked=*/false);
+  // Verify the prefs are up-to-date.
+  GREYAssertTrue(translatePrefs_->IsBlockedLanguage("tr"),
+                 @"Turkish is expected to be Translate-blocked");
+
+  // Make "Turkish" the target language.
+  translatePrefs_->SetRecentTargetLanguage("tr");
+
+  // Go to the Language Settings page.
+  [ChromeEarlGreyUI tapSettingsMenuButton:LanguageSettingsButton()];
+
+  // Go to the "Turkish" Language Details page.
+  NSString* languageEntryLabel = [NSString
+      stringWithFormat:kLanguageEntryThreeLabelsTemplate, kTurkishLabel,
+                       kTurkishNativeLabel, kNeverTranslateLabel];
+  [[EarlGrey selectElementWithMatcher:LanguageEntry(languageEntryLabel)]
+      performAction:grey_tap()];
+
+  // Verify the "Never Translate" option is enabled and selected while
+  // "Offer to Translate" is disabled and unselected.
+  [[EarlGrey selectElementWithMatcher:NeverTranslateButton()]
+      assertWithMatcher:grey_allOf(grey_userInteractionEnabled(),
+                                   ElementIsSelected(YES), nil)];
+  [[EarlGrey selectElementWithMatcher:OfferToTranslateButton()]
+      assertWithMatcher:grey_allOf(grey_not(grey_userInteractionEnabled()),
+                                   ElementIsSelected(NO), nil)];
+}
+
+// Tests that the last Translate-blocked language cannot be unblocked.
+- (void)testUnblockLastBlockedLanguage {
+  [ChromeEarlGreyUI openSettingsMenu];
+
+  // Make sure "Turkish" is the target language and not "en".
+  translatePrefs_->SetRecentTargetLanguage("tr");
+
+  // Go to the Language Settings page.
+  [ChromeEarlGreyUI tapSettingsMenuButton:LanguageSettingsButton()];
+
+  // Go to the "Turkish" Language Details page.
+  NSString* languageEntryLabel = [NSString
+      stringWithFormat:kLanguageEntryThreeLabelsTemplate, kEnglishLabel,
+                       kEnglishLabel, kNeverTranslateLabel];
+  [[EarlGrey selectElementWithMatcher:LanguageEntry(languageEntryLabel)]
+      performAction:grey_tap()];
+
+  // Verify the "Never Translate" option is enabled and selected while
+  // "Offer to Translate" is disabled and unselected.
+  [[EarlGrey selectElementWithMatcher:NeverTranslateButton()]
+      assertWithMatcher:grey_allOf(grey_userInteractionEnabled(),
+                                   ElementIsSelected(YES), nil)];
+  [[EarlGrey selectElementWithMatcher:OfferToTranslateButton()]
+      assertWithMatcher:grey_allOf(grey_not(grey_userInteractionEnabled()),
+                                   ElementIsSelected(NO), nil)];
+}
+
+// Tests that an unsupported language cannot be unblocked.
+- (void)testUnblockUnsupportedLanguage {
+  [ChromeEarlGreyUI openSettingsMenu];
+
+  // Add "Aragonese" to the list of accept languages.
+  translatePrefs_->AddToLanguageList("an", /*force_blocked=*/false);
+  // Verify the prefs are up-to-date.
+  GREYAssertTrue(translatePrefs_->IsBlockedLanguage("an"),
+                 @"Aragonese is expected to be Translate-blocked");
+
+  // Go to the Language Settings page.
+  [ChromeEarlGreyUI tapSettingsMenuButton:LanguageSettingsButton()];
+
+  // Go to the "Aragonese" Language Details page.
+  NSString* languageEntryLabel = [NSString
+      stringWithFormat:kLanguageEntryThreeLabelsTemplate, kAragoneseLabel,
+                       kAragoneseLabel, kNeverTranslateLabel];
+  [[EarlGrey selectElementWithMatcher:LanguageEntry(languageEntryLabel)]
+      performAction:grey_tap()];
+
+  // Verify the "Never Translate" option is enabled and selected while
+  // "Offer to Translate" is disabled and unselected.
+  [[EarlGrey selectElementWithMatcher:NeverTranslateButton()]
+      assertWithMatcher:grey_allOf(grey_userInteractionEnabled(),
+                                   ElementIsSelected(YES), nil)];
+  [[EarlGrey selectElementWithMatcher:OfferToTranslateButton()]
+      assertWithMatcher:grey_allOf(grey_not(grey_userInteractionEnabled()),
+                                   ElementIsSelected(NO), nil)];
+}
+
+// Tests that the Add Language button as well as the Translate switch are
+// disabled in edit mode.
+- (void)testEditMode {
+  [ChromeEarlGreyUI openSettingsMenu];
+
+  // Go to the Language Settings page.
+  [ChromeEarlGreyUI tapSettingsMenuButton:LanguageSettingsButton()];
+
+  // Switch on edit mode.
+  [[EarlGrey selectElementWithMatcher:NavigationBarEditButton()]
+      performAction:grey_tap()];
+
+  // Verify that the Add Language button is disabled.
+  [[EarlGrey selectElementWithMatcher:AddLanguageButton()]
+      assertWithMatcher:grey_not(grey_userInteractionEnabled())];
+
+  // Verify that the Translate switch is on and disabled.
+  [[EarlGrey
+      selectElementWithMatcher:SettingsSwitchCell(
+                                   kTranslateSwitchAccessibilityIdentifier, YES,
+                                   NO)] assertWithMatcher:grey_notNil()];
+}
+
+// Tests that languages, except the last one, can be deleted from the list of
+// accept languages.
+- (void)testDeleteLanguage {
+  [ChromeEarlGreyUI openSettingsMenu];
+
+  // Add "Turkish" to the list of accept languages.
+  translatePrefs_->AddToLanguageList("tr", /*force_blocked=*/false);
+  // Verify the prefs are up-to-date.
+  GREYAssertTrue(translatePrefs_->IsBlockedLanguage("tr"),
+                 @"Turkish is expected to be Translate-blocked");
+
+  // Go to the Language Settings page.
+  [ChromeEarlGreyUI tapSettingsMenuButton:LanguageSettingsButton()];
+
+  // Swipe left on the "English" language entry.
+  NSString* englishLanguageEntryLabel = [NSString
+      stringWithFormat:kLanguageEntryThreeLabelsTemplate, kEnglishLabel,
+                       kEnglishLabel, kNeverTranslateLabel];
+  id swipeAction = grey_swipeFastInDirection(kGREYDirectionLeft);
+  [[EarlGrey selectElementWithMatcher:LanguageEntry(englishLanguageEntryLabel)]
+      performAction:swipeAction];
+
+  // Verify that a delete button is visible.
+  [[EarlGrey selectElementWithMatcher:LanguageEntryDeleteButton()]
+      assertWithMatcher:grey_notNil()];
+
+  // Swipe left on the "Turkish" language entry.
+  NSString* turkishLanguageEntryLabel = [NSString
+      stringWithFormat:kLanguageEntryThreeLabelsTemplate, kTurkishLabel,
+                       kTurkishNativeLabel, kNeverTranslateLabel];
+  [[EarlGrey selectElementWithMatcher:LanguageEntry(turkishLanguageEntryLabel)]
+      performAction:swipeAction];
+
+  // Verify that a delete button is visible and tap it.
+  [[EarlGrey selectElementWithMatcher:LanguageEntryDeleteButton()]
+      performAction:grey_tap()];
+
+  // Verify that the "Turkish" language entry does not exist anymore.
+  [[EarlGrey selectElementWithMatcher:LanguageEntry(turkishLanguageEntryLabel)]
+      assertWithMatcher:grey_nil()];
+
+  // Verify the prefs are up-to-date.
+  ios::ChromeBrowserState* browserState = GetOriginalBrowserState();
+  GREYAssertEqual("en", browserState->GetPrefs()->GetString(kAcceptLanguages),
+                  @"Unexpected value for kAcceptLanguages pref");
+
+  // Swipe left on the "English" language entry.
+  [[EarlGrey selectElementWithMatcher:LanguageEntry(englishLanguageEntryLabel)]
+      performAction:swipeAction];
+
+  // Verify that a delete button is not visible.
+  [[EarlGrey selectElementWithMatcher:LanguageEntryDeleteButton()]
+      assertWithMatcher:grey_nil()];
+}
+
 @end
diff --git a/media/gpu/vaapi/OWNERS b/media/gpu/vaapi/OWNERS
index 02abd7f..7671f22 100644
--- a/media/gpu/vaapi/OWNERS
+++ b/media/gpu/vaapi/OWNERS
@@ -4,3 +4,6 @@
 
 # (M)JPEG related stuff
 per-file *jpeg*=andrescj@chromium.org
+
+# General VA-API decoding related stuff
+per-file *image_decoder*=andrescj@chromium.org
diff --git a/media/gpu/vaapi/vaapi_image_decoder.cc b/media/gpu/vaapi/vaapi_image_decoder.cc
index c94aaf0..5bafbd6 100644
--- a/media/gpu/vaapi/vaapi_image_decoder.cc
+++ b/media/gpu/vaapi/vaapi_image_decoder.cc
@@ -6,13 +6,37 @@
 
 #include <va/va.h>
 
+#include "base/logging.h"
 #include "media/gpu/vaapi/vaapi_wrapper.h"
 
 namespace media {
 
+namespace {
+
+VAProfile ConvertToVAProfile(VaapiImageDecoder::Type type) {
+  switch (type) {
+    case VaapiImageDecoder::Type::kJpeg:
+      return VAProfileJPEGBaseline;
+    case VaapiImageDecoder::Type::kWebP:
+      return VAProfileVP8Version0_3;
+    default:
+      NOTREACHED() << "Undefined Type value";
+      return VAProfileNone;
+  }
+}
+
+}  // namespace
+
 VaapiImageDecoder::VaapiImageDecoder()
     : va_surface_id_(VA_INVALID_SURFACE), va_rt_format_(kInvalidVaRtFormat) {}
 
 VaapiImageDecoder::~VaapiImageDecoder() = default;
 
+bool VaapiImageDecoder::Initialize(const base::RepeatingClosure& error_uma_cb) {
+  const VAProfile va_profile = ConvertToVAProfile(GetType());
+  vaapi_wrapper_ =
+      VaapiWrapper::Create(VaapiWrapper::kDecode, va_profile, error_uma_cb);
+  return !!vaapi_wrapper_;
+}
+
 }  // namespace media
diff --git a/media/gpu/vaapi/vaapi_image_decoder.h b/media/gpu/vaapi/vaapi_image_decoder.h
index 39a3072..68eaf26 100644
--- a/media/gpu/vaapi/vaapi_image_decoder.h
+++ b/media/gpu/vaapi/vaapi_image_decoder.h
@@ -50,11 +50,17 @@
 // more implementing classes are added (e.g. VaapiWebPDecoder).
 class VaapiImageDecoder {
  public:
+  // Type of image decoder.
+  enum class Type {
+    kJpeg,
+    kWebP,
+  };
+
   virtual ~VaapiImageDecoder();
 
-  // Initializes |vaapi_wrapper_| in kDecode mode with the appropriate VAAPI
-  // profile and |error_uma_cb| for error reporting.
-  virtual bool Initialize(const base::RepeatingClosure& error_uma_cb) = 0;
+  // Uses GetType() to initialize |vaapi_wrapper_| in kDecode mode with the
+  // appropriate VAAPI profile and |error_uma_cb| for error reporting.
+  bool Initialize(const base::RepeatingClosure& error_uma_cb);
 
   // Decodes a picture. It will fill VA-API parameters and call the
   // corresponding VA-API methods according to the image in |encoded_image|.
@@ -66,6 +72,9 @@
       base::span<const uint8_t> encoded_image,
       VaapiImageDecodeStatus* status) = 0;
 
+  // Returns the type of the current decoder.
+  virtual Type GetType() const = 0;
+
  protected:
   VaapiImageDecoder();
 
diff --git a/media/gpu/vaapi/vaapi_jpeg_decoder.cc b/media/gpu/vaapi/vaapi_jpeg_decoder.cc
index f7af7fd..53b26a8 100644
--- a/media/gpu/vaapi/vaapi_jpeg_decoder.cc
+++ b/media/gpu/vaapi/vaapi_jpeg_decoder.cc
@@ -217,16 +217,6 @@
   }
 }
 
-bool VaapiJpegDecoder::Initialize(const base::RepeatingClosure& error_uma_cb) {
-  vaapi_wrapper_ = VaapiWrapper::Create(VaapiWrapper::kDecode,
-                                        VAProfileJPEGBaseline, error_uma_cb);
-  if (!vaapi_wrapper_) {
-    VLOGF(1) << "Failed initializing VAAPI";
-    return false;
-  }
-  return true;
-}
-
 scoped_refptr<VASurface> VaapiJpegDecoder::Decode(
     base::span<const uint8_t> encoded_image,
     VaapiImageDecodeStatus* status) {
@@ -342,6 +332,10 @@
                                          base::DoNothing() /* release_cb */);
 }
 
+VaapiImageDecoder::Type VaapiJpegDecoder::GetType() const {
+  return VaapiImageDecoder::Type::kJpeg;
+}
+
 std::unique_ptr<ScopedVAImage> VaapiJpegDecoder::GetImage(
     uint32_t preferred_image_fourcc,
     VaapiImageDecodeStatus* status) {
diff --git a/media/gpu/vaapi/vaapi_jpeg_decoder.h b/media/gpu/vaapi/vaapi_jpeg_decoder.h
index e4e27a7..270a748 100644
--- a/media/gpu/vaapi/vaapi_jpeg_decoder.h
+++ b/media/gpu/vaapi/vaapi_jpeg_decoder.h
@@ -30,9 +30,9 @@
   ~VaapiJpegDecoder() override;
 
   // VaapiImageDecoder implementation.
-  bool Initialize(const base::RepeatingClosure& error_uma_cb) override;
   scoped_refptr<VASurface> Decode(base::span<const uint8_t> encoded_image,
                                   VaapiImageDecodeStatus* status) override;
+  Type GetType() const override;
 
   // Get the decoded data from the last Decode() call as a ScopedVAImage. The
   // VAImage's format will be either |preferred_image_fourcc| if the conversion
diff --git a/mojo/core/channel_mac.cc b/mojo/core/channel_mac.cc
index 1737269..8820afe 100644
--- a/mojo/core/channel_mac.cc
+++ b/mojo/core/channel_mac.cc
@@ -353,8 +353,8 @@
         sizeof(mach_msg_header_t) + sizeof(mach_msg_body_t) +
         (message->num_handles() * sizeof(mach_msg_port_descriptor_t));
     const size_t expected_message_size =
-        round_msg(mach_header_size + sizeof(uint64_t) +
-                  message->data_num_bytes() + sizeof(mach_msg_audit_trailer_t));
+        round_msg(mach_header_size + message->data_num_bytes() +
+                  sizeof(mach_msg_audit_trailer_t));
     const bool transfer_message_ool =
         expected_message_size >= send_buffer_.size();
 
diff --git a/mojo/core/channel_unittest.cc b/mojo/core/channel_unittest.cc
index e561db2..14db631 100644
--- a/mojo/core/channel_unittest.cc
+++ b/mojo/core/channel_unittest.cc
@@ -11,9 +11,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/process/process_handle.h"
-#include "base/process/process_metrics.h"
 #include "base/run_loop.h"
-#include "base/strings/stringprintf.h"
 #include "base/threading/thread.h"
 #include "build/build_config.h"
 #include "mojo/core/platform_handle_utils.h"
@@ -510,68 +508,6 @@
   EXPECT_EQ(0u, delegate_b.error_count_);
 }
 
-class SingleMessageWaiterDelegate : public Channel::Delegate {
- public:
-  SingleMessageWaiterDelegate() {}
-
-  void OnChannelMessage(const void* payload,
-                        size_t payload_size,
-                        std::vector<PlatformHandle> handles) override {
-    message_received_ = true;
-    run_loop_->Quit();
-  }
-
-  void OnChannelError(Channel::Error error) override {
-    channel_error_ = true;
-    run_loop_->Quit();
-  }
-
-  void Reset(base::RunLoop* loop) {
-    run_loop_ = loop;
-    message_received_ = false;
-    channel_error_ = false;
-  }
-
-  bool message_received() { return message_received_; }
-  bool channel_error() { return channel_error_; }
-
- private:
-  bool message_received_ = false;
-  bool channel_error_ = false;
-  base::RunLoop* run_loop_;
-  DISALLOW_COPY_AND_ASSIGN(SingleMessageWaiterDelegate);
-};
-
-TEST(ChannelTest, MessageSizeTest) {
-  base::MessageLoop message_loop(base::MessageLoop::TYPE_IO);
-  PlatformChannel platform_channel;
-
-  SingleMessageWaiterDelegate receiver_delegate;
-  scoped_refptr<Channel> receiver = Channel::Create(
-      &receiver_delegate,
-      ConnectionParams(platform_channel.TakeLocalEndpoint()),
-      Channel::HandlePolicy::kAcceptHandles, message_loop.task_runner());
-  receiver->Start();
-
-  MockChannelDelegate sender_delegate;
-  scoped_refptr<Channel> sender = Channel::Create(
-      &sender_delegate, ConnectionParams(platform_channel.TakeRemoteEndpoint()),
-      Channel::HandlePolicy::kAcceptHandles, message_loop.task_runner());
-  sender->Start();
-
-  for (uint32_t i = 0; i < base::GetPageSize() * 4; ++i) {
-    SCOPED_TRACE(base::StringPrintf("message size %d", i));
-    sender->Write(std::make_unique<Channel::Message>(i, 0));
-
-    base::RunLoop loop;
-    receiver_delegate.Reset(&loop);
-    loop.Run();
-
-    EXPECT_TRUE(receiver_delegate.message_received());
-    EXPECT_FALSE(receiver_delegate.channel_error());
-  }
-}
-
 }  // namespace
 }  // namespace core
 }  // namespace mojo
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
index 451cc99..dd27bf5 100644
--- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -51,7 +51,6 @@
   "//ui/base/ime/mojo/typemaps.gni",
   "//ui/base/mojo/typemaps.gni",
   "//ui/display/mojo/typemaps.gni",
-  "//ui/events/devices/mojo/typemaps.gni",
   "//ui/events/mojo/typemaps.gni",
   "//ui/gfx/typemaps.gni",
   "//ui/latency/mojo/typemaps.gni",
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 3cb6a04..524fbbcd 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -51,10 +51,6 @@
     ]
   }
 
-  if (use_aura) {
-    deps += [ "//services/ws/input_devices:tests" ]
-  }
-
   if (is_android) {
     deps += [
       "//services/data_decoder/public/cpp/android:safe_json_java",
diff --git a/services/content/public/cpp/DEPS b/services/content/public/cpp/DEPS
index 5b70cd5..b541399 100644
--- a/services/content/public/cpp/DEPS
+++ b/services/content/public/cpp/DEPS
@@ -1,8 +1,5 @@
 include_rules = [
   "+net/http/http_response_headers.h",
-
-  "+services/ws/public",
-
   "+ui/accessibility",
   "+ui/aura",
   "+ui/views",
diff --git a/services/tracing/perfetto/json_trace_exporter.cc b/services/tracing/perfetto/json_trace_exporter.cc
index 0b9d58e..f350433 100644
--- a/services/tracing/perfetto/json_trace_exporter.cc
+++ b/services/tracing/perfetto/json_trace_exporter.cc
@@ -216,7 +216,7 @@
   // Delegate to the subclasses to parse the packets. It will create
   // ScopedJSONTraceEventAppenders to write the contained events along with
   // other trace fields.
-  ProcessPackets(packets);
+  ProcessPackets(packets, has_more);
 
   if (!has_more) {
     if (label_filter_.empty()) {
diff --git a/services/tracing/perfetto/json_trace_exporter.h b/services/tracing/perfetto/json_trace_exporter.h
index 596d778d..f14ca24 100644
--- a/services/tracing/perfetto/json_trace_exporter.h
+++ b/services/tracing/perfetto/json_trace_exporter.h
@@ -221,8 +221,8 @@
   // Subclasses implement this to add data from |packets| to the JSON output.
   // For example they can add traceEvents through AddTraceEvent(), or add
   // metadata through AddChromeMetadata().
-  virtual void ProcessPackets(
-      const std::vector<perfetto::TracePacket>& packets) = 0;
+  virtual void ProcessPackets(const std::vector<perfetto::TracePacket>& packets,
+                              bool has_more) = 0;
 
   // If true then all trace events should be skipped. AddTraceEvent should not
   // be called.
diff --git a/services/tracing/perfetto/json_trace_exporter_unittest.cc b/services/tracing/perfetto/json_trace_exporter_unittest.cc
index 107033c..eabd334 100644
--- a/services/tracing/perfetto/json_trace_exporter_unittest.cc
+++ b/services/tracing/perfetto/json_trace_exporter_unittest.cc
@@ -104,8 +104,8 @@
   std::vector<perfetto::protos::TraceStats> stats;
 
  protected:
-  void ProcessPackets(
-      const std::vector<perfetto::TracePacket>& packets) override {
+  void ProcessPackets(const std::vector<perfetto::TracePacket>& packets,
+                      bool has_more) override {
     ++process_packets_calls_;
     DCHECK(packets.size() == infos_.size())
         << " different sizes of packets versus expected behaviour test set up "
diff --git a/services/tracing/perfetto/track_event_json_exporter.cc b/services/tracing/perfetto/track_event_json_exporter.cc
index 2dfb388..5c6be7d 100644
--- a/services/tracing/perfetto/track_event_json_exporter.cc
+++ b/services/tracing/perfetto/track_event_json_exporter.cc
@@ -5,6 +5,7 @@
 #include "services/tracing/perfetto/track_event_json_exporter.h"
 
 #include <cinttypes>
+#include <memory>
 
 #include "base/json/string_escape.h"
 #include "base/strings/string_util.h"
@@ -69,12 +70,15 @@
     : JSONTraceExporter(std::move(argument_filter_predicate),
                         std::move(metadata_filter_predicate),
                         std::move(callback)),
-      current_state_(0) {}
+      current_state_(std::make_unique<ProducerWriterState>(0)) {}
 
-TrackEventJSONExporter::~TrackEventJSONExporter() {}
+TrackEventJSONExporter::~TrackEventJSONExporter() {
+  DCHECK(!current_state_ || !current_state_->last_seen_thread_descriptor);
+}
 
 void TrackEventJSONExporter::ProcessPackets(
-    const std::vector<perfetto::TracePacket>& packets) {
+    const std::vector<perfetto::TracePacket>& packets,
+    bool has_more) {
   for (auto& encoded_packet : packets) {
     // These are perfetto::TracePackets, but ChromeTracePacket is a mirror that
     // reduces binary bloat and only has the fields we are interested in. So
@@ -86,7 +90,7 @@
     // If this is a different packet_sequence_id we have to reset all our state
     // and wait for the first state_clear before emitting anything.
     if (packet.trusted_packet_sequence_id() !=
-        current_state_.trusted_packet_sequence_id) {
+        current_state_->trusted_packet_sequence_id) {
       StartNewState(packet.trusted_packet_sequence_id(),
                     packet.incremental_state_cleared());
     } else if (packet.incremental_state_cleared()) {
@@ -95,10 +99,10 @@
       // If we've lost packets we can no longer trust any timestamp data and
       // other state which might have been dropped. We will keep skipping events
       // until we start a new sequence.
-      LOG_IF(ERROR, current_state_.incomplete)
+      LOG_IF(ERROR, current_state_->incomplete)
           << "Previous packet was dropped. Skipping TraceEvents until reset or "
           << "new sequence.";
-      current_state_.incomplete = true;
+      current_state_->incomplete = true;
     }
 
     // Now we process the data from the packet. First by getting the interned
@@ -123,37 +127,42 @@
       // and has no equivalent in the old trace format. We thus ignore it.
     }
   }
+  if (!has_more) {
+    EmitThreadDescriptorIfNeeded();
+  }
 }
 
 TrackEventJSONExporter::ProducerWriterState::ProducerWriterState(
     uint32_t sequence_id)
-    : ProducerWriterState(sequence_id, false, false, t