diff --git a/DEPS b/DEPS
index 0b94e37..16cbd52 100644
--- a/DEPS
+++ b/DEPS
@@ -137,7 +137,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '8e003ab0c8aac7afc5c48931fa6f8ed667942621',
+  'v8_revision': '09be7f2ffcd2aab7221a97235793c8e8ea7a2293',
   # 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.
@@ -145,15 +145,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': '04e389d154819234ae1d4abb92102ebdffa8de8c',
+  'angle_revision': 'aefbea29626b8ace9fdb19b1d829bd076f4a87fa',
   # 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': '6cf65f671c1d62283819253b7d03d37134ed22fa',
+  'swiftshader_revision': 'ab1e2b49fe88bc2067a9e0961bc20a550facdcb7',
   # 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': '31136552bc0ca1848addede014d518a5ef6be646',
+  'pdfium_revision': '908d73f65ad915f54a726653abc41b54e4411ef5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -196,7 +196,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': '72ee2533b189d865435d3d712fdd15a72a61491e',
+  'catapult_revision': 'fd64d5d2d4a77c909aa24986a25b43f685d4504b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -704,7 +704,7 @@
     Var('chromium_git') + '/angle/angle.git' + '@' +  Var('angle_revision'),
 
   'src/third_party/dav1d/libdav1d':
-    Var('chromium_git') + '/external/github.com/videolan/dav1d.git' + '@' + 'd400361524ce739db30d552a9e54809d812710c6',
+    Var('chromium_git') + '/external/github.com/videolan/dav1d.git' + '@' + 'fc3777b44c0449180073665eb78070d388b11738',
 
   'src/third_party/dawn':
     Var('dawn_git') + '/dawn.git' + '@' +  Var('dawn_revision'),
@@ -802,7 +802,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c3df392d69c9e160a0c04a73ecafb47c173559a8',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '57f13dec7309ccdba1c7e400980031dde2072cbd',
       'condition': 'checkout_linux',
   },
 
@@ -1345,7 +1345,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6f0b34abee8dba611c253738d955c59f703c147a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'f0792ce4107c5eca12fff033ee6f16d37e69033a',
+    Var('webrtc_git') + '/src.git' + '@' + 'fe57f6252fbd4d56fffbeef72dc677b11540af9e',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1386,7 +1386,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6901330cf32ab3d5cb308c808fd84e340973ed81',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@4a8e975fb266ea607ee566754f0e3550cfd3af70',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/common/crash_reporter/aw_crash_reporter_client.cc b/android_webview/common/crash_reporter/aw_crash_reporter_client.cc
index 74eba68..cd0db55 100644
--- a/android_webview/common/crash_reporter/aw_crash_reporter_client.cc
+++ b/android_webview/common/crash_reporter/aw_crash_reporter_client.cc
@@ -78,8 +78,13 @@
       return 100;
     }
 
-    if (version_info::android::GetChannel() == version_info::Channel::STABLE)
+    version_info::Channel channel = version_info::android::GetChannel();
+    // Downsample unknown channel as a precaution in case it ends up being
+    // shipped.
+    if (channel == version_info::Channel::STABLE ||
+        channel == version_info::Channel::UNKNOWN) {
       return kCrashDumpPercentageForStable;
+    }
 
     return 100;
   }
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 584ce69..65c9105 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -475,8 +475,6 @@
     "metrics/task_switch_source.h",
     "metrics/task_switch_time_tracker.cc",
     "metrics/task_switch_time_tracker.h",
-    "metrics/time_to_first_present_recorder.cc",
-    "metrics/time_to_first_present_recorder.h",
     "metrics/user_metrics_action.h",
     "metrics/user_metrics_recorder.cc",
     "metrics/user_metrics_recorder.h",
@@ -1214,8 +1212,6 @@
     "wm/toplevel_window_event_handler.h",
     "wm/video_detector.cc",
     "wm/video_detector.h",
-    "wm/widget_finder.cc",
-    "wm/widget_finder.h",
     "wm/window_animations.cc",
     "wm/window_animations.h",
     "wm/window_cycle_controller.cc",
@@ -1289,8 +1285,6 @@
     "//services/device/public/mojom",
     "//services/media_session/public/mojom",
     "//services/service_manager/public/cpp",
-    "//services/ws:host",
-    "//services/ws:lib",
     "//services/ws/common",
     "//services/ws/public/cpp",
     "//services/ws/public/cpp/input_devices",
@@ -1385,11 +1379,8 @@
     "//services/data_decoder/public/cpp",
     "//services/preferences/public/cpp",
     "//services/service_manager/public/cpp",
-    "//services/ws/gpu_host",
-    "//services/ws/public/cpp/host",
     "//services/ws/public/cpp/input_devices:input_device_controller",
     "//services/ws/public/mojom/input_devices",
-    "//services/ws/remote_view_host",
 
     # TODO(msw): Remove this; ash should not depend on blink/webkit.
     "//third_party/blink/public:blink_headers",
@@ -1548,11 +1539,6 @@
     "//device/bluetooth",
     "//net",
     "//services/device/public/mojom",
-    "//services/ws:lib",
-    "//services/ws/ime/test_ime_driver:lib",
-    "//services/ws/ime/test_ime_driver/public/cpp:manifest",
-    "//services/ws/ime/test_ime_driver/public/mojom",
-    "//services/ws/remote_view_host",
     "//skia",
     "//ui/aura",
     "//ui/base",
@@ -1966,7 +1952,6 @@
     "//services/media_session/public/cpp/test:test_support",
     "//services/media_session/public/mojom",
     "//services/viz/public/cpp:manifest",
-    "//services/ws:test_support",
     "//services/ws/public/cpp/input_devices:test_support",
     "//services/ws/public/mojom",
     "//skia",
@@ -2108,8 +2093,6 @@
     "login/login_screen_test_api.h",
     "metrics/task_switch_time_tracker_test_api.cc",
     "metrics/task_switch_time_tracker_test_api.h",
-    "metrics/time_to_first_present_recorder_test_api.cc",
-    "metrics/time_to_first_present_recorder_test_api.h",
     "metrics/user_metrics_recorder_test_api.cc",
     "metrics/user_metrics_recorder_test_api.h",
     "mojo_test_interface_factory.cc",
@@ -2233,9 +2216,7 @@
     "//components/viz/test:test_support",
     "//device/bluetooth",
     "//services/device/public/mojom",
-    "//services/ws:test_support",
     "//services/ws/public/cpp",
-    "//services/ws/public/cpp/host",
     "//services/ws/public/cpp/input_devices",
     "//services/ws/public/cpp/input_devices:test_support",
     "//services/ws/public/mojom",
diff --git a/ash/accelerators/debug_commands.cc b/ash/accelerators/debug_commands.cc
index 26db6e8..d161a8b 100644
--- a/ash/accelerators/debug_commands.cc
+++ b/ash/accelerators/debug_commands.cc
@@ -13,7 +13,6 @@
 #include "ash/touch/touch_devices_controller.h"
 #include "ash/wallpaper/wallpaper_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "ash/wm/widget_finder.h"
 #include "ash/wm/window_properties.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
@@ -52,7 +51,7 @@
   aura::Window* active_window = wm::GetActiveWindow();
   if (!active_window)
     return;
-  views::Widget* widget = GetInternalWidgetForWindow(active_window);
+  views::Widget* widget = views::Widget::GetWidgetForNativeView(active_window);
   if (!widget)
     return;
   views::PrintViewHierarchy(widget->GetRootView());
diff --git a/ash/accessibility/focus_ring_controller.cc b/ash/accessibility/focus_ring_controller.cc
index 03fade2..e673a66 100644
--- a/ash/accessibility/focus_ring_controller.cc
+++ b/ash/accessibility/focus_ring_controller.cc
@@ -5,7 +5,6 @@
 #include "ash/accessibility/focus_ring_controller.h"
 
 #include "ash/accessibility/focus_ring_layer.h"
-#include "ash/shell.h"
 #include "ash/system/tray/actionable_view.h"
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/wm/window_util.h"
@@ -29,14 +28,12 @@
   visible_ = visible;
 
   if (visible_) {
-    views::WidgetFocusManager::GetInstance(Shell::GetPrimaryRootWindow())
-        ->AddFocusChangeListener(this);
+    views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this);
     aura::Window* active_window = wm::GetActiveWindow();
     if (active_window)
       SetWidget(views::Widget::GetWidgetForNativeWindow(active_window));
   } else {
-    views::WidgetFocusManager::GetInstance(Shell::GetPrimaryRootWindow())
-        ->RemoveFocusChangeListener(this);
+    views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this);
     SetWidget(nullptr);
   }
 }
diff --git a/ash/app_list/BUILD.gn b/ash/app_list/BUILD.gn
index 12c0d68..be73921 100644
--- a/ash/app_list/BUILD.gn
+++ b/ash/app_list/BUILD.gn
@@ -118,9 +118,6 @@
     "//components/sync",
     "//mojo/public/cpp/bindings",
     "//services/content/public/cpp",
-    "//services/ws/public/cpp",
-    "//services/ws/public/mojom",
-    "//services/ws/remote_view_host",
     "//skia",
     "//third_party/icu",
     "//ui/accessibility",
@@ -138,6 +135,7 @@
     "//ui/strings",
     "//ui/views",
     "//ui/wm",
+    "//ui/wm/public",
   ]
 
   # TODO(hejq): Remove this once app_list is migrated. http://crbug.com/733662
diff --git a/ash/app_list/DEPS b/ash/app_list/DEPS
index 828f97b..4cc7154 100644
--- a/ash/app_list/DEPS
+++ b/ash/app_list/DEPS
@@ -9,7 +9,6 @@
   "+components/sync",
   "+mojo/public/cpp",
   "+net/http/http_response_headers.h",
-  "+services/ws/public",
   "+skia",
   "+third_party/google_toolbox_for_mac/src",
   "+third_party/skia",
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index cc22f13..c0f5062 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -24,6 +24,7 @@
 #include "ash/assistant/util/deep_link_util.h"
 #include "ash/home_screen/home_launcher_gesture_handler.h"
 #include "ash/home_screen/home_screen_controller.h"
+#include "ash/public/cpp/app_list/app_list_client.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_metrics.h"
 #include "ash/public/cpp/ash_pref_names.h"
@@ -166,8 +167,8 @@
       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
 }
 
-void AppListControllerImpl::SetClient(mojom::AppListClientPtr client_ptr) {
-  client_ = std::move(client_ptr);
+void AppListControllerImpl::SetClient(app_list::AppListClient* client) {
+  client_ = client;
 }
 
 void AppListControllerImpl::BindRequest(
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h
index e56de73..f181a39 100644
--- a/ash/app_list/app_list_controller_impl.h
+++ b/ash/app_list/app_list_controller_impl.h
@@ -22,6 +22,7 @@
 #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/shelf_types.h"
 #include "ash/public/interfaces/app_list.mojom.h"
@@ -52,7 +53,8 @@
 // functions that allow Chrome to modify and observe the Shelf and AppListModel
 // state.
 class ASH_EXPORT AppListControllerImpl
-    : public mojom::AppListController,
+    : public app_list::AppListController,
+      public mojom::AppListController,
       public SessionObserver,
       public app_list::AppListModelObserver,
       public app_list::AppListViewDelegate,
@@ -81,8 +83,10 @@
 
   app_list::AppListPresenterImpl* presenter() { return &presenter_; }
 
+  // app_list::AppListController:
+  void SetClient(app_list::AppListClient* client) override;
+
   // mojom::AppListController:
-  void SetClient(mojom::AppListClientPtr client_ptr) override;
   void AddItem(AppListItemMetadataPtr app_item) override;
   void AddItemToFolder(AppListItemMetadataPtr app_item,
                        const std::string& folder_id) override;
@@ -354,7 +358,7 @@
   // Record the app launch for AppListAppLaunchedV2 metric.
   void RecordAppLaunched(mojom::AppListLaunchedFrom launched_from);
 
-  mojom::AppListClientPtr client_;
+  app_list::AppListClient* client_ = nullptr;
 
   std::unique_ptr<app_list::AppListModel> model_;
   app_list::SearchModel search_model_;
diff --git a/ash/app_list/app_list_metrics.cc b/ash/app_list/app_list_metrics.cc
index 1eb216e72..1567ef71 100644
--- a/ash/app_list/app_list_metrics.cc
+++ b/ash/app_list/app_list_metrics.cc
@@ -282,8 +282,8 @@
   ash::CommandId command_id = static_cast<ash::CommandId>(command_id_number);
 
   // Consider all platform app menu options as launches.
-  if (command_id >= ash::CommandId::USE_LAUNCH_TYPE_COMMAND_END &&
-      command_id < ash::CommandId::LAUNCH_APP_SHORTCUT_FIRST) {
+  if (command_id >= ash::CommandId::EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
+      command_id < ash::CommandId::EXTENSIONS_CONTEXT_CUSTOM_LAST) {
     return true;
   }
 
@@ -332,6 +332,8 @@
     case ash::CommandId::USE_LAUNCH_TYPE_WINDOW:
     case ash::CommandId::USE_LAUNCH_TYPE_COMMAND_END:
     case ash::CommandId::STOP_APP:
+    case ash::CommandId::EXTENSIONS_CONTEXT_CUSTOM_FIRST:
+    case ash::CommandId::EXTENSIONS_CONTEXT_CUSTOM_LAST:
     case ash::CommandId::COMMAND_ID_COUNT:
       return false;
   }
diff --git a/ash/app_list/test/app_list_test_helper.cc b/ash/app_list/test/app_list_test_helper.cc
index ebeb739..c2a1868 100644
--- a/ash/app_list/test/app_list_test_helper.cc
+++ b/ash/app_list/test/app_list_test_helper.cc
@@ -22,11 +22,17 @@
 
   // Use a new app list client for each test
   app_list_client_ = std::make_unique<TestAppListClient>();
-  app_list_controller_->SetClient(
-      app_list_client_->CreateInterfacePtrAndBind());
+  app_list_controller_->SetClient(app_list_client_.get());
 }
 
-AppListTestHelper::~AppListTestHelper() = default;
+AppListTestHelper::~AppListTestHelper() {
+  // |app_list_controller_| could be released before Shell in KioskNextShell
+  // tests.
+  if (app_list_controller_ &&
+      app_list_controller_ == Shell::Get()->app_list_controller()) {
+    app_list_controller_->SetClient(nullptr);
+  }
+}
 
 void AppListTestHelper::WaitUntilIdle() {
   app_list_controller_->FlushForTesting();
diff --git a/ash/app_list/test/test_app_list_client.cc b/ash/app_list/test/test_app_list_client.cc
index b926817..eb64a4f 100644
--- a/ash/app_list/test/test_app_list_client.cc
+++ b/ash/app_list/test/test_app_list_client.cc
@@ -8,15 +8,8 @@
 
 namespace ash {
 
-TestAppListClient::TestAppListClient() : binding_(this) {}
-
-TestAppListClient::~TestAppListClient() {}
-
-mojom::AppListClientPtr TestAppListClient::CreateInterfacePtrAndBind() {
-  mojom::AppListClientPtr ptr;
-  binding_.Bind(mojo::MakeRequest(&ptr));
-  return ptr;
-}
+TestAppListClient::TestAppListClient() = default;
+TestAppListClient::~TestAppListClient() = default;
 
 void TestAppListClient::GetSearchResultContextMenuModel(
     const std::string& result_id,
diff --git a/ash/app_list/test/test_app_list_client.h b/ash/app_list/test/test_app_list_client.h
index 09f9f25..6ca8d929 100644
--- a/ash/app_list/test/test_app_list_client.h
+++ b/ash/app_list/test/test_app_list_client.h
@@ -7,22 +7,19 @@
 
 #include <string>
 
-#include "ash/public/interfaces/app_list.mojom.h"
+#include "ash/public/cpp/app_list/app_list_client.h"
 #include "base/macros.h"
-#include "mojo/public/cpp/bindings/binding.h"
 
 namespace ash {
 
 // A test implementation of AppListClient that records function call counts.
 // Registers itself as the presenter for the app list on construction.
-class TestAppListClient : public mojom::AppListClient {
+class TestAppListClient : public app_list::AppListClient {
  public:
   TestAppListClient();
   ~TestAppListClient() override;
 
-  mojom::AppListClientPtr CreateInterfacePtrAndBind();
-
-  // ash::mojom::AppListClient:
+  // app_list::AppListClient:
   void StartSearch(const base::string16& trimmed_query) override {}
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
@@ -69,8 +66,6 @@
                                        bool visibility) override {}
 
  private:
-  mojo::Binding<mojom::AppListClient> binding_;
-
   DISALLOW_COPY_AND_ASSIGN(TestAppListClient);
 };
 
diff --git a/ash/components/fast_ink/BUILD.gn b/ash/components/fast_ink/BUILD.gn
index e7d7bd2..7a4345f 100644
--- a/ash/components/fast_ink/BUILD.gn
+++ b/ash/components/fast_ink/BUILD.gn
@@ -19,7 +19,6 @@
     "//gpu",
     "//gpu/command_buffer/client",
     "//gpu/command_buffer/common",
-    "//services/ws/public/mojom",
     "//skia",
     "//ui/aura",
     "//ui/gfx",
diff --git a/ash/focus_cycler.cc b/ash/focus_cycler.cc
index 3069721..eed988b 100644
--- a/ash/focus_cycler.cc
+++ b/ash/focus_cycler.cc
@@ -6,7 +6,6 @@
 
 #include "ash/shell.h"
 #include "ash/wm/mru_window_tracker.h"
-#include "ash/wm/widget_finder.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ui/views/accessible_pane_view.h"
@@ -42,7 +41,7 @@
 void FocusCycler::RotateFocus(Direction direction) {
   aura::Window* window = wm::GetActiveWindow();
   if (window) {
-    views::Widget* widget = GetInternalWidgetForWindow(window);
+    views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
     // First try to rotate focus within the active widget. If that succeeds,
     // we're done.
     if (widget &&
@@ -87,7 +86,7 @@
         break;
       auto* window = mru_windows.front();
       wm::GetWindowState(window)->Activate();
-      views::Widget* widget = GetInternalWidgetForWindow(window);
+      views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
       if (!widget)
         break;
       views::FocusManager* focus_manager = widget->GetFocusManager();
diff --git a/ash/kiosk_next/kiosk_next_shell_controller.cc b/ash/kiosk_next/kiosk_next_shell_controller.cc
index 4549bc4..88e8f49 100644
--- a/ash/kiosk_next/kiosk_next_shell_controller.cc
+++ b/ash/kiosk_next/kiosk_next_shell_controller.cc
@@ -12,14 +12,45 @@
 #include "ash/kiosk_next/kiosk_next_shell_observer.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_pref_names.h"
+#include "ash/public/cpp/shelf_model.h"
 #include "ash/session/session_controller_impl.h"
+#include "ash/shelf/home_button_delegate.h"
 #include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
 #include "components/account_id/account_id.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace ash {
 
+namespace {
+
+std::unique_ptr<ShelfModel> CreateKioskNextShelfModel() {
+  auto shelf_model = std::make_unique<ShelfModel>();
+
+  shelf_model->SetShelfItemDelegate(ShelfID(kBackButtonId), nullptr);
+  shelf_model->SetShelfItemDelegate(ShelfID(kAppListId),
+                                    std::make_unique<HomeButtonDelegate>());
+
+  DCHECK_EQ(0, shelf_model->ItemIndexByID(ShelfID(kBackButtonId)));
+  DCHECK_EQ(1, shelf_model->ItemIndexByID(ShelfID(kAppListId)));
+
+  ShelfItem back_item = shelf_model->items()[0];
+  ShelfItem home_item = shelf_model->items()[1];
+
+  back_item.title = l10n_util::GetStringUTF16(IDS_ASH_SHELF_BACK_BUTTON_TITLE);
+  home_item.title =
+      l10n_util::GetStringUTF16(IDS_ASH_SHELF_APP_LIST_LAUNCHER_TITLE);
+
+  shelf_model->Set(0, back_item);
+  shelf_model->Set(1, home_item);
+  return shelf_model;
+}
+
+}  // namespace
+
 KioskNextShellController::KioskNextShellController() = default;
 
 KioskNextShellController::~KioskNextShellController() = default;
@@ -90,6 +121,8 @@
   kiosk_next_shell_client_->LaunchKioskNextShell(
       session_controller->GetPrimaryUserSession()->user_info.account_id);
 
+  shelf_model_ = CreateKioskNextShelfModel();
+
   // Notify observers that KioskNextShell has been enabled.
   for (KioskNextShellObserver& observer : observer_list_) {
     observer.OnKioskNextEnabled();
diff --git a/ash/kiosk_next/kiosk_next_shell_controller.h b/ash/kiosk_next/kiosk_next_shell_controller.h
index ecc046b..6de77c06 100644
--- a/ash/kiosk_next/kiosk_next_shell_controller.h
+++ b/ash/kiosk_next/kiosk_next_shell_controller.h
@@ -5,6 +5,8 @@
 #ifndef ASH_KIOSK_NEXT_KIOSK_NEXT_SHELL_CONTROLLER_H_
 #define ASH_KIOSK_NEXT_KIOSK_NEXT_SHELL_CONTROLLER_H_
 
+#include <memory>
+
 #include "ash/ash_export.h"
 #include "ash/kiosk_next/kiosk_next_shell_observer.h"
 #include "ash/public/interfaces/kiosk_next_shell.mojom.h"
@@ -18,6 +20,7 @@
 namespace ash {
 
 class KioskNextHomeController;
+class ShelfModel;
 
 // KioskNextShellController allows an ash consumer to manage a Kiosk Next
 // session. During this session most system functions are disabled and we launch
@@ -48,6 +51,8 @@
   // SessionObserver:
   void OnActiveUserPrefServiceChanged(PrefService* pref_service) override;
 
+  ShelfModel* shelf_model() { return shelf_model_.get(); }
+
  private:
   // Launches Kiosk Next if the pref is enabled and the KioskNextShellClient is
   // available.
@@ -62,6 +67,11 @@
   // Controls the KioskNext home screen when the Kiosk Next Shell is enabled.
   std::unique_ptr<KioskNextHomeController> kiosk_next_home_controller_;
 
+  // When KioskNextShell is enabled, only the home button and back button are
+  // made visible on the Shelf. KioskNextShellController therefore hosts its own
+  // ShelfModel to control the entries visible on the shelf.
+  std::unique_ptr<ShelfModel> shelf_model_;
+
   DISALLOW_COPY_AND_ASSIGN(KioskNextShellController);
 };
 
diff --git a/ash/login/ui/arrow_button_view.cc b/ash/login/ui/arrow_button_view.cc
index 16caa92..36b59c25 100644
--- a/ash/login/ui/arrow_button_view.cc
+++ b/ash/login/ui/arrow_button_view.cc
@@ -5,6 +5,7 @@
 #include "ash/login/ui/arrow_button_view.h"
 
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ui/accessibility/ax_node_data.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/paint_vector_icon.h"
 
@@ -45,6 +46,11 @@
   // Draw arrow icon.
   views::ImageButton::PaintButtonContents(canvas);
 }
+void ArrowButtonView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+  // TODO(tbarzic): Fix this - https://crbug.com/961930.
+  if (GetAccessibleName().empty())
+    node_data->SetNameExplicitlyEmpty();
+}
 
 void ArrowButtonView::SetBackgroundColor(SkColor color) {
   background_color_ = color;
diff --git a/ash/login/ui/arrow_button_view.h b/ash/login/ui/arrow_button_view.h
index 441b044..ff61ccc 100644
--- a/ash/login/ui/arrow_button_view.h
+++ b/ash/login/ui/arrow_button_view.h
@@ -20,6 +20,7 @@
 
   // views::Button:
   void PaintButtonContents(gfx::Canvas* canvas) override;
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
   // Set background color of the button.
   void SetBackgroundColor(SkColor color);
diff --git a/ash/metrics/time_to_first_present_recorder.cc b/ash/metrics/time_to_first_present_recorder.cc
deleted file mode 100644
index f03d7f7..0000000
--- a/ash/metrics/time_to_first_present_recorder.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/metrics/time_to_first_present_recorder.h"
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/time/time.h"
-#include "ui/aura/window.h"
-#include "ui/aura/window_tree_host.h"
-#include "ui/compositor/compositor.h"
-#include "ui/gfx/presentation_feedback.h"
-
-namespace ash {
-
-TimeToFirstPresentRecorder::TimeToFirstPresentRecorder(aura::Window* window) {
-  aura::WindowTreeHost* window_tree_host = window->GetHost();
-  DCHECK(window_tree_host);
-  window_tree_host->compositor()->RequestPresentationTimeForNextFrame(
-      base::BindOnce(&TimeToFirstPresentRecorder::DidPresentCompositorFrame,
-                     base::Unretained(this)));
-}
-
-TimeToFirstPresentRecorder::~TimeToFirstPresentRecorder() = default;
-
-void TimeToFirstPresentRecorder::Bind(
-    mojom::ProcessCreationTimeRecorderRequest request) {
-  // Process createion time should only be set once.
-  if (binding_.is_bound() || !process_creation_time_.is_null())
-    return;
-
-  binding_.Bind(std::move(request));
-}
-
-void TimeToFirstPresentRecorder::SetMainProcessCreationTime(
-    base::TimeTicks start_time) {
-  if (!process_creation_time_.is_null())
-    return;
-
-  process_creation_time_ = start_time;
-  LogTime();
-
-  // Process creation time should be set only once.
-  binding_.Close();
-}
-
-void TimeToFirstPresentRecorder::LogTime() {
-  if (present_time_.is_null() || process_creation_time_.is_null())
-    return;
-
-  UMA_HISTOGRAM_TIMES("Ash.ProcessCreationToFirstPresent",
-                      time_to_first_present());
-  if (log_callback_)
-    std::move(log_callback_).Run();
-}
-
-void TimeToFirstPresentRecorder::DidPresentCompositorFrame(
-    const gfx::PresentationFeedback& feedback) {
-  DCHECK(present_time_.is_null());  // This should only be called once.
-  present_time_ = feedback.timestamp;
-  LogTime();
-}
-
-}  // namespace ash
diff --git a/ash/metrics/time_to_first_present_recorder.h b/ash/metrics/time_to_first_present_recorder.h
deleted file mode 100644
index 1e5c453..0000000
--- a/ash/metrics/time_to_first_present_recorder.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_METRICS_TIME_TO_FIRST_PRESENT_RECORDER_H_
-#define ASH_METRICS_TIME_TO_FIRST_PRESENT_RECORDER_H_
-
-#include <stdint.h>
-
-#include "ash/public/interfaces/process_creation_time_recorder.mojom.h"
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/time/time.h"
-#include "mojo/public/cpp/bindings/binding.h"
-
-namespace aura {
-class Window;
-}
-
-namespace gfx {
-struct PresentationFeedback;
-}
-
-namespace ash {
-
-class TimeToFirstPresentRecorderTestApi;
-
-// Used for tracking the time main started to the time the first bits make it
-// the screen and logging a histogram of the time. Chrome is responsible for
-// providing the start time by way of ProcessCreationTimeRecorder.
-//
-// This only logs the time to present the primary root window.
-class TimeToFirstPresentRecorder : public mojom::ProcessCreationTimeRecorder {
- public:
-  explicit TimeToFirstPresentRecorder(aura::Window* window);
-  ~TimeToFirstPresentRecorder() override;
-
-  void Bind(mojom::ProcessCreationTimeRecorderRequest request);
-
- private:
-  friend class TimeToFirstPresentRecorderTestApi;
-
-  // If both times are available the time to present is logged.
-  void LogTime();
-
-  // Callback from the compositor when it presented a valid frame.
-  void DidPresentCompositorFrame(const gfx::PresentationFeedback& feedback);
-
-  base::TimeDelta time_to_first_present() const {
-    return present_time_ - process_creation_time_;
-  }
-
-  // mojom::ProcessCreationTimeRecorder:
-  void SetMainProcessCreationTime(base::TimeTicks start_time) override;
-
-  base::TimeTicks process_creation_time_;
-  base::TimeTicks present_time_;
-
-  // Only used by tests. If valid it's Run() when both times are determined.
-  base::OnceClosure log_callback_;
-
-  mojo::Binding<mojom::ProcessCreationTimeRecorder> binding_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(TimeToFirstPresentRecorder);
-};
-
-}  // namespace ash
-
-#endif  // ASH_METRICS_TIME_TO_FIRST_PRESENT_RECORDER_H_
diff --git a/ash/metrics/time_to_first_present_recorder_test_api.cc b/ash/metrics/time_to_first_present_recorder_test_api.cc
deleted file mode 100644
index c00be7d..0000000
--- a/ash/metrics/time_to_first_present_recorder_test_api.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/metrics/time_to_first_present_recorder_test_api.h"
-
-#include "ash/metrics/time_to_first_present_recorder.h"
-#include "ash/shell.h"
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
-
-namespace ash {
-
-TimeToFirstPresentRecorderTestApi::TimeToFirstPresentRecorderTestApi() =
-    default;
-
-TimeToFirstPresentRecorderTestApi::~TimeToFirstPresentRecorderTestApi() =
-    default;
-
-// static
-void TimeToFirstPresentRecorderTestApi::BindRequest(
-    mojom::TimeToFirstPresentRecorderTestApiRequest request) {
-  mojo::MakeStrongBinding(std::make_unique<TimeToFirstPresentRecorderTestApi>(),
-                          std::move(request));
-}
-
-void TimeToFirstPresentRecorderTestApi::GetProcessCreationToFirstPresentTime(
-    GetProcessCreationToFirstPresentTimeCallback callback) {
-  TimeToFirstPresentRecorder* recorder =
-      Shell::Get()->time_to_first_present_recorder();
-  if (recorder->process_creation_time_.is_null() ||
-      recorder->present_time_.is_null()) {
-    // Still waiting for time. Schedule a callback with
-    // TimeToFirstPresentRecorder. This only supports one callback at a time,
-    // which should be fine for tests.
-    DCHECK(recorder->log_callback_.is_null());
-    recorder->log_callback_ = base::BindOnce(
-        &TimeToFirstPresentRecorderTestApi::OnLog, base::Unretained(this));
-    DCHECK(!get_creation_time_callback_);
-    get_creation_time_callback_ = std::move(callback);
-    return;
-  }
-  std::move(callback).Run(recorder->time_to_first_present());
-}
-
-void TimeToFirstPresentRecorderTestApi::OnLog() {
-  TimeToFirstPresentRecorder* recorder =
-      Shell::Get()->time_to_first_present_recorder();
-  DCHECK(!recorder->process_creation_time_.is_null() &&
-         !recorder->present_time_.is_null());
-  std::move(get_creation_time_callback_)
-      .Run(Shell::Get()
-               ->time_to_first_present_recorder()
-               ->time_to_first_present());
-}
-
-}  // namespace ash
diff --git a/ash/metrics/time_to_first_present_recorder_test_api.h b/ash/metrics/time_to_first_present_recorder_test_api.h
deleted file mode 100644
index 0b1240d..0000000
--- a/ash/metrics/time_to_first_present_recorder_test_api.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_METRICS_TIME_TO_FIRST_PRESENT_RECORDER_TEST_API_H_
-#define ASH_METRICS_TIME_TO_FIRST_PRESENT_RECORDER_TEST_API_H_
-
-#include "ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom.h"
-#include "base/macros.h"
-
-namespace ash {
-
-class TimeToFirstPresentRecorderTestApi
-    : public mojom::TimeToFirstPresentRecorderTestApi {
- public:
-  TimeToFirstPresentRecorderTestApi();
-  ~TimeToFirstPresentRecorderTestApi() override;
-
-  // Creates and binds an instance from a remote request (e.g. from chrome).
-  static void BindRequest(
-      mojom::TimeToFirstPresentRecorderTestApiRequest request);
-
-  // mojom::TimeToFirstPresentRecorderTestApi:
-  void GetProcessCreationToFirstPresentTime(
-      GetProcessCreationToFirstPresentTimeCallback callback) override;
-
- private:
-  void OnLog();
-
-  // If valid GetProcessCreationToFirstPresentTimeCallback() was called and
-  // we're waiting for TimeToFirstPresentRecorder to see the first log.
-  GetProcessCreationToFirstPresentTimeCallback get_creation_time_callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(TimeToFirstPresentRecorderTestApi);
-};
-
-}  // namespace ash
-
-#endif  // ASH_METRICS_TIME_TO_FIRST_PRESENT_RECORDER_TEST_API_H_
diff --git a/ash/mojo_interface_factory.cc b/ash/mojo_interface_factory.cc
index b39adac..01edd0b 100644
--- a/ash/mojo_interface_factory.cc
+++ b/ash/mojo_interface_factory.cc
@@ -26,7 +26,6 @@
 #include "ash/kiosk_next/kiosk_next_shell_controller.h"
 #include "ash/login/login_screen_controller.h"
 #include "ash/media/media_controller.h"
-#include "ash/metrics/time_to_first_present_recorder.h"
 #include "ash/new_window_controller.h"
 #include "ash/note_taking_controller.h"
 #include "ash/public/cpp/ash_features.h"
@@ -187,11 +186,6 @@
   Shell::Get()->note_taking_controller()->BindRequest(std::move(request));
 }
 
-void BindProcessCreationTimeRecorderOnMainThread(
-    mojom::ProcessCreationTimeRecorderRequest request) {
-  Shell::Get()->time_to_first_present_recorder()->Bind(std::move(request));
-}
-
 void BindShelfRequestOnMainThread(mojom::ShelfControllerRequest request) {
   Shell::Get()->shelf_controller()->BindRequest(std::move(request));
 }
@@ -318,9 +312,6 @@
   registry->AddInterface(
       base::BindRepeating(&BindNoteTakingControllerRequestOnMainThread),
       main_thread_task_runner);
-  registry->AddInterface(
-      base::BindRepeating(&BindProcessCreationTimeRecorderOnMainThread),
-      main_thread_task_runner);
   registry->AddInterface(base::BindRepeating(&BindShelfRequestOnMainThread),
                          main_thread_task_runner);
   registry->AddInterface(
diff --git a/ash/mojo_test_interface_factory.cc b/ash/mojo_test_interface_factory.cc
index a1f8918..fcd401d 100644
--- a/ash/mojo_test_interface_factory.cc
+++ b/ash/mojo_test_interface_factory.cc
@@ -7,12 +7,10 @@
 #include <utility>
 
 #include "ash/login/login_screen_test_api.h"
-#include "ash/metrics/time_to_first_present_recorder_test_api.h"
 #include "ash/public/interfaces/login_screen_test_api.test-mojom.h"
 #include "ash/public/interfaces/shelf_test_api.test-mojom.h"
 #include "ash/public/interfaces/status_area_widget_test_api.test-mojom.h"
 #include "ash/public/interfaces/system_tray_test_api.test-mojom.h"
-#include "ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom.h"
 #include "ash/shelf/shelf_test_api.h"
 #include "ash/system/status_area_widget_test_api.h"
 #include "ash/system/unified/unified_system_tray_test_api.h"
@@ -45,11 +43,6 @@
   UnifiedSystemTrayTestApi::BindRequest(std::move(request));
 }
 
-void BindTimeToFirstPresentRecorderTestApiOnMainThread(
-    mojom::TimeToFirstPresentRecorderTestApiRequest request) {
-  TimeToFirstPresentRecorderTestApi::BindRequest(std::move(request));
-}
-
 }  // namespace
 
 void RegisterInterfaces(
@@ -63,9 +56,6 @@
                          main_thread_task_runner);
   registry->AddInterface(base::Bind(&BindSystemTrayTestApiOnMainThread),
                          main_thread_task_runner);
-  registry->AddInterface(
-      base::Bind(&BindTimeToFirstPresentRecorderTestApiOnMainThread),
-      main_thread_task_runner);
 }
 
 }  // namespace mojo_test_interface_factory
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 828be8cb..164bb24 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -9,8 +9,11 @@
   sources = [
     "accelerators.cc",
     "accelerators.h",
+    "app_list/app_list_client.h",
     "app_list/app_list_config.cc",
     "app_list/app_list_config.h",
+    "app_list/app_list_controller.cc",
+    "app_list/app_list_controller.h",
     "app_list/app_list_features.cc",
     "app_list/app_list_features.h",
     "app_list/app_list_metrics.cc",
@@ -149,6 +152,7 @@
     "//chromeos/constants",
     "//chromeos/dbus/power:power_manager_proto",
     "//components/prefs",
+    "//components/sync:rest_of_sync",
     "//mojo/public/cpp/bindings",
     "//services/service_manager/public/cpp",
     "//services/ws/public/cpp",
diff --git a/ash/public/cpp/app_list/app_list_client.h b/ash/public/cpp/app_list/app_list_client.h
new file mode 100644
index 0000000..0f23e71
--- /dev/null
+++ b/ash/public/cpp/app_list/app_list_client.h
@@ -0,0 +1,148 @@
+// 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_APP_LIST_APP_LIST_CLIENT_H_
+#define ASH_PUBLIC_CPP_APP_LIST_APP_LIST_CLIENT_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "ash/public/cpp/ash_public_export.h"
+#include "ash/public/interfaces/app_list.mojom.h"
+// TODO(crbug.com/958208): Remove menu.mojom.h
+#include "ash/public/interfaces/menu.mojom.h"
+#include "base/callback_forward.h"
+#include "base/strings/string16.h"
+#include "components/sync/model/string_ordinal.h"
+#include "ui/base/models/menu_model.h"
+
+namespace app_list {
+
+// TODO(crbug.com/958134): Remove the alias and app_list.mojom.h include.
+using AppListLaunchedFrom = ash::mojom::AppListLaunchedFrom;
+using AppListLaunchType = ash::mojom::AppListLaunchType;
+
+// A client interface implemented in Chrome to handle calls from Ash.
+// These include:
+// - When Chrome components are needed to get involved in the user's actions on
+//   app list views. This can happen while the user is searching, clicking on
+//   any app list item, etc.
+// - When view changes in Ash and we want to notify Chrome. This can happen
+//   while app list is performing animations.
+// - When a user action on views need information from Chrome to complete. This
+//   can happen while populating context menu models, which depends on item data
+//   in Chrome.
+class ASH_PUBLIC_EXPORT AppListClient {
+ public:
+  //////////////////////////////////////////////////////////////////////////////
+  // Interfaces on searching:
+  // Triggers a search query.
+  // |trimmed_query|: the trimmed input texts from the search text field.
+  virtual void StartSearch(const base::string16& trimmed_query) = 0;
+  // Opens a search result and logs to metrics when its view is clicked or
+  // pressed.
+  // |result_id|: the id of the search result the user wants to open.
+  // |launched_from|: where the result was launched.
+  // |launch_type|: how the result is represented in the UI.
+  // |suggestion_index|: the position of the result as a suggestion chip in
+  // the AppsGridView or the position of the result in the zero state search
+  // page.
+  virtual void OpenSearchResult(const std::string& result_id,
+                                int event_flags,
+                                AppListLaunchedFrom launched_from,
+                                AppListLaunchType launch_type,
+                                int suggestion_index) = 0;
+  // Invokes a custom action on a result with |result_id|.
+  // |action_index| corresponds to the index of an action on the search result,
+  // for example, installing. They are stored in SearchResult::actions_.
+  virtual void InvokeSearchResultAction(const std::string& result_id,
+                                        int action_index,
+                                        int event_flags) = 0;
+  // Returns the context menu model for the search result with |result_id|, or
+  // an empty array if there is currently no menu for the result.
+  using GetSearchResultContextMenuModelCallback =
+      base::OnceCallback<void(std::vector<::ash::mojom::MenuItemPtr>)>;
+  virtual void GetSearchResultContextMenuModel(
+      const std::string& result_id,
+      GetSearchResultContextMenuModelCallback callback) = 0;
+  // Invoked when a context menu item of a search result is clicked.
+  // |result_id|: the clicked search result's id.
+  // |command_id|: the clicked menu item's command id.
+  // |event_flags|: flags from the event which triggered this command.
+  virtual void SearchResultContextMenuItemSelected(const std::string& result_id,
+                                                   int command_id,
+                                                   int event_flags) = 0;
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Interfaces on the app list UI:
+  // Invoked when the app list is shown in the display with |display_id|.
+  virtual void ViewShown(int64_t display_id) = 0;
+  // Invoked when the app list is closed.
+  virtual void ViewClosing() = 0;
+  // Notifies target visibility changes of the app list.
+  virtual void OnAppListTargetVisibilityChanged(bool visible) = 0;
+  // Notifies visibility changes of the app list.
+  virtual void OnAppListVisibilityChanged(bool visible) = 0;
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Interfaces on app list items. |profile_id| indicates the profile to which
+  // app list items belong. In multi-profile mode, each profile has its own
+  // app list model updater:
+  // Activates (opens) the item with |id|.
+  virtual void ActivateItem(int profile_id,
+                            const std::string& id,
+                            int event_flags) = 0;
+  // Returns the context menu model for the item with |id|, or an empty array if
+  // there is currently no menu for the item (e.g. during install).
+  using GetContextMenuModelCallback =
+      base::OnceCallback<void(std::vector<::ash::mojom::MenuItemPtr>)>;
+  virtual void GetContextMenuModel(int profile_id,
+                                   const std::string& id,
+                                   GetContextMenuModelCallback callback) = 0;
+  // Invoked when a context menu item of an app list item is clicked.
+  // |id|: the clicked AppListItem's id.
+  // |command_id|: the clicked menu item's command id.
+  // |event_flags|: flags from the event which triggered this command.
+  virtual void ContextMenuItemSelected(int profile_id,
+                                       const std::string& id,
+                                       int command_id,
+                                       int event_flags) = 0;
+  // Invoked when a folder is created in Ash (e.g. merge items into a folder).
+  virtual void OnFolderCreated(int profile_id,
+                               ash::mojom::AppListItemMetadataPtr folder) = 0;
+  // Invoked when a folder has only one item left and so gets removed.
+  virtual void OnFolderDeleted(int profile_id,
+                               ash::mojom::AppListItemMetadataPtr folder) = 0;
+  // Invoked when user changes a folder's name or an item's position.
+  virtual void OnItemUpdated(int profile_id,
+                             ash::mojom::AppListItemMetadataPtr folder) = 0;
+  // Invoked when a "page break" item is added with |id| and |position|.
+  virtual void OnPageBreakItemAdded(int profile_id,
+                                    const std::string& id,
+                                    const syncer::StringOrdinal& position) = 0;
+  // Invoked when a "page break" item with |id| is deleted.
+  virtual void OnPageBreakItemDeleted(int profile_id,
+                                      const std::string& id) = 0;
+
+  // Updated when item with |id| is set to |visible|. Only sent if
+  // |notify_visibility_change| was set on the SearchResultMetadata.
+  virtual void OnSearchResultVisibilityChanged(const std::string& id,
+                                               bool visibility) = 0;
+
+  // Acquires a NavigableContentsFactory (indirectly) from the Content Service
+  // to allow the app list to display embedded web contents. Currently used only
+  // for answer card search results.
+  virtual void GetNavigableContentsFactory(
+      mojo::PendingReceiver<content::mojom::NavigableContentsFactory>
+          receiver) = 0;
+
+ protected:
+  virtual ~AppListClient() {}
+};
+
+}  // namespace app_list
+
+#endif  // ASH_PUBLIC_CPP_APP_LIST_APP_LIST_CLIENT_H_
diff --git a/ash/public/cpp/app_list/app_list_controller.cc b/ash/public/cpp/app_list/app_list_controller.cc
new file mode 100644
index 0000000..007374e
--- /dev/null
+++ b/ash/public/cpp/app_list/app_list_controller.cc
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/public/cpp/app_list/app_list_controller.h"
+
+#include "base/logging.h"
+
+namespace app_list {
+
+namespace {
+
+AppListController* g_instance = nullptr;
+
+}  // namespace
+
+// static
+AppListController* AppListController::Get() {
+  return g_instance;
+}
+
+AppListController::AppListController() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
+
+AppListController::~AppListController() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
+
+}  // namespace app_list
diff --git a/ash/public/cpp/app_list/app_list_controller.h b/ash/public/cpp/app_list/app_list_controller.h
new file mode 100644
index 0000000..0c8b840
--- /dev/null
+++ b/ash/public/cpp/app_list/app_list_controller.h
@@ -0,0 +1,37 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_APP_LIST_APP_LIST_CONTROLLER_H_
+#define ASH_PUBLIC_CPP_APP_LIST_APP_LIST_CONTROLLER_H_
+
+#include "ash/public/cpp/ash_public_export.h"
+
+namespace app_list {
+
+class AppListClient;
+
+// An interface implemented in Ash to handle calls from Chrome.
+// These include:
+// - When app list data changes in Chrome, it should notifies the UI models and
+//   views in Ash to get updated. This can happen while syncing, searching, etc.
+// - When Chrome needs real-time UI information from Ash. This can happen while
+//   calculating recommended search results based on the app list item order.
+// - When app list states in Chrome change that require UI's response. This can
+//   happen while installing/uninstalling apps and the app list gets toggled.
+class ASH_PUBLIC_EXPORT AppListController {
+ public:
+  // Gets the instance.
+  static AppListController* Get();
+
+  // Sets a client to handle calls from Ash.
+  virtual void SetClient(AppListClient* client) = 0;
+
+ protected:
+  AppListController();
+  virtual ~AppListController();
+};
+
+}  // namespace app_list
+
+#endif  // ASH_PUBLIC_CPP_APP_LIST_APP_LIST_CONTROLLER_H_
diff --git a/ash/public/cpp/app_menu_constants.h b/ash/public/cpp/app_menu_constants.h
index b1b429e..0063ed6 100644
--- a/ash/public/cpp/app_menu_constants.h
+++ b/ash/public/cpp/app_menu_constants.h
@@ -59,6 +59,14 @@
   // Command for stopping an app, or stopping a VM via an associated app. Used
   // by AppContextMenu and LauncherContextMenu.
   STOP_APP = 2000,
+
+  // Reserved range for extension/app custom menus as defined by
+  //   IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST
+  //   IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST
+  // in chrome/app/chrome_command_ids.h and used in ContextMenuMatcher.
+  EXTENSIONS_CONTEXT_CUSTOM_FIRST = 49000,
+  EXTENSIONS_CONTEXT_CUSTOM_LAST = 50000,
+
   COMMAND_ID_COUNT
 };
 
diff --git a/ash/public/cpp/docked_magnifier_controller.h b/ash/public/cpp/docked_magnifier_controller.h
index 82b1e92..88f9583 100644
--- a/ash/public/cpp/docked_magnifier_controller.h
+++ b/ash/public/cpp/docked_magnifier_controller.h
@@ -6,7 +6,10 @@
 #define ASH_PUBLIC_CPP_DOCKED_MAGNIFIER_CONTROLLER_H_
 
 #include "ash/ash_export.h"
-#include "ui/gfx/geometry/point.h"
+
+namespace gfx {
+class Point;
+}
 
 namespace ash {
 
diff --git a/ash/public/cpp/manifest.cc b/ash/public/cpp/manifest.cc
index 671c836..25d4b88 100644
--- a/ash/public/cpp/manifest.cc
+++ b/ash/public/cpp/manifest.cc
@@ -24,7 +24,6 @@
 #include "ash/public/interfaces/new_window.mojom.h"
 #include "ash/public/interfaces/night_light_controller.mojom.h"
 #include "ash/public/interfaces/note_taking_controller.mojom.h"
-#include "ash/public/interfaces/process_creation_time_recorder.mojom.h"
 #include "ash/public/interfaces/shelf.mojom.h"
 #include "ash/public/interfaces/shelf_integration_test_api.mojom.h"
 #include "ash/public/interfaces/shutdown.mojom.h"
@@ -85,8 +84,7 @@
                   mojom::KeyboardController, mojom::LocaleUpdateController,
                   mojom::LoginScreen, mojom::MediaController,
                   mojom::NewWindowController, mojom::NightLightController,
-                  mojom::NoteTakingController,
-                  mojom::ProcessCreationTimeRecorder, mojom::ShelfController,
+                  mojom::NoteTakingController, mojom::ShelfController,
                   mojom::ShutdownController, mojom::SystemTray,
                   mojom::TabletModeController, mojom::TrayAction,
                   mojom::VoiceInteractionController, mojom::VpnList,
diff --git a/ash/public/cpp/test_manifest.cc b/ash/public/cpp/test_manifest.cc
index 73c598d..1989dbfa 100644
--- a/ash/public/cpp/test_manifest.cc
+++ b/ash/public/cpp/test_manifest.cc
@@ -8,7 +8,6 @@
 #include "ash/public/interfaces/shelf_test_api.test-mojom.h"
 #include "ash/public/interfaces/status_area_widget_test_api.test-mojom.h"
 #include "ash/public/interfaces/system_tray_test_api.test-mojom.h"
-#include "ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom.h"
 #include "base/no_destructor.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
 
@@ -21,8 +20,7 @@
               "test",
               service_manager::Manifest::InterfaceList<
                   mojom::LoginScreenTestApi, mojom::ShelfTestApi,
-                  mojom::StatusAreaWidgetTestApi, mojom::SystemTrayTestApi,
-                  mojom::TimeToFirstPresentRecorderTestApi>())
+                  mojom::StatusAreaWidgetTestApi, mojom::SystemTrayTestApi>())
           .Build()};
   return *manifest;
 }
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index 958795c..82dacf8 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -45,7 +45,6 @@
     "new_window.mojom",
     "night_light_controller.mojom",
     "note_taking_controller.mojom",
-    "process_creation_time_recorder.mojom",
     "shelf.mojom",
     "shelf_integration_test_api.mojom",
     "shutdown.mojom",
@@ -97,7 +96,6 @@
     "shelf_test_api.test-mojom",
     "status_area_widget_test_api.test-mojom",
     "system_tray_test_api.test-mojom",
-    "time_to_first_present_recorder_test_api.test-mojom",
   ]
   deps = [
     "//ash/public/interfaces:interfaces_internal",
diff --git a/ash/public/interfaces/app_list.mojom b/ash/public/interfaces/app_list.mojom
index 86d636c..2fb0a61 100644
--- a/ash/public/interfaces/app_list.mojom
+++ b/ash/public/interfaces/app_list.mojom
@@ -4,7 +4,6 @@
 
 module ash.mojom;
 
-import "ash/public/interfaces/menu.mojom";
 import "components/sync/mojo/syncer.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 import "mojo/public/mojom/base/unguessable_token.mojom";
@@ -213,9 +212,6 @@
 // - When app list states in Chrome change that require UI's response. This can
 //   happen while installing/uninstalling apps and the app list gets toggled.
 interface AppListController {
-  // Sets a client to handle calls from Ash.
-  SetClient(AppListClient client);
-
   //////////////////////////////////////////////////////////////////////////////
   // Interfaces that come from AppListModelUpdater:
   // The following interfaces are called to update the app list model in Ash,
@@ -317,99 +313,3 @@
   // Shows the app list.
   ShowAppList();
 };
-
-// In contrast to AppListController, this client is implemented in Chrome to
-// handle calls from Ash. These include:
-// - When Chrome components are needed to get involved in the user's actions on
-//   app list views. This can happen while the user is searching, clicking on
-//   any app list item, etc.
-// - When view changes in Ash and we want to notify Chrome. This can happen
-//   while app list is performing animations.
-// - When a user action on views need information from Chrome to complete. This
-//   can happen while populating context menu models, which depends on item data
-//   in Chrome.
-interface AppListClient {
-  //////////////////////////////////////////////////////////////////////////////
-  // Interfaces on searching:
-  // Triggers a search query.
-  // |trimmed_query|: the trimmed input texts from the search text field.
-  StartSearch(mojo_base.mojom.String16 trimmed_query);
-  // Opens a search result and logs to metrics when its view is clicked or
-  // pressed.
-  // |result_id|: the id of the search result the user wants to open.
-  // |launched_from|: where the result was launched.
-  // |launch_type|: how the result is represented in the UI.
-  // |suggestion_index|: the position of the result as a suggestion chip in
-  // the AppsGridView or the position of the result in the zero state search
-  // page.
-  OpenSearchResult(string result_id, int32 event_flags,
-                   AppListLaunchedFrom launched_from,
-                   AppListLaunchType launch_type, int32 suggestion_index);
-  // Invokes a custom action on a result with |result_id|.
-  // |action_index| corresponds to the index of an action on the search result,
-  // for example, installing. They are stored in SearchResult::actions_.
-  InvokeSearchResultAction(string result_id,
-                           int32 action_index,
-                           int32 event_flags);
-  // Returns the context menu model for the search result with |result_id|, or
-  // an empty array if there is currently no menu for the result.
-  GetSearchResultContextMenuModel(string result_id) => (array<MenuItem> items);
-  // Invoked when a context menu item of a search result is clicked.
-  // |result_id|: the clicked search result's id.
-  // |command_id|: the clicked menu item's command id.
-  // |event_flags|: flags from the event which triggered this command.
-  SearchResultContextMenuItemSelected(string result_id,
-                                      int32 command_id,
-                                      int32 event_flags);
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Interfaces on the app list UI:
-  // Invoked when the app list is shown in the display with |display_id|.
-  ViewShown(int64 display_id);
-  // Invoked when the app list is closed.
-  ViewClosing();
-  // Notifies target visibility changes of the app list.
-  OnAppListTargetVisibilityChanged(bool visible);
-  // Notifies visibility changes of the app list.
-  OnAppListVisibilityChanged(bool visible);
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Interfaces on app list items. |profile_id| indicates the profile to which
-  // app list items belong. In multi-profile mode, each profile has its own
-  // app list model updater:
-  // Activates (opens) the item with |id|.
-  ActivateItem(int32 profile_id, string id, int32 event_flags);
-  // Returns the context menu model for the item with |id|, or an empty array if
-  // there is currently no menu for the item (e.g. during install).
-  GetContextMenuModel(int32 profile_id, string id) => (array<MenuItem> items);
-  // Invoked when a context menu item of an app list item is clicked.
-  // |id|: the clicked AppListItem's id.
-  // |command_id|: the clicked menu item's command id.
-  // |event_flags|: flags from the event which triggered this command.
-  ContextMenuItemSelected(int32 profile_id,
-                          string id,
-                          int32 command_id,
-                          int32 event_flags);
-  // Invoked when a folder is created in Ash (e.g. merge items into a folder).
-  OnFolderCreated(int32 profile_id, AppListItemMetadata folder);
-  // Invoked when a folder has only one item left and so gets removed.
-  OnFolderDeleted(int32 profile_id, AppListItemMetadata folder);
-  // Invoked when user changes a folder's name or an item's position.
-  OnItemUpdated(int32 profile_id, AppListItemMetadata folder);
-  // Invoked when a "page break" item is added with |id| and |position|.
-  OnPageBreakItemAdded(int32 profile_id,
-                       string id,
-                       syncer.mojom.StringOrdinal position);
-  // Invoked when a "page break" item with |id| is deleted.
-  OnPageBreakItemDeleted(int32 profile_id, string id);
-
-  // Updated when item with |id| is set to |visible|. Only sent if
-  // |notify_visibility_change| was set on the SearchResultMetadata.
-  OnSearchResultVisibilityChanged(string id, bool visibility);
-
-  // Acquires a NavigableContentsFactory (indirectly) from the Content Service
-  // to allow the app list to display embedded web contents. Currently used only
-  // for answer card search results.
-  GetNavigableContentsFactory(
-      pending_receiver<content.mojom.NavigableContentsFactory> receiver);
-};
diff --git a/ash/public/interfaces/process_creation_time_recorder.mojom b/ash/public/interfaces/process_creation_time_recorder.mojom
deleted file mode 100644
index 9e13caf3..0000000
--- a/ash/public/interfaces/process_creation_time_recorder.mojom
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module ash.mojom;
-
-import "mojo/public/mojom/base/time.mojom";
-
-interface ProcessCreationTimeRecorder {
-  // Sets the time the main process was created at, used for logging metrics.
-  SetMainProcessCreationTime(mojo_base.mojom.TimeTicks start_time);
-};
diff --git a/ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom b/ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom
deleted file mode 100644
index a4f0c02..0000000
--- a/ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module ash.mojom;
-
-import "mojo/public/mojom/base/time.mojom";
-
-// Used to test internal state of TimeToFirstPresentRecorder.
-interface TimeToFirstPresentRecorderTestApi {
-  // Returns the time between process creation and the first pixels shown on
-  // screen.
-  GetProcessCreationToFirstPresentTime() => (mojo_base.mojom.TimeDelta delta);
-};
diff --git a/ash/shelf/back_button_unittest.cc b/ash/shelf/back_button_unittest.cc
index adbb525..8a53429 100644
--- a/ash/shelf/back_button_unittest.cc
+++ b/ash/shelf/back_button_unittest.cc
@@ -32,14 +32,13 @@
   BackButtonTest() = default;
   ~BackButtonTest() override = default;
 
-  BackButton* back_button() { return back_button_; }
+  BackButton* back_button() { return test_api_->shelf_view()->GetBackButton(); }
   ShelfViewTestAPI* test_api() { return test_api_.get(); }
 
   void SetUp() override {
     AshTestBase::SetUp();
     test_api_ = std::make_unique<ShelfViewTestAPI>(
         GetPrimaryShelf()->GetShelfViewForTesting());
-    back_button_ = test_api_->shelf_view()->GetBackButton();
 
     // Finish all setup tasks. In particular we want to finish the
     // GetSwitchStates post task in (Fake)PowerManagerClient which is triggered
@@ -49,7 +48,6 @@
   }
 
  private:
-  BackButton* back_button_ = nullptr;
   std::unique_ptr<ShelfViewTestAPI> test_api_;
 
   DISALLOW_COPY_AND_ASSIGN(BackButtonTest);
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index a382f27..6c09131 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -12,7 +12,6 @@
 #include "ash/focus_cycler.h"
 #include "ash/keyboard/keyboard_util.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
-#include "ash/kiosk_next/kiosk_next_shell_controller.h"
 #include "ash/metrics/user_metrics_recorder.h"
 #include "ash/public/cpp/ash_constants.h"
 #include "ash/public/cpp/ash_features.h"
@@ -1234,17 +1233,6 @@
   if (is_overflow_mode())
     return strategy;
 
-  // We only show the back and home buttons in Kiosk Next, with no overflow
-  // button.
-  // TODO(https://crbug.com/951212): Prevent creation of the app item views
-  // instead of using this hack. This is difficult today due to the tight
-  // coupling of the view model and shelf model.
-  if (Shell::Get()->kiosk_next_shell_controller() &&
-      Shell::Get()->kiosk_next_shell_controller()->IsEnabled()) {
-    last_visible_index_ = 1;
-    return strategy;
-  }
-
   const int total_available_size = shelf_->PrimaryAxisValue(width(), height());
   StatusAreaWidget* status_widget = shelf_widget_->status_area_widget();
   const int status_widget_size =
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index f147e98..9e9a2af 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -13,6 +13,7 @@
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/focus_cycler.h"
 #include "ash/ime/ime_controller.h"
+#include "ash/kiosk_next/kiosk_next_shell_controller.h"
 #include "ash/kiosk_next/kiosk_next_shell_test_util.h"
 #include "ash/kiosk_next/mock_kiosk_next_shell_client.h"
 #include "ash/public/cpp/ash_features.h"
@@ -3953,6 +3954,15 @@
     client_ = BindMockKioskNextShellClient();
   }
 
+ protected:
+  void LogInKioskNextUserInternal() {
+    LogInKioskNextUser(GetSessionControllerClient());
+
+    // The shelf_view_ in ShelfWidget will be replaced. Therefore, we need
+    // to update |shelf_view_|.
+    shelf_view_ = GetPrimaryShelf()->GetShelfViewForTesting();
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<MockKioskNextShellClient> client_;
@@ -3961,16 +3971,22 @@
 };
 
 TEST_F(KioskNextShelfViewTest, AppButtonHidden) {
-  LogInKioskNextUser(GetSessionControllerClient());
+  // When a KioskNextUser is not logged in, the shelf model is not hosted
+  // in KioskNextSellController.
+  EXPECT_FALSE(shelf_view_->model() ==
+               Shell::Get()->kiosk_next_shell_controller()->shelf_model());
+
+  LogInKioskNextUserInternal();
+
+  // When a KiosknextUser is logged in, the shelf model for the shelf view
+  // is hosted in KioskNextShellController.
+  EXPECT_TRUE(shelf_view_->model() ==
+              Shell::Get()->kiosk_next_shell_controller()->shelf_model());
 
   // The home and back buttons are always visible.
   EXPECT_TRUE(shelf_view_->GetAppListButton()->GetVisible());
   EXPECT_TRUE(shelf_view_->GetBackButton()->GetVisible());
 
-  // Adding app items doesn't add them to the visible shelf, and the overflow
-  // button remains hidden.
-  AddApp();
-  AddApp();
   ASSERT_FALSE(shelf_view_->GetOverflowButton()->GetVisible());
   EXPECT_EQ(1, shelf_view_->last_visible_index());
 }
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
index 8f1bc3f..79b5606 100644
--- a/ash/shelf/shelf_widget.cc
+++ b/ash/shelf/shelf_widget.cc
@@ -9,6 +9,7 @@
 #include "ash/animation/animation_change_type.h"
 #include "ash/focus_cycler.h"
 #include "ash/keyboard/ui/keyboard_controller.h"
+#include "ash/kiosk_next/kiosk_next_shell_controller.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/window_properties.h"
@@ -363,6 +364,14 @@
 
   background_animator_.AddObserver(delegate_view_);
   shelf_->AddObserver(this);
+
+  // KioskNextShell controller may have already notified its observers that
+  // it has been enabled by the time this ShelfWidget is being created.
+  if (Shell::Get()->kiosk_next_shell_controller()->IsEnabled()) {
+    OnKioskNextEnabled();
+  } else {
+    Shell::Get()->kiosk_next_shell_controller()->AddObserver(this);
+  }
 }
 
 ShelfWidget::~ShelfWidget() {
@@ -391,6 +400,9 @@
   background_animator_.RemoveObserver(delegate_view_);
   shelf_->RemoveObserver(this);
 
+  if (Shell::Get()->kiosk_next_shell_controller())
+    Shell::Get()->kiosk_next_shell_controller()->RemoveObserver(this);
+
   // Don't need to observe focus/activation during shutdown.
   Shell::Get()->focus_cycler()->RemoveWidget(this);
   SetFocusCycler(nullptr);
@@ -585,6 +597,18 @@
   login_shelf_view_->UpdateAfterSessionChange();
 }
 
+void ShelfWidget::OnKioskNextEnabled() {
+  // Hide the shelf view and delete/remove it.
+  shelf_view_->SetVisible(false);
+  delete shelf_view_;
+
+  shelf_view_ = new ShelfView(
+      Shell::Get()->kiosk_next_shell_controller()->shelf_model(), shelf_, this);
+  shelf_view_->Init();
+  GetContentsView()->AddChildView(shelf_view_);
+  shelf_view_->SetVisible(true);
+}
+
 SkColor ShelfWidget::GetShelfBackgroundColor() const {
   return delegate_view_->GetShelfBackgroundColor();
 }
diff --git a/ash/shelf/shelf_widget.h b/ash/shelf/shelf_widget.h
index 2ff4ee8..4f804e1 100644
--- a/ash/shelf/shelf_widget.h
+++ b/ash/shelf/shelf_widget.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/kiosk_next/kiosk_next_shell_observer.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/session/session_observer.h"
 #include "ash/shelf/shelf_background_animator.h"
@@ -39,7 +40,8 @@
                                public views::WidgetObserver,
                                public ShelfLayoutManagerObserver,
                                public ShelfObserver,
-                               public SessionObserver {
+                               public SessionObserver,
+                               public KioskNextShellObserver {
  public:
   ShelfWidget(aura::Window* shelf_container, Shelf* shelf);
   ~ShelfWidget() override;
@@ -118,6 +120,9 @@
   void OnSessionStateChanged(session_manager::SessionState state) override;
   void OnUserSessionAdded(const AccountId& account_id) override;
 
+  // KioskNextShellObserver:
+  void OnKioskNextEnabled() override;
+
   SkColor GetShelfBackgroundColor() const;
   bool GetHitTestRects(aura::Window* target,
                        gfx::Rect* hit_test_rect_mouse,
@@ -162,7 +167,7 @@
 
   // View containing the shelf items within an active user session. Owned by
   // the views hierarchy.
-  ShelfView* const shelf_view_;
+  ShelfView* shelf_view_;
 
   // View containing the shelf items for Login/Lock/OOBE/Add User screens.
   // Owned by the views hierarchy.
diff --git a/ash/shell.cc b/ash/shell.cc
index 385fdc9..3ce3a07 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -66,7 +66,6 @@
 #include "ash/magnifier/partial_magnification_controller.h"
 #include "ash/media/media_controller.h"
 #include "ash/media/media_notification_controller.h"
-#include "ash/metrics/time_to_first_present_recorder.h"
 #include "ash/multi_device_setup/multi_device_notification_presenter.h"
 #include "ash/new_window_controller.h"
 #include "ash/note_taking_controller.h"
@@ -686,7 +685,6 @@
   // Destroy tablet mode controller early on since it has some observers which
   // need to be removed.
   tablet_mode_controller_.reset();
-  kiosk_next_shell_controller_.reset();
 
   toast_manager_.reset();
 
@@ -731,6 +729,9 @@
   // Close all widgets (including the shelf) and destroy all window containers.
   CloseAllRootWindowChildWindows();
 
+  // Destruct KioskNextShellController after Shelf
+  kiosk_next_shell_controller_.reset();
+
   login_screen_controller_.reset();
   system_notification_controller_.reset();
   // Should be destroyed after Shelf and |system_notification_controller_|.
@@ -984,9 +985,6 @@
   if (features::IsVirtualDesksEnabled())
     desks_controller_ = std::make_unique<DesksController>();
 
-  time_to_first_present_recorder_ =
-      std::make_unique<TimeToFirstPresentRecorder>(GetPrimaryRootWindow());
-
   shell_state_->SetRootWindowForNewWindows(GetPrimaryRootWindow());
 
   resolution_notification_controller_ =
diff --git a/ash/shell.h b/ash/shell.h
index 7f45e7c..0760035 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -186,7 +186,6 @@
 class SystemNotificationController;
 class SystemTrayModel;
 class SystemTrayNotifier;
-class TimeToFirstPresentRecorder;
 class ToastManager;
 class ToplevelWindowEventHandler;
 class TouchDevicesController;
@@ -499,9 +498,6 @@
   TabletModeController* tablet_mode_controller() {
     return tablet_mode_controller_.get();
   }
-  TimeToFirstPresentRecorder* time_to_first_present_recorder() {
-    return time_to_first_present_recorder_.get();
-  }
   ToastManager* toast_manager() { return toast_manager_.get(); }
   views::corewm::TooltipController* tooltip_controller() {
     return tooltip_controller_.get();
@@ -726,7 +722,6 @@
   std::unique_ptr<SystemTrayNotifier> system_tray_notifier_;
   std::unique_ptr<ToastManager> toast_manager_;
   std::unique_ptr<TouchDevicesController> touch_devices_controller_;
-  std::unique_ptr<TimeToFirstPresentRecorder> time_to_first_present_recorder_;
   std::unique_ptr<TrayAction> tray_action_;
   std::unique_ptr<VoiceInteractionController> voice_interaction_controller_;
   std::unique_ptr<VpnList> vpn_list_;
diff --git a/ash/shell/content/client/DEPS b/ash/shell/content/client/DEPS
index 79c2529..3eaa3d0 100644
--- a/ash/shell/content/client/DEPS
+++ b/ash/shell/content/client/DEPS
@@ -6,5 +6,4 @@
   "+content/shell",
   "+storage/browser/quota",
   "+services/device/public",
-  "+services/ws/ime/test_ime_driver",
 ]
diff --git a/ash/shell/content/client/shell_browser_main_parts.cc b/ash/shell/content/client/shell_browser_main_parts.cc
index 923cf4a..4efba66 100644
--- a/ash/shell/content/client/shell_browser_main_parts.cc
+++ b/ash/shell/content/client/shell_browser_main_parts.cc
@@ -37,8 +37,6 @@
 #include "device/bluetooth/dbus/bluez_dbus_manager.h"
 #include "net/base/net_module.h"
 #include "services/service_manager/public/cpp/connector.h"
-#include "services/ws/ime/test_ime_driver/public/mojom/constants.mojom.h"
-#include "services/ws/public/mojom/constants.mojom.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
@@ -120,9 +118,6 @@
 
   ash::Shell::GetPrimaryRootWindow()->GetHost()->Show();
 
-  // TODO(https://crbug.com/904148): This should not use |WarmService()|.
-  connector->WarmService(service_manager::ServiceFilter::ByName(
-      test_ime_driver::mojom::kServiceName));
   ash::Shell::Get()->InitWaylandServer(nullptr);
 }
 
diff --git a/ash/shell/content/client/shell_content_browser_client.cc b/ash/shell/content/client/shell_content_browser_client.cc
index 4b5ec277..d81ea47 100644
--- a/ash/shell/content/client/shell_content_browser_client.cc
+++ b/ash/shell/content/client/shell_content_browser_client.cc
@@ -27,9 +27,6 @@
 #include "services/device/public/mojom/constants.mojom.h"
 #include "services/service_manager/public/cpp/manifest.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
-#include "services/ws/ime/test_ime_driver/public/cpp/manifest.h"
-#include "services/ws/ime/test_ime_driver/public/mojom/constants.mojom.h"
-#include "services/ws/public/mojom/constants.mojom.h"
 #include "storage/browser/quota/quota_settings.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
@@ -51,7 +48,6 @@
       service_manager::ManifestBuilder()
           .PackageService(service_manager::Manifest(ash::GetManifest())
                               .Amend(ash::GetManifestOverlayForTesting()))
-          .PackageService(test_ime_driver::GetManifest())
           .Build()};
   return *manifest;
 }
@@ -90,11 +86,5 @@
   return base::nullopt;
 }
 
-void ShellContentBrowserClient::RegisterOutOfProcessServices(
-    OutOfProcessServiceMap* services) {
-  (*services)[test_ime_driver::mojom::kServiceName] = base::BindRepeating(
-      &base::ASCIIToUTF16, test_ime_driver::mojom::kServiceName);
-}
-
 }  // namespace shell
 }  // namespace ash
diff --git a/ash/shell/content/client/shell_content_browser_client.h b/ash/shell/content/client/shell_content_browser_client.h
index a924dce..4caa12d 100644
--- a/ash/shell/content/client/shell_content_browser_client.h
+++ b/ash/shell/content/client/shell_content_browser_client.h
@@ -34,7 +34,6 @@
       storage::OptionalQuotaSettingsCallback callback) override;
   base::Optional<service_manager::Manifest> GetServiceManifestOverlay(
       base::StringPiece name) override;
-  void RegisterOutOfProcessServices(OutOfProcessServiceMap* services) override;
 
  private:
   ShellBrowserMainParts* shell_browser_main_parts_;
diff --git a/ash/shell/content/client/shell_main_delegate.cc b/ash/shell/content/client/shell_main_delegate.cc
index c096e9a..e4dcde1 100644
--- a/ash/shell/content/client/shell_main_delegate.cc
+++ b/ash/shell/content/client/shell_main_delegate.cc
@@ -10,53 +10,13 @@
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/path_service.h"
-#include "content/public/utility/content_utility_client.h"
 #include "content/public/utility/utility_thread.h"
 #include "services/service_manager/public/cpp/service.h"
-#include "services/ws/ime/test_ime_driver/public/mojom/constants.mojom.h"
-#include "services/ws/ime/test_ime_driver/test_ime_application.h"
 #include "ui/base/ime/init/input_method_initializer.h"
 #include "ui/base/resource/resource_bundle.h"
 
 namespace ash {
 namespace shell {
-namespace {
-
-void TerminateThisProcess() {
-  content::UtilityThread::Get()->ReleaseProcess();
-}
-
-std::unique_ptr<service_manager::Service> CreateTestImeDriver(
-    service_manager::mojom::ServiceRequest request) {
-  return std::make_unique<ws::test::TestIMEApplication>(std::move(request));
-}
-
-class ShellContentUtilityClient : public content::ContentUtilityClient {
- public:
-  ShellContentUtilityClient() = default;
-  ~ShellContentUtilityClient() override = default;
-
-  // content::ContentUtilityClient:
-  bool HandleServiceRequest(
-      const std::string& service_name,
-      service_manager::mojom::ServiceRequest request) override {
-    std::unique_ptr<service_manager::Service> service;
-    if (service_name == test_ime_driver::mojom::kServiceName)
-      service = CreateTestImeDriver(std::move(request));
-
-    if (service) {
-      service_manager::Service::RunAsyncUntilTermination(
-          std::move(service), base::BindOnce(&TerminateThisProcess));
-      return true;
-    }
-    return false;
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ShellContentUtilityClient);
-};
-
-}  // namespace
 
 ShellMainDelegate::ShellMainDelegate() = default;
 
@@ -98,10 +58,5 @@
   }
 }
 
-content::ContentUtilityClient* ShellMainDelegate::CreateContentUtilityClient() {
-  utility_client_ = std::make_unique<ShellContentUtilityClient>();
-  return utility_client_.get();
-}
-
 }  // namespace shell
 }  // namespace ash
diff --git a/ash/shell/content/client/shell_main_delegate.h b/ash/shell/content/client/shell_main_delegate.h
index ce6363b..53e615f6 100644
--- a/ash/shell/content/client/shell_main_delegate.h
+++ b/ash/shell/content/client/shell_main_delegate.h
@@ -25,14 +25,12 @@
   bool BasicStartupComplete(int* exit_code) override;
   void PreSandboxStartup() override;
   content::ContentBrowserClient* CreateContentBrowserClient() override;
-  content::ContentUtilityClient* CreateContentUtilityClient() override;
 
  private:
   void InitializeResourceBundle();
 
   std::unique_ptr<ShellContentBrowserClient> browser_client_;
   content::ShellContentClient content_client_;
-  std::unique_ptr<content::ContentUtilityClient> utility_client_;
 
   DISALLOW_COPY_AND_ASSIGN(ShellMainDelegate);
 };
diff --git a/ash/shell/example_app_list_client.cc b/ash/shell/example_app_list_client.cc
index 21df496..06741cd 100644
--- a/ash/shell/example_app_list_client.cc
+++ b/ash/shell/example_app_list_client.cc
@@ -185,13 +185,15 @@
 
 ExampleAppListClient::ExampleAppListClient(AppListControllerImpl* controller)
     : controller_(controller) {
-  controller_->SetClient(CreateInterfacePtrAndBind());
+  controller_->SetClient(this);
 
   PopulateApps();
   DecorateSearchBox();
 }
 
-ExampleAppListClient::~ExampleAppListClient() = default;
+ExampleAppListClient::~ExampleAppListClient() {
+  controller_->SetClient(nullptr);
+}
 
 void ExampleAppListClient::PopulateApps() {
   for (int i = 0; i < static_cast<int>(WindowTypeShelfItem::LAST_TYPE); ++i) {
diff --git a/ash/system/tray/tray_bubble_wrapper.cc b/ash/system/tray/tray_bubble_wrapper.cc
index aa1f112..b925b6c 100644
--- a/ash/system/tray/tray_bubble_wrapper.cc
+++ b/ash/system/tray/tray_bubble_wrapper.cc
@@ -10,7 +10,6 @@
 #include "ash/system/tray/tray_bubble_view.h"
 #include "ash/system/tray/tray_event_filter.h"
 #include "ash/wm/container_finder.h"
-#include "ash/wm/widget_finder.h"
 #include "ui/aura/window.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/transient_window_manager.h"
@@ -129,7 +128,7 @@
   views::Widget* bubble_widget = bubble_view()->GetWidget();
   // Don't close the bubble if a transient child is gaining or losing
   // activation.
-  if (bubble_widget == GetInternalWidgetForWindow(gained_active) ||
+  if (bubble_widget == views::Widget::GetWidgetForNativeView(gained_active) ||
       ::wm::HasTransientAncestor(gained_active,
                                  bubble_widget->GetNativeWindow()) ||
       (lost_active && ::wm::HasTransientAncestor(
diff --git a/ash/system/unified/unified_system_tray_bubble.cc b/ash/system/unified/unified_system_tray_bubble.cc
index 8687290..f7013b4 100644
--- a/ash/system/unified/unified_system_tray_bubble.cc
+++ b/ash/system/unified/unified_system_tray_bubble.cc
@@ -16,7 +16,6 @@
 #include "ash/system/unified/unified_system_tray_view.h"
 #include "ash/wm/container_finder.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "ash/wm/widget_finder.h"
 #include "ash/wm/work_area_insets.h"
 #include "base/metrics/histogram_macros.h"
 #include "ui/aura/window.h"
@@ -268,7 +267,7 @@
 
   // Don't close the bubble if a transient child is gaining or losing
   // activation.
-  if (bubble_widget_ == GetInternalWidgetForWindow(gained_active) ||
+  if (bubble_widget_ == views::Widget::GetWidgetForNativeView(gained_active) ||
       ::wm::HasTransientAncestor(gained_active,
                                  bubble_widget_->GetNativeWindow()) ||
       (lost_active && ::wm::HasTransientAncestor(
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 08af48c..1e1564d 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -198,6 +198,7 @@
   widget->SetContentsView(new DropTargetView(
       dragged_window->GetProperty(ash::kTabDraggingSourceWindowKey)));
   aura::Window* drop_target_window = widget->GetNativeWindow();
+  drop_target_window->SetProperty(kHideInDeskMiniViewKey, true);
   drop_target_window->parent()->StackChildAtBottom(drop_target_window);
   widget->Show();
 
@@ -256,9 +257,14 @@
   }
 }
 
-gfx::Rect GetDesksWidgetBounds(aura::Window* root, int overview_grid_width) {
-  return screen_util::SnapBoundsToDisplayEdge(
-      gfx::Rect(overview_grid_width, DesksBarView::GetBarHeight()), root);
+// Returns the desks widget bounds in root, given the screen bounds of the
+// overview grid.
+gfx::Rect GetDesksWidgetBounds(aura::Window* root,
+                               const gfx::Rect& overview_grid_screen_bounds) {
+  gfx::Rect desks_widget_root_bounds = overview_grid_screen_bounds;
+  ::wm::ConvertRectFromScreen(root, &desks_widget_root_bounds);
+  desks_widget_root_bounds.set_height(DesksBarView::GetBarHeight());
+  return screen_util::SnapBoundsToDisplayEdge(desks_widget_root_bounds, root);
 }
 
 }  // namespace
@@ -656,10 +662,8 @@
     const gfx::Rect& bounds_in_screen,
     const base::flat_set<OverviewItem*>& ignored_items) {
   bounds_ = bounds_in_screen;
-  if (desks_widget_) {
-    desks_widget_->SetBounds(
-        GetDesksWidgetBounds(root_window_, bounds_.width()));
-  }
+  if (desks_widget_)
+    desks_widget_->SetBounds(GetDesksWidgetBounds(root_window_, bounds_));
   PositionWindows(/*animate=*/true, ignored_items);
 }
 
@@ -1309,7 +1313,7 @@
     return;
 
   desks_widget_ = DesksBarView::CreateDesksWidget(
-      root_window_, GetDesksWidgetBounds(root_window_, bounds_.width()));
+      root_window_, GetDesksWidgetBounds(root_window_, bounds_));
   desks_bar_view_ = new DesksBarView;
 
   // The following order of function calls is significant: SetContentsView()
diff --git a/ash/wm/pip/pip_window_resizer.cc b/ash/wm/pip/pip_window_resizer.cc
index aa26fc8..0021a93 100644
--- a/ash/wm/pip/pip_window_resizer.cc
+++ b/ash/wm/pip/pip_window_resizer.cc
@@ -10,7 +10,6 @@
 #include "ash/metrics/pip_uma.h"
 #include "ash/wm/collision_detection/collision_detection_utils.h"
 #include "ash/wm/pip/pip_positioner.h"
-#include "ash/wm/widget_finder.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
 #include "base/metrics/histogram_functions.h"
diff --git a/ash/wm/widget_finder.cc b/ash/wm/widget_finder.cc
deleted file mode 100644
index 8701c51..0000000
--- a/ash/wm/widget_finder.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/wm/widget_finder.h"
-
-#include "services/ws/window_service.h"
-#include "ui/aura/window.h"
-#include "ui/views/widget/widget.h"
-
-namespace ash {
-
-views::Widget* GetInternalWidgetForWindow(aura::Window* window) {
-  return ws::WindowService::IsProxyWindow(window)
-             ? nullptr
-             : views::Widget::GetWidgetForNativeView(window);
-}
-
-}  // namespace ash
diff --git a/ash/wm/widget_finder.h b/ash/wm/widget_finder.h
deleted file mode 100644
index 1bf286c..0000000
--- a/ash/wm/widget_finder.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_WIDGET_FINDER_H_
-#define ASH_WIDGET_FINDER_H_
-
-#include "ash/ash_export.h"
-
-namespace aura {
-class Window;
-}
-
-namespace views {
-class Widget;
-}
-
-namespace ash {
-
-// Returns the widget associated with |window|, or null if not associated with
-// a widget. Only ash system UI widgets are returned, not widgets created
-// by the mus window manager code to show a non-client frame.
-ASH_EXPORT views::Widget* GetInternalWidgetForWindow(aura::Window* window);
-
-}  // namespace ash
-
-#endif  // ASH_WIDGET_FINDER_H_
diff --git a/ash/wm/window_finder.cc b/ash/wm/window_finder.cc
index 1168a22..bc8a8fb4 100644
--- a/ash/wm/window_finder.cc
+++ b/ash/wm/window_finder.cc
@@ -10,7 +10,6 @@
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/root_window_finder.h"
-#include "services/ws/window_service.h"
 #include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_targeter.h"
@@ -20,10 +19,7 @@
 
 // Returns true if |window| is considered to be a toplevel window.
 bool IsTopLevelWindow(aura::Window* window) {
-  // ui::LAYER_TEXTURED is for non-mash environment. For Mash, browser windows
-  // are not with LAYER_TEXTURED but have a remote client.
-  return window->layer()->type() == ui::LAYER_TEXTURED ||
-         ws::WindowService::IsProxyWindow(window);
+  return window->layer()->type() == ui::LAYER_TEXTURED;
 }
 
 // Returns true if |window| can be a target at |screen_point| by |targeter|.
diff --git a/ash/wm/window_mirror_view.cc b/ash/wm/window_mirror_view.cc
index 6a167e5..ca74726 100644
--- a/ash/wm/window_mirror_view.cc
+++ b/ash/wm/window_mirror_view.cc
@@ -7,7 +7,6 @@
 #include <algorithm>
 #include <memory>
 
-#include "ash/wm/widget_finder.h"
 #include "ash/wm/window_state.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/env.h"
diff --git a/ash/wm/window_modality_controller_unittest.cc b/ash/wm/window_modality_controller_unittest.cc
index b4a2abc..a6d13ca 100644
--- a/ash/wm/window_modality_controller_unittest.cc
+++ b/ash/wm/window_modality_controller_unittest.cc
@@ -9,7 +9,7 @@
 #include "ash/wm/test_child_modal_parent.h"
 #include "ash/wm/window_util.h"
 #include "base/stl_util.h"
-#include "services/ws/public/mojom/window_manager.mojom.h"
+#include "services/ws/public/mojom/window_tree_constants.mojom.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/capture_client.h"
 #include "ui/aura/test/test_window_delegate.h"
diff --git a/ash/wm/window_util.cc b/ash/wm/window_util.cc
index 63ca0ac..700140b 100644
--- a/ash/wm/window_util.cc
+++ b/ash/wm/window_util.cc
@@ -18,7 +18,6 @@
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_observer.h"
-#include "ash/wm/widget_finder.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/wm_event.h"
@@ -234,7 +233,7 @@
 }
 
 void CloseWidgetForWindow(aura::Window* window) {
-  views::Widget* widget = GetInternalWidgetForWindow(window);
+  views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
   DCHECK(widget);
   widget->Close();
 }
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
index 5ec3b896..f105b0e 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -132,14 +132,12 @@
 
         @Override
         public boolean bind() {
-            if (!mBound) {
-                try {
-                    TraceEvent.begin("ChildProcessConnection.ChildServiceConnectionImpl.bind");
-                    mBound = BindService.doBindService(mContext, mBindIntent, this, mBindFlags,
-                            mHandler, mExecutor, mInstanceName);
-                } finally {
-                    TraceEvent.end("ChildProcessConnection.ChildServiceConnectionImpl.bind");
-                }
+            try {
+                TraceEvent.begin("ChildProcessConnection.ChildServiceConnectionImpl.bind");
+                mBound = BindService.doBindService(mContext, mBindIntent, this, mBindFlags,
+                        mHandler, mExecutor, mInstanceName);
+            } finally {
+                TraceEvent.end("ChildProcessConnection.ChildServiceConnectionImpl.bind");
             }
             return mBound;
         }
@@ -413,6 +411,16 @@
     }
 
     /**
+     * Call bindService again on this connection. This must be called while connection is already
+     * bound. This is useful for controlling the recency of this connection, and also for updating
+     */
+    public void rebind() {
+        assert isRunningOnLauncherThread();
+        assert mWaivedBinding.isBound();
+        mWaivedBinding.bind();
+    }
+
+    /**
      * Sets-up the connection after it was started with start().
      * @param connectionBundle a bundle passed to the service that can be used to pass various
      *         parameters to the service
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py
index 6e950e5..1ec9569 100755
--- a/base/android/jni_generator/jni_generator.py
+++ b/base/android/jni_generator/jni_generator.py
@@ -12,6 +12,7 @@
 import collections
 import errno
 import hashlib
+import itertools
 import optparse
 import os
 import re
@@ -1503,35 +1504,6 @@
   return '\n'.join(ret)
 
 
-def ExtractJarInputFile(jar_file, input_file, out_dir):
-  """Extracts input file from jar and returns the filename.
-
-  The input file is extracted to the same directory that the generated jni
-  headers will be placed in.  This is passed as an argument to script.
-
-  Args:
-    jar_file: the jar file containing the input files to extract.
-    input_files: the list of files to extract from the jar file.
-    out_dir: the name of the directories to extract to.
-
-  Returns:
-    the name of extracted input file.
-  """
-  jar_file = zipfile.ZipFile(jar_file)
-
-  out_dir = os.path.join(out_dir, os.path.dirname(input_file))
-  try:
-    os.makedirs(out_dir)
-  except OSError as e:
-    if e.errno != errno.EEXIST:
-      raise
-  extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
-  with open(extracted_file_name, 'wb') as outfile:
-    outfile.write(jar_file.read(input_file))
-
-  return extracted_file_name
-
-
 def GenerateJNIHeader(input_file, output_file, options):
   try:
     if os.path.splitext(input_file)[1] == '.class':
@@ -1579,13 +1551,13 @@
                            help='Uses as a namespace in the generated header '
                            'instead of the javap class name, or when there is '
                            'no JNINamespace annotation in the java source.')
-  option_parser.add_option('--input_file',
-                           help='Single input file name. The output file name '
-                           'will be derived from it. Must be used with '
-                           '--output_dir.')
+  option_parser.add_option('--input_files',
+                           help='Input file names, or paths within a .jar if '
+                           '--jar-file is used.')
+  option_parser.add_option('--output_files',
+                           help='Output file names.')
   option_parser.add_option('--output_dir',
-                           help='The output directory. Must be used with '
-                           '--input')
+                           help='The output directory.')
   option_parser.add_option('--script_name', default=GetScriptName(),
                            help='The name of this script in the generated '
                            'header.')
@@ -1614,20 +1586,23 @@
       'in @JniNatives interface. And uses a shorter name and package'
       ' than GEN_JNI.')
   options, args = option_parser.parse_args(argv)
-  if options.jar_file:
-    input_file = ExtractJarInputFile(options.jar_file, options.input_file,
-                                     options.output_dir)
-  elif options.input_file:
-    input_file = options.input_file
-  else:
-    option_parser.print_help()
-    print('\nError: Must specify --jar_file or --input_file.')
-    return 1
-  output_file = None
-  if options.output_dir:
-    root_name = os.path.splitext(os.path.basename(input_file))[0]
-    output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
-  GenerateJNIHeader(input_file, output_file, options)
+  input_files = build_utils.ParseGnList(options.input_files)
+  output_files = build_utils.ParseGnList(options.output_files)
+
+  # Prevent stale file issues when moving file locations (crbug.com/951701).
+  if options.output_dir and os.path.exists(options.output_dir):
+    build_utils.DeleteDirectory(options.output_dir)
+    build_utils.MakeDirectory(options.output_dir)
+
+  with build_utils.TempDir() as temp_dir:
+    if options.jar_file:
+      with zipfile.ZipFile(options.jar_file) as z:
+        z.extractall(temp_dir, input_files)
+      input_files = [os.path.join(temp_dir, f) for f in input_files]
+
+    for java_path, header_path in itertools.izip_longest(
+        input_files, output_files):
+      GenerateJNIHeader(java_path, header_path, options)
 
 
 if __name__ == '__main__':
diff --git a/base/logging.cc b/base/logging.cc
index 833d97c..010b8e9 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -189,7 +189,7 @@
 #if defined(OS_WIN)
   return GetTickCount();
 #elif defined(OS_FUCHSIA)
-  return zx_clock_get(ZX_CLOCK_MONOTONIC) /
+  return zx_clock_get_monotonic() /
          static_cast<zx_time_t>(base::Time::kNanosecondsPerMicrosecond);
 #elif defined(OS_MACOSX)
   return mach_absolute_time();
diff --git a/base/test/android/javatests/src/org/chromium/base/test/TestChildProcessConnection.java b/base/test/android/javatests/src/org/chromium/base/test/TestChildProcessConnection.java
index 94e51f0d..9dac8b7 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/TestChildProcessConnection.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/TestChildProcessConnection.java
@@ -39,6 +39,7 @@
     private int mPid;
     private boolean mConnected;
     private ServiceCallback mServiceCallback;
+    private boolean mRebindCalled;
 
     /**
      * Creates a mock binding corresponding to real ManagedChildProcessConnection after the
@@ -74,6 +75,12 @@
     }
 
     @Override
+    public void rebind() {
+        super.rebind();
+        mRebindCalled = true;
+    }
+
+    @Override
     public void stop() {
         super.stop();
         mConnected = false;
@@ -87,4 +94,10 @@
     public ServiceCallback getServiceCallback() {
         return mServiceCallback;
     }
+
+    public boolean getAndResetRebindCalled() {
+        boolean called = mRebindCalled;
+        mRebindCalled = false;
+        return called;
+    }
 }
diff --git a/base/time/time_fuchsia.cc b/base/time/time_fuchsia.cc
index 8b658b4..5b896b3 100644
--- a/base/time/time_fuchsia.cc
+++ b/base/time/time_fuchsia.cc
@@ -7,6 +7,7 @@
 #include <zircon/syscalls.h>
 
 #include "base/compiler_specific.h"
+#include "base/fuchsia/fuchsia_logging.h"
 #include "base/numerics/checked_math.h"
 #include "base/time/time_override.h"
 
@@ -28,8 +29,10 @@
 
 namespace subtle {
 Time TimeNowIgnoringOverride() {
-  const zx_time_t nanos_since_unix_epoch = zx_clock_get(ZX_CLOCK_UTC);
-  CHECK(nanos_since_unix_epoch != 0);
+  zx_time_t nanos_since_unix_epoch;
+  zx_status_t status = zx_clock_get_new(ZX_CLOCK_UTC, &nanos_since_unix_epoch);
+  ZX_CHECK(status == ZX_OK, status);
+  DCHECK(nanos_since_unix_epoch != 0);
   // The following expression will overflow in the year 289938 A.D.:
   return Time() + TimeDelta::FromMicroseconds(
                       ZxTimeToMicroseconds(nanos_since_unix_epoch) +
@@ -46,7 +49,7 @@
 
 namespace subtle {
 TimeTicks TimeTicksNowIgnoringOverride() {
-  const zx_time_t nanos_since_boot = zx_clock_get(ZX_CLOCK_MONOTONIC);
+  const zx_time_t nanos_since_boot = zx_clock_get_monotonic();
   CHECK(nanos_since_boot != 0);
   return TimeTicks() +
          TimeDelta::FromMicroseconds(ZxTimeToMicroseconds(nanos_since_boot));
@@ -83,8 +86,11 @@
 
 namespace subtle {
 ThreadTicks ThreadTicksNowIgnoringOverride() {
-  const zx_time_t nanos_since_thread_started = zx_clock_get(ZX_CLOCK_THREAD);
-  CHECK(nanos_since_thread_started != 0);
+  zx_time_t nanos_since_thread_started;
+  zx_status_t status =
+      zx_clock_get_new(ZX_CLOCK_THREAD, &nanos_since_thread_started);
+  ZX_CHECK(status == ZX_OK, status);
+  DCHECK(nanos_since_thread_started != 0);
   return ThreadTicks() + TimeDelta::FromMicroseconds(
                              ZxTimeToMicroseconds(nanos_since_thread_started));
 }
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index ef398fd..704e4414 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -189,30 +189,14 @@
   import("//build/config/sanitizers/sanitizers.gni")
   import("//tools/grit/grit_rule.gni")
 
-  # Declare a jni target
-  #
-  # This target generates the native jni bindings for a set of .java files.
-  #
-  # See base/android/jni_generator/jni_generator.py for more info about the
-  # format of generating JNI bindings.
-  #
-  # Variables
-  #   sources: list of .java files to generate jni for
-  #   jni_package: subdirectory path for generated bindings
-  #
-  # Example
-  #   generate_jni("foo_jni") {
-  #     sources = [
-  #       "android/java/src/org/chromium/foo/Foo.java",
-  #       "android/java/src/org/chromium/foo/FooUtil.java",
-  #     ]
-  #     jni_package = "foo"
-  #   }
-  template("generate_jni") {
+  # JNI target implementation. See generate_jni or generate_jar_jni for usage.
+  template("generate_jni_impl") {
     set_sources_assignment_filter([])
     forward_variables_from(invoker, [ "testonly" ])
 
-    _base_output_dir = "${target_gen_dir}/${target_name}"
+    # TODO(crbug.com/963471): Change include paths to be relative to
+    # $root_gen_dir to prevent issues when moving JNI targets.
+    _base_output_dir = "${target_gen_dir}/${target_name}_"
     _package_output_dir = "${_base_output_dir}/${invoker.jni_package}"
     _jni_output_dir = "${_package_output_dir}/jni"
 
@@ -232,16 +216,13 @@
       ]
     }
 
-    _foreach_target_name = "${target_name}__jni_gen"
-    action_foreach_with_pydeps(_foreach_target_name) {
+    _jni_target_name = "${target_name}__jni"
+    action_with_pydeps(_jni_target_name) {
+      # The sources aren't compiled so don't check their dependencies.
+      check_includes = false
       script = "//base/android/jni_generator/jni_generator.py"
-      sources = invoker.sources
-      outputs = [
-        "${_jni_output_dir}/{{source_name_part}}_jni.h",
-      ]
-
+      inputs = []
       args = [
-        "--input_file={{source}}",
         "--ptr_type=long",
         "--output_dir",
         rebase_path(_jni_output_dir, root_build_dir),
@@ -249,23 +230,54 @@
         rebase_path(_jni_generator_include, _jni_output_dir),
       ]
 
-      if (use_hashed_jni_names) {
-        args += [ "--use_proxy_hash" ]
+      if (defined(invoker.classes)) {
+        if (defined(invoker.jar_file)) {
+          _jar_file = invoker.jar_file
+        } else {
+          _jar_file = android_sdk_jar
+        }
+        inputs += [ _jar_file ]
+        args += [
+          "--jar_file",
+          rebase_path(_jar_file, root_build_dir),
+          "--input_files=${invoker.classes}",
+        ]
+        _input_names = invoker.classes
+        if (defined(invoker.always_mangle) && invoker.always_mangle) {
+          args += [ "--always_mangle" ]
+        }
+      } else {
+        assert(defined(invoker.sources))
+        inputs += invoker.sources
+        _rebased_sources = rebase_path(invoker.sources, root_build_dir)
+        args += [ "--input_files=$_rebased_sources" ]
+        _input_names = invoker.sources
+        if (use_hashed_jni_names) {
+          args += [ "--use_proxy_hash" ]
+        }
+        if (defined(invoker.namespace)) {
+          args += [ "-n ${invoker.namespace}" ]
+        }
       }
 
+      outputs = []
+      foreach(_name, _input_names) {
+        _name_part = get_path_info(_name, "name")
+        outputs += [ "${_jni_output_dir}/${_name_part}_jni.h" ]
+      }
+      _rebased_outputs = rebase_path(outputs, root_build_dir)
+      args += [ "--output_files=$_rebased_outputs" ]
+
       if (enable_profiling) {
         args += [ "--enable_profiling" ]
       }
-      if (defined(invoker.namespace)) {
-        args += [ "-n ${invoker.namespace}" ]
-      }
       if (enable_jni_tracing) {
         args += [ "--enable_tracing" ]
       }
     }
 
     config("jni_includes_${target_name}") {
-      # TODO(cjhopman): #includes should probably all be relative to
+      # TODO: #includes should probably all be relative to
       # _base_output_dir. Remove that from this config once the includes are
       # updated.
       include_dirs = [
@@ -284,12 +296,38 @@
       if (!defined(public_deps)) {
         public_deps = []
       }
-      public_deps += [ ":$_foreach_target_name" ]
+      public_deps += [ ":$_jni_target_name" ]
       public_deps += _jni_generator_include_deps
       public_configs = [ ":jni_includes_${target_name}" ]
     }
   }
 
+  # Declare a jni target
+  #
+  # This target generates the native jni bindings for a set of .java files.
+  #
+  # See base/android/jni_generator/jni_generator.py for more info about the
+  # format of generating JNI bindings.
+  #
+  # Variables
+  #   sources: list of .java files to generate jni for
+  #   jni_package: subdirectory path for generated bindings
+  #   namespace: Specify the namespace for the generated header file.
+  #
+  # Example
+  #   generate_jni("foo_jni") {
+  #     sources = [
+  #       "android/java/src/org/chromium/foo/Foo.java",
+  #       "android/java/src/org/chromium/foo/FooUtil.java",
+  #     ]
+  #     jni_package = "foo"
+  #   }
+  template("generate_jni") {
+    generate_jni_impl(target_name) {
+      forward_variables_from(invoker, "*")
+    }
+  }
+
   # Declare a jni target for a prebuilt jar
   #
   # This target generates the native jni bindings for a set of classes in a .jar.
@@ -316,82 +354,8 @@
   #     jni_package = "foo"
   #   }
   template("generate_jar_jni") {
-    forward_variables_from(invoker, [ "testonly" ])
-
-    if (defined(invoker.jar_file)) {
-      _jar_file = invoker.jar_file
-    } else {
-      _jar_file = android_sdk_jar
-    }
-
-    _always_mangle = defined(invoker.always_mangle) && invoker.always_mangle
-
-    _base_output_dir = "${target_gen_dir}/${target_name}/${invoker.jni_package}"
-    _jni_output_dir = "${_base_output_dir}/jni"
-
-    if (defined(invoker.jni_generator_include)) {
-      _jni_generator_include = invoker.jni_generator_include
-    } else {
-      _jni_generator_include =
-          "//base/android/jni_generator/jni_generator_helper.h"
-    }
-
-    # TODO(cjhopman): make jni_generator.py support generating jni for multiple
-    # .class files from a .jar.
-    _jni_actions = []
-    foreach(_class, invoker.classes) {
-      _classname = get_path_info(_class, "name")
-      _jni_target_name = "${target_name}__jni_${_classname}"
-      _jni_actions += [ ":$_jni_target_name" ]
-      action_with_pydeps(_jni_target_name) {
-        # The sources aren't compiled so don't check their dependencies.
-        check_includes = false
-        script = "//base/android/jni_generator/jni_generator.py"
-        inputs = [
-          _jar_file,
-        ]
-        outputs = [
-          "${_jni_output_dir}/${_classname}_jni.h",
-        ]
-
-        args = [
-          "--jar_file",
-          rebase_path(_jar_file, root_build_dir),
-          "--input_file",
-          _class,
-          "--ptr_type=long",
-          "--output_dir",
-          rebase_path(_jni_output_dir, root_build_dir),
-          "--includes",
-          rebase_path(_jni_generator_include, _jni_output_dir),
-        ]
-
-        if (enable_profiling) {
-          args += [ "--enable_profiling" ]
-        }
-        if (enable_jni_tracing) {
-          args += [ "--enable_tracing" ]
-        }
-        if (_always_mangle) {
-          args += [ "--always_mangle" ]
-        }
-      }
-    }
-
-    config("jni_includes_${target_name}") {
-      include_dirs = [ _base_output_dir ]
-    }
-
-    group(target_name) {
-      public_deps = []
-      forward_variables_from(invoker,
-                             [
-                               "deps",
-                               "public_deps",
-                               "visibility",
-                             ])
-      public_deps += _jni_actions
-      public_configs = [ ":jni_includes_${target_name}" ]
+    generate_jni_impl(target_name) {
+      forward_variables_from(invoker, "*")
     }
   }
 
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index f080458..e80a142 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8913297003674584800
\ No newline at end of file
+8913272851295197632
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 7e927c0..adb5a7d 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8913311497044026416
\ No newline at end of file
+8913299131746549040
\ No newline at end of file
diff --git a/cc/tiles/gpu_image_decode_cache.cc b/cc/tiles/gpu_image_decode_cache.cc
index 4ce9651..30ce3e4 100644
--- a/cc/tiles/gpu_image_decode_cache.cc
+++ b/cc/tiles/gpu_image_decode_cache.cc
@@ -2107,6 +2107,7 @@
       images_pending_unlock_.push_back(image_data->upload.y_image().get());
       images_pending_unlock_.push_back(image_data->upload.u_image().get());
       images_pending_unlock_.push_back(image_data->upload.v_image().get());
+      yuv_images_pending_unlock_.push_back(image_data->upload.image());
     } else {
       images_pending_unlock_.push_back(image_data->upload.image().get());
     }
@@ -2137,11 +2138,32 @@
   }
 }
 
+// YUV images are handled slightly differently because they are not themselves
+// registered with the discardable memory system. We cannot use
+// GlIdFromSkImage on these YUV SkImages to flush pending operations because
+// doing so will flatten it to RGB.
+void GpuImageDecodeCache::FlushYUVImages(
+    std::vector<sk_sp<SkImage>>* yuv_images) {
+  CheckContextLockAcquiredIfNecessary();
+  lock_.AssertAcquired();
+  for (auto& image : *yuv_images) {
+    image->flush(context_->GrContext());
+  }
+  yuv_images->clear();
+}
+
 // We always run pending operations in the following order:
-//   Lock > Unlock > Delete
+//   > Lock
+//   > Flush YUV images that will be unlocked
+//   > Unlock
+//   > Flush YUV images that will be deleted
+//   > Delete
 // This ensures that:
 //   a) We never fully unlock an image that's pending lock (lock before unlock)
 //   b) We never delete an image that has pending locks/unlocks.
+//   c) We never unlock or delete the underlying texture planes for a YUV
+//      image before all operations referencing it have completed.
+//
 // As this can be run at-raster, to unlock/delete an image that was just used,
 // we need to call GlIdFromSkImage, which flushes pending IO on the image,
 // rather than just using a cached GL ID.
@@ -2159,6 +2181,7 @@
   }
   images_pending_complete_lock_.clear();
 
+  FlushYUVImages(&yuv_images_pending_unlock_);
   for (auto* image : images_pending_unlock_) {
     context_->ContextGL()->UnlockDiscardableTextureCHROMIUM(
         GlIdFromSkImage(image));
@@ -2171,8 +2194,7 @@
   }
   ids_pending_unlock_.clear();
 
-  yuv_images_pending_deletion_.clear();
-
+  FlushYUVImages(&yuv_images_pending_deletion_);
   for (auto& image : images_pending_deletion_) {
     uint32_t texture_id = GlIdFromSkImage(image.get());
     if (context_->ContextGL()->LockDiscardableTextureCHROMIUM(texture_id)) {
diff --git a/cc/tiles/gpu_image_decode_cache.h b/cc/tiles/gpu_image_decode_cache.h
index fe40792..81ca628 100644
--- a/cc/tiles/gpu_image_decode_cache.h
+++ b/cc/tiles/gpu_image_decode_cache.h
@@ -593,6 +593,10 @@
   void UploadImageIfNecessary(const DrawImage& draw_image,
                               ImageData* image_data);
 
+  // Flush pending operations on context_->GrContext() for each element of
+  // |yuv_images| and then clear the vector.
+  void FlushYUVImages(std::vector<sk_sp<SkImage>>* yuv_images);
+
   // Runs pending operations that required the |context_| lock to be held, but
   // were queued up during a time when the |context_| lock was unavailable.
   // These including deleting, unlocking, and locking textures.
@@ -660,7 +664,10 @@
   std::vector<sk_sp<SkImage>> images_pending_deletion_;
   // Images that are backed by planar textures must be handled differently
   // to avoid inadvertently flattening to RGB and creating additional textures.
+  // See comment in RunPendingContextThreadOperations().
   std::vector<sk_sp<SkImage>> yuv_images_pending_deletion_;
+  std::vector<sk_sp<SkImage>> yuv_images_pending_unlock_;
+  const sk_sp<SkColorSpace> target_color_space_;
 
   std::vector<uint32_t> ids_pending_unlock_;
   std::vector<uint32_t> ids_pending_deletion_;
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 633a8f4..17e97f3 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2483,6 +2483,8 @@
     "java/src/org/chromium/chrome/browser/findinpage/FindInPageBridge.java",
     "java/src/org/chromium/chrome/browser/gesturenav/CompositorNavigationGlow.java",
     "java/src/org/chromium/chrome/browser/history/BrowsingHistoryBridge.java",
+    "java/src/org/chromium/chrome/browser/history/HistoryDeletionBridge.java",
+    "java/src/org/chromium/chrome/browser/history/HistoryDeletionInfo.java",
     "java/src/org/chromium/chrome/browser/historyreport/HistoryReportJniBridge.java",
     "java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcherBridge.java",
     "java/src/org/chromium/chrome/browser/infobar/AdsBlockedInfoBar.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 2344331..1f90f33 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -704,6 +704,8 @@
   "java/src/org/chromium/chrome/browser/history/BrowsingHistoryBridge.java",
   "java/src/org/chromium/chrome/browser/history/HistoryActivity.java",
   "java/src/org/chromium/chrome/browser/history/HistoryAdapter.java",
+  "java/src/org/chromium/chrome/browser/history/HistoryDeletionBridge.java",
+  "java/src/org/chromium/chrome/browser/history/HistoryDeletionInfo.java",
   "java/src/org/chromium/chrome/browser/history/HistoryItem.java",
   "java/src/org/chromium/chrome/browser/history/HistoryItemView.java",
   "java/src/org/chromium/chrome/browser/history/HistoryManager.java",
diff --git a/chrome/android/java/res/layout/data_reduction_main_menu_item.xml b/chrome/android/java/res/layout/data_reduction_main_menu_item.xml
index a2e11d9..f803d56 100644
--- a/chrome/android/java/res/layout/data_reduction_main_menu_item.xml
+++ b/chrome/android/java/res/layout/data_reduction_main_menu_item.xml
@@ -26,7 +26,7 @@
         style="@style/AppMenuItem" >
 
         <!-- ContentDescription is set in Java code. -->
-        <ImageView
+        <org.chromium.ui.widget.ChromeImageView
             tools:ignore="ContentDescription"
             android:id="@+id/icon"
             android:src="@drawable/preview_pin_round"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryDeletionBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryDeletionBridge.java
new file mode 100644
index 0000000..289dde4
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryDeletionBridge.java
@@ -0,0 +1,55 @@
+// 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.history;
+
+import org.chromium.base.ObserverList;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.CalledByNative;
+
+/** The JNI bridge for Android to receive notifications about history deletions. */
+// TODO(crbug.com/964072): Write unit tests for this class.
+public class HistoryDeletionBridge {
+    /**
+     * Allows derived class to listen to history deletions that pass through this bridge. The
+     * HistoryDeletionInfo passed as a parameter is only valid for the duration of the method.
+     */
+    public interface Observer { void onURLsDeleted(HistoryDeletionInfo historyDeletionInfo); }
+
+    private static HistoryDeletionBridge sInstance;
+
+    /**
+     * @return Singleton instance for this class.
+     */
+    public static HistoryDeletionBridge getInstance() {
+        ThreadUtils.assertOnUiThread();
+        if (sInstance == null) {
+            sInstance = new HistoryDeletionBridge();
+        }
+
+        return sInstance;
+    }
+
+    private final ObserverList<Observer> mObservers = new ObserverList<>();
+
+    HistoryDeletionBridge() {
+        // This object is a singleton and therefore will be implicitly destroyed.
+        nativeInit();
+    }
+
+    public void addObserver(Observer observer) {
+        mObservers.addObserver(observer);
+    }
+
+    public void removeObserver(Observer observer) {
+        mObservers.removeObserver(observer);
+    }
+
+    @CalledByNative
+    private void onURLsDeleted(HistoryDeletionInfo historyDeletionInfo) {
+        for (Observer observer : mObservers) observer.onURLsDeleted(historyDeletionInfo);
+    }
+
+    private native long nativeInit();
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryDeletionInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryDeletionInfo.java
new file mode 100644
index 0000000..4688965
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryDeletionInfo.java
@@ -0,0 +1,66 @@
+// 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.history;
+
+import org.chromium.base.annotations.CalledByNative;
+
+/**
+ * Android wrapper of the native history::DeletionInfo class. Any class that uses this needs to
+ * register a {@link HistoryDeletionBridge.Observer} on {@Link HistoryDeletionBridge} to listen for
+ * the native signals that produce this signal.
+ */
+public class HistoryDeletionInfo {
+    private final long mHistoryDeletionInfoPtr;
+
+    @CalledByNative
+    private static HistoryDeletionInfo create(long historyDeletionInfoPtr) {
+        return new HistoryDeletionInfo(historyDeletionInfoPtr);
+    }
+
+    HistoryDeletionInfo(long historyDeletionInfoPtr) {
+        mHistoryDeletionInfoPtr = historyDeletionInfoPtr;
+    }
+
+    /**
+     * @return An array of URLs that were deleted.
+     */
+    public String[] getDeletedURLs() {
+        return nativeGetDeletedURLs(mHistoryDeletionInfoPtr);
+    }
+
+    /**
+     * @return True if the time range is valid.
+     */
+    public boolean isTimeRangeValid() {
+        return nativeIsTimeRangeValid(mHistoryDeletionInfoPtr);
+    }
+
+    /**
+     * @return True if the time range is for all time.
+     */
+    public boolean isTimeRangeForAllTime() {
+        return nativeIsTimeRangeForAllTime(mHistoryDeletionInfoPtr);
+    }
+
+    /**
+     * @return The beginning of the time range if the time range is valid.
+     */
+    public long getTimeRangeBegin() {
+        return nativeGetTimeRangeBegin(mHistoryDeletionInfoPtr);
+    }
+
+    /**
+     * @return The end of the time range if the time range is valid.
+     */
+    public long getTimeRangeEnd() {
+        return nativeGetTimeRangeBegin(mHistoryDeletionInfoPtr);
+    }
+
+    private static native String[] nativeGetDeletedURLs(long historyDeletionInfoPtr);
+    private static native boolean nativeIsTimeRangeValid(long historyDeletionInfoPtr);
+    private static native boolean nativeIsTimeRangeForAllTime(long historyDeletionInfoPtr);
+    private static native long nativeGetTimeRangeBegin(long historyDeletionInfoPtr);
+    private static native long nativeGetTimeRangeEnd(long historyDeletionInfoPtr);
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
index 79e27fa..e43af6e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java
@@ -6,6 +6,7 @@
 
 import android.app.Activity;
 import android.graphics.Canvas;
+import android.os.Build;
 import android.support.v4.view.ViewCompat;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -56,6 +57,12 @@
 
         mIncognitoNTPBackgroundColor =
                 ApiCompatibilityUtils.getColor(activity.getResources(), R.color.ntp_bg_incognito);
+
+        // Work around https://crbug.com/943873 and https://crbug.com/963385 where default focus
+        // highlight shows up after toggling dark mode.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            getView().setDefaultFocusHighlightEnabled(false);
+        }
     }
 
     @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/EmulatedVrController.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/EmulatedVrController.java
index 1059297..d487414 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/EmulatedVrController.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/EmulatedVrController.java
@@ -203,6 +203,14 @@
         getApi().touchEvent.endTouchSequence(xEnd, yEnd, timestamp, simulatedDelay, speed);
     }
 
+    public void setTouchpadPosition(float xPos, float yPos) {
+        getApi().touchEvent.sendRawTouchEvent(xPos, yPos, 0, 0, 0);
+    }
+
+    public void stopTouchingTouchpad(float xPos, float yPos) {
+        getApi().touchEvent.endTouchSequence(xPos, yPos, 0, 0, 0);
+    }
+
     /**
      * Instantly moves the controller to the specified quaternion coordinates.
      *
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/README.md b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/README.md
index 29e0ece..d263575 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/README.md
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/README.md
@@ -4,11 +4,18 @@
 
 1. Get a rooted Pixel device of some sort.
 2. Make sure "VR Services" is up to date in the Playstore.
-3. Run `ninja -C out/Debug chrome_public_test_vr_apk
+3. Set lock screen timeout to at least 5 minutes. If screen is locked or device
+   goes to sleep while tests are still running, they will fail.
+4. Run `ninja -C out/Debug chrome_public_test_vr_apk
         && out/Debug/bin/run_chrome_public_test_vr_apk
         --num-retries=0
         --shared-prefs-file=//chrome/android/shared_preference_files/test/vr_ddview_skipdon_setupcomplete.json
         --test-filter=<failing test case>`
+   Don't touch phone while the tests are running.
+
+**NOTE** The message "Main  Unable to find package info for org.chromium.chrome"
+         is usually displayed when the test package is being installed and does
+         not indicate any problem.
 
 ## Introduction
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
index 31ad417..7dda5e3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
@@ -53,6 +53,7 @@
 import org.chromium.content_public.browser.test.util.TouchCommon;
 
 import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
@@ -233,6 +234,98 @@
         mWebXrVrTestFramework.endTest();
     }
 
+    /**
+     * Tests that Daydream controller is exposed as a Gamepad on an
+     * XRInputSource in an immersive session and that button clicks and touchpad
+     * movements are registered.
+     */
+    @Test
+    @MediumTest
+    @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
+    @CommandLineFlags
+            .Remove({"enable-webvr"})
+            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
+            public void testControllerExposedAsGamepadOnDaydream_WebXr()
+            throws InterruptedException {
+        EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
+        mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
+                WebXrVrTestFramework.getFileUrlForHtmlTestFile("test_webxr_gamepad_support"),
+                PAGE_LOAD_TIMEOUT_S);
+        mWebXrVrTestFramework.enterSessionWithUserGestureOrFail();
+
+        // There must be interaction with the controller before an XRInputSource
+        // is recognized. Wait for JS to register the select event to avoid a
+        // race condition.
+        mWebXrVrTestFramework.runJavaScriptOrFail("stepSetupListeners()", POLL_TIMEOUT_SHORT_MS);
+        controller.sendClickButtonToggleEvent();
+        controller.sendClickButtonToggleEvent();
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail("selectCount > 0", POLL_TIMEOUT_SHORT_MS);
+
+        // Daydream controller should not be 'xr-standard' mapping since it does
+        // not meet the requirements.
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "isMappingEqualTo('')", POLL_TIMEOUT_SHORT_MS);
+
+        // Daydream controller should only expose a single button and set of
+        // input axes (the pressable touchpad). It should not expose any of the
+        // platform buttons such as "home" or "back".
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "isButtonCountEqualTo(1)", POLL_TIMEOUT_SHORT_MS);
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "isAxisPairCountEqualTo(1)", POLL_TIMEOUT_SHORT_MS);
+
+        // Initially, the touchpad should not be touched.
+        validateTouchpadNotTouched();
+
+        // Make sure pressing the touchpad button works.
+        controller.sendClickButtonToggleEvent();
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "isButtonPressedEqualTo(0, true)", POLL_TIMEOUT_SHORT_MS);
+        controller.sendClickButtonToggleEvent();
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "isButtonPressedEqualTo(0, false)", POLL_TIMEOUT_SHORT_MS);
+
+        // Make sure setting the touchpad position works.
+        setAndValidateTouchpadPosition(controller, 0.0f, 1.0f);
+        setAndValidateTouchpadPosition(controller, 1.0f, 0.0f);
+        setAndValidateTouchpadPosition(controller, 0.5f, 0.5f);
+
+        controller.stopTouchingTouchpad(0.5f, 0.5f);
+        validateTouchpadNotTouched();
+
+        mWebXrVrTestFramework.runJavaScriptOrFail("done()", POLL_TIMEOUT_SHORT_MS);
+        mWebXrVrTestFramework.endTest();
+    }
+
+    // When touchpad is not touched, it should not be reported as pressed
+    // either. The input axis values should be at the origin: (0, 0).
+    private void validateTouchpadNotTouched() {
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "isButtonTouchedEqualTo(0, false)", POLL_TIMEOUT_SHORT_MS);
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "isButtonPressedEqualTo(0, false)", POLL_TIMEOUT_SHORT_MS);
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "areAxesValuesEqualTo(0, 0.0, 0.0)", POLL_TIMEOUT_SHORT_MS);
+    }
+
+    // Device code reports touchpad position in range [0.0, 1.0].
+    // WebXR reports Gamepad input axis position in range [-1.0, 1.0].
+    private float rawToWebXRTouchpadPosition(float raw) {
+        return (raw * 2.0f) - 1.0f;
+    }
+
+    private void setAndValidateTouchpadPosition(EmulatedVrController controller, float x, float y) {
+        controller.setTouchpadPosition(x, y);
+        float xExpected = rawToWebXRTouchpadPosition(x);
+        float yExpected = rawToWebXRTouchpadPosition(y);
+        String js =
+                String.format(Locale.US, "areAxesValuesEqualTo(0, %f, %f)", xExpected, yExpected);
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(js, POLL_TIMEOUT_SHORT_MS);
+        mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                "isButtonTouchedEqualTo(0, true)", POLL_TIMEOUT_SHORT_MS);
+    }
+
     private long sendScreenTouchDown(final View view, final int x, final int y) {
         long downTime = SystemClock.uptimeMillis();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
@@ -613,7 +706,8 @@
             // Verify that there is a gamepad on the XRInputSource and that it
             // has the expected mapping of '' (the Daydream controller does not
             // meet the 'xr-standard' mapping requirements).
-            mWebXrVrTestFramework.executeStepAndWait("validateMapping('')");
+            mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                    "isMappingEqualTo('')", POLL_TIMEOUT_SHORT_MS);
         } else {
             int x = mWebXrVrTestFramework.getCurrentContentView().getWidth() / 2;
             int y = mWebXrVrTestFramework.getCurrentContentView().getHeight() / 2;
@@ -621,7 +715,8 @@
                     TestVrShellDelegate.getVrShellForTesting().getPresentationViewForTesting();
             spamScreenTaps(presentationView, x, y, numIterations);
 
-            mWebXrVrTestFramework.executeStepAndWait("validateInputSourceHasNoGamepad()");
+            mWebXrVrTestFramework.pollJavaScriptBooleanOrFail(
+                    "inputSourceHasNoGamepad()", POLL_TIMEOUT_SHORT_MS);
         }
 
         mWebVrTestFramework.runJavaScriptOrFail("done()", POLL_TIMEOUT_SHORT_MS);
diff --git a/chrome/android/touchless/java/res/drawable/suggestion_tile.xml b/chrome/android/touchless/java/res/drawable/suggestion_tile_background.xml
similarity index 62%
copy from chrome/android/touchless/java/res/drawable/suggestion_tile.xml
copy to chrome/android/touchless/java/res/drawable/suggestion_tile_background.xml
index 9ae6fed..d1599ca 100644
--- a/chrome/android/touchless/java/res/drawable/suggestion_tile.xml
+++ b/chrome/android/touchless/java/res/drawable/suggestion_tile_background.xml
@@ -3,5 +3,6 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/tile_view_icon_focused" android:state_focused="true"/>
+    <item android:drawable="@drawable/suggestion_tile_focused_background" android:state_focused="true"/>
+    <item android:drawable="@drawable/tile_view_icon_background_modern" />
 </selector>
\ No newline at end of file
diff --git a/chrome/android/touchless/java/res/drawable/tile_view_icon_focused.xml b/chrome/android/touchless/java/res/drawable/suggestion_tile_focused_background.xml
similarity index 73%
copy from chrome/android/touchless/java/res/drawable/tile_view_icon_focused.xml
copy to chrome/android/touchless/java/res/drawable/suggestion_tile_focused_background.xml
index a19a724..bb77a80 100644
--- a/chrome/android/touchless/java/res/drawable/tile_view_icon_focused.xml
+++ b/chrome/android/touchless/java/res/drawable/suggestion_tile_focused_background.xml
@@ -4,5 +4,5 @@
      found in the LICENSE file. -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="oval">
-    <stroke android:width="@dimen/most_likely_tile_focus_stroke_width" android:color="@color/light_active_color"/>
+    <solid android:color="@color/modern_blue_600_alpha_12" />
 </shape>
\ No newline at end of file
diff --git a/chrome/android/touchless/java/res/drawable/tile_view_icon_focused.xml b/chrome/android/touchless/java/res/drawable/suggestion_tile_focused_highlight.xml
similarity index 73%
rename from chrome/android/touchless/java/res/drawable/tile_view_icon_focused.xml
rename to chrome/android/touchless/java/res/drawable/suggestion_tile_focused_highlight.xml
index a19a724..7529ac6 100644
--- a/chrome/android/touchless/java/res/drawable/tile_view_icon_focused.xml
+++ b/chrome/android/touchless/java/res/drawable/suggestion_tile_focused_highlight.xml
@@ -4,5 +4,5 @@
      found in the LICENSE file. -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="oval">
-    <stroke android:width="@dimen/most_likely_tile_focus_stroke_width" android:color="@color/light_active_color"/>
+    <stroke android:width="@dimen/focus_ring_stroke_width" android:color="@color/modern_blue_800"/>
 </shape>
\ No newline at end of file
diff --git a/chrome/android/touchless/java/res/drawable/suggestion_tile.xml b/chrome/android/touchless/java/res/drawable/suggestion_tile_highlight.xml
similarity index 73%
rename from chrome/android/touchless/java/res/drawable/suggestion_tile.xml
rename to chrome/android/touchless/java/res/drawable/suggestion_tile_highlight.xml
index 9ae6fed..4a64b1d 100644
--- a/chrome/android/touchless/java/res/drawable/suggestion_tile.xml
+++ b/chrome/android/touchless/java/res/drawable/suggestion_tile_highlight.xml
@@ -3,5 +3,5 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/tile_view_icon_focused" android:state_focused="true"/>
+    <item android:drawable="@drawable/suggestion_tile_focused_highlight" android:state_focused="true"/>
 </selector>
\ No newline at end of file
diff --git a/chrome/android/touchless/java/res/layout/touchless_suggestions_tile_view.xml b/chrome/android/touchless/java/res/layout/touchless_suggestions_tile_view.xml
index bc1dcd9..ae5d82d 100644
--- a/chrome/android/touchless/java/res/layout/touchless_suggestions_tile_view.xml
+++ b/chrome/android/touchless/java/res/layout/touchless_suggestions_tile_view.xml
@@ -9,13 +9,14 @@
     android:layout_width="@dimen/tile_view_icon_size"
     android:layout_height="@dimen/tile_view_icon_size"
     android:layout_gravity="center"
-    android:background="@drawable/suggestion_tile">
+    android:background="@drawable/suggestion_tile_highlight">
     <!-- The icon background. -->
     <View
         android:id="@+id/tile_view_icon_background"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@drawable/tile_view_icon_background_modern" />
+        android:background="@drawable/suggestion_tile_background"
+        android:duplicateParentState="true" />
 
     <!-- The main icon. -->
     <org.chromium.chrome.browser.touchless.QuantizedSizeIconView
@@ -23,8 +24,8 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="center"
-	app:smallSize="@dimen/most_likely_quantized_icon_size_small"
-	app:largeSize="@dimen/most_likely_quantized_icon_size_large"
+        app:smallSize="@dimen/most_likely_quantized_icon_size_small"
+        app:largeSize="@dimen/most_likely_quantized_icon_size_large"
         android:layout_margin="@dimen/most_likely_quantized_icon_margin"
         android:importantForAccessibility="no" />
 
@@ -32,8 +33,8 @@
     <View
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@drawable/suggestion_tile"
-        android:duplicateParentState="true"/>
+        android:background="@drawable/suggestion_tile_highlight"
+        android:duplicateParentState="true" />
 
     <!-- The offline badge. -->
     <ImageView
@@ -43,6 +44,6 @@
         android:layout_gravity="top|end"
         android:layout_marginEnd="@dimen/tile_view_offline_badge_margin_end_modern_condensed"
         android:visibility="gone"
-        android:contentDescription="@string/accessibility_ntp_offline_badge"
+        android:importantForAccessibility="no"
         app:srcCompat="@drawable/ic_offline_pin_blue_white" />
 </org.chromium.chrome.browser.touchless.SiteSuggestionsTileView>
diff --git a/chrome/android/touchless/java/res/values-v17/colors.xml b/chrome/android/touchless/java/res/values-v17/colors.xml
index d198266..26ea761 100644
--- a/chrome/android/touchless/java/res/values-v17/colors.xml
+++ b/chrome/android/touchless/java/res/values-v17/colors.xml
@@ -5,6 +5,7 @@
 
 <resources>
     <color name="modern_blue_100">#D2E3FC</color>
+    <color name="modern_blue_600_alpha_12">#1F185ABC</color>
     <color name="notouch_progress_bar_foreground">@color/modern_blue_100</color>
     <color name="notouch_progress_bar_text">@color/modern_grey_800</color>
     <color name="notouch_tooltip_background">@color/modern_grey_800</color>
diff --git a/chrome/android/touchless/java/res/values-v17/dimens.xml b/chrome/android/touchless/java/res/values-v17/dimens.xml
index d80f8f6..e8774cd 100644
--- a/chrome/android/touchless/java/res/values-v17/dimens.xml
+++ b/chrome/android/touchless/java/res/values-v17/dimens.xml
@@ -15,6 +15,8 @@
     <dimen name="notouch_key_functions_tooltip_item_image_vertical_padding">5dp</dimen>
     <dimen name="notouch_key_functions_tooltip_item_horizontal_margin">4dp</dimen>
 
+    <dimen name="focus_ring_stroke_width">2dp</dimen>
+
     <!-- Open last tab placeholder dimensions. -->
     <dimen name="open_last_tab_placeholder_image_size">24dp</dimen>
     <dimen name="open_last_tab_placeholder_image_margin_bottom">13dp</dimen>
@@ -27,7 +29,6 @@
     <dimen name="open_last_tab_timestamp_text_margin_left">10dp</dimen>
 
     <!-- Most likely carousel dimensions. -->
-    <dimen name="most_likely_tile_focus_stroke_width">2dp</dimen>
     <dimen name="most_likely_carousel_edge_spacer">8dp</dimen>
     <dimen name="most_likely_tile_size">40dp</dimen>
     <!-- Desired 24dp, but the edge spacer is also in effect. 8 + 16 = 24 -->
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
index 46d53c7..16d0e4d 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsAdapter.java
@@ -133,6 +133,9 @@
 
         mModel.get(SUGGESTIONS_KEY).addObserver(this);
         mModel.addObserver(this);
+
+        // Initialize the titleView text.
+        onPropertyChanged(mModel, CURRENT_INDEX_KEY);
     }
 
     @Override
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsLayoutManager.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsLayoutManager.java
index 5d7cfe1..a859ea4 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsLayoutManager.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsLayoutManager.java
@@ -275,38 +275,44 @@
      */
     private void resizeViews() {
         int leftOffset = 0;
+        final int defaultIconSize =
+                mContext.getResources().getDimensionPixelSize(R.dimen.most_likely_tile_size);
         // Amount of space to leave between items such that it would take up all available width.
         final int horizontalSpacer =
-                (int) ((getWidth()
-                               - SUM_FACTORS
-                                       * mContext.getResources().getDimensionPixelSize(
-                                               R.dimen.most_likely_tile_size))
-                        / (MAX_TILES - 1));
+                (int) ((getWidth() - SUM_FACTORS * defaultIconSize) / (MAX_TILES - 1));
 
-        for (int i = 0; i < Math.min(getChildCount(), MAX_TILES); i++) {
-            View child = getChildAt(i);
+        // Number of "empty" spaces to leave on the left side.
+        // This helps ensure that mFocusPosition is laid out in the center even if there are
+        // no elements to the left of it.
+        int leftSpaceCount =
+                (SCALE_FACTORS.length / 2) - mFocusPosition + findFirstVisibleItemPosition();
+        int leftDecrement = leftSpaceCount;
 
-            // Scale according to position.
-            ViewGroup.LayoutParams params = child.getLayoutParams();
-            int iconSize = (int) (mContext.getResources().getDimensionPixelSize(
-                                          R.dimen.most_likely_tile_size)
-                    * SCALE_FACTORS[i]);
-            params.height = iconSize;
-            params.width = iconSize;
-            child.setLayoutParams(params);
+        for (int i = 0; i < Math.min(getChildCount() + leftSpaceCount, MAX_TILES); i++) {
+            int iconSize = (int) (defaultIconSize * SCALE_FACTORS[i]);
+            int decoratedChildWidth = iconSize;
+            if (leftDecrement == 0) {
+                View child = getChildAt(i - leftSpaceCount);
 
-            // Offset the top so that it's centered vertically.
-            int topOffset =
-                    (mContext.getResources().getDimensionPixelSize(R.dimen.most_likely_tile_size)
-                            - iconSize)
-                    / 2;
+                // Scale according to position.
+                ViewGroup.LayoutParams params = child.getLayoutParams();
 
-            measureChildWithMargins(child, 0, 0);
-            int decoratedChildWidth = getDecoratedMeasuredWidth(child);
-            int decoratedChildHeight = getDecoratedMeasuredHeight(child);
+                params.height = iconSize;
+                params.width = iconSize;
+                child.setLayoutParams(params);
 
-            layoutDecoratedWithMargins(child, leftOffset, topOffset,
-                    leftOffset + decoratedChildWidth, topOffset + decoratedChildHeight);
+                // Offset the top so that it's centered vertically.
+                int topOffset = (defaultIconSize - iconSize) / 2;
+
+                measureChildWithMargins(child, 0, 0);
+                decoratedChildWidth = getDecoratedMeasuredWidth(child);
+                int decoratedChildHeight = getDecoratedMeasuredHeight(child);
+
+                layoutDecoratedWithMargins(child, leftOffset, topOffset,
+                        leftOffset + decoratedChildWidth, topOffset + decoratedChildHeight);
+            } else {
+                leftDecrement--;
+            }
 
             // The next tile needs to be <horizontal spacer> padded away from this tile.
             leftOffset += decoratedChildWidth + horizontalSpacer;
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
index 0bae4c1..0c22dd26 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsMediator.java
@@ -150,12 +150,17 @@
                          Toast.LENGTH_SHORT)
                     .show();
 
-            // When we remove an item, reset the item count key first
+            // When we remove an item, reset the item count key first.
             int itemCount =
                     getItemCount(mModel.get(SiteSuggestionsCoordinator.SUGGESTIONS_KEY).size() - 1);
             mModel.set(SiteSuggestionsCoordinator.ITEM_COUNT_KEY, itemCount);
             // Actually remove the suggestion.
             mModel.get(SiteSuggestionsCoordinator.SUGGESTIONS_KEY).remove(suggestion);
+            if (itemCount == 1) {
+                // If we removed everything except "All Apps", reset the index to 0.
+                mModel.set(SiteSuggestionsCoordinator.INITIAL_INDEX_KEY, 0);
+                mModel.set(SiteSuggestionsCoordinator.CURRENT_INDEX_KEY, 0);
+            }
             // When removal of a site causes us to have fewer sites than we want to display, fetch
             // again.
             if (itemCount < MAX_DISPLAYED_TILES) {
diff --git a/chrome/app/chrome_exe_main_mac.cc b/chrome/app/chrome_exe_main_mac.cc
index daf521f..4f6ca44 100644
--- a/chrome/app/chrome_exe_main_mac.cc
+++ b/chrome/app/chrome_exe_main_mac.cc
@@ -62,6 +62,8 @@
   }
 
 #if BUILDFLAG(NEW_MAC_BUNDLE_STRUCTURE)
+  // The helper lives within the versioned framework directory, so simply
+  // go up to find the main dylib.
   const char rel_path[] = "../../../../" PRODUCT_FULLNAME_STRING " Framework";
 #else
   const char rel_path[] =
@@ -70,10 +72,9 @@
 #endif  // NEW_MAC_BUNDLE_STRUCTURE
 #else
 #if BUILDFLAG(NEW_MAC_BUNDLE_STRUCTURE)
-  const char rel_path[] =
-      "../Frameworks/" PRODUCT_FULLNAME_STRING
-      " Framework.framework/Versions/Current/" PRODUCT_FULLNAME_STRING
-      " Framework";
+  const char rel_path[] = "../Frameworks/" PRODUCT_FULLNAME_STRING
+                          " Framework.framework/Versions/" CHROME_VERSION_STRING
+                          "/" PRODUCT_FULLNAME_STRING " Framework";
 #else
   const char rel_path[] =
       "../Versions/" CHROME_VERSION_STRING "/" PRODUCT_FULLNAME_STRING
diff --git a/chrome/app/chrome_main_mac.mm b/chrome/app/chrome_main_mac.mm
index 8e806e8..354916f 100644
--- a/chrome/app/chrome_main_mac.mm
+++ b/chrome/app/chrome_main_mac.mm
@@ -30,8 +30,6 @@
 #if BUILDFLAG(NEW_MAC_BUNDLE_STRUCTURE)
   base::FilePath child_exe_path =
       chrome::GetFrameworkBundlePath()
-          .Append("Versions")
-          .Append(chrome::kChromeVersion)
           .Append("Helpers")
           .Append(chrome::kHelperProcessExecutablePath);
 #else
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index c6cf99c..558d552c 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -949,7 +949,7 @@
           If an image doesn’t have a useful description, Chromium will try to provide one for you. To create descriptions, images are sent to Google.
         </message>
         <message name="IDS_CONTENT_CONTEXT_SPELLING_BUBBLE_TEXT" desc="The text of a bubble that confirms users allows integrating the spelling service of Google to Chrome.">
-          This uses the same spellchecker that's used in Google search. Text you type in the browser is sent to Google. You can always change this behavior in settings.
+          This uses the same spell checker that's used in Google search. Text you type in the browser is sent to Google. You can always change this behavior in settings.
         </message>
         <if expr="not use_titlecase">
           <message name="IDS_CONTENT_CONTEXT_OPENLINKNEWTAB_INAPP" desc="The name of the command to open a link in a newly created browser tab when the user is in an app window">
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 5b4b274..106cf8c0 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -790,10 +790,10 @@
             Turn On Enhanced Spell Check
           </message>
           <message name="IDS_CONTENT_CONTEXT_SPELLING_BUBBLE_ENABLE" desc="In Title Case: The button text that allows integrating the spelling service of Google.">
-            Enable
+            Turn On
           </message>
           <message name="IDS_CONTENT_CONTEXT_SPELLING_BUBBLE_DISABLE" desc="In Title Case: The button text that disallows integrating the spelling service of Google.">
-            No Thanks
+            Cancel
           </message>
           <message name="IDS_CONTENT_CONTEXT_SPELLING_CHECKING" desc="The place-holder message shown while the Spelling service is checking text">
             Loading suggestion
@@ -848,7 +848,7 @@
       </if>
       <if expr="not is_macosx">
         <message name="IDS_CONTENT_CONTEXT_SPELLCHECK_MENU" desc="The name of the menu item that shows the submenu for spellcheck options">
-          &amp;Spellcheck
+          &amp;Spell check
         </message>
         <message name="IDS_CONTENT_CONTEXT_LANGUAGE_SETTINGS" desc="The name of the menu item that opens spellcheck settings in a new tab">
           &amp;Language settings
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 6f3c51c..5dec1cc 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -968,7 +968,7 @@
           If an image doesn’t have a useful description, Chrome will try to provide one for you. To create descriptions, images are sent to Google.
         </message>
         <message name="IDS_CONTENT_CONTEXT_SPELLING_BUBBLE_TEXT" desc="The text of a bubble that confirms users allows integrating the spelling service of Google to Chrome.">
-          This uses the same spellchecker that's used in Google search. Text you type in the browser is sent to Google. You can always change this behavior in settings.
+          This uses the same spell checker that's used in Google search. Text you type in the browser is sent to Google. You can always change this behavior in settings.
         </message>
         <if expr="not use_titlecase">
           <message name="IDS_CONTENT_CONTEXT_OPENLINKNEWTAB_INAPP" desc="The name of the command to open a link in a newly created browser tab when the user is in an app window">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 2b8cc75..d034178 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2382,6 +2382,10 @@
       "android/headers_classifier.cc",
       "android/history/browsing_history_bridge.cc",
       "android/history/browsing_history_bridge.h",
+      "android/history/history_deletion_bridge.cc",
+      "android/history/history_deletion_bridge.h",
+      "android/history/history_deletion_info.cc",
+      "android/history/history_deletion_info.h",
       "android/history_report/data_observer.cc",
       "android/history_report/data_observer.h",
       "android/history_report/data_provider.cc",
@@ -2606,6 +2610,9 @@
       "autofill/android/phone_number_util_android.cc",
       "autofill/autofill_keyboard_accessory_adapter.cc",
       "autofill/autofill_keyboard_accessory_adapter.h",
+      "autofill/credit_card_accessory_controller.h",
+      "autofill/credit_card_accessory_controller_impl.cc",
+      "autofill/credit_card_accessory_controller_impl.h",
       "autofill/manual_filling_controller.h",
       "autofill/manual_filling_controller_impl.cc",
       "autofill/manual_filling_controller_impl.h",
diff --git a/chrome/browser/android/history/history_deletion_bridge.cc b/chrome/browser/android/history/history_deletion_bridge.cc
new file mode 100644
index 0000000..bce4453
--- /dev/null
+++ b/chrome/browser/android/history/history_deletion_bridge.cc
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/history/history_deletion_bridge.h"
+
+#include <string>
+#include <vector>
+
+#include "chrome/browser/android/history/history_deletion_info.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "components/history/core/browser/history_service.h"
+#include "jni/HistoryDeletionBridge_jni.h"
+
+using base::android::JavaParamRef;
+using base::android::JavaRef;
+using base::android::ScopedJavaGlobalRef;
+
+static jlong JNI_HistoryDeletionBridge_Init(JNIEnv* env,
+                                            const JavaParamRef<jobject>& jobj) {
+  return reinterpret_cast<intptr_t>(new HistoryDeletionBridge(jobj));
+}
+
+HistoryDeletionBridge::HistoryDeletionBridge(const JavaRef<jobject>& jobj)
+    : jobj_(ScopedJavaGlobalRef<jobject>(jobj)),
+      profile_(ProfileManager::GetLastUsedProfile()->GetOriginalProfile()) {
+  history::HistoryService* history_service =
+      HistoryServiceFactory::GetForProfile(profile_,
+                                           ServiceAccessType::IMPLICIT_ACCESS);
+  if (history_service)
+    history_service->AddObserver(this);
+}
+
+HistoryDeletionBridge::~HistoryDeletionBridge() {
+  history::HistoryService* history_service =
+      HistoryServiceFactory::GetForProfile(profile_,
+                                           ServiceAccessType::IMPLICIT_ACCESS);
+  if (history_service)
+    history_service->RemoveObserver(this);
+}
+
+void HistoryDeletionBridge::OnURLsDeleted(
+    history::HistoryService* history_service,
+    const history::DeletionInfo& deletion_info) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_HistoryDeletionBridge_onURLsDeleted(
+      env, jobj_, CreateHistoryDeletionInfo(env, &deletion_info));
+}
diff --git a/chrome/browser/android/history/history_deletion_bridge.h b/chrome/browser/android/history/history_deletion_bridge.h
new file mode 100644
index 0000000..0edddc6
--- /dev/null
+++ b/chrome/browser/android/history/history_deletion_bridge.h
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_HISTORY_HISTORY_DELETION_BRIDGE_H_
+#define CHROME_BROWSER_ANDROID_HISTORY_HISTORY_DELETION_BRIDGE_H_
+
+#include "base/macros.h"
+#include "components/history/core/browser/history_service_observer.h"
+#include "components/history/core/browser/history_types.h"
+
+namespace history {
+class HistoryService;
+}  // namespace history
+
+class Profile;
+
+// Native counterpart of HistoryDeletionBridge.java. Receives history deletion
+// events that originate in native code and forwards them to Java.
+class HistoryDeletionBridge : public history::HistoryServiceObserver {
+ public:
+  explicit HistoryDeletionBridge(const base::android::JavaRef<jobject>& j_this);
+
+  // history::HistoryServiceObserver.
+  void OnURLsDeleted(history::HistoryService* history_service,
+                     const history::DeletionInfo& deletion_info) override;
+
+ private:
+  ~HistoryDeletionBridge() override;
+
+  // Reference to the Java half of this bridge. Always valid.
+  base::android::ScopedJavaGlobalRef<jobject> jobj_;
+
+  Profile* profile_;
+
+  DISALLOW_COPY_AND_ASSIGN(HistoryDeletionBridge);
+};
+
+#endif  // CHROME_BROWSER_ANDROID_HISTORY_HISTORY_DELETION_BRIDGE_H_
diff --git a/chrome/browser/android/history/history_deletion_info.cc b/chrome/browser/android/history/history_deletion_info.cc
new file mode 100644
index 0000000..7326443
--- /dev/null
+++ b/chrome/browser/android/history/history_deletion_info.cc
@@ -0,0 +1,70 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/history/history_deletion_info.h"
+
+#include "base/android/jni_array.h"
+#include "components/history/core/browser/history_types.h"
+#include "jni/HistoryDeletionInfo_jni.h"
+
+using base::android::ScopedJavaLocalRef;
+
+namespace {
+
+history::DeletionInfo* ToDeletionInfo(jlong j_deletion_info) {
+  return reinterpret_cast<history::DeletionInfo*>(j_deletion_info);
+}
+
+}  // namespace
+
+ScopedJavaLocalRef<jobjectArray> JNI_HistoryDeletionInfo_GetDeletedURLs(
+    JNIEnv* env,
+    jlong history_deletion_info_ptr) {
+  history::DeletionInfo* deletion_info =
+      ToDeletionInfo(history_deletion_info_ptr);
+  std::vector<std::string> deleted_urls;
+  for (auto row : deletion_info->deleted_rows()) {
+    deleted_urls.push_back(row.url().spec());
+  }
+
+  return base::android::ToJavaArrayOfStrings(env, deleted_urls);
+}
+
+jboolean JNI_HistoryDeletionInfo_IsTimeRangeValid(
+    JNIEnv* env,
+    jlong history_deletion_info_ptr) {
+  history::DeletionInfo* deletion_info =
+      ToDeletionInfo(history_deletion_info_ptr);
+  return deletion_info->time_range().IsValid();
+}
+
+jboolean JNI_HistoryDeletionInfo_IsTimeRangeForAllTime(
+    JNIEnv* env,
+    jlong history_deletion_info_ptr) {
+  history::DeletionInfo* deletion_info =
+      ToDeletionInfo(history_deletion_info_ptr);
+  return deletion_info->time_range().IsAllTime();
+}
+
+jlong JNI_HistoryDeletionInfo_GetTimeRangeBegin(
+    JNIEnv* env,
+    jlong history_deletion_info_ptr) {
+  history::DeletionInfo* deletion_info =
+      ToDeletionInfo(history_deletion_info_ptr);
+  return deletion_info->time_range().begin().ToJavaTime();
+}
+
+jlong JNI_HistoryDeletionInfo_GetTimeRangeEnd(JNIEnv* env,
+                                              jlong history_deletion_info_ptr) {
+  history::DeletionInfo* deletion_info =
+      ToDeletionInfo(history_deletion_info_ptr);
+  return deletion_info->time_range().end().ToJavaTime();
+}
+
+ScopedJavaLocalRef<jobject> CreateHistoryDeletionInfo(
+    JNIEnv* env,
+    const history::DeletionInfo* deletion_info) {
+  return Java_HistoryDeletionInfo_create(
+      env, reinterpret_cast<intptr_t>(deletion_info));
+}
diff --git a/chrome/browser/android/history/history_deletion_info.h b/chrome/browser/android/history/history_deletion_info.h
new file mode 100644
index 0000000..cc9866b
--- /dev/null
+++ b/chrome/browser/android/history/history_deletion_info.h
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_HISTORY_HISTORY_DELETION_INFO_H_
+#define CHROME_BROWSER_ANDROID_HISTORY_HISTORY_DELETION_INFO_H_
+
+#include "base/android/jni_weak_ref.h"
+#include "base/android/scoped_java_ref.h"
+
+namespace history {
+class DeletionInfo;
+}  // namespace history
+
+// Create a Java wrapper object of history::DeletionInfo to pass over the JNI.
+base::android::ScopedJavaLocalRef<jobject> CreateHistoryDeletionInfo(
+    JNIEnv* env,
+    const history::DeletionInfo* deletion_info);
+
+#endif  // CHROME_BROWSER_ANDROID_HISTORY_HISTORY_DELETION_INFO_H_
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 06125c5..0f4d4b1 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -180,8 +180,14 @@
                                                 base::BlockingType::MAY_BLOCK);
 
 #if BUILDFLAG(NEW_MAC_BUNDLE_STRUCTURE)
-  base::FilePath app_bundle_path =
-      chrome::GetFrameworkBundlePath().DirName().DirName().DirName();
+  // Go up five levels from the versioned sub-directory of the framework, which
+  // is at C.app/Contents/Frameworks/C.framework/Versions/V.
+  base::FilePath app_bundle_path = chrome::GetFrameworkBundlePath()
+                                       .DirName()
+                                       .DirName()
+                                       .DirName()
+                                       .DirName()
+                                       .DirName();
 #else
   base::FilePath app_bundle_path =
       chrome::GetVersionedDirectory().DirName().DirName().DirName();
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index c631933..b908adf 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -73,6 +73,7 @@
 #include "content/public/common/child_process_host.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/mime_handler_view_mode.h"
 #include "content/public/common/result_codes.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test_utils.h"
@@ -3319,6 +3320,12 @@
 }
 
 IN_PROC_BROWSER_TEST_F(WebViewPluginTest, TestLoadPluginInternalResource) {
+  if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+    // Permissions are broken with frame-based MimeHandlerView as it never goes
+    // through the same plugin checks when attaching an <embed>. Fix this asap.
+    // (https://crbug.com/963694).
+    return;
+  }
   const char kTestMimeType[] = "application/pdf";
   const char kTestFileType[] = "pdf";
   content::WebPluginInfo plugin_info;
diff --git a/chrome/browser/autofill/credit_card_accessory_controller.h b/chrome/browser/autofill/credit_card_accessory_controller.h
new file mode 100644
index 0000000..c84dc97
--- /dev/null
+++ b/chrome/browser/autofill/credit_card_accessory_controller.h
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_CREDIT_CARD_ACCESSORY_CONTROLLER_H_
+#define CHROME_BROWSER_AUTOFILL_CREDIT_CARD_ACCESSORY_CONTROLLER_H_
+
+#include "chrome/browser/autofill/accessory_controller.h"
+
+namespace autofill {
+
+// Interface for credit card-specific keyboard accessory controller between the
+// ManualFillingController and Autofill backend logic.
+class CreditCardAccessoryController : public AccessoryController {
+ public:
+  CreditCardAccessoryController() = default;
+  ~CreditCardAccessoryController() override = default;
+
+  // Fetches suggestions and propagates to the frontend.
+  virtual void RefreshSuggestionsForField() = 0;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_AUTOFILL_CREDIT_CARD_ACCESSORY_CONTROLLER_H_
diff --git a/chrome/browser/autofill/credit_card_accessory_controller_impl.cc b/chrome/browser/autofill/credit_card_accessory_controller_impl.cc
new file mode 100644
index 0000000..ddb002e
--- /dev/null
+++ b/chrome/browser/autofill/credit_card_accessory_controller_impl.cc
@@ -0,0 +1,99 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/credit_card_accessory_controller_impl.h"
+
+#include <iterator>
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/autofill/manual_filling_controller.h"
+#include "chrome/browser/autofill/manual_filling_utils.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace autofill {
+
+namespace {
+
+base::string16 GetTitle(bool unused) {
+  return l10n_util::GetStringUTF16(IDS_MANUAL_FILLING_CREDIT_CARD_SHEET_TITLE);
+}
+
+void AddField(const base::string16& data, UserInfo* user_info) {
+  user_info->add_field(UserInfo::Field(data, data,
+                                       /*is_password=*/false,
+                                       /*selectable=*/true));
+}
+
+UserInfo Translate(const CreditCard* data) {
+  DCHECK(data);
+
+  UserInfo user_info;
+
+  AddField(data->ObfuscatedLastFourDigits(), &user_info);
+
+  if (data->HasValidExpirationDate()) {
+    // TOOD(crbug.com/902425): Pass expiration date as grouped values
+    AddField(data->ExpirationMonthAsString(), &user_info);
+    AddField(data->Expiration4DigitYearAsString(), &user_info);
+  }
+
+  if (data->HasNameOnCard()) {
+    AddField(data->GetRawInfo(autofill::CREDIT_CARD_NAME_FULL), &user_info);
+  }
+
+  return user_info;
+}
+
+}  // namespace
+
+CreditCardAccessoryControllerImpl::CreditCardAccessoryControllerImpl(
+    autofill::PersonalDataManager* personal_data_manager,
+    content::WebContents* web_contents)
+    : personal_data_manager_(personal_data_manager),
+      web_contents_(web_contents) {}
+
+CreditCardAccessoryControllerImpl::~CreditCardAccessoryControllerImpl() =
+    default;
+
+void CreditCardAccessoryControllerImpl::RefreshSuggestionsForField() {
+  const std::vector<CreditCard*> suggestions = GetSuggestions();
+  std::vector<UserInfo> info_to_add;
+  std::transform(suggestions.begin(), suggestions.end(),
+                 std::back_inserter(info_to_add), &Translate);
+
+  // TODO(crbug.com/902425): Add "Manage payment methods" footer command
+  std::vector<FooterCommand> footer_commands;
+
+  bool has_suggestions = !info_to_add.empty();
+  GetManualFillingController()->RefreshSuggestionsForField(
+      /*is_fillable=*/true,
+      autofill::CreateAccessorySheetData(GetTitle(has_suggestions),
+                                         std::move(info_to_add),
+                                         std::move(footer_commands)));
+}
+
+void CreditCardAccessoryControllerImpl::SetManualFillingControllerForTesting(
+    base::WeakPtr<ManualFillingController> controller) {
+  mf_controller_ = controller;
+}
+
+const std::vector<CreditCard*>
+CreditCardAccessoryControllerImpl::GetSuggestions() {
+  DCHECK(personal_data_manager_);
+  return personal_data_manager_->GetCreditCardsToSuggest(
+      /*include_server_cards=*/true);
+}
+
+base::WeakPtr<ManualFillingController>
+CreditCardAccessoryControllerImpl::GetManualFillingController() {
+  if (!mf_controller_)
+    mf_controller_ = ManualFillingController::GetOrCreate(web_contents_);
+  DCHECK(mf_controller_);
+  return mf_controller_;
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/autofill/credit_card_accessory_controller_impl.h b/chrome/browser/autofill/credit_card_accessory_controller_impl.h
new file mode 100644
index 0000000..e898d9f
--- /dev/null
+++ b/chrome/browser/autofill/credit_card_accessory_controller_impl.h
@@ -0,0 +1,48 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_AUTOFILL_CREDIT_CARD_ACCESSORY_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_AUTOFILL_CREDIT_CARD_ACCESSORY_CONTROLLER_IMPL_H_
+
+#include "chrome/browser/autofill/credit_card_accessory_controller.h"
+
+#include "base/memory/weak_ptr.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+
+namespace content {
+class WebContents;
+}
+
+class ManualFillingController;
+
+namespace autofill {
+
+class CreditCardAccessoryControllerImpl : public CreditCardAccessoryController {
+ public:
+  CreditCardAccessoryControllerImpl(PersonalDataManager* personal_data_manager,
+                                    content::WebContents* web_contents);
+  ~CreditCardAccessoryControllerImpl() override;
+
+  // AccessoryController:
+  // TODO(crbug.com/902425): Implement filling logic.
+  void OnFillingTriggered(const UserInfo::Field& selection) override {}
+
+  // CreditCardAccessoryController:
+  void RefreshSuggestionsForField() override;
+
+  void SetManualFillingControllerForTesting(
+      base::WeakPtr<ManualFillingController> controller);
+
+ private:
+  const std::vector<CreditCard*> GetSuggestions();
+  base::WeakPtr<ManualFillingController> GetManualFillingController();
+
+  const PersonalDataManager* personal_data_manager_;
+  content::WebContents* web_contents_;
+  base::WeakPtr<ManualFillingController> mf_controller_;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_AUTOFILL_CREDIT_CARD_ACCESSORY_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc b/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc
new file mode 100644
index 0000000..8f18a14
--- /dev/null
+++ b/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/credit_card_accessory_controller_impl.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/autofill/mock_manual_filling_controller.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::SaveArg;
+
+namespace autofill {
+
+class CreditCardAccessoryControllerTest : public testing::Test {
+ public:
+  CreditCardAccessoryControllerTest()
+      : controller_(&data_manager_, /*web_contents=*/nullptr) {}
+
+  void SetUp() override {
+    controller_.SetManualFillingControllerForTesting(
+        mock_mf_controller_.AsWeakPtr());
+    data_manager_.SetPrefService(profile_.GetPrefs());
+  }
+
+  void TearDown() override {
+    data_manager_.SetPrefService(nullptr);
+    data_manager_.ClearCreditCards();
+  }
+
+ protected:
+  content::TestBrowserThreadBundle
+      test_browser_thread_bundle_;  // for |profile_|
+  CreditCardAccessoryControllerImpl controller_;
+  testing::StrictMock<MockManualFillingController> mock_mf_controller_;
+  autofill::TestPersonalDataManager data_manager_;
+  TestingProfile profile_;
+};
+
+TEST_F(CreditCardAccessoryControllerTest, RefreshSuggestionsForField) {
+  autofill::CreditCard card;
+  card.SetNumber(base::ASCIIToUTF16("4111111111111111"));
+  card.SetExpirationMonth(04);
+  card.SetExpirationYear(39);
+  card.SetRawInfo(autofill::CREDIT_CARD_NAME_FULL,
+                  base::ASCIIToUTF16("Kirby Puckett"));
+
+  data_manager_.AddCreditCard(card);
+
+  autofill::AccessorySheetData result(autofill::FallbackSheetType::PASSWORD,
+                                      base::string16());
+
+  EXPECT_CALL(mock_mf_controller_, RefreshSuggestionsForField(
+                                       /*is_fillable=*/true, _))
+      .WillOnce(SaveArg<1>(&result));
+
+  controller_.RefreshSuggestionsForField();
+
+  ASSERT_EQ(1u, result.user_info_list().size());
+  auto info = result.user_info_list().at(0);
+  ASSERT_EQ(4u, info.fields().size());
+  base::string16 expected_cc_string =
+      autofill::internal::GetObfuscatedStringForCardDigits(
+          base::ASCIIToUTF16("1111"));
+  EXPECT_EQ(expected_cc_string, info.fields().at(0).display_text());
+  EXPECT_EQ(base::ASCIIToUTF16("04"), info.fields().at(1).display_text());
+  EXPECT_EQ(base::ASCIIToUTF16("2039"), info.fields().at(2).display_text());
+  EXPECT_EQ(base::ASCIIToUTF16("Kirby Puckett"),
+            info.fields().at(3).display_text());
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index 084f92d..4a24994 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -249,9 +249,19 @@
   Profile* profile = Profile::FromBrowserContext(browser_context());
   PrefService* const service = profile->GetPrefs();
 
-  if (params->change_info.cellular_disabled)
+  if (params->change_info.cellular_disabled) {
     service->SetBoolean(drive::prefs::kDisableDriveOverCellular,
                         *params->change_info.cellular_disabled);
+  }
+  if (params->change_info.arc_enabled) {
+    service->SetBoolean(arc::prefs::kArcEnabled,
+                        *params->change_info.arc_enabled);
+  }
+  if (params->change_info.arc_removable_media_access_enabled) {
+    service->SetBoolean(
+        arc::prefs::kArcHasAccessToRemovableMedia,
+        *params->change_info.arc_removable_media_access_enabled);
+  }
 
   return RespondNow(NoArguments());
 }
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index d656009..cb34061 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -258,6 +258,8 @@
         TestCase("fileDisplayMtp"),
         TestCase("fileDisplayUsb"),
         TestCase("fileDisplayUsbPartition"),
+        TestCase("fileDisplayUsbToast"),
+        TestCase("fileDisplayUsbNoToast"),
         TestCase("fileDisplayPartitionFileTable"),
         TestCase("fileSearch"),
         TestCase("fileSearch").EnableMyFilesVolume(),
diff --git a/chrome/browser/chromeos/file_manager/file_manager_string_util.cc b/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
index e52b52e..24b688d 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
@@ -768,6 +768,10 @@
   SET_STRING("TOTAL_FILE_COUNT", IDS_FILE_BROWSER_TOTAL_FILE_COUNT_LABEL);
   SET_STRING("IMAGE_RESOLUTION_COLUMN_LABEL",
              IDS_FILE_BROWSER_IMAGE_RESOLUTION_COLUMN_LABEL);
+  SET_STRING("FILE_BROWSER_PLAY_STORE_ACCESS_LABEL",
+             IDS_FILE_BROWSER_PLAY_STORE_ACCESS_LABEL);
+  SET_STRING("FILE_BROWSER_OPEN_PLAY_STORE_SETTINGS_LABEL",
+             IDS_FILE_BROWSER_OPEN_PLAY_STORE_SETTINGS_LABEL);
   SET_STRING("ANDROID_FILES_ROOT_LABEL",
              IDS_FILE_BROWSER_ANDROID_FILES_ROOT_LABEL);
   SET_STRING("SHOW_ALL_ANDROID_FOLDERS_OPTION",
diff --git a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
index dec97be..9b52f7b 100644
--- a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
+++ b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
@@ -19,8 +19,11 @@
 #include "chrome/browser/chromeos/login/screens/recommend_apps/scoped_test_recommend_apps_fetcher_factory.h"
 #include "chrome/browser/chromeos/login/screens/sync_consent_screen.h"
 #include "chrome/browser/chromeos/login/screens/update_screen.h"
+#include "chrome/browser/chromeos/login/test/device_state_mixin.h"
+#include "chrome/browser/chromeos/login/test/embedded_test_server_mixin.h"
 #include "chrome/browser/chromeos/login/test/fake_gaia_mixin.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
 #include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_exit_waiter.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
@@ -38,6 +41,7 @@
 #include "chrome/browser/ui/webui/chromeos/login/network_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/recommend_apps_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/terms_of_service_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/update_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/welcome_screen_handler.h"
 #include "chromeos/constants/chromeos_switches.h"
@@ -117,12 +121,7 @@
       ScopedQuickUnlockPrivateGetAuthTokenFunctionObserver);
 };
 
-}  // namespace
-
-class OobeInteractiveUITest
-    : public OobeBaseTest,
-      public extensions::QuickUnlockPrivateGetAuthTokenFunction::TestObserver,
-      public ::testing::WithParamInterface<std::tuple<bool, bool, ArcState>> {
+class OobeEndToEndTestSetupMixin : public InProcessBrowserTestMixin {
  public:
   struct Parameters {
     bool is_tablet;
@@ -137,33 +136,30 @@
     }
   };
 
-  OobeInteractiveUITest() = default;
-  ~OobeInteractiveUITest() override = default;
-
-  void SetUp() override {
-    params_ = Parameters();
-    std::tie(params_->is_tablet, params_->is_quick_unlock_enabled,
-             params_->arc_state) = GetParam();
-    LOG(INFO) << "OobeInteractiveUITest() started with params "
-              << params_->ToString();
-    if (params_->arc_state != ArcState::kNotAvailable)
-      feature_list_.InitAndEnableFeature(switches::kAssistantFeature);
-    OobeBaseTest::SetUp();
+  explicit OobeEndToEndTestSetupMixin(
+      InProcessBrowserTestMixinHost* mixin_host,
+      net::EmbeddedTestServer* arc_tos_server,
+      const std::tuple<bool, bool, ArcState>& parameters)
+      : InProcessBrowserTestMixin(mixin_host), arc_tos_server_(arc_tos_server) {
+    std::tie(params_.is_tablet, params_.is_quick_unlock_enabled,
+             params_.arc_state) = parameters;
   }
+  ~OobeEndToEndTestSetupMixin() override = default;
 
-  void TearDown() override {
-    quick_unlock::EnabledForTesting(false);
-    OobeBaseTest::TearDown();
-    params_.reset();
+  // InProcessBrowserTestMixin:
+  void SetUp() override {
+    LOG(INFO) << "OOBE end-to-end test  started with params "
+              << params_.ToString();
+
+    if (params_.arc_state != ArcState::kNotAvailable)
+      feature_list_.InitAndEnableFeature(switches::kAssistantFeature);
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    OobeBaseTest::SetUpCommandLine(command_line);
-
-    if (params_->is_tablet)
+    if (params_.is_tablet)
       command_line->AppendSwitch(ash::switches::kAshEnableTabletMode);
 
-    if (params_->arc_state != ArcState::kNotAvailable) {
+    if (params_.arc_state != ArcState::kNotAvailable) {
       arc::SetArcAvailableCommandLineForTesting(command_line);
       // Prevent encryption migration screen from showing up after user login
       // with ARC available.
@@ -172,55 +168,45 @@
   }
 
   void SetUpInProcessBrowserTestFixture() override {
-    OobeBaseTest::SetUpInProcessBrowserTestFixture();
-
-    if (params_->is_quick_unlock_enabled)
+    if (params_.is_quick_unlock_enabled)
       quick_unlock::EnabledForTesting(true);
 
-    if (params_->arc_state != ArcState::kNotAvailable) {
+    if (params_.arc_state != ArcState::kNotAvailable) {
       recommend_apps_fetcher_factory_ =
           std::make_unique<ScopedTestRecommendAppsFetcherFactory>(
               base::BindRepeating(&CreateRecommendAppsFetcher));
-      arc_tos_server_.RegisterRequestHandler(base::BindRepeating(
-          &OobeInteractiveUITest::HandleRequest, base::Unretained(this)));
+      if (arc_tos_server_) {
+        arc_tos_server_->RegisterRequestHandler(
+            base::BindRepeating(&OobeEndToEndTestSetupMixin::HandleRequest,
+                                base::Unretained(this)));
+      }
     }
   }
 
   void SetUpOnMainThread() override {
-    OobeBaseTest::SetUpOnMainThread();
-
-    if (params_->is_tablet)
+    if (params_.is_tablet)
       TabletModeClient::Get()->OnTabletModeToggled(true);
 
-    if (params_->arc_state != ArcState::kNotAvailable) {
+    if (params_.arc_state != ArcState::kNotAvailable) {
       // Init ArcSessionManager for testing.
       arc::ArcServiceLauncher::Get()->ResetForTesting();
       arc::ArcSessionManager::Get()->SetArcSessionRunnerForTesting(
           std::make_unique<arc::ArcSessionRunner>(
               base::BindRepeating(arc::FakeArcSession::Create)));
 
-      test::OobeJS().Evaluate(base::StringPrintf(
-          "login.ArcTermsOfServiceScreen.setTosHostNameForTesting('%s');",
-          arc_tos_server_.GetURL("/arc-tos").spec().c_str()));
+      if (arc_tos_server_) {
+        test::OobeJS().Evaluate(base::StringPrintf(
+            "login.ArcTermsOfServiceScreen.setTosHostNameForTesting('%s');",
+            arc_tos_server_->GetURL("/arc-tos").spec().c_str()));
+      }
     }
   }
-
-  void TearDownOnMainThread() override {
-    // If the login display is still showing, exit gracefully.
-    if (LoginDisplayHost::default_host()) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(&chrome::AttemptExit));
-      RunUntilBrowserProcessQuits();
-    }
-
-    OobeBaseTest::TearDownOnMainThread();
-  }
-
   void TearDownInProcessBrowserTestFixture() override {
     recommend_apps_fetcher_factory_.reset();
-    OobeBaseTest::TearDownInProcessBrowserTestFixture();
   }
 
+  void TearDown() override { quick_unlock::EnabledForTesting(false); }
+
   std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
     auto response = std::make_unique<BasicHttpResponse>();
     if (request.relative_url != "/arc-tos/about/play-terms.html") {
@@ -233,6 +219,46 @@
     return response;
   }
 
+  bool is_tablet() const { return params_.is_tablet; }
+
+  bool is_quick_unlock_enabled() const {
+    return params_.is_quick_unlock_enabled;
+  }
+
+  ArcState arc_state() const { return params_.arc_state; }
+
+ private:
+  Parameters params_;
+
+  base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<ScopedTestRecommendAppsFetcherFactory>
+      recommend_apps_fetcher_factory_;
+  net::EmbeddedTestServer* arc_tos_server_;
+
+  DISALLOW_COPY_AND_ASSIGN(OobeEndToEndTestSetupMixin);
+};
+
+}  // namespace
+
+class OobeInteractiveUITest
+    : public OobeBaseTest,
+      public extensions::QuickUnlockPrivateGetAuthTokenFunction::TestObserver,
+      public ::testing::WithParamInterface<std::tuple<bool, bool, ArcState>> {
+ public:
+  OobeInteractiveUITest() = default;
+  ~OobeInteractiveUITest() override = default;
+
+  // OobeInteractiveUITest:
+  void TearDownOnMainThread() override {
+    // If the login display is still showing, exit gracefully.
+    if (LoginDisplayHost::default_host()) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&chrome::AttemptExit));
+      RunUntilBrowserProcessQuits();
+    }
+    OobeBaseTest::TearDownOnMainThread();
+  }
+
   // QuickUnlockPrivateGetAuthTokenFunction::TestObserver:
   void OnGetAuthTokenCalled(const std::string& password) override {
     quick_unlock_private_get_auth_token_password_ = password;
@@ -574,9 +600,9 @@
 
   void SimpleEndToEnd();
 
+  const OobeEndToEndTestSetupMixin* test_setup() const { return &setup_; }
+
   base::Optional<std::string> quick_unlock_private_get_auth_token_password_;
-  base::Optional<Parameters> params_;
-  base::test::ScopedFeatureList feature_list_;
 
  private:
   FakeGaiaMixin fake_gaia_{&mixin_host_, embedded_test_server()};
@@ -584,15 +610,12 @@
   net::EmbeddedTestServer arc_tos_server_{net::EmbeddedTestServer::TYPE_HTTPS};
   EmbeddedTestServerSetupMixin arc_tos_server_setup_{&mixin_host_,
                                                      &arc_tos_server_};
-
-  std::unique_ptr<ScopedTestRecommendAppsFetcherFactory>
-      recommend_apps_fetcher_factory_;
+  OobeEndToEndTestSetupMixin setup_{&mixin_host_, &arc_tos_server_, GetParam()};
 
   DISALLOW_COPY_AND_ASSIGN(OobeInteractiveUITest);
 };
 
 void OobeInteractiveUITest::SimpleEndToEnd() {
-  ASSERT_TRUE(params_.has_value());
   ScopedQuickUnlockPrivateGetAuthTokenFunctionObserver scoped_observer(this);
 
   WaitForOobeWelcomeScreen();
@@ -619,7 +642,7 @@
   // may cause flaky load failures.
   // TODO(https://crbug/com/959902): Fix ARC terms of service screen to better
   //     handle this case.
-  if (params_->arc_state != ArcState::kNotAvailable) {
+  if (test_setup()->arc_state() != ArcState::kNotAvailable) {
     test::OobeJS()
         .CreateHasClassWaiter(true, "arc-tos-loaded",
                               {"arc-tos-root", "arc-tos-dialog"})
@@ -633,28 +656,29 @@
   ExitScreenSyncConsent();
 #endif
 
-  if (quick_unlock::IsEnabledForTesting()) {
+  if (test_setup()->is_quick_unlock_enabled()) {
     WaitForFingerprintScreen();
     RunFingerprintScreenChecks();
     ExitFingerprintPinSetupScreen();
   }
 
-  if (params_->is_tablet) {
+  if (test_setup()->is_tablet()) {
     WaitForDiscoverScreen();
     RunDiscoverScreenChecks();
     ExitDiscoverPinSetupScreen();
   }
 
-  if (params_->arc_state != ArcState::kNotAvailable) {
-    HandleArcTermsOfServiceScreen(params_->arc_state == ArcState::kAcceptTerms);
+  if (test_setup()->arc_state() != ArcState::kNotAvailable) {
+    HandleArcTermsOfServiceScreen(test_setup()->arc_state() ==
+                                  ArcState::kAcceptTerms);
   }
 
-  if (params_->arc_state == ArcState::kAcceptTerms) {
+  if (test_setup()->arc_state() == ArcState::kAcceptTerms) {
     HandleRecommendAppsScreen();
     HandleAppDownloadingScreen();
   }
 
-  if (params_->arc_state != ArcState::kNotAvailable) {
+  if (test_setup()->arc_state() != ArcState::kNotAvailable) {
     HandleAssistantOptInScreen();
   }
 
@@ -674,4 +698,111 @@
                                      ArcState::kAcceptTerms,
                                      ArcState::kDeclineTerms)));
 
+class PublicSessionOobeTest
+    : public MixinBasedInProcessBrowserTest,
+      public ::testing::WithParamInterface<std::tuple<bool, bool, ArcState>> {
+ public:
+  PublicSessionOobeTest()
+      : PublicSessionOobeTest(false /*requires_terms_of_service*/) {}
+
+  explicit PublicSessionOobeTest(bool requires_terms_of_service)
+      : requires_terms_of_service_(requires_terms_of_service) {
+    // Prevents Chrome from starting to quit right after login display is
+    // finalized.
+    login_manager_.set_should_launch_browser(true);
+  }
+
+  ~PublicSessionOobeTest() override = default;
+
+  void SetUpInProcessBrowserTestFixture() override {
+    std::unique_ptr<ScopedDevicePolicyUpdate> device_policy_update =
+        device_state_.RequestDevicePolicyUpdate();
+
+    const std::string kAccountId = "public-session@test";
+
+    enterprise_management::DeviceLocalAccountsProto* const
+        device_local_accounts = device_policy_update->policy_payload()
+                                    ->mutable_device_local_accounts();
+
+    // Add public session account.
+    enterprise_management::DeviceLocalAccountInfoProto* const account =
+        device_local_accounts->add_account();
+    account->set_account_id(kAccountId);
+    account->set_type(enterprise_management::DeviceLocalAccountInfoProto::
+                          ACCOUNT_TYPE_PUBLIC_SESSION);
+
+    // Set the public session to auto-launch.
+    device_local_accounts->set_auto_login_id(kAccountId);
+    device_policy_update.reset();
+
+    std::unique_ptr<ScopedUserPolicyUpdate> device_local_account_policy_update =
+        device_state_.RequestDeviceLocalAccountPolicyUpdate(kAccountId);
+    // Specify terms of service if needed.
+    if (requires_terms_of_service_) {
+      device_local_account_policy_update->policy_payload()
+          ->mutable_termsofserviceurl()
+          ->set_value(embedded_test_server()
+                          ->GetURL("/chromeos/enterprise/tos.txt")
+                          .spec());
+    }
+    device_local_account_policy_update.reset();
+
+    MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
+  }
+
+  LoginManagerMixin login_manager_{&mixin_host_, {}};
+
+ private:
+  const bool requires_terms_of_service_;
+
+  OobeEndToEndTestSetupMixin setup_{&mixin_host_, nullptr, GetParam()};
+  DeviceStateMixin device_state_{
+      &mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
+};
+
+IN_PROC_BROWSER_TEST_P(PublicSessionOobeTest, NoTermsOfService) {
+  login_manager_.WaitForActiveSession();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    PublicSessionOobeTestImpl,
+    PublicSessionOobeTest,
+    testing::Combine(testing::Bool(),
+                     testing::Bool(),
+                     testing::Values(ArcState::kNotAvailable,
+                                     ArcState::kDeclineTerms)));
+
+class PublicSessionWithTermsOfServiceOobeTest : public PublicSessionOobeTest {
+ public:
+  PublicSessionWithTermsOfServiceOobeTest()
+      : PublicSessionOobeTest(true /*requires_terms_od_service*/) {}
+  ~PublicSessionWithTermsOfServiceOobeTest() override = default;
+
+  // Use embedded test server to serve Public session Terms of Service.
+  EmbeddedTestServerSetupMixin embedded_test_server_setup_{
+      &mixin_host_, embedded_test_server()};
+};
+
+IN_PROC_BROWSER_TEST_P(PublicSessionWithTermsOfServiceOobeTest,
+                       AcceptTermsOfService) {
+  OobeScreenWaiter(TermsOfServiceScreenView::kScreenId).Wait();
+
+  test::OobeJS()
+      .CreateWaiter(test::GetOobeElementPath({"tos-accept-button"}))
+      ->Wait();
+  test::OobeJS().ClickOnPath({"tos-accept-button"});
+
+  OobeScreenExitWaiter(TermsOfServiceScreenView::kScreenId).Wait();
+
+  login_manager_.WaitForActiveSession();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    PublicSessionWithTermsOfServiceOobeTestImpl,
+    PublicSessionWithTermsOfServiceOobeTest,
+    testing::Combine(testing::Bool(),
+                     testing::Bool(),
+                     testing::Values(ArcState::kNotAvailable,
+                                     ArcState::kDeclineTerms)));
+
 }  //  namespace chromeos
diff --git a/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc b/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc
index 17af443..93b5c14 100644
--- a/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc
@@ -3,10 +3,10 @@
 // found in the LICENSE file.
 
 #include <map>
+#include <memory>
 #include <string>
 
 #include "base/macros.h"
-#include "base/optional.h"
 #include "base/scoped_observer.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -17,6 +17,7 @@
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/extensions/extension_test_util.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/version_info/channel.h"
 #include "content/public/browser/web_contents.h"
@@ -95,17 +96,12 @@
     : public ExtensionActionAPITest,
       public testing::WithParamInterface<ActionInfo::Type> {
  public:
-  MultiActionAPITest() {
-    if (GetParam() == ActionInfo::TYPE_ACTION) {
-      // The "action" key is currently restricted to trunk. Used a fake channel
-      // iff we're testing that key, so that we still get multi-channel coverage
-      // for browser and page actions.
-      current_channel_.emplace(version_info::Channel::UNKNOWN);
-    }
-  }
+  MultiActionAPITest()
+      : current_channel_(
+            extension_test_util::GetOverrideChannelForActionType(GetParam())) {}
 
  private:
-  base::Optional<ScopedCurrentChannel> current_channel_;
+  std::unique_ptr<ScopedCurrentChannel> current_channel_;
 
   DISALLOW_COPY_AND_ASSIGN(MultiActionAPITest);
 };
diff --git a/chrome/browser/extensions/api/tabs/tabs_test.cc b/chrome/browser/extensions/api/tabs/tabs_test.cc
index 8b117e1..748dc56 100644
--- a/chrome/browser/extensions/api/tabs/tabs_test.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_test.cc
@@ -47,6 +47,7 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/common/mime_handler_view_mode.h"
 #include "content/public/common/page_zoom.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test_utils.h"
@@ -2087,10 +2088,18 @@
       pdf_extension_test_util::EnsurePDFHasLoaded(second_web_contents);
   EXPECT_TRUE(load_success);
 
+  auto* web_contents_for_click = second_web_contents;
+  if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+    auto inner_web_contents = web_contents_for_click->GetInnerWebContents();
+    ASSERT_EQ(1U, inner_web_contents.size());
+    // With MimeHandlerViewInCrossProcessFrame input should directly route to
+    // the guest WebContents as there is no longer a BrowserPlugin involved.
+    web_contents_for_click = inner_web_contents[0];
+  }
   // The actual PDF page coordinates that this click goes to is (346, 333),
   // after several space transformations, not (400, 400). This clicks on a link
   // to "http://www.facebook.com:83".
-  content::SimulateMouseClickAt(second_web_contents, 0,
+  content::SimulateMouseClickAt(web_contents_for_click, 0,
                                 blink::WebMouseEvent::Button::kLeft,
                                 gfx::Point(400, 400));
 
diff --git a/chrome/browser/extensions/extension_action_manager_unittest.cc b/chrome/browser/extensions/extension_action_manager_unittest.cc
index 104d7ce..335fe9d 100644
--- a/chrome/browser/extensions/extension_action_manager_unittest.cc
+++ b/chrome/browser/extensions/extension_action_manager_unittest.cc
@@ -4,11 +4,16 @@
 
 #include "chrome/browser/extensions/extension_action_manager.h"
 
+#include <memory>
+
 #include "chrome/browser/extensions/extension_action.h"
+#include "chrome/common/extensions/extension_test_util.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/version_info/channel.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension_builder.h"
+#include "extensions/common/features/feature_channel.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "extensions/common/value_builder.h"
@@ -49,11 +54,18 @@
   content::TestBrowserThreadBundle thread_bundle_;
   ExtensionRegistry* registry_;
   ExtensionActionManager* manager_;
+
+  // Instantiate the channel override, if any, before the profile.
+  std::unique_ptr<ScopedCurrentChannel> current_channel_;
   std::unique_ptr<TestingProfile> profile_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionActionManagerTest);
 };
 
 ExtensionActionManagerTest::ExtensionActionManagerTest()
-    : profile_(std::make_unique<TestingProfile>()) {
+    : current_channel_(
+          extension_test_util::GetOverrideChannelForActionType(GetParam())),
+      profile_(std::make_unique<TestingProfile>()) {
   registry_ = ExtensionRegistry::Get(profile_.get());
   manager_ = ExtensionActionManager::Get(profile_.get());
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index f13fa76..5d3d454 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2112,8 +2112,10 @@
   },
   {
     "name": "in-product-help-demo-mode-choice",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
+    "owners": [ "dtrainor", "nyquist" ],
+    // This flag is used by teams as they develop in-product help integrations,
+    // as well as by QA; this feature is difficult to test without this.
+    "expiry_milestone": -1
   },
   {
     "name": "in-session-password-change",
diff --git a/chrome/browser/flag-never-expire-list.json b/chrome/browser/flag-never-expire-list.json
index f7310f8..df1a9e7 100644
--- a/chrome/browser/flag-never-expire-list.json
+++ b/chrome/browser/flag-never-expire-list.json
@@ -61,6 +61,7 @@
   "force-update-menu-type",
   "ignore-gpu-blacklist",
   "ignore-previews-blocklist",
+  "in-product-help-demo-mode-choice",
   "list-all-display-modes",
   "load-media-router-component-extension",
   "memlog",
diff --git a/chrome/browser/performance_manager/webui_graph_dump.mojom b/chrome/browser/performance_manager/webui_graph_dump.mojom
index c61ed35..eb3af7d 100644
--- a/chrome/browser/performance_manager/webui_graph_dump.mojom
+++ b/chrome/browser/performance_manager/webui_graph_dump.mojom
@@ -39,6 +39,15 @@
   uint64 private_footprint_kb;
 };
 
+// Used to transport favicon data.
+struct WebUIFavIconInfo {
+  int64 node_id;
+
+  // Contains the base64-encoded icon data, suitable for inclusion in a
+  // data URL.
+  string icon_data;
+};
+
 interface WebUIGraphChangeStream {
   FrameCreated(WebUIFrameInfo frame);
   PageCreated(WebUIPageInfo pages);
@@ -48,6 +57,8 @@
   PageChanged(WebUIPageInfo page);
   ProcessChanged(WebUIProcessInfo process);
 
+  FavIconDataAvailable(WebUIFavIconInfo favicon);
+
   NodeDeleted(int64 node_id);
 };
 
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl.cc b/chrome/browser/performance_manager/webui_graph_dump_impl.cc
index 674aba5..9dc98b9 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl.cc
+++ b/chrome/browser/performance_manager/webui_graph_dump_impl.cc
@@ -4,30 +4,127 @@
 
 #include "chrome/browser/performance_manager/webui_graph_dump_impl.h"
 
+#include "base/base64.h"
+#include "base/bind.h"
 #include "base/macros.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chrome/browser/favicon/favicon_service_factory.h"
 #include "chrome/browser/performance_manager/graph/frame_node_impl.h"
 #include "chrome/browser/performance_manager/graph/graph_impl.h"
 #include "chrome/browser/performance_manager/graph/page_node_impl.h"
 #include "chrome/browser/performance_manager/graph/process_node_impl.h"
 #include "chrome/browser/performance_manager/graph/system_node_impl.h"
+#include "chrome/browser/performance_manager/public/web_contents_proxy.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/favicon/core/favicon_service.h"
+#include "components/favicon_base/favicon_callback.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
 
 namespace performance_manager {
 
+class WebUIGraphDumpImpl::FaviconRequestHelper {
+ public:
+  FaviconRequestHelper(base::WeakPtr<WebUIGraphDumpImpl> graph_dump,
+                       scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+  void RequestFavicon(GURL page_url,
+                      WebContentsProxy contents_proxy,
+                      int64_t serialization_id);
+  void FaviconDataAvailable(int64_t serialization_id,
+                            const favicon_base::FaviconRawBitmapResult& result);
+
+ private:
+  std::unique_ptr<base::CancelableTaskTracker> cancelable_task_tracker_;
+
+  base::WeakPtr<WebUIGraphDumpImpl> graph_dump_;
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(FaviconRequestHelper);
+};
+
+WebUIGraphDumpImpl::FaviconRequestHelper::FaviconRequestHelper(
+    base::WeakPtr<WebUIGraphDumpImpl> graph_dump,
+    scoped_refptr<base::SequencedTaskRunner> task_runner)
+    : graph_dump_(graph_dump), task_runner_(task_runner) {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+void WebUIGraphDumpImpl::FaviconRequestHelper::RequestFavicon(
+    GURL page_url,
+    WebContentsProxy contents_proxy,
+    int64_t serialization_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  content::WebContents* web_contents = contents_proxy.Get();
+  if (!web_contents)
+    return;
+
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  if (!profile)
+    return;
+
+  favicon::FaviconService* favicon_service =
+      FaviconServiceFactory::GetForProfile(profile,
+                                           ServiceAccessType::EXPLICIT_ACCESS);
+  if (!favicon_service)
+    return;
+
+  if (!cancelable_task_tracker_)
+    cancelable_task_tracker_ = std::make_unique<base::CancelableTaskTracker>();
+
+  constexpr size_t kIconSize = 16;
+  constexpr bool kFallbackToHost = true;
+  // It's safe to pass this unretained here, as the tasks are cancelled
+  // on deletion of the cancelable task tracker.
+  favicon_service->GetRawFaviconForPageURL(
+      page_url, {favicon_base::IconType::kFavicon}, kIconSize, kFallbackToHost,
+      base::BindRepeating(&FaviconRequestHelper::FaviconDataAvailable,
+                          base::Unretained(this), serialization_id),
+      cancelable_task_tracker_.get());
+}
+
+void WebUIGraphDumpImpl::FaviconRequestHelper::FaviconDataAvailable(
+    int64_t serialization_id,
+    const favicon_base::FaviconRawBitmapResult& result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!result.is_valid())
+    return;
+
+  task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&WebUIGraphDumpImpl::SendFaviconNotification, graph_dump_,
+                     serialization_id, result.bitmap_data));
+}
+
 WebUIGraphDumpImpl::WebUIGraphDumpImpl(GraphImpl* graph)
-    : graph_(graph), binding_(this) {
+    : graph_(graph), binding_(this), weak_factory_(this) {
   DCHECK(graph);
 }
 
 WebUIGraphDumpImpl::~WebUIGraphDumpImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (change_subscriber_) {
     graph_->UnregisterObserver(this);
     for (auto* node : graph_->nodes())
       node->RemoveObserver(this);
   }
+
+  // The favicon helper must be deleted on the UI thread.
+  if (favicon_request_helper_) {
+    content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
+                                       std::move(favicon_request_helper_));
+  }
 }
 
 void WebUIGraphDumpImpl::Bind(mojom::WebUIGraphDumpRequest request,
                               base::OnceClosure error_handler) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   binding_.Bind(std::move(request));
   binding_.set_connection_error_handler(std::move(error_handler));
 }
@@ -46,6 +143,7 @@
 
 void WebUIGraphDumpImpl::SubscribeToChanges(
     mojom::WebUIGraphChangeStreamPtr change_subscriber) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   change_subscriber_ = std::move(change_subscriber);
 
   // Send creation notifications for all existing nodes and subscribe to them.
@@ -55,6 +153,7 @@
   }
   for (PageNodeImpl* page_node : graph_->GetAllPageNodes()) {
     SendPageNotification(page_node, true);
+    StartPageFaviconRequest(page_node);
     page_node->AddObserver(this);
 
     // Dispatch preorder frame notifications.
@@ -62,6 +161,7 @@
       ForFrameAndOffspring(main_frame_node, [this](FrameNodeImpl* frame_node) {
         frame_node->AddObserver(this);
         this->SendFrameNotification(frame_node, true);
+        this->StartFrameFaviconRequest(frame_node);
       });
     }
   }
@@ -71,17 +171,23 @@
 }
 
 bool WebUIGraphDumpImpl::ShouldObserve(const NodeBase* node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return true;
 }
 
 void WebUIGraphDumpImpl::OnNodeAdded(NodeBase* node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   switch (node->type()) {
-    case FrameNodeImpl::Type():
-      SendFrameNotification(FrameNodeImpl::FromNodeBase(node), true);
-      break;
-    case PageNodeImpl::Type():
-      SendPageNotification(PageNodeImpl::FromNodeBase(node), true);
-      break;
+    case FrameNodeImpl::Type(): {
+      FrameNodeImpl* frame_node = FrameNodeImpl::FromNodeBase(node);
+      SendFrameNotification(frame_node, true);
+      StartFrameFaviconRequest(frame_node);
+    } break;
+    case PageNodeImpl::Type(): {
+      PageNodeImpl* page_node = PageNodeImpl::FromNodeBase(node);
+      SendPageNotification(page_node, true);
+      StartPageFaviconRequest(page_node);
+    } break;
     case ProcessNodeImpl::Type():
       SendProcessNotification(ProcessNodeImpl::FromNodeBase(node), true);
       break;
@@ -128,9 +234,16 @@
   SendPageNotification(page_node, false);
 }
 
+void WebUIGraphDumpImpl::OnFaviconUpdated(PageNodeImpl* page_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  StartPageFaviconRequest(page_node);
+}
+
 void WebUIGraphDumpImpl::OnMainFrameNavigationCommitted(
     PageNodeImpl* page_node) {
   SendPageNotification(page_node, false);
+  StartPageFaviconRequest(page_node);
 }
 
 void WebUIGraphDumpImpl::OnExpectedTaskQueueingDurationSample(
@@ -151,8 +264,44 @@
   DCHECK_EQ(graph_, graph);
 }
 
+WebUIGraphDumpImpl::FaviconRequestHelper*
+WebUIGraphDumpImpl::EnsureFaviconRequestHelper() {
+  if (!favicon_request_helper_) {
+    favicon_request_helper_ = std::make_unique<FaviconRequestHelper>(
+        weak_factory_.GetWeakPtr(), base::SequencedTaskRunnerHandle::Get());
+  }
+
+  return favicon_request_helper_.get();
+}
+
+void WebUIGraphDumpImpl::StartPageFaviconRequest(PageNodeImpl* page_node) {
+  if (!page_node->main_frame_url().is_valid())
+    return;
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {content::BrowserThread::UI},
+      base::BindOnce(&FaviconRequestHelper::RequestFavicon,
+                     base::Unretained(EnsureFaviconRequestHelper()),
+                     page_node->main_frame_url(), page_node->contents_proxy(),
+                     NodeBase::GetSerializationId(page_node)));
+}
+
+void WebUIGraphDumpImpl::StartFrameFaviconRequest(FrameNodeImpl* frame_node) {
+  if (!frame_node->url().is_valid())
+    return;
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {content::BrowserThread::UI},
+      base::BindOnce(&FaviconRequestHelper::RequestFavicon,
+                     base::Unretained(EnsureFaviconRequestHelper()),
+                     frame_node->url(),
+                     frame_node->page_node()->contents_proxy(),
+                     NodeBase::GetSerializationId(frame_node)));
+}
+
 void WebUIGraphDumpImpl::SendFrameNotification(FrameNodeImpl* frame,
                                                bool created) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // TODO(https://crbug.com/961785): Add more frame properties.
   mojom::WebUIFrameInfoPtr frame_info = mojom::WebUIFrameInfo::New();
 
@@ -175,13 +324,14 @@
     change_subscriber_->FrameChanged(std::move(frame_info));
 }
 
-void WebUIGraphDumpImpl::SendPageNotification(PageNodeImpl* page,
+void WebUIGraphDumpImpl::SendPageNotification(PageNodeImpl* page_node,
                                               bool created) {
-  // TODO(https://crbug.com/961785): Add more page properties.
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // TODO(https://crbug.com/961785): Add more page_node properties.
   mojom::WebUIPageInfoPtr page_info = mojom::WebUIPageInfo::New();
 
-  page_info->id = NodeBase::GetSerializationId(page);
-  page_info->main_frame_url = page->main_frame_url();
+  page_info->id = NodeBase::GetSerializationId(page_node);
+  page_info->main_frame_url = page_node->main_frame_url();
   if (created)
     change_subscriber_->PageCreated(std::move(page_info));
   else
@@ -205,7 +355,25 @@
 }
 
 void WebUIGraphDumpImpl::SendDeletionNotification(NodeBase* node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   change_subscriber_->NodeDeleted(NodeBase::GetSerializationId(node));
 }
 
+void WebUIGraphDumpImpl::SendFaviconNotification(
+    int64_t serialization_id,
+    scoped_refptr<base::RefCountedMemory> bitmap_data) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_NE(0u, bitmap_data->size());
+
+  mojom::WebUIFavIconInfoPtr icon_info = mojom::WebUIFavIconInfo::New();
+  icon_info->node_id = serialization_id;
+
+  base::Base64Encode(
+      base::StringPiece(reinterpret_cast<const char*>(bitmap_data->front()),
+                        bitmap_data->size()),
+      &icon_info->icon_data);
+
+  change_subscriber_->FavIconDataAvailable(std::move(icon_info));
+}
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl.h b/chrome/browser/performance_manager/webui_graph_dump_impl.h
index fb33678..408970da 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl.h
+++ b/chrome/browser/performance_manager/webui_graph_dump_impl.h
@@ -5,6 +5,9 @@
 #ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
 
+#include "base/memory/ref_counted_memory.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
 #include "chrome/browser/performance_manager/observers/graph_observer.h"
 #include "chrome/browser/performance_manager/webui_graph_dump.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -44,7 +47,7 @@
   void OnPageAlmostIdleChanged(PageNodeImpl* page_node) override;
 
   // Event notification.
-  void OnFaviconUpdated(PageNodeImpl* page_node) override {}
+  void OnFaviconUpdated(PageNodeImpl* page_node) override;
   // Event notification.
   void OnTitleUpdated(PageNodeImpl* page_node) override {}
 
@@ -63,19 +66,37 @@
   void SetNodeGraph(GraphImpl* graph) override;
 
  private:
+  // The favicon requests happen on the UI thread. This helper class
+  // maintains the state required to do that.
+  class FaviconRequestHelper;
+
+  FaviconRequestHelper* EnsureFaviconRequestHelper();
+
+  void StartPageFaviconRequest(PageNodeImpl* page_node);
+  void StartFrameFaviconRequest(FrameNodeImpl* frame_node);
+
   void SendFrameNotification(FrameNodeImpl* frame, bool created);
   void SendPageNotification(PageNodeImpl* page, bool created);
   void SendProcessNotification(ProcessNodeImpl* process, bool created);
   void SendDeletionNotification(NodeBase* node);
+  void SendFaviconNotification(
+      int64_t serialization_id,
+      scoped_refptr<base::RefCountedMemory> bitmap_data);
 
   GraphImpl* graph_;
 
+  std::unique_ptr<FaviconRequestHelper> favicon_request_helper_;
+
   // The current change subscriber to this dumper. This instance is subscribed
   // to every node in |graph_| save for the system node, so long as there is a
   // subscriber.
   mojom::WebUIGraphChangeStreamPtr change_subscriber_;
   mojo::Binding<mojom::WebUIGraphDump> binding_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<WebUIGraphDumpImpl> weak_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(WebUIGraphDumpImpl);
 };
 
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc b/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc
index a0645af..6b34213 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc
+++ b/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc
@@ -11,10 +11,10 @@
 #include "base/stl_util.h"
 #include "base/test/bind_test_util.h"
 #include "base/time/time.h"
-#include "chrome/browser/performance_manager/graph/graph_test_harness.h"
 #include "chrome/browser/performance_manager/graph/mock_graphs.h"
 #include "chrome/browser/performance_manager/graph/page_node_impl.h"
 #include "chrome/browser/performance_manager/performance_manager_clock.h"
+#include "content/public/test/test_browser_thread_bundle.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -81,6 +81,8 @@
     ++num_changes_;
   }
 
+  void FavIconDataAvailable(mojom::WebUIFavIconInfoPtr favicon) override {}
+
   void NodeDeleted(int64_t node_id) override {
     EXPECT_EQ(1u, id_set_.erase(node_id));
 
@@ -110,9 +112,9 @@
 
 }  // namespace
 
-class WebUIGraphDumpImplTest : public GraphTestHarness {};
+TEST(WebUIGraphDumpImplTest, ChangeStream) {
+  content::TestBrowserThreadBundle browser_thread_bundle;
 
-TEST_F(WebUIGraphDumpImplTest, ChangeStream) {
   GraphImpl graph;
   MockMultiplePagesWithMultipleProcessesGraph mock_graph(&graph);
 
@@ -133,7 +135,7 @@
   TestChangeStream change_stream;
   impl_proxy->SubscribeToChanges(change_stream.GetProxy());
 
-  task_env().RunUntilIdle();
+  browser_thread_bundle.RunUntilIdle();
 
   // Validate that the initial graph state dump is complete.
   EXPECT_EQ(0u, change_stream.num_changes());
@@ -182,7 +184,7 @@
       NodeBase::GetSerializationId(mock_graph.child_frame.get());
   mock_graph.child_frame.reset();
 
-  task_env().RunUntilIdle();
+  browser_thread_bundle.RunUntilIdle();
 
   EXPECT_EQ(1u, change_stream.num_changes());
   EXPECT_FALSE(base::ContainsKey(change_stream.id_set(), child_frame_id));
@@ -191,6 +193,8 @@
       NodeBase::GetSerializationId(mock_graph.page.get()));
   ASSERT_TRUE(main_page_it != change_stream.page_map().end());
   EXPECT_EQ(kAnotherURL, main_page_it->second->main_frame_url);
+
+  browser_thread_bundle.RunUntilIdle();
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
index bcd1b0f..d1a0480 100644
--- a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
+++ b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
@@ -139,6 +139,25 @@
     EXPECT_TRUE(result);
   }
 
+  // The WebContents that is passed to this method must have a
+  // "isInPictureInPicture()" method returning a boolean and when the video
+  // leaves Picture-in-Picture, it must change the WebContents title to
+  // "leavepictureinpicture".
+  void ExpectLeavePictureInPicture(content::WebContents* web_contents) {
+    // 'leavepictureinpicture' is the title of the tab when the event is
+    // received.
+    const base::string16 expected_title =
+        base::ASCIIToUTF16("leavepictureinpicture");
+    EXPECT_EQ(
+        expected_title,
+        content::TitleWatcher(web_contents, expected_title).WaitAndGetTitle());
+
+    bool in_picture_in_picture = true;
+    EXPECT_TRUE(ExecuteScriptAndExtractBool(
+        web_contents, "isInPictureInPicture();", &in_picture_in_picture));
+    EXPECT_FALSE(in_picture_in_picture);
+  }
+
   class WidgetBoundsChangeWaiter : public views::WidgetObserver {
    public:
     explicit WidgetBoundsChangeWaiter(views::Widget* widget)
@@ -470,7 +489,7 @@
 
   window_controller()->Close(true /* should_pause_video */);
 
-  base::string16 expected_title = base::ASCIIToUTF16("left");
+  base::string16 expected_title = base::ASCIIToUTF16("leavepictureinpicture");
   EXPECT_EQ(expected_title,
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
@@ -509,7 +528,7 @@
   ASSERT_TRUE(window_controller());
   window_controller()->Close(true /* should_pause_video */);
 
-  base::string16 expected_title = base::ASCIIToUTF16("left");
+  base::string16 expected_title = base::ASCIIToUTF16("leavepictureinpicture");
   EXPECT_EQ(expected_title,
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
@@ -582,7 +601,7 @@
       content::ExecuteScript(active_web_contents, "exitPictureInPicture();"));
 
   // 'left' is sent when the first video leaves Picture-in-Picture.
-  base::string16 expected_title = base::ASCIIToUTF16("left");
+  base::string16 expected_title = base::ASCIIToUTF16("leavepictureinpicture");
   EXPECT_EQ(expected_title,
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
@@ -628,7 +647,7 @@
       content::ExecuteScript(active_web_contents, "exitPictureInPicture();"));
 
   // 'left' is sent when the video leaves Picture-in-Picture.
-  base::string16 expected_title = base::ASCIIToUTF16("left");
+  base::string16 expected_title = base::ASCIIToUTF16("leavepictureinpicture");
   EXPECT_EQ(expected_title,
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
@@ -683,16 +702,7 @@
   EXPECT_TRUE(
       content::ExecuteScript(active_web_contents, "secondPictureInPicture();"));
 
-  // 'left' is sent when the first video leaves Picture-in-Picture.
-  base::string16 expected_title = base::ASCIIToUTF16("left");
-  EXPECT_EQ(expected_title,
-            content::TitleWatcher(active_web_contents, expected_title)
-                .WaitAndGetTitle());
-
-  in_picture_in_picture = false;
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      active_web_contents, "isInPictureInPicture();", &in_picture_in_picture));
-  EXPECT_FALSE(in_picture_in_picture);
+  ExpectLeavePictureInPicture(active_web_contents);
 
   bool is_paused = false;
   EXPECT_TRUE(ExecuteScriptAndExtractBool(active_web_contents, "isPaused();",
@@ -859,7 +869,7 @@
   window_controller()->Close(true /* should_pause_video */);
 
   // Wait for the window to close.
-  base::string16 expected_title = base::ASCIIToUTF16("left");
+  base::string16 expected_title = base::ASCIIToUTF16("leavepictureinpicture");
   EXPECT_EQ(expected_title,
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
@@ -984,7 +994,7 @@
     EXPECT_TRUE(pip_window_controller->GetWindowForTesting()->IsVisible());
 
     // 'left' is sent when the first tab leaves Picture-in-Picture.
-    base::string16 expected_title = base::ASCIIToUTF16("left");
+    base::string16 expected_title = base::ASCIIToUTF16("leavepictureinpicture");
     EXPECT_EQ(expected_title,
               content::TitleWatcher(initial_web_contents, expected_title)
                   .WaitAndGetTitle());
@@ -1355,10 +1365,7 @@
   // Simulate closing from the system.
   overlay_window->OnNativeWidgetDestroyed();
 
-  bool in_picture_in_picture = false;
-  ASSERT_TRUE(ExecuteScriptAndExtractBool(
-      active_web_contents, "isInPictureInPicture();", &in_picture_in_picture));
-  EXPECT_FALSE(in_picture_in_picture);
+  ExpectLeavePictureInPicture(active_web_contents);
 }
 
 // Tests that the play/pause icon state is properly updated when a
@@ -1443,12 +1450,8 @@
   pip_window_manager->EnterPictureInPictureWithController(&mock_controller());
 
   // WebContents sourced Picture-in-Picture should stop.
-  bool in_picture_in_picture = false;
-  content::WebContents* active_web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  EXPECT_TRUE(ExecuteScriptAndExtractBool(
-      active_web_contents, "isInPictureInPicture();", &in_picture_in_picture));
-  EXPECT_FALSE(in_picture_in_picture);
+  ExpectLeavePictureInPicture(
+      browser()->tab_strip_model()->GetActiveWebContents());
 }
 
 IN_PROC_BROWSER_TEST_F(PictureInPictureWindowControllerBrowserTest,
@@ -1508,10 +1511,8 @@
 
   window_controller()->Close(true /* should_pause_video */);
 
-  result = false;
-  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
-      active_web_contents, "isInPictureInPicture();", &result));
-  EXPECT_FALSE(result);
+  // The video should leave Picture-in-Picture mode.
+  ExpectLeavePictureInPicture(active_web_contents);
 }
 
 // Tests that opening a Picture-in-Picture window from a video in an iframe
@@ -2784,7 +2785,7 @@
                                      "video.src=''; exitPictureInPicture();"));
 
   // 'left' is sent when the first video leaves Picture-in-Picture.
-  base::string16 expected_title = base::ASCIIToUTF16("left");
+  base::string16 expected_title = base::ASCIIToUTF16("leavepictureinpicture");
   EXPECT_EQ(expected_title,
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
@@ -2851,7 +2852,7 @@
       content::ExecuteScript(active_web_contents, "secondVideo.muted = true;"));
   ASSERT_TRUE(
       content::ExecuteScript(active_web_contents, "secondPictureInPicture();"));
-  base::string16 expected_title = base::ASCIIToUTF16("left");
+  base::string16 expected_title = base::ASCIIToUTF16("leavepictureinpicture");
   EXPECT_EQ(expected_title,
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
@@ -2909,7 +2910,7 @@
                                      "secondVideo.muted = false;"));
   ASSERT_TRUE(
       content::ExecuteScript(active_web_contents, "secondPictureInPicture();"));
-  expected_title = base::ASCIIToUTF16("left");
+  expected_title = base::ASCIIToUTF16("leavepictureinpicture");
   EXPECT_EQ(expected_title,
             content::TitleWatcher(active_web_contents, expected_title)
                 .WaitAndGetTitle());
@@ -2979,3 +2980,33 @@
   EXPECT_EQ(overlay_window->muted_state_for_testing(),
             OverlayWindowViews::MutedState::kNoAudio);
 }
+
+// Tests that when closing the window after the player was reset, the <video>
+// element is still notified.
+IN_PROC_BROWSER_TEST_F(PictureInPictureWindowControllerBrowserTest,
+                       ResetPlayerCloseWindowNotifiesElement) {
+  LoadTabAndEnterPictureInPicture(browser());
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // Video should be in Picture-in-Picture.
+  {
+    bool in_picture_in_picture = false;
+    ASSERT_TRUE(ExecuteScriptAndExtractBool(active_web_contents,
+                                            "isInPictureInPicture();",
+                                            &in_picture_in_picture));
+    EXPECT_TRUE(in_picture_in_picture);
+  }
+
+  // Reset video source and wait for the notification.
+  ASSERT_TRUE(content::ExecuteScript(active_web_contents, "resetVideo();"));
+  base::string16 expected_title = base::ASCIIToUTF16("emptied");
+  EXPECT_EQ(expected_title,
+            content::TitleWatcher(active_web_contents, expected_title)
+                .WaitAndGetTitle());
+
+  window_controller()->Close(true /* should_pause_video */);
+
+  // Video should no longer be in Picture-in-Picture.
+  ExpectLeavePictureInPicture(active_web_contents);
+}
diff --git a/chrome/browser/resources/discards/graph_doc.js b/chrome/browser/resources/discards/graph_doc.js
index 0e0e90c..959cb80 100644
--- a/chrome/browser/resources/discards/graph_doc.js
+++ b/chrome/browser/resources/discards/graph_doc.js
@@ -25,6 +25,8 @@
     this.id = id;
     /** @type {string} */
     this.color = 'black';
+    /** @type {string} */
+    this.iconUrl = '';
 
     // Implementation of the d3.ForceNode interface.
     // See https://github.com/d3/d3-force#simulation_nodes.
@@ -311,55 +313,49 @@
     this.nodeGroup_ = svg.append('g').attr('class', 'nodes');
   }
 
-  /**
-   * @param {!performanceManager.mojom.WebUIFrameInfo} frame
-   */
+  /** @override */
   frameCreated(frame) {
     this.addNode_(new FrameNode(frame));
   }
 
-  /**
-   * @param {!performanceManager.mojom.WebUIPageInfo} page
-   */
+  /** @override */
   pageCreated(page) {
     this.addNode_(new PageNode(page));
   }
 
-  /**
-   * @param {!performanceManager.mojom.WebUIProcessInfo} process
-   */
+  /** @override */
   processCreated(process) {
     this.addNode_(new ProcessNode(process));
   }
 
-  /**
-   * @param {!performanceManager.mojom.WebUIFrameInfo} frame
-   */
+  /** @override */
   frameChanged(frame) {
     const frameNode = /** @type {!FrameNode} */ (this.nodes_.get(frame.id));
     frameNode.frame = frame;
   }
 
-  /**
-   * @param {!performanceManager.mojom.WebUIPageInfo} page
-   */
+  /** @override */
   pageChanged(page) {
     const pageNode = /** @type {!PageNode} */ (this.nodes_.get(page.id));
     pageNode.page = page;
   }
 
-  /**
-   * @param {!performanceManager.mojom.WebUIProcessInfo} process
-   */
+  /** @override */
   processChanged(process) {
     const processNode =
         /** @type {!ProcessNode} */ (this.nodes_.get(process.id));
     processNode.process = process;
   }
 
-  /**
-   * @param {!number} nodeId
-   */
+  /** @override */
+  favIconDataAvailable(iconInfo) {
+    const graphNode = this.nodes_.get(iconInfo.nodeId);
+    if (graphNode) {
+      graphNode.iconUrl = 'data:image/png;base64,' + iconInfo.iconData;
+    }
+  }
+
+  /** @override */
   nodeDeleted(nodeId) {
     const node = this.nodes_.get(nodeId);
 
@@ -403,6 +399,10 @@
         this.processChanged(
             /** @type {!performanceManager.mojom.WebUIProcessInfo} */ (data));
         break;
+      case 'favIconDataAvailable':
+        this.favIconDataAvailable(
+            /** @type {!performanceManager.mojom.WebUIFavIconInfo} */ (data));
+        break;
       case 'nodeDeleted':
         this.nodeDeleted(/** @type {number} */ (data));
         break;
@@ -411,7 +411,22 @@
     this.render_();
   }
 
-  /** @private */
+  /**
+   * Renders nodes_ and edges_ to the SVG DOM.
+   *
+   * Each edge is a line element.
+   * Each node is represented as a group element with three children:
+   *   1. A circle that has a color and which animates the node on creation
+   *      and deletion.
+   *   2. An image that is provided a data URL for the nodes favicon, when
+   *      available.
+   *   3. A title element that presents the nodes URL on hover-over, if
+   *      available.
+   * Deleted nodes are classed '.dead', and CSS takes care of hiding their
+   * image element if it's been populated with an icon.
+   *
+   * @private
+   */
   render_() {
     // Select the links.
     const link = this.linkGroup_.selectAll('line').data(this.links_);
@@ -423,7 +438,7 @@
     // Select the nodes, except for any dead ones that are still transitioning.
     const nodes = Array.from(this.nodes_.values());
     const node =
-        this.nodeGroup_.selectAll('circle:not(.dead)').data(nodes, d => d.id);
+        this.nodeGroup_.selectAll('g:not(.dead)').data(nodes, d => d.id);
 
     // Add new nodes, if any.
     if (!node.enter().empty()) {
@@ -432,12 +447,15 @@
       drag.on('drag', this.onDrag_.bind(this));
       drag.on('end', this.onDragEnd_.bind(this));
 
-      const circles = node.enter()
-                          .append('circle')
-                          .attr('r', 9)
-                          .attr('fill', 'green')  // New nodes appear green.
-                          .call(drag);
-      circles.append('title');
+      const newNodes = node.enter().append('g').call(drag);
+      const circles = newNodes.append('circle').attr('r', 9).attr(
+          'fill', 'green');  // New nodes appear green.
+      newNodes.append('image')
+          .attr('x', -8)
+          .attr('y', -8)
+          .attr('width', 16)
+          .attr('height', 16);
+      newNodes.append('title');
 
       // Transition new nodes to their chosen color in 2 seconds.
       circles.transition()
@@ -448,9 +466,9 @@
 
     // Give dead nodes a distinguishing class to exclude them from the selection
     // above. Interrupt any ongoing transitions, then transition them out.
-    node.exit()
-        .classed('dead', true)
-        .interrupt()
+    const deletedNodes = node.exit().classed('dead', true).interrupt();
+
+    deletedNodes.select('circle')
         .attr('r', 9)
         .attr('fill', 'red')
         .transition()
@@ -460,6 +478,8 @@
 
     // Update the title for all nodes.
     node.selectAll('title').text(d => d.title);
+    // Update the favicon for all nodes.
+    node.selectAll('image').attr('href', d => d.iconUrl);
 
     // Update and restart the simulation if the graph changed.
     if (!node.enter().empty() || !node.exit().empty() ||
@@ -473,8 +493,8 @@
 
   /** @private */
   onTick_() {
-    const circles = this.nodeGroup_.selectAll('circle');
-    circles.attr('cx', d => d.x).attr('cy', d => d.y);
+    const nodes = this.nodeGroup_.selectAll('g');
+    nodes.attr('transform', d => `translate(${d.x},${d.y})`);
 
     const lines = this.linkGroup_.selectAll('line');
     lines.attr('x1', d => d.source.x)
diff --git a/chrome/browser/resources/discards/graph_doc_template.html b/chrome/browser/resources/discards/graph_doc_template.html
index dae29c0..126ee75 100644
--- a/chrome/browser/resources/discards/graph_doc_template.html
+++ b/chrome/browser/resources/discards/graph_doc_template.html
@@ -27,6 +27,11 @@
         stroke: #000;
         stroke-width: 1.5px;
       }
+
+      .dead image {
+        display: none;
+      }
+
     </style>
   <script src="https://ajax.googleapis.com/ajax/libs/d3js/5.7.0/d3.min.js"
       integrity="sha384-HL96dun1KbYEq6UT/ZlsspAODCyQ+Zp4z318ajUPBPSMzy5dvxl6ziwmnil8/Cpd"
diff --git a/chrome/browser/resources/discards/graph_tab.js b/chrome/browser/resources/discards/graph_tab.js
index edfa059..0231a77 100644
--- a/chrome/browser/resources/discards/graph_tab.js
+++ b/chrome/browser/resources/discards/graph_tab.js
@@ -20,51 +20,42 @@
       this.contentWindow_.postMessage([type, data], '*');
     }
 
-    /**
-     * @param {!performanceManager.mojom.WebUIFrameInfo} frame
-     */
+    /** @override */
     frameCreated(frame) {
       this.postMessage_('frameCreated', frame);
     }
 
-    /**
-     * @param {!performanceManager.mojom.WebUIPageInfo} page
-     */
+    /** @override */
     pageCreated(page) {
       this.postMessage_('pageCreated', page);
     }
 
-    /**
-     * @param {!performanceManager.mojom.WebUIProcessInfo} process
-     */
+    /** @override */
     processCreated(process) {
       this.postMessage_('processCreated', process);
     }
 
-    /**
-     * @param {!performanceManager.mojom.WebUIFrameInfo} frame
-     */
+    /** @override */
     frameChanged(frame) {
       this.postMessage_('frameChanged', frame);
     }
 
-    /**
-     * @param {!performanceManager.mojom.WebUIPageInfo} page
-     */
+    /** @override */
     pageChanged(page) {
       this.postMessage_('pageChanged', page);
     }
 
-    /**
-     * @param {!performanceManager.mojom.WebUIProcessInfo} process
-     */
+    /** @override */
     processChanged(process) {
       this.postMessage_('processChanged', process);
     }
 
-    /**
-     * @param {!number} nodeId
-     */
+    /** @override */
+    favIconDataAvailable(icon_info) {
+      this.postMessage_('favIconDataAvailable', icon_info);
+    }
+
+    /** @override */
     nodeDeleted(nodeId) {
       this.postMessage_('nodeDeleted', nodeId);
     }
diff --git a/chrome/browser/resources/gaia_auth_host/authenticator.js b/chrome/browser/resources/gaia_auth_host/authenticator.js
index a165c0d..f311151 100644
--- a/chrome/browser/resources/gaia_auth_host/authenticator.js
+++ b/chrome/browser/resources/gaia_auth_host/authenticator.js
@@ -21,8 +21,6 @@
   // of hardcoding the prod URL here.  As is, this does not work with staging
   // environments.
   const IDP_ORIGIN = 'https://accounts.google.com/';
-  const CONTINUE_URL =
-      'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
   const SIGN_IN_HEADER = 'google-accounts-signin';
   const EMBEDDED_FORM_HEADER = 'google-accounts-embedded';
   const LOCATION_HEADER = 'location';
@@ -61,7 +59,6 @@
     'gaiaPath',      // Gaia path to use without a leading slash.
     'hl',            // Language code for the user interface.
     'service',       // Name of Gaia service.
-    'continueUrl',   // Continue url to use.
     'frameUrl',      // Initial frame URL to use. If empty defaults to
                      // gaiaUrl.
     'constrained',   // Whether the extension is loaded in a constrained
@@ -132,8 +129,6 @@
       this.authDomain = '';
       this.videoEnabled = false;
       this.idpOrigin_ = null;
-      this.continueUrl_ = null;
-      this.continueUrlWithoutParams_ = null;
       this.initialFrameUrl_ = null;
       this.reloadUrl_ = null;
       this.trusted_ = true;
@@ -286,10 +281,6 @@
       // gaiaUrl parameter is used for testing. Once defined, it is never
       // changed.
       this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
-      this.continueUrl_ = data.continueUrl || CONTINUE_URL;
-      this.continueUrlWithoutParams_ =
-          this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
-          this.continueUrl_;
       this.isConstrainedWindow_ = data.constrained == '1';
       this.clientId_ = data.clientId;
       this.dontResizeNonEmbeddedPages = data.dontResizeNonEmbeddedPages;
diff --git a/chrome/browser/resources/local_ntp/icons/backgrounds.svg b/chrome/browser/resources/local_ntp/icons/backgrounds.svg
new file mode 100644
index 0000000..94aa1e3
--- /dev/null
+++ b/chrome/browser/resources/local_ntp/icons/backgrounds.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
+    <title>ic-ntp-background</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <path d="M4,4 L11,4 L11,2 L4,2 C2.9,2 2,2.9 2,4 L2,11 L4,11 L4,4 Z M10,13 L6,18 L18,18 L15,14 L12.97,16.71 L10,13 Z M17,8.5 C17,7.67 16.33,7 15.5,7 C14.67,7 14,7.67 14,8.5 C14,9.33 14.67,10 15.5,10 C16.33,10 17,9.33 17,8.5 Z M20,2 L13,2 L13,4 L20,4 L20,11 L22,11 L22,4 C22,2.9 21.1,2 20,2 Z M20,20 L13,20 L13,22 L20,22 C21.1,22 22,21.1 22,20 L22,13 L20,13 L20,20 Z M4,13 L2,13 L2,20 C2,21.1 2.9,22 4,22 L11,22 L11,20 L4,20 L4,13 Z" id="path-1"></path>
+    </defs>
+    <g id="ic-ntp-background" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <mask id="mask-2" fill="white">
+            <use xlink:href="#path-1"></use>
+        </mask>
+        <use id="Shape" fill="#000000" fill-rule="nonzero" xlink:href="#path-1"></use>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/local_ntp/icons/colors.svg b/chrome/browser/resources/local_ntp/icons/colors.svg
new file mode 100644
index 0000000..eda9b94
--- /dev/null
+++ b/chrome/browser/resources/local_ntp/icons/colors.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
+    <title>ic-ntp-colors</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <path d="M12,3 C7.03,3 3,7.03 3,12 C3,16.97 7.03,21 12,21 C12.83,21 13.5,20.33 13.5,19.5 C13.5,19.11 13.35,18.76 13.11,18.49 C12.88,18.23 12.73,17.88 12.73,17.5 C12.73,16.67 13.4,16 14.23,16 L16,16 C18.76,16 21,13.76 21,11 C21,6.58 16.97,3 12,3 Z M6.5,12 C5.67,12 5,11.33 5,10.5 C5,9.67 5.67,9 6.5,9 C7.33,9 8,9.67 8,10.5 C8,11.33 7.33,12 6.5,12 Z M9.5,8 C8.67,8 8,7.33 8,6.5 C8,5.67 8.67,5 9.5,5 C10.33,5 11,5.67 11,6.5 C11,7.33 10.33,8 9.5,8 Z M14.5,8 C13.67,8 13,7.33 13,6.5 C13,5.67 13.67,5 14.5,5 C15.33,5 16,5.67 16,6.5 C16,7.33 15.33,8 14.5,8 Z M17.5,12 C16.67,12 16,11.33 16,10.5 C16,9.67 16.67,9 17.5,9 C18.33,9 19,9.67 19,10.5 C19,11.33 18.33,12 17.5,12 Z" id="path-1"></path>
+    </defs>
+    <g id="ic-ntp-colors" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <mask id="mask-2" fill="white">
+            <use xlink:href="#path-1"></use>
+        </mask>
+        <use id="Shape" fill="#000000" fill-rule="nonzero" xlink:href="#path-1"></use>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index 2126036..37ad14e 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -906,7 +906,6 @@
 }
 
 .menu-option-icon {
-  -webkit-mask-image: url(icons/icon_pencil.svg);
   -webkit-mask-position-x: center;
   -webkit-mask-position-y: center;
   -webkit-mask-repeat: no-repeat;
@@ -931,6 +930,14 @@
   -webkit-mask-image: url(icons/link.svg);
 }
 
+#backgrounds-icon {
+  -webkit-mask-image: url(icons/backgrounds.svg);
+}
+
+#colors-icon {
+  -webkit-mask-image: url(icons/colors.svg);
+}
+
 .menu-option-label {
   display: inline-block;
   height: 32px;
diff --git a/chrome/browser/resources/local_ntp/local_ntp.html b/chrome/browser/resources/local_ntp/local_ntp.html
index 7a12890..f94487d 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.html
+++ b/chrome/browser/resources/local_ntp/local_ntp.html
@@ -181,7 +181,7 @@
     <div id="menu-nav-panel">
       <div id="backgrounds-button" class="menu-option selected" tabindex="0">
         <div class="menu-option-icon-wrapper">
-          <div ids="backgrounds-icon" class="menu-option-icon"></div>
+          <div id="backgrounds-icon" class="menu-option-icon"></div>
         </div>
         <div class="menu-option-label">$i18n{backgroundsOption}</div>
       </div>
diff --git a/chrome/browser/signin/signin_promo.cc b/chrome/browser/signin/signin_promo.cc
index 1daa649..c43f554 100644
--- a/chrome/browser/signin/signin_promo.cc
+++ b/chrome/browser/signin/signin_promo.cc
@@ -6,7 +6,6 @@
 
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/extensions/signin/gaia_auth_extension_loader.h"
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/google/google_brand.h"
 #include "chrome/browser/profiles/profile.h"
@@ -33,34 +32,6 @@
 const char kSignInPromoQueryKeyAutoClose[] = "auto_close";
 const char kSignInPromoQueryKeyForceKeepData[] = "force_keep_data";
 const char kSignInPromoQueryKeyReason[] = "reason";
-const char kSignInPromoQueryKeySource[] = "source";
-const char kSigninPromoLandingURLSuccessPage[] = "success.html";
-
-GURL GetLandingURL(signin_metrics::AccessPoint access_point) {
-  GURL url(extensions::kGaiaAuthExtensionOrigin);
-  GURL::Replacements replacements;
-  replacements.SetPathStr(kSigninPromoLandingURLSuccessPage);
-  url = url.ReplaceComponents(replacements);
-
-  url = net::AppendQueryParameter(
-      url, kSignInPromoQueryKeyAccessPoint,
-      base::NumberToString(static_cast<int>(access_point)));
-
-  // TODO(gogerald): right now, gaia server needs to distinguish the source from
-  // signin_metrics::SOURCE_START_PAGE, signin_metrics::SOURCE_SETTINGS and
-  // the others to show advanced sync settings, remove them after
-  // switching to Minute Maid sign in flow.
-  signin_metrics::Source source = signin_metrics::SOURCE_OTHERS;
-  if (access_point == signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE) {
-    source = signin_metrics::SOURCE_START_PAGE;
-  } else if (access_point ==
-             signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS) {
-    source = signin_metrics::SOURCE_SETTINGS;
-  }
-  url = net::AppendQueryParameter(url, signin::kSignInPromoQueryKeySource,
-                                  base::NumberToString(source));
-  return GURL(url);
-}
 
 #if !defined(OS_CHROMEOS)
 GURL GetEmbeddedPromoURL(signin_metrics::AccessPoint access_point,
diff --git a/chrome/browser/signin/signin_promo.h b/chrome/browser/signin/signin_promo.h
index d3c4510..cfde093 100644
--- a/chrome/browser/signin/signin_promo.h
+++ b/chrome/browser/signin/signin_promo.h
@@ -8,7 +8,6 @@
 #include <string>
 
 #include "build/build_config.h"
-#include "chrome/browser/ui/profile_chooser_constants.h"
 #include "components/signin/core/browser/signin_metrics.h"
 
 class GURL;
@@ -24,11 +23,6 @@
 extern const char kSignInPromoQueryKeyAutoClose[];
 extern const char kSignInPromoQueryKeyForceKeepData[];
 extern const char kSignInPromoQueryKeyReason[];
-extern const char kSignInPromoQueryKeySource[];
-extern const char kSigninPromoLandingURLSuccessPage[];
-
-// Gets the sign in landing page URL.
-GURL GetLandingURL(signin_metrics::AccessPoint access_point);
 
 #if !defined(OS_CHROMEOS)
 // These functions are only used to unlock the profile from the desktop user
diff --git a/chrome/browser/signin/signin_promo_unittest.cc b/chrome/browser/signin/signin_promo_unittest.cc
index 88ba43a..ee6a086 100644
--- a/chrome/browser/signin/signin_promo_unittest.cc
+++ b/chrome/browser/signin/signin_promo_unittest.cc
@@ -37,25 +37,6 @@
 }
 #endif  // !defined(OS_CHROMEOS)
 
-TEST(SigninPromoTest, TestLandingURL) {
-  GURL expected_url_1(
-      "chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/"
-      "success.html?access_point=1&source=13");
-  EXPECT_EQ(expected_url_1,
-            GetLandingURL(signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK));
-  GURL expected_url_2(
-      "chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/"
-      "success.html?access_point=0&source=0");
-  EXPECT_EQ(
-      expected_url_2,
-      GetLandingURL(signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE));
-  GURL expected_url_3(
-      "chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/"
-      "success.html?access_point=3&source=3");
-  EXPECT_EQ(expected_url_3,
-            GetLandingURL(signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS));
-}
-
 TEST(SigninPromoTest, SigninURLForDice) {
   EXPECT_EQ(
       "https://accounts.google.com/signin/chrome/sync?ssp=1&"
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.cc b/chrome/browser/ui/app_list/app_list_client_impl.cc
index 9bad5789..318106f 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl.cc
@@ -9,6 +9,7 @@
 #include <utility>
 #include <vector>
 
+#include "ash/public/cpp/app_list/app_list_controller.h"
 #include "ash/public/cpp/menu_utils.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "base/bind.h"
@@ -65,17 +66,14 @@
 }
 ///////////////////////////////////////////////////////////////////////////////
 
-AppListClientImpl::AppListClientImpl()
-    : template_url_service_observer_(this),
-      binding_(this),
-      weak_ptr_factory_(this) {
-  // Bind this to the AppListController in Ash.
+AppListClientImpl::AppListClientImpl() {
+  app_list::AppListController::Get()->SetClient(this);
+
+  // Get the mojo AppListController in Ash.
   content::ServiceManagerConnection::GetForProcess()
       ->GetConnector()
       ->BindInterface(ash::mojom::kServiceName, &app_list_controller_);
-  ash::mojom::AppListClientPtr client;
-  binding_.Bind(mojo::MakeRequest(&client));
-  app_list_controller_->SetClient(std::move(client));
+
   user_manager::UserManager::Get()->AddSessionStateObserver(this);
 
   DCHECK(!g_app_list_client_instance);
@@ -90,6 +88,9 @@
 
   DCHECK_EQ(this, g_app_list_client_instance);
   g_app_list_client_instance = nullptr;
+
+  if (app_list::AppListController::Get())
+    app_list::AppListController::Get()->SetClient(nullptr);
 }
 
 // static
@@ -564,5 +565,4 @@
 
 void AppListClientImpl::FlushMojoForTesting() {
   app_list_controller_.FlushForTesting();
-  binding_.FlushForTesting();
 }
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.h b/chrome/browser/ui/app_list/app_list_client_impl.h
index aa9571f..9b30a67 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.h
+++ b/chrome/browser/ui/app_list/app_list_client_impl.h
@@ -10,6 +10,7 @@
 #include <memory>
 #include <string>
 
+#include "ash/public/cpp/app_list/app_list_client.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/interfaces/app_list.mojom.h"
 #include "base/callback_forward.h"
@@ -22,8 +23,6 @@
 #include "components/search_engines/template_url_service.h"
 #include "components/search_engines/template_url_service_observer.h"
 #include "components/user_manager/user_manager.h"
-#include "mojo/public/cpp/bindings/associated_binding.h"
-#include "mojo/public/cpp/bindings/binding.h"
 #include "ui/display/types/display_constants.h"
 
 namespace app_list {
@@ -37,7 +36,7 @@
 class Profile;
 
 class AppListClientImpl
-    : public ash::mojom::AppListClient,
+    : public app_list::AppListClient,
       public AppListControllerDelegate,
       public user_manager::UserManager::UserSessionStateObserver,
       public TemplateURLServiceObserver {
@@ -64,7 +63,7 @@
 
   static AppListClientImpl* GetInstance();
 
-  // ash::mojom::AppListClient:
+  // app_list::AppListClient:
   void StartSearch(const base::string16& trimmed_query) override;
   void OpenSearchResult(const std::string& result_id,
                         int event_flags,
@@ -210,9 +209,8 @@
   std::unique_ptr<MojoRecorderForTest> mojo_recorder_for_test_;
 
   ScopedObserver<TemplateURLService, AppListClientImpl>
-      template_url_service_observer_;
+      template_url_service_observer_{this};
 
-  mojo::Binding<ash::mojom::AppListClient> binding_;
   ash::mojom::AppListControllerPtr app_list_controller_;
 
   bool app_list_target_visibility_ = false;
@@ -220,7 +218,7 @@
 
   app_list::AppLaunchEventLogger app_launch_event_logger_;
 
-  base::WeakPtrFactory<AppListClientImpl> weak_ptr_factory_;
+  base::WeakPtrFactory<AppListClientImpl> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(AppListClientImpl);
 };
diff --git a/chrome/browser/ui/app_list/app_list_model_updater.h b/chrome/browser/ui/app_list/app_list_model_updater.h
index cc45a96..02101ec 100644
--- a/chrome/browser/ui/app_list/app_list_model_updater.h
+++ b/chrome/browser/ui/app_list/app_list_model_updater.h
@@ -16,6 +16,10 @@
 #include "chrome/browser/ui/app_list/app_list_model_updater_observer.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
 
+namespace ui {
+class MenuModel;
+}
+
 class ChromeAppListItem;
 class ChromeSearchResult;
 
diff --git a/chrome/browser/ui/app_list/search/chrome_search_result.h b/chrome/browser/ui/app_list/search/chrome_search_result.h
index 5eb9fd3..96d3554 100644
--- a/chrome/browser/ui/app_list/search/chrome_search_result.h
+++ b/chrome/browser/ui/app_list/search/chrome_search_result.h
@@ -14,6 +14,7 @@
 #include "ash/public/interfaces/app_list.mojom.h"
 #include "base/macros.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
+#include "ui/base/models/menu_model.h"
 
 namespace app_list {
 class AppContextMenu;
diff --git a/chrome/browser/ui/app_list/search/search_controller.cc b/chrome/browser/ui/app_list/search/search_controller.cc
index 2a81d51..0664807 100644
--- a/chrome/browser/ui/app_list/search/search_controller.cc
+++ b/chrome/browser/ui/app_list/search/search_controller.cc
@@ -26,6 +26,9 @@
 #include "chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.h"
 #include "chrome/browser/ui/ash/tablet_mode_client.h"
+#include "third_party/metrics_proto/chrome_os_app_list_launch_event.pb.h"
+
+using metrics::ChromeOSAppListLaunchEventProto;
 
 namespace app_list {
 
@@ -163,12 +166,12 @@
 
 void SearchController::Train(const std::string& id, RankingItemType type) {
   if (app_list_features::IsAppListLaunchRecordingEnabled()) {
-    AppListLaunchRecorder::LaunchType launch_type;
+    ChromeOSAppListLaunchEventProto::LaunchType launch_type;
     if (type == RankingItemType::kApp ||
         type == RankingItemType::kArcAppShortcut) {
-      launch_type = AppListLaunchRecorder::APP_TILES;
+      launch_type = ChromeOSAppListLaunchEventProto::APP_TILES;
     } else {
-      launch_type = AppListLaunchRecorder::RESULTS_LIST;
+      launch_type = ChromeOSAppListLaunchEventProto::RESULTS_LIST;
     }
 
     // TODO(951287): Record the last-used domain and app.
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.cc b/chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.cc
index 2508a50..b4d957a1d 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.cc
@@ -11,7 +11,7 @@
 using LaunchInfo = AppListLaunchRecorder::LaunchInfo;
 
 AppListLaunchRecorder::LaunchInfo::LaunchInfo(
-    AppListLaunchRecorder::LaunchType launch_type,
+    metrics::ChromeOSAppListLaunchEventProto::LaunchType launch_type,
     const std::string& target,
     const std::string& query,
     const std::string& domain,
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.h b/chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.h
index 1d65fe5..7fdfda5 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.h
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.h
@@ -12,6 +12,7 @@
 #include "base/no_destructor.h"
 #include "base/observer_list.h"
 #include "base/sequence_checker.h"
+#include "third_party/metrics_proto/chrome_os_app_list_launch_event.pb.h"
 
 namespace app_list {
 
@@ -25,14 +26,10 @@
 // This should only be called from the browser UI thread.
 class AppListLaunchRecorder {
  public:
-  // TODO(951287): Replace this with the relevant enum from the proto to be
-  // added to ChromeUserMetricsExtension.
-  enum LaunchType { APP_TILES, RESULTS_LIST };
-
   // Stores information about a single launch event to be logged by a call to
   // Record.
   struct LaunchInfo {
-    LaunchInfo(LaunchType launch_type,
+    LaunchInfo(metrics::ChromeOSAppListLaunchEventProto::LaunchType launch_type,
                const std::string& target,
                const std::string& query,
                const std::string& domain,
@@ -40,7 +37,8 @@
     LaunchInfo(const LaunchInfo& other);
     ~LaunchInfo();
 
-    LaunchType launch_type;
+    // Specifies which UI component this event was launched from.
+    metrics::ChromeOSAppListLaunchEventProto::LaunchType launch_type;
     // A string identifier of the item being launched, eg. an app ID or
     // filepath.
     std::string target;
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
index 031a43d3..f45a7cc 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
@@ -10,7 +10,6 @@
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/interfaces/constants.mojom.h"
-#include "ash/public/interfaces/process_creation_time_recorder.mojom.h"
 #include "ash/shell.h"
 #include "base/command_line.h"
 #include "base/task/post_task.h"
@@ -71,20 +70,6 @@
 #include "chrome/browser/exo_parts.h"
 #endif
 
-namespace {
-
-void PushProcessCreationTimeToAsh() {
-  ash::mojom::ProcessCreationTimeRecorderPtr recorder;
-  content::ServiceManagerConnection::GetForProcess()
-      ->GetConnector()
-      ->BindInterface(ash::mojom::kServiceName, &recorder);
-  DCHECK(!startup_metric_utils::MainEntryPointTicks().is_null());
-  recorder->SetMainProcessCreationTime(
-      startup_metric_utils::MainEntryPointTicks());
-}
-
-}  // namespace
-
 namespace internal {
 
 // Creates a ChromeLauncherController on the first active session notification.
@@ -204,8 +189,6 @@
 #if BUILDFLAG(ENABLE_WAYLAND_SERVER)
   exo_parts_ = ExoParts::CreateIfNecessary();
 #endif
-
-  PushProcessCreationTimeToAsh();
 }
 
 void ChromeBrowserMainExtraPartsAsh::PostProfileInit() {
diff --git a/chrome/browser/ui/ash/time_to_first_present_recorder_browsertest.cc b/chrome/browser/ui/ash/time_to_first_present_recorder_browsertest.cc
deleted file mode 100644
index 2587031..0000000
--- a/chrome/browser/ui/ash/time_to_first_present_recorder_browsertest.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/public/interfaces/constants.mojom.h"
-#include "ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom-test-utils.h"
-#include "ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom.h"
-#include "base/command_line.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "content/public/common/service_manager_connection.h"
-#include "services/service_manager/public/cpp/connector.h"
-
-using TimeToFirstPresentRecorderTest = InProcessBrowserTest;
-
-IN_PROC_BROWSER_TEST_F(TimeToFirstPresentRecorderTest, VerifyTimeCalculated) {
-  ash::mojom::TimeToFirstPresentRecorderTestApiPtr recorder_test_api;
-  content::ServiceManagerConnection::GetForProcess()
-      ->GetConnector()
-      ->BindInterface(ash::mojom::kServiceName, &recorder_test_api);
-  ash::mojom::TimeToFirstPresentRecorderTestApiAsyncWaiter recorder(
-      recorder_test_api.get());
-  base::TimeDelta time_delta;
-  recorder.GetProcessCreationToFirstPresentTime(&time_delta);
-  EXPECT_FALSE(time_delta.is_zero());
-}
diff --git a/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h b/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h
index 88ce0e7..9de5257 100644
--- a/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h
+++ b/chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h
@@ -88,7 +88,7 @@
   // The index of the original parent folder.
   int node_parent_index_;
 
-  base::ObserverList<ui::ComboboxModelObserver>::Unchecked observers_;
+  base::ObserverList<ui::ComboboxModelObserver> observers_;
 
   DISALLOW_COPY_AND_ASSIGN(RecentlyUsedFoldersComboModel);
 };
diff --git a/chrome/browser/ui/uma_browsing_activity_observer.cc b/chrome/browser/ui/uma_browsing_activity_observer.cc
index 0424931..3c21131 100644
--- a/chrome/browser/ui/uma_browsing_activity_observer.cc
+++ b/chrome/browser/ui/uma_browsing_activity_observer.cc
@@ -90,10 +90,9 @@
   if (upgrade_detected_time.is_null())
     return;
   const base::Time now = base::Time::Now();
-  UMA_HISTOGRAM_CUSTOM_TIMES("UpgradeDetector.TimeBeforeUpgrade",
-                             base::TimeDelta(now - upgrade_detected_time),
-                             base::TimeDelta::FromHours(1),
-                             base::TimeDelta::FromDays(20), 50);
+  UMA_HISTOGRAM_EXACT_LINEAR(
+      "UpgradeDetector.DaysBeforeUpgrade",
+      base::TimeDelta(now - upgrade_detected_time).InDays(), 30);
 }
 
 void UMABrowsingActivityObserver::LogRenderProcessHostCount() const {
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc
index 2cdd70e..e9b60dbc 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc
@@ -80,9 +80,13 @@
   if (base::win::GetVersion() < base::win::Version::WIN10)
     return;  // VirtualDesktopManager isn't support pre Win-10.
 
-  CHECK(SUCCEEDED(::CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr,
-                                     CLSCTX_ALL,
-                                     IID_PPV_ARGS(&virtual_desktop_manager_))));
+  // Virtual Desktops on Windows are best-effort and may not always be
+  // available.
+  if (FAILED(::CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr,
+                                CLSCTX_ALL,
+                                IID_PPV_ARGS(&virtual_desktop_manager_)))) {
+    return;
+  }
 
   if (!params.workspace.empty()) {
     GUID guid = GUID_NULL;
@@ -95,10 +99,6 @@
       virtual_desktop_manager_->MoveWindowToDesktop(GetHWND(), guid);
     }
   }
-  // This will force the window to re-open in this desktop on restart.
-  // We always want to do this even if |params.workspace| is empty, to handle
-  // the case of new windows.
-  OnHostWorkspaceChanged();
 }
 
 std::string BrowserDesktopWindowTreeHostWin::GetWorkspace() const {
diff --git a/chrome/browser/ui/views/tabs/window_finder_win.cc b/chrome/browser/ui/views/tabs/window_finder_win.cc
index 0711bb5..76bcfde 100644
--- a/chrome/browser/ui/views/tabs/window_finder_win.cc
+++ b/chrome/browser/ui/views/tabs/window_finder_win.cc
@@ -176,7 +176,7 @@
     RECT r;
 
     // Make sure the window is on the same virtual desktop.
-    if (base::win::GetVersion() >= base::win::Version::WIN10) {
+    if (virtual_desktop_manager_) {
       BOOL on_current_desktop;
       if (SUCCEEDED(virtual_desktop_manager_->IsWindowOnCurrentVirtualDesktop(
               hwnd, &on_current_desktop)) &&
@@ -199,9 +199,8 @@
       : BaseWindowFinder(ignore),
         result_(NULL) {
     if (base::win::GetVersion() >= base::win::Version::WIN10) {
-      CHECK(SUCCEEDED(::CoCreateInstance(
-          __uuidof(VirtualDesktopManager), nullptr, CLSCTX_ALL,
-          IID_PPV_ARGS(&virtual_desktop_manager_))));
+      ::CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr, CLSCTX_ALL,
+                         IID_PPV_ARGS(&virtual_desktop_manager_));
     }
     screen_loc_ = display::win::ScreenWin::DIPToScreenPoint(screen_loc);
     EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, as_lparam());
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler.cc b/chrome/browser/ui/webui/signin/inline_login_handler.cc
index b21e566..7794057 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler.cc
@@ -122,8 +122,6 @@
     params.SetBoolean("isLoginPrimaryAccount", true);
   }
 
-  params.SetString("continueUrl", signin::GetLandingURL(access_point).spec());
-
   Profile* profile = Profile::FromWebUI(web_ui());
   std::string default_email;
   if (reason == signin_metrics::Reason::REASON_SIGNIN_PRIMARY_ACCOUNT ||
diff --git a/chrome/browser/vr/webxr_vr_input_browser_test.cc b/chrome/browser/vr/webxr_vr_input_browser_test.cc
index 6b9e39e..c7f45eb 100644
--- a/chrome/browser/vr/webxr_vr_input_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_input_browser_test.cc
@@ -200,6 +200,70 @@
   std::move(callback).Run();
 }
 
+// Test that inputsourceschange events contain only the expected added/removed
+// input sources when a mock controller is connected/disconnected.
+// Also validates that if an input source changes substantially we get an event
+// containing both the removal of the old one and the additon of the new one,
+// rather than two events.
+IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard, TestInputSourcesChange) {
+  WebXrControllerInputMock my_mock;
+
+  // TODO(crbug.com/963676): Figure out if the race is a product or test bug.
+  // There's a potential for a race causing the input sources change event to
+  // fire multiple times if we disconnect a controller that has a gamepad.
+  uint64_t insufficient_buttons =
+      vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Trigger);
+  std::map<vr::EVRButtonId, unsigned int> insufficient_axis_types = {
+      {vr::k_EButton_SteamVR_Trigger, vr::k_eControllerAxis_Trigger},
+  };
+  unsigned int controller_index = my_mock.CreateAndConnectController(
+      device::ControllerRole::kControllerRoleRight, insufficient_axis_types,
+      insufficient_buttons);
+
+  LoadUrlAndAwaitInitialization(
+      GetFileUrlForHtmlTestFile("test_webxr_input_sources_change_event"));
+  EnterSessionWithUserGestureOrFail();
+
+  // Wait for the first changed event
+  PollJavaScriptBooleanOrFail("inputChangeEvents === 1", kPollTimeoutShort);
+
+  // Validate that we only have one controller added, and no controller removed.
+  RunJavaScriptOrFail("validateAdded(1)");
+  RunJavaScriptOrFail("validateRemoved(0)");
+  RunJavaScriptOrFail("updateCachedInputSource(0)");
+  RunJavaScriptOrFail("validateCachedAddedPresence(true)");
+
+  // Disconnect the controller and validate that we only have one controller
+  // removed, and that our previously cached controller is in the removed array.
+  my_mock.DisconnectController(controller_index);
+  PollJavaScriptBooleanOrFail("inputChangeEvents === 2", kPollTimeoutShort);
+
+  RunJavaScriptOrFail("validateAdded(0)");
+  RunJavaScriptOrFail("validateRemoved(1)");
+  RunJavaScriptOrFail("validateCachedRemovedPresence(true)");
+
+  // Connect a controller, and then change enough properties that the system
+  // recalculates its status as a valid controller, so that we can verify
+  // it is both added and removed.
+  // Since we're changing the controller state without disconnecting it, we can
+  // (and should) use the minimal gamepad here.
+  controller_index = my_mock.CreateAndConnectMinimalGamepad();
+  PollJavaScriptBooleanOrFail("inputChangeEvents === 3", kPollTimeoutShort);
+  RunJavaScriptOrFail("updateCachedInputSource(0)");
+
+  my_mock.UpdateControllerSupport(controller_index, insufficient_axis_types,
+                                  insufficient_buttons);
+
+  PollJavaScriptBooleanOrFail("inputChangeEvents === 4", kPollTimeoutShort);
+  RunJavaScriptOrFail("validateAdded(1)");
+  RunJavaScriptOrFail("validateRemoved(1)");
+  RunJavaScriptOrFail("validateCachedAddedPresence(false)");
+  RunJavaScriptOrFail("validateCachedRemovedPresence(true)");
+
+  RunJavaScriptOrFail("done()");
+  EndTest();
+}
+
 // Ensure that changes to a gamepad object respect that it is the same object
 // and that if whether or not an input source has a gamepad changes that the
 // input source change event is fired and a new input source is created.
@@ -287,7 +351,7 @@
   LoadUrlAndAwaitInitialization(
       GetFileUrlForHtmlTestFile("test_webxr_gamepad_support"));
   EnterSessionWithUserGestureOrFail();
-  ExecuteStepAndWait("validateInputSourceHasNoGamepad()");
+  PollJavaScriptBooleanOrFail("inputSourceHasNoGamepad()", kPollTimeoutShort);
   RunJavaScriptOrFail("done()");
   EndTest();
 }
@@ -310,9 +374,12 @@
 
   // The trigger should be button 0, and the first set of axes should have it's
   // value set.
-  ExecuteStepAndWait("validateMapping('xr-standard')");
-  ExecuteStepAndWait("validateButtonPressed(0)");
-  ExecuteStepAndWait("validateAxesValues(0, 0.5, -0.5)");
+  PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
+                              kPollTimeoutShort);
+  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, true)",
+                              kPollTimeoutShort);
+  PollJavaScriptBooleanOrFail("areAxesValuesEqualTo(0, 0.5, -0.5)",
+                              kPollTimeoutShort);
   RunJavaScriptOrFail("done()");
   EndTest();
 }
@@ -359,18 +426,23 @@
                         vr::ButtonMaskFromId(vr::k_EButton_Grip));
 
   // Controller should meet the requirements for the 'xr-standard' mapping.
-  ExecuteStepAndWait("validateMapping('xr-standard')");
+  PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
+                              kPollTimeoutShort);
 
   // The secondary set of axes should be set appropriately.
-  ExecuteStepAndWait("validateAxesValues(1, 0.25, -0.25)");
+  PollJavaScriptBooleanOrFail("areAxesValuesEqualTo(1, 0.25, -0.25)",
+                              kPollTimeoutShort);
 
   // Button 2 is reserved for the Grip, and should be pressed.
-  ExecuteStepAndWait("validateButtonPressed(2)");
+  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(2, true)",
+                              kPollTimeoutShort);
 
   // Button 3 is reserved for the secondary trackpad/joystick and should be
   // touched but not pressed.
-  ExecuteStepAndWait("validateButtonNotPressed(3)");
-  ExecuteStepAndWait("validateButtonTouched(3)");
+  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(3, false)",
+                              kPollTimeoutShort);
+  PollJavaScriptBooleanOrFail("isButtonTouchedEqualTo(3, true)",
+                              kPollTimeoutShort);
 
   RunJavaScriptOrFail("done()");
   EndTest();
@@ -410,12 +482,18 @@
   // Index 2 and 3 are reserved for the grip and secondary joystick.
   // As our controller doesn't support them, they should be present but not
   // pressed, and our "extra" button should be index 4 and should be pressed.
-  ExecuteStepAndWait("validateMapping('xr-standard')");
-  ExecuteStepAndWait("validateButtonPressed(0)");
-  ExecuteStepAndWait("validateButtonPressed(1)");
-  ExecuteStepAndWait("validateButtonNotPressed(2)");
-  ExecuteStepAndWait("validateButtonNotPressed(3)");
-  ExecuteStepAndWait("validateButtonPressed(4)");
+  PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
+                              kPollTimeoutShort);
+  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, true)",
+                              kPollTimeoutShort);
+  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(1, true)",
+                              kPollTimeoutShort);
+  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(2, false)",
+                              kPollTimeoutShort);
+  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(3, false)",
+                              kPollTimeoutShort);
+  PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(4, true)",
+                              kPollTimeoutShort);
 
   RunJavaScriptOrFail("done()");
   EndTest();
diff --git a/chrome/common/chrome_paths_internal.h b/chrome/common/chrome_paths_internal.h
index 13ece3e..57664fee 100644
--- a/chrome/common/chrome_paths_internal.h
+++ b/chrome/common/chrome_paths_internal.h
@@ -73,11 +73,19 @@
 base::FilePath GetVersionedDirectory();
 #endif
 
+#if BUILDFLAG(NEW_MAC_BUNDLE_STRUCTURE)
+// Most of the application is further contained within the framework, which
+// resides in the Frameworks directory of the top-level Contents folder. The
+// framework is versioned with the full product version. This function returns
+// the full path to the versioned sub-directory of the framework, i.e.:
+// Chromium.app/Contents/Frameworks/Chromium Framework.framework/Versions/X.
+#else
 // Most of the application is further contained within the framework.  The
 // framework bundle is located within the versioned directory at a specific
 // path.  The only components in the versioned directory not included in the
 // framework are things that also depend on the framework, such as the helper
 // app bundle.
+#endif
 base::FilePath GetFrameworkBundlePath();
 
 // Get the local library directory.
diff --git a/chrome/common/chrome_paths_mac.mm b/chrome/common/chrome_paths_mac.mm
index 913858d..67f7e35 100644
--- a/chrome/common/chrome_paths_mac.mm
+++ b/chrome/common/chrome_paths_mac.mm
@@ -38,9 +38,11 @@
   }
 
 #if BUILDFLAG(NEW_MAC_BUNDLE_STRUCTURE)
-  // From C.app/Contents/Frameworks/C.framework, go up three steps to C.app.
+  // From C.app/Contents/Frameworks/C.framework/Versions/1.2.3.4, go up five
+  // steps to C.app.
   base::FilePath framework_path = chrome::GetFrameworkBundlePath();
-  base::FilePath outer_app_dir = framework_path.DirName().DirName().DirName();
+  base::FilePath outer_app_dir =
+      framework_path.DirName().DirName().DirName().DirName().DirName();
 #else
   // From C.app/Contents/Versions/1.2.3.4, go up three steps to get to C.app.
   base::FilePath versioned_dir = chrome::GetVersionedDirectory();
@@ -203,15 +205,30 @@
 
   if (base::mac::IsBackgroundOnlyProcess()) {
     // |path| is Chromium.app/Contents/Frameworks/Chromium Framework.framework/
-    // Versions/X/Helpers/Chromium Helper.app/Contents. Go up five times to
-    // the framework directory.
-    path = path.DirName().DirName().DirName().DirName().DirName();
-    DCHECK_EQ(path.BaseName().value(), kFrameworkName);
+    // Versions/X/Helpers/Chromium Helper.app/Contents. Go up three times to
+    // the versioned framework directory.
+    path = path.DirName().DirName().DirName();
   } else {
     // |path| is Chromium.app/Contents, so go down to
-    // Chromium.app/Contents/Frameworks/Chromium Framework.framework.
-    path = path.Append("Frameworks").Append(kFrameworkName);
+    // Chromium.app/Contents/Frameworks/Chromium Framework.framework/Versions/X.
+    path = path.Append("Frameworks")
+               .Append(kFrameworkName)
+               .Append("Versions")
+               .Append(kChromeVersion);
   }
+  DCHECK_EQ(path.BaseName().value(), kChromeVersion);
+  DCHECK_EQ(path.DirName().BaseName().value(), "Versions");
+  DCHECK_EQ(path.DirName().DirName().BaseName().value(), kFrameworkName);
+  DCHECK_EQ(path.DirName().DirName().DirName().BaseName().value(),
+            "Frameworks");
+  DCHECK_EQ(path.DirName()
+                .DirName()
+                .DirName()
+                .DirName()
+                .DirName()
+                .BaseName()
+                .Extension(),
+            ".app");
   return path;
 #else
   // The framework bundle is at a known path and name from the browser .app's
diff --git a/chrome/common/extensions/api/file_manager_private.idl b/chrome/common/extensions/api/file_manager_private.idl
index cd9fd74..3e3d884 100644
--- a/chrome/common/extensions/api/file_manager_private.idl
+++ b/chrome/common/extensions/api/file_manager_private.idl
@@ -565,6 +565,8 @@
 
 dictionary PreferencesChange {
   boolean? cellularDisabled;
+  boolean? arcEnabled;
+  boolean? arcRemovableMediaAccessEnabled;
 };
 
 dictionary SearchParams {
diff --git a/chrome/common/extensions/extension_test_util.cc b/chrome/common/extensions/extension_test_util.cc
index aa2d2c3..52058c1 100644
--- a/chrome/common/extensions/extension_test_util.cc
+++ b/chrome/common/extensions/extension_test_util.cc
@@ -13,8 +13,10 @@
 #include "base/values.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
+#include "components/version_info/channel.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extensions_client.h"
+#include "extensions/common/features/feature_channel.h"
 #include "extensions/common/manifest_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -98,4 +100,22 @@
   extensions::ExtensionsClient::Get()->InitializeWebStoreUrls(command_line);
 }
 
+std::unique_ptr<extensions::ScopedCurrentChannel>
+GetOverrideChannelForActionType(extensions::ActionInfo::Type action_type) {
+  std::unique_ptr<extensions::ScopedCurrentChannel> channel;
+  // The "action" key is currently restricted to trunk. Use a fake channel iff
+  // we're testing that key, so that we still get multi-channel coverage for
+  // browser and page actions.
+  switch (action_type) {
+    case extensions::ActionInfo::TYPE_ACTION:
+      channel = std::make_unique<extensions::ScopedCurrentChannel>(
+          version_info::Channel::UNKNOWN);
+      break;
+    case extensions::ActionInfo::TYPE_PAGE:
+    case extensions::ActionInfo::TYPE_BROWSER:
+      break;
+  }
+  return channel;
+}
+
 }  // namespace extension_test_util
diff --git a/chrome/common/extensions/extension_test_util.h b/chrome/common/extensions/extension_test_util.h
index 42282d5..ec8930c 100644
--- a/chrome/common/extensions/extension_test_util.h
+++ b/chrome/common/extensions/extension_test_util.h
@@ -5,15 +5,18 @@
 #ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
 #define CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
 
+#include <memory>
 #include <string>
 
 #include "base/memory/ref_counted.h"
+#include "chrome/common/extensions/api/extension_action/action_info.h"
 #include "extensions/common/manifest.h"
 
 class GURL;
 
 namespace extensions {
 class Extension;
+class ScopedCurrentChannel;
 }
 
 namespace extension_test_util {
@@ -55,6 +58,14 @@
 void SetGalleryURL(const GURL& new_url);
 void SetGalleryUpdateURL(const GURL& new_url);
 
+// Returns a ScopedCurrentChannel object to use in tests if one is necessary for
+// the given |action_type| specified in the manifest. This will only return
+// non-null if the "action" manifest key is used.
+// TODO(https://crbug.com/893373): Remove this one the "action" key is launched
+// to stable.
+std::unique_ptr<extensions::ScopedCurrentChannel>
+GetOverrideChannelForActionType(extensions::ActionInfo::Type action_type);
+
 }  // namespace extension_test_util
 
 #endif  // CHROME_COMMON_EXTENSIONS_EXTENSION_TEST_UTIL_H_
diff --git a/chrome/common/media/cdm_host_file_path.cc b/chrome/common/media/cdm_host_file_path.cc
index 36e336b..a1624df 100644
--- a/chrome/common/media/cdm_host_file_path.cc
+++ b/chrome/common/media/cdm_host_file_path.cc
@@ -77,17 +77,12 @@
 #elif defined(OS_MACOSX)
 
 #if BUILDFLAG(NEW_MAC_BUNDLE_STRUCTURE)
-  // Always access the framework through the currently-running version, in case
-  // an update has been staged and updated the Current symlinks.
-  base::FilePath versioned_framework_dir = base::mac::FrameworkBundlePath()
-                                               .Append("Versions")
-                                               .Append(chrome::kChromeVersion);
+  base::FilePath framework_dir = base::mac::FrameworkBundlePath();
   base::FilePath chrome_framework_path =
-      versioned_framework_dir.Append(chrome::kFrameworkExecutableName);
+      framework_dir.Append(chrome::kFrameworkExecutableName);
   // The signature file lives inside
   // Google Chrome Framework.framework/Versions/X/Resources/.
-  base::FilePath widevine_signature_path =
-      versioned_framework_dir.Append("Resources");
+  base::FilePath widevine_signature_path = framework_dir.Append("Resources");
 #else
   base::FilePath chrome_framework_path =
       base::mac::FrameworkBundlePath().Append(chrome::kFrameworkExecutableName);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index e5970eb..59f6b85 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -297,6 +297,13 @@
     ]
   }
 
+  if (is_win) {
+    sources += [
+      "pixel/skia_gold_pixel_diff.cc",
+      "pixel/skia_gold_pixel_diff.h",
+    ]
+  }
+
   if (toolkit_views) {
     public_deps += [ "//ui/views:test_support" ]
     sources += [
@@ -2036,7 +2043,6 @@
         "../browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc",
         "../browser/ui/ash/tab_scrubber_browsertest.cc",
         "../browser/ui/ash/tablet_mode_page_behavior_browsertest.cc",
-        "../browser/ui/ash/time_to_first_present_recorder_browsertest.cc",
         "../browser/ui/ash/volume_controller_browsertest.cc",
         "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc",
         "../browser/ui/views/arc_app_dialog_view_browsertest.cc",
@@ -3077,6 +3083,13 @@
     }
   }
 
+  # Pixeltests only support win.
+  if (is_win) {
+    sources += [
+      "../test/pixel/skia_gold_pixel_diff_unittest.cc",
+    ]
+  }
+
   if (!is_android) {
     sources += [
       # CRLSets are not supported on Android or iOS, but available on all other
@@ -3292,6 +3305,7 @@
       "../browser/autofill/autofill_credit_card_filling_infobar_delegate_mobile_unittest.cc",
       "../browser/autofill/autofill_keyboard_accessory_adapter_unittest.cc",
       "../browser/autofill/autofill_save_card_infobar_delegate_mobile_unittest.cc",
+      "../browser/autofill/credit_card_accessory_controller_impl_unittest.cc",
       "../browser/autofill/manual_filling_controller_impl_unittest.cc",
       "../browser/media/android/cdm/media_drm_origin_id_manager_unittest.cc",
       "../browser/metrics/android_metrics_provider_unittest.cc",
@@ -6047,6 +6061,27 @@
   }
 }
 
+if (is_win) {
+  test("pixel_browser_tests") {
+    sources = [
+      "pixel/demo/skia_gold_demo_pixeltest.cc",
+    ]
+    configs += [ "//build/config:precompiled_headers" ]
+    defines = [
+      "HAS_OUT_OF_PROC_TEST_RUNNER",
+    ]
+    deps = [
+      ":test_support",
+      ":browser_tests_runner",
+    ]
+    if (is_win) {
+      data = [ "//tools/skia_goldctl/goldctl.exe" ]
+    } else {
+      data = [ "//tools/skia_goldctl/goldctl" ]
+    }
+  }
+}
+
 # Test binary for the ChromeOS usage time limit processor tests.
 if (is_chromeos) {
   test("usage_time_limit_unittests") {
diff --git a/chrome/test/data/local_ntp/local_ntp_browsertest.html b/chrome/test/data/local_ntp/local_ntp_browsertest.html
index 726edc8..e8fd20a 100644
--- a/chrome/test/data/local_ntp/local_ntp_browsertest.html
+++ b/chrome/test/data/local_ntp/local_ntp_browsertest.html
@@ -167,13 +167,13 @@
       <div id="menu-nav-panel">
         <div id="backgrounds-button" class="menu-option selected" tabindex="0">
           <div class="menu-option-icon-wrapper">
-            <div ids="backgrounds-icon" class="menu-option-icon"></div>
+            <div id="backgrounds-icon" class="menu-option-icon"></div>
           </div>
           <div class="menu-option-label">$i18n{backgroundsOption}</div>
         </div>
         <div id="shortcuts-button" class="menu-option" tabindex="0">
           <div class="menu-option-icon-wrapper">
-            <div ids="shortcuts-icon" class="menu-option-icon"></div>
+            <div id="shortcuts-icon" class="menu-option-icon"></div>
           </div>
           <div class="menu-option-label">$i18n{shortcutsOption}</div>
         </div>
diff --git a/chrome/test/data/media/picture-in-picture/player_preload_none.html b/chrome/test/data/media/picture-in-picture/player_preload_none.html
index 55c4002..5a7159e 100644
--- a/chrome/test/data/media/picture-in-picture/player_preload_none.html
+++ b/chrome/test/data/media/picture-in-picture/player_preload_none.html
@@ -14,6 +14,10 @@
   function enterPictureInPicture() {
     video.requestPictureInPicture().then(() => {
       window.domAutomationController.send(true);
+
+      video.addEventListener('leavepictureinpicture', () => {
+        document.title = 'leavepictureinpicture';
+      }, { once: true });
     });
   }
 
diff --git a/chrome/test/data/media/picture-in-picture/window-size.html b/chrome/test/data/media/picture-in-picture/window-size.html
index 50c954c..d795263 100644
--- a/chrome/test/data/media/picture-in-picture/window-size.html
+++ b/chrome/test/data/media/picture-in-picture/window-size.html
@@ -46,7 +46,7 @@
       }, { once: true });
 
       video.addEventListener('leavepictureinpicture', () => {
-        document.title = 'left';
+        document.title = 'leavepictureinpicture';
       }, { once: true });
 
       window.domAutomationController.send(true);
@@ -145,5 +145,12 @@
       .catch(e => { document.title = 'failed to enter Picture-in-Picture after leaving'; });
     });
   }
+
+  function resetVideo() {
+    video.addEventListener('emptied', () => {
+      document.title = 'emptied';
+    }, { once: true });
+    video.src = '';
+  }
 </script>
 </html>
diff --git a/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support.html b/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support.html
index a56efa1..edd47f4 100644
--- a/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support.html
+++ b/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support.html
@@ -17,6 +17,20 @@
       // always null.
       window.addEventListener("gamepadconnected", function(e) {});
 
+      let selectCount = 0;
+
+      function onSelect() {
+        selectCount++;
+      }
+
+      function currentImmersiveSession() {
+        return sessionInfos[sessionTypes.IMMERSIVE].currentSession;
+      }
+
+      function stepSetupListeners() {
+        currentImmersiveSession().addEventListener('select', onSelect, false);
+      }
+
       // TODO(https://crbug.com/961774): On some Windows machines, a phantom
       // gamepad shows up in the navigator.getGamepads() array once a WebXR
       // gamepad sends any input. Until that's fixed, filter out such devices
@@ -26,137 +40,159 @@
         return gamepad.id == "Unknown Gamepad (Vendor: 0000 Product: 0000)";
       }
 
-      function assertNumNavigatorGamepadsMatchesExpectation(numExpectedGamepads) {
+      function navigatorGamepadCount() {
         let numGamepads = 0;
         for (gamepad of navigator.getGamepads()) {
           if ((gamepad !== null) && !isPhantomGamepad(gamepad)) {
             numGamepads++;
           }
         }
-        assert_equals(numGamepads, numExpectedGamepads,
-            "Number of returned gamepads matches expectation");
+        return numGamepads;
       }
 
-      function assertNumInputSourceGamepadsMatchesExpectation(numExpectedGamepads) {
-        let currentSession = sessionInfos[sessionTypes.IMMERSIVE].currentSession;
+      function inputSourceCount() {
+        return currentImmersiveSession().getInputSources().length;
+      }
+
+      function inputSourceWithGamepadCount() {
         let numGamepads = 0;
-        for (source of currentSession.getInputSources()) {
+        for (source of currentImmersiveSession().getInputSources()) {
           if (source.gamepad !== null) {
             numGamepads++;
           }
         }
-        assert_equals(numGamepads, numExpectedGamepads,
-            "Number of XRInputSources with Gamepads on XRSession matches expectation");
+        return numGamepads;
       }
 
-      function assertNumInputSourcesMatchesExpectation(numExpectedSources) {
-        let currentSession = sessionInfos[sessionTypes.IMMERSIVE].currentSession;
-        assert_equals(currentSession.getInputSources().length, numExpectedSources,
-            "Number of XRInputSources on XRSession matches expectation");
-      }
-
-      function validateInputSourceHasNoGamepad() {
-        assert_equals(onImmersiveXRFrameCallback, null);
-        onImmersiveXRFrameCallback = function(session) {
-          // Clear the callback first in case any of the following asserts fail.
-          // If any asserts trigger, we don't run any code that follows, so if
-          // we did this as the last step of the callback and any of the asserts
-          // failed, we would be continually called, which just makes it harder
-          // to debug.
-          onImmersiveXRFrameCallback = null;
-
-          // We don't expect to have attached any non-vr gamepads, and vr
-          // gamepads shouldn't show up in navigator.getGamepads()
-          assertNumNavigatorGamepadsMatchesExpectation(0);
-
-          // There should only be one input source, but there should not be
-          // enough data to make a gamepad.
-          assertNumInputSourcesMatchesExpectation(1);
-          assertNumInputSourceGamepadsMatchesExpectation(0);
-          finishJavaScriptStep();
+      // Especially on Android, there can be a small delay between when the test
+      // code sends simulated input and when that input is registered by WebXR.
+      // These functions return bools instead of asserting so that test code can
+      // poll on the return values to avoid race conditions.
+      function inputSourceHasNoGamepad() {
+        // We don't expect to have attached any non-vr gamepads, and vr
+        // gamepads shouldn't show up in navigator.getGamepads()
+        if (navigatorGamepadCount() != 0) {
+          return false;
         }
+
+        // There should only be one input source, but there should not be
+        // enough data to make a gamepad.
+        if (inputSourceCount() != 1) {
+          return false;
+        }
+        if (inputSourceWithGamepadCount() != 0) {
+          return false;
+        }
+
+        return true;
       }
 
-      function validateFirstGamepad(validationFunction) {
-        assert_equals(onImmersiveXRFrameCallback, null);
-        onImmersiveXRFrameCallback = function(session) {
-          // Clear the callback first in case any of the following asserts fail.
-          // If any asserts trigger, we don't run any code that follows, so if
-          // we did this as the last step of the callback and any of the asserts
-          // failed, we would be continually called, which just makes it harder
-          // to debug.
-          onImmersiveXRFrameCallback = null;
+      // Especially on Android, there can be a small delay between when the test
+      // code sends simulated input and when that input is registered by WebXR.
+      // These functions return bools instead of asserting so that test code can
+      // poll on the return values to avoid race conditions.
+      function isFirstGamepadAsExpected(verificationFunction) {
+        // We don't expect to have attached any non-vr gamepads, and vr
+        // gamepads shouldn't show up in navigator.getGamepads()
+        if (navigatorGamepadCount() != 0) {
+          return false;
+        }
 
-          // We don't expect to have attached any non-vr gamepads, and vr
-          // gamepads shouldn't show up in navigator.getGamepads()
-          assertNumNavigatorGamepadsMatchesExpectation(0);
+        // There should only be one input source and it should have a gamepad.
+        if (inputSourceCount() != 1) {
+          return false;
+        }
+        if (inputSourceWithGamepadCount() != 1) {
+          return false;
+        }
 
-          // There should only be one input source and it should have a gamepad.
-          assertNumInputSourcesMatchesExpectation(1);
-          assertNumInputSourceGamepadsMatchesExpectation(1);
+        if (verificationFunction) {
+          let gamepad = currentImmersiveSession().getInputSources()[0].gamepad;
+          if (!verificationFunction(gamepad)) {
+            return false;
+          }
+        }
 
-          let gamepad = session.getInputSources()[0].gamepad;
-          if (validationFunction) {
-            validationFunction(gamepad);
+        return true;
+      }
+
+      function isMappingEqualTo(expected_mapping) {
+        let verificationFunction = function(gamepad) {
+          return gamepad.mapping == expected_mapping;
+        }
+
+        return isFirstGamepadAsExpected(verificationFunction);
+      }
+
+      function isButtonCountEqualTo(expected_count) {
+        let verificationFunction = function(gamepad) {
+          return gamepad.buttons.length == expected_count;
+        }
+
+        return isFirstGamepadAsExpected(verificationFunction);
+      }
+
+      // WebXR only exposes axes in (x, y) pairs because triggers are reported
+      // as buttons on the Gamepad. The GamepadButton interface has a "value"
+      // attribute that supports one-dimensional analog input.
+      function isAxisPairCountEqualTo(expected_count) {
+        let verificationFunction = function(gamepad) {
+          return gamepad.axes.length == (expected_count * 2);
+        }
+
+        return isFirstGamepadAsExpected(verificationFunction);
+      }
+
+      function isButtonPressedEqualTo(button_index, expected_pressed) {
+        let verificationFunction = function(gamepad) {
+          if (button_index >= gamepad.buttons.length) {
+            return false;
           }
 
-          finishJavaScriptStep();
-        }
-      }
-
-      function validateMapping(expected_mapping) {
-        let validationFunction = function(gamepad) {
-          assert_equals(gamepad.mapping, expected_mapping);
+          return gamepad.buttons[button_index].pressed == expected_pressed;
         }
 
-        validateFirstGamepad(validationFunction);
+        return isFirstGamepadAsExpected(verificationFunction);
       }
 
-      function validateButtonNotPressed(button_index) {
-        let validationFunction = function(gamepad) {
-          assert_less_than(button_index, gamepad.buttons.length,
-            "Verify that we have at least as many buttons as requested");
-          assert_false(gamepad.buttons[button_index].pressed);
+      function isButtonTouchedEqualTo(button_index, expected_touched) {
+        let verificationFunction = function(gamepad) {
+          if (button_index >= gamepad.buttons.length) {
+            return false;
+          }
+
+          return gamepad.buttons[button_index].touched == expected_touched;
         }
 
-        validateFirstGamepad(validationFunction);
+        return isFirstGamepadAsExpected(verificationFunction);
       }
 
-      function validateButtonPressed(button_index) {
-        let validationFunction = function(gamepad) {
-          assert_less_than(button_index, gamepad.buttons.length,
-            "Verify that we have at least as many buttons as requested");
-          assert_true(gamepad.buttons[button_index].pressed);
-        }
-
-        validateFirstGamepad(validationFunction);
-      }
-
-      function validateButtonTouched(button_index) {
-        let validationFunction = function(gamepad) {
-          assert_less_than(button_index, gamepad.buttons.length,
-            "Verify that we have at least as many buttons as requested");
-          assert_true(gamepad.buttons[button_index].touched);
-        }
-
-        validateFirstGamepad(validationFunction);
-      }
-
-      function validateAxesValues(axes_pair_index, x_axis_value, y_axis_value) {
-        let validationFunction = function(gamepad) {
+      function areAxesValuesEqualTo(axes_pair_index, x_axis_value, y_axis_value) {
+        let verificationFunction = function(gamepad) {
           let epsilon = 0.001;
           let x_index = (2 * axes_pair_index);
           let y_index = (2 * axes_pair_index) + 1;
 
-          assert_less_than(y_index, gamepad.axes.length,
-            "Verify that we have at least as many axes as requested");
-          assert_true(Math.abs(gamepad.axes[x_index] - x_axis_value) < epsilon,
-            "X approximately equals expected");
-          assert_true(Math.abs(gamepad.axes[y_index] - y_axis_value) < epsilon,
-            "Y approximately equals expected");
+          if (y_index >= gamepad.axes.length) {
+            return false;
+          }
+
+          function approxEquals(a, b) {
+            return Math.abs(a - b) < epsilon;
+          }
+
+          if (!approxEquals(gamepad.axes[x_index], x_axis_value)) {
+            return false;
+          }
+
+          if (!approxEquals(gamepad.axes[y_index], y_axis_value)) {
+            return false;
+          }
+
+          return true;
         }
 
-        validateFirstGamepad(validationFunction);
+        return isFirstGamepadAsExpected(verificationFunction);
       }
     </script>
   </body>
diff --git a/chrome/test/data/xr/e2e_test_files/html/test_webxr_input_sources_change_event.html b/chrome/test/data/xr/e2e_test_files/html/test_webxr_input_sources_change_event.html
new file mode 100644
index 0000000..edca160
--- /dev/null
+++ b/chrome/test/data/xr/e2e_test_files/html/test_webxr_input_sources_change_event.html
@@ -0,0 +1,84 @@
+<!doctype html>
+<!--
+A collection of helper functions and listeners to confirm the state of input
+sources for the same object tests.
+-->
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="../resources/webxr_e2e.css">
+  </head>
+  <body>
+    <canvas id="webgl-canvas"></canvas>
+    <script src="../../../../../../third_party/blink/web_tests/resources/testharness.js"></script>
+    <script src="../resources/webxr_e2e.js"></script>
+    <script src="../resources/webxr_boilerplate.js"></script>
+    <script>
+      let inputChangeEvents = 0;
+      let lastAdded = null;
+      let lastRemoved = null;
+      function onInputSourcesChange(event) {
+        lastAdded = event.added;
+        lastRemoved = event.removed;
+        inputChangeEvents++;
+      }
+
+      onSessionStartedCallback = function(session) {
+        if (session.mode.startsWith("immersive")) {
+          session.addEventListener('inputsourceschange', onInputSourcesChange, false);
+        }
+      }
+
+      function getCurrentInputSources() {
+        let currentSession = sessionInfos[sessionTypes.IMMERSIVE].currentSession;
+        return currentSession.getInputSources();
+      }
+
+      let cached_input_source = null;
+      function updateCachedInputSource(id) {
+        let input_sources = getCurrentInputSources();
+        assert_less_than(id, input_sources.length);
+        cached_input_source = input_sources[id];
+      }
+
+      function validateAdded(length) {
+        assert_not_equals(lastAdded, null);
+        assert_equals(lastAdded.length, length,
+            "Added length matches expectations");
+
+        let currentSources = getCurrentInputSources();
+        lastAdded.forEach((source) => {
+          assert_true(currentSources.includes(source),
+            "Every element in added should be in the input source list");
+        });
+      }
+
+      function validateRemoved(length) {
+        assert_not_equals(lastRemoved, null);
+        assert_equals(lastRemoved.length, length,
+            "Removed length matches expectations");
+
+        let currentSources = getCurrentInputSources();
+        lastRemoved.forEach((source) => {
+          assert_false(currentSources.includes(source),
+            "No element in removed should be in the input source list");
+        });
+      }
+
+      function validateCachedAddedPresence(presence) {
+        assert_not_equals(lastAdded, null);
+        assert_not_equals(cached_input_source, null);
+        assert_equals(lastAdded.includes(cached_input_source), presence,
+          "Presence of cached input in lastAdded matches expectation");
+      }
+
+      function validateCachedRemovedPresence(presence) {
+        assert_not_equals(lastRemoved, null);
+        assert_not_equals(cached_input_source, null);
+        assert_equals(lastRemoved.includes(cached_input_source), presence,
+          "Presence of cached input in lastRemoved matches expectation");
+      }
+
+
+    </script>
+  </body>
+</html>
diff --git a/chromecast/media/cma/backend/media_pipeline_backend_for_mixer.cc b/chromecast/media/cma/backend/media_pipeline_backend_for_mixer.cc
index 8edb274..3370dcb 100644
--- a/chromecast/media/cma/backend/media_pipeline_backend_for_mixer.cc
+++ b/chromecast/media/cma/backend/media_pipeline_backend_for_mixer.cc
@@ -251,7 +251,7 @@
 }
 #elif defined(OS_FUCHSIA)
 int64_t MediaPipelineBackendForMixer::MonotonicClockNow() const {
-  return zx_clock_get(ZX_CLOCK_MONOTONIC) / 1000;
+  return zx_clock_get_monotonic() / 1000;
 }
 #endif
 
diff --git a/chromeos/network/proxy/proxy_config_service_impl.cc b/chromeos/network/proxy/proxy_config_service_impl.cc
index 3d64717..eaf4dbb1 100644
--- a/chromeos/network/proxy/proxy_config_service_impl.cc
+++ b/chromeos/network/proxy/proxy_config_service_impl.cc
@@ -271,11 +271,10 @@
   PrefProxyConfigTrackerImpl::OnProxyConfigChanged(effective_config_state,
                                                    effective_config);
   if (VLOG_IS_ON(1)) {
-    std::unique_ptr<base::DictionaryValue> config_dict(
-        effective_config.value().ToValue());
+    base::Value config_dict = effective_config.value().ToValue();
     VLOG(1) << this << ": Proxy changed: "
             << ProxyPrefs::ConfigStateToDebugString(effective_config_state)
-            << ", " << *config_dict;
+            << ", " << config_dict;
   }
 }
 
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index bd7b23f..5b70e1d 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -124,6 +124,7 @@
     "//ui/events",
     "//ui/events:dom_keycode_converter",
     "//ui/events/ozone:events_ozone_evdev",
+    "//ui/wm/public",
     "//url:url",
   ]
 }
diff --git a/components/autofill/core/browser/autofill_metrics.cc b/components/autofill/core/browser/autofill_metrics.cc
index 8f8695c..9cf11fd9 100644
--- a/components/autofill/core/browser/autofill_metrics.cc
+++ b/components/autofill/core/browser/autofill_metrics.cc
@@ -562,6 +562,13 @@
 const int kMaxBucketsCount = 50;
 
 // static
+void AutofillMetrics::LogProfileSuggestionsMadeWithFormatter(
+    bool made_with_formatter) {
+  UMA_HISTOGRAM_BOOLEAN("Autofill.ProfileSuggestionsMadeWithFormatter",
+                        made_with_formatter);
+}
+
+// static
 void AutofillMetrics::LogSubmittedCardStateMetric(
     SubmittedCardStateMetric metric) {
   DCHECK_LT(metric, NUM_SUBMITTED_CARD_STATE_METRICS);
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index c7373a8..904d04d 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -889,6 +889,11 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(UkmTimestampPin);
   };
 
+  // When the autofill-use-improved-label-disambiguation experiment is enabled
+  // and suggestions are available, records if a LabelFormatter successfully
+  // created the suggestions.
+  static void LogProfileSuggestionsMadeWithFormatter(bool made_with_formatter);
+
   static void LogSubmittedCardStateMetric(SubmittedCardStateMetric metric);
 
   // If a credit card that matches a server card (unmasked or not) was submitted
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc
index a059529..9d02f4b 100644
--- a/components/autofill/core/browser/personal_data_manager.cc
+++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -1170,10 +1170,11 @@
   std::unique_ptr<LabelFormatter> formatter;
 
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
+  bool use_improved_label_disambiguation = base::FeatureList::IsEnabled(
+      autofill::features::kAutofillUseImprovedLabelDisambiguation);
   // The formatter stores a constant reference to |unique_matched_profiles|.
   // This is safe since the formatter is destroyed when this function returns.
-  formatter = base::FeatureList::IsEnabled(
-                  autofill::features::kAutofillUseImprovedLabelDisambiguation)
+  formatter = use_improved_label_disambiguation
                   ? LabelFormatter::Create(unique_matched_profiles, app_locale_,
                                            type.GetStorableType(), field_types)
                   : nullptr;
@@ -1188,9 +1189,16 @@
                                           type.GetStorableType(), 1,
                                           app_locale_, &labels);
   }
-
   suggestion_selection::PrepareSuggestions(formatter != nullptr, labels,
                                            &unique_suggestions);
+
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+  if (use_improved_label_disambiguation && !unique_suggestions.empty()) {
+    AutofillMetrics::LogProfileSuggestionsMadeWithFormatter(formatter !=
+                                                            nullptr);
+  }
+#endif  // #if !defined(OS_ANDROID) && !defined(OS_IOS)
+
   return unique_suggestions;
 }
 
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc
index b0cc2cf..41e19dd 100644
--- a/components/autofill/core/browser/personal_data_manager_unittest.cc
+++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -2577,7 +2577,33 @@
 }
 
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
-TEST_F(PersonalDataManagerTest, GetProfileSuggestions_ContactForm) {
+TEST_F(PersonalDataManagerTest,
+       GetProfileSuggestions_LogProfileSuggestionsMadeWithFormatter) {
+  AutofillProfile profile(base::GenerateGUID(), test::kEmptyOrigin);
+  test::SetProfileInfo(&profile, "Hoa", "", "Pham", "hoa.pham@comcast.net", "",
+                       "401 Merrimack St", "", "Lowell", "MA", "01852", "US",
+                       "19786744120");
+  AddProfileToPersonalDataManager(profile);
+
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitAndEnableFeature(
+      features::kAutofillUseImprovedLabelDisambiguation);
+
+  base::HistogramTester histogram_tester;
+  EXPECT_THAT(
+      personal_data_->GetProfileSuggestions(
+          AutofillType(NAME_FIRST), base::string16(), false,
+          std::vector<ServerFieldType>{NAME_FIRST, NAME_LAST, EMAIL_ADDRESS,
+                                       PHONE_HOME_WHOLE_NUMBER}),
+      ElementsAre(
+          testing::Field(&Suggestion::value, base::ASCIIToUTF16("Hoa"))));
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.ProfileSuggestionsMadeWithFormatter", true, 1);
+}
+#endif  // #if !defined(OS_ANDROID) && !defined(OS_IOS)
+
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+TEST_F(PersonalDataManagerTest, GetProfileSuggestions_ForContactForm) {
   AutofillProfile profile(base::GenerateGUID(), test::kEmptyOrigin);
   test::SetProfileInfo(&profile, "Hoa", "", "Pham", "hoa.pham@comcast.net", "",
                        "401 Merrimack St", "", "Lowell", "MA", "01852", "US",
diff --git a/components/autofill/core/browser/ui/address_combobox_model.h b/components/autofill/core/browser/ui/address_combobox_model.h
index 738c337..dda2c0f 100644
--- a/components/autofill/core/browser/ui/address_combobox_model.h
+++ b/components/autofill/core/browser/ui/address_combobox_model.h
@@ -72,7 +72,7 @@
   std::string default_selected_guid_;
 
   // To be called when the data for the given country code was loaded.
-  base::ObserverList<ui::ComboboxModelObserver>::Unchecked observers_;
+  base::ObserverList<ui::ComboboxModelObserver> observers_;
 
   DISALLOW_COPY_AND_ASSIGN(AddressComboboxModel);
 };
diff --git a/components/autofill/core/browser/ui/region_combobox_model.h b/components/autofill/core/browser/ui/region_combobox_model.h
index 32a84fb..7e1018f 100644
--- a/components/autofill/core/browser/ui/region_combobox_model.h
+++ b/components/autofill/core/browser/ui/region_combobox_model.h
@@ -70,7 +70,7 @@
   std::vector<std::pair<std::string, std::string>> regions_;
 
   // To be called when the data for the given country code was loaded.
-  base::ObserverList<ui::ComboboxModelObserver>::Unchecked observers_;
+  base::ObserverList<ui::ComboboxModelObserver> observers_;
 
   DISALLOW_COPY_AND_ASSIGN(RegionComboboxModel);
 };
diff --git a/components/autofill_strings.grdp b/components/autofill_strings.grdp
index 5cb74fa3..6a6886e 100644
--- a/components/autofill_strings.grdp
+++ b/components/autofill_strings.grdp
@@ -251,4 +251,11 @@
     </else>
   </if>
 
+  <!-- Manual filling (keyboard accessory) strings -->
+  <if expr="is_android">
+    <message name="IDS_MANUAL_FILLING_CREDIT_CARD_SHEET_TITLE" desc="Title shown at the top of a sheet where users can select indiviual data pieces (e.g., the cardholder name) related to their stored payment methods (e.g., credit cards) to fill that data piece into the focused form field. Sentence-cased.">
+      Payment methods
+    </message>
+  </if>
+
 </grit-part>
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index dd6de25..0e1828a 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -427,6 +427,16 @@
   pending_scale_ = scale;
 }
 
+void ClientControlledShellSurface::CommitPendingScale() {
+  if (pending_scale_ != scale_) {
+    gfx::Transform transform;
+    DCHECK_NE(pending_scale_, 0.0);
+    transform.Scale(1.0 / pending_scale_, 1.0 / pending_scale_);
+    host_window()->SetTransform(transform);
+    scale_ = pending_scale_;
+  }
+}
+
 void ClientControlledShellSurface::SetTopInset(int height) {
   TRACE_EVENT1("exo", "ClientControlledShellSurface::SetTopInset", "height",
                height);
@@ -979,13 +989,7 @@
   }
 
   // Update surface scale.
-  if (pending_scale_ != scale_) {
-    gfx::Transform transform;
-    DCHECK_NE(pending_scale_, 0.0);
-    transform.Scale(1.0 / pending_scale_, 1.0 / pending_scale_);
-    host_window()->SetTransform(transform);
-    scale_ = pending_scale_;
-  }
+  CommitPendingScale();
 
   orientation_ = pending_orientation_;
   if (expected_orientation_ == orientation_)
diff --git a/components/exo/client_controlled_shell_surface.h b/components/exo/client_controlled_shell_surface.h
index 122856a..4247160 100644
--- a/components/exo/client_controlled_shell_surface.h
+++ b/components/exo/client_controlled_shell_surface.h
@@ -135,8 +135,13 @@
   // Set shadow bounds in surface coordinates. Empty bounds disable the shadow.
   void SetShadowBounds(const gfx::Rect& bounds);
 
+  // Set the pending scale.
   void SetScale(double scale);
 
+  // Commit the pending scale if it was changed. The scale set by SetScale() is
+  // otherwise committed by OnPostWidgetCommit().
+  void CommitPendingScale();
+
   // Set top inset for surface.
   void SetTopInset(int height);
 
@@ -225,6 +230,10 @@
 
   ash::WideFrameView* wide_frame_for_test() { return wide_frame_.get(); }
 
+  // Exposed for testing. Returns the effective scale as opposed to
+  // |pending_scale_|.
+  double scale() const { return scale_; }
+
  private:
   class ScopedSetBoundsLocally;
   class ScopedLockedToRoot;
diff --git a/components/exo/display.cc b/components/exo/display.cc
index 8337349..aad19dd 100644
--- a/components/exo/display.cc
+++ b/components/exo/display.cc
@@ -174,6 +174,7 @@
                                                      container));
   DCHECK_GE(default_device_scale_factor, 1.0);
   shell_surface->SetScale(default_device_scale_factor);
+  shell_surface->CommitPendingScale();
   return shell_surface;
 }
 
diff --git a/components/exo/display_unittest.cc b/components/exo/display_unittest.cc
index fafa4de..ea75532 100644
--- a/components/exo/display_unittest.cc
+++ b/components/exo/display_unittest.cc
@@ -120,11 +120,12 @@
   ASSERT_TRUE(surface2);
 
   // Create a remote shell surface for surface1.
-  std::unique_ptr<ShellSurfaceBase> shell_surface1 =
+  std::unique_ptr<ClientControlledShellSurface> shell_surface1 =
       display->CreateClientControlledShellSurface(
           surface1.get(), ash::kShellWindowId_SystemModalContainer,
           2.0 /* default_scale_factor */);
-  EXPECT_TRUE(shell_surface1);
+  ASSERT_TRUE(shell_surface1);
+  EXPECT_EQ(shell_surface1->scale(), 2.0);
 
   // Create a remote shell surface for surface2.
   std::unique_ptr<ShellSurfaceBase> shell_surface2 =
diff --git a/components/exo/test/run_all_unittests.cc b/components/exo/test/run_all_unittests.cc
index bc8f14b..0f1c677 100644
--- a/components/exo/test/run_all_unittests.cc
+++ b/components/exo/test/run_all_unittests.cc
@@ -26,7 +26,7 @@
   mojo::core::Init();
 #endif
 
-  return base::LaunchUnitTestsSerially(
+  return base::LaunchUnitTests(
       argc, argv,
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
 }
diff --git a/components/exo/wayland/BUILD.gn b/components/exo/wayland/BUILD.gn
index 41c700e..d501af23 100644
--- a/components/exo/wayland/BUILD.gn
+++ b/components/exo/wayland/BUILD.gn
@@ -220,6 +220,7 @@
       "//ui/compositor:test_support",
       "//ui/display",
       "//ui/gfx",
+      "//ui/wm/public",
     ]
   }
 }
diff --git a/components/feature_engagement/public/event_constants.cc b/components/feature_engagement/public/event_constants.cc
index 13b4484..ac1ebc2 100644
--- a/components/feature_engagement/public/event_constants.cc
+++ b/components/feature_engagement/public/event_constants.cc
@@ -24,6 +24,9 @@
 
 const char kReopenTabConditionsMet[] = "reopen_tab_conditions_met";
 const char kTabReopened[] = "tab_reopened";
+
+const char kFocusModeOpened[] = "focus_mode_opened";
+const char kFocusModeConditionsMet[] = "focus_mode_conditions_met";
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
 
 #if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_IOS)
diff --git a/components/feature_engagement/public/event_constants.h b/components/feature_engagement/public/event_constants.h
index 0f3ffef..18d1ccd 100644
--- a/components/feature_engagement/public/event_constants.h
+++ b/components/feature_engagement/public/event_constants.h
@@ -47,6 +47,14 @@
 extern const char kReopenTabConditionsMet[];
 // The user reopened a previously closed tab.
 extern const char kTabReopened[];
+
+// All the events declared below are the string names of deferred onboarding
+// events for the Focus Mode feature.
+
+// The user has opened a Focus Mode window.
+extern const char kFocusModeOpened[];
+// All conditions for show Focus Mode IPH were met.
+extern const char kFocusModeConditionsMet[];
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
 
 #if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_IOS)
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 278f3b1..1990bf0 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -76,6 +76,8 @@
                                       base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHReopenTabFeature{"IPH_ReopenTab",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kIPHFocusModeFeature{"IPH_FocusMode",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IPH)
 
 #if defined(OS_IOS)
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 25c3ba0..67c8142 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -53,6 +53,7 @@
 extern const base::Feature kIPHIncognitoWindowFeature;
 extern const base::Feature kIPHNewTabFeature;
 extern const base::Feature kIPHReopenTabFeature;
+extern const base::Feature kIPHFocusModeFeature;
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IPH)
 
 #if defined(OS_IOS)
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index 0647dda..bac3721 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -48,6 +48,7 @@
     &kIPHIncognitoWindowFeature,
     &kIPHNewTabFeature,
     &kIPHReopenTabFeature,
+    &kIPHFocusModeFeature,
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
 #if defined(OS_IOS)
     &kIPHBottomToolbarTipFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index ff0d63a..979f5f4 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -92,6 +92,7 @@
 DEFINE_VARIATION_PARAM(kIPHIncognitoWindowFeature, "IPH_IncognitoWindow");
 DEFINE_VARIATION_PARAM(kIPHNewTabFeature, "IPH_NewTab");
 DEFINE_VARIATION_PARAM(kIPHReopenTabFeature, "IPH_ReopenTab");
+DEFINE_VARIATION_PARAM(kIPHFocusModeFeature, "IPH_FocusMode");
 #endif  // BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
 #if defined(OS_IOS)
 DEFINE_VARIATION_PARAM(kIPHBottomToolbarTipFeature, "IPH_BottomToolbarTip");
@@ -142,6 +143,7 @@
         VARIATION_ENTRY(kIPHIncognitoWindowFeature),
         VARIATION_ENTRY(kIPHNewTabFeature),
         VARIATION_ENTRY(kIPHReopenTabFeature),
+        VARIATION_ENTRY(kIPHFocusModeFeature),
 #elif defined(OS_IOS)
         VARIATION_ENTRY(kIPHBottomToolbarTipFeature),
         VARIATION_ENTRY(kIPHLongPressToolbarTipFeature),
diff --git a/components/gwp_asan/crash_handler/crash_analyzer.cc b/components/gwp_asan/crash_handler/crash_analyzer.cc
index 929ae1d..3cf146b 100644
--- a/components/gwp_asan/crash_handler/crash_analyzer.cc
+++ b/components/gwp_asan/crash_handler/crash_analyzer.cc
@@ -28,6 +28,30 @@
 namespace gwp_asan {
 namespace internal {
 
+namespace {
+
+// Report failure for a particular allocator's histogram.
+void ReportHistogram(Crash_Allocator allocator,
+                     GwpAsanCrashAnalysisResult result) {
+  DCHECK_LE(result, GwpAsanCrashAnalysisResult::kMaxValue);
+
+  switch (allocator) {
+    case Crash_Allocator_MALLOC:
+      UMA_HISTOGRAM_ENUMERATION("GwpAsan.CrashAnalysisResult.Malloc", result);
+      break;
+
+    case Crash_Allocator_PARTITIONALLOC:
+      UMA_HISTOGRAM_ENUMERATION("GwpAsan.CrashAnalysisResult.PartitionAlloc",
+                                result);
+      break;
+
+    default:
+      DCHECK(false) << "Unknown allocator value!";
+  }
+}
+
+}  // namespace
+
 using GetMetadataReturnType = AllocatorState::GetMetadataReturnType;
 
 bool CrashAnalyzer::GetExceptionInfo(
@@ -75,6 +99,7 @@
 bool CrashAnalyzer::GetAllocatorState(
     const crashpad::ProcessSnapshot& process_snapshot,
     const char* crash_key,
+    Crash_Allocator allocator,
     AllocatorState* state) {
   crashpad::VMAddress gpa_addr =
       GetAllocatorAddress(process_snapshot, crash_key);
@@ -89,7 +114,8 @@
 
   if (!exception->Context()) {
     DLOG(ERROR) << "Missing crash CPU context information.";
-    ReportHistogram(GwpAsanCrashAnalysisResult::kErrorNullCpuContext);
+    ReportHistogram(allocator,
+                    GwpAsanCrashAnalysisResult::kErrorNullCpuContext);
     return false;
   }
 
@@ -103,26 +129,30 @@
   // state bitness-independently.
   if (exception->Context()->Is64Bit() != is_64_bit) {
     DLOG(ERROR) << "Mismatched process bitness.";
-    ReportHistogram(GwpAsanCrashAnalysisResult::kErrorMismatchedBitness);
+    ReportHistogram(allocator,
+                    GwpAsanCrashAnalysisResult::kErrorMismatchedBitness);
     return false;
   }
 
   const crashpad::ProcessMemory* memory = process_snapshot.Memory();
   if (!memory) {
     DLOG(ERROR) << "Null ProcessMemory.";
-    ReportHistogram(GwpAsanCrashAnalysisResult::kErrorNullProcessMemory);
+    ReportHistogram(allocator,
+                    GwpAsanCrashAnalysisResult::kErrorNullProcessMemory);
     return false;
   }
 
   if (!memory->Read(gpa_addr, sizeof(*state), state)) {
     DLOG(ERROR) << "Failed to read AllocatorState from process.";
-    ReportHistogram(GwpAsanCrashAnalysisResult::kErrorFailedToReadAllocator);
+    ReportHistogram(allocator,
+                    GwpAsanCrashAnalysisResult::kErrorFailedToReadAllocator);
     return false;
   }
 
   if (!state->IsValid()) {
     DLOG(ERROR) << "Allocator sanity check failed!";
     ReportHistogram(
+        allocator,
         GwpAsanCrashAnalysisResult::kErrorAllocatorFailedSanityCheck);
     return false;
   }
@@ -136,7 +166,7 @@
     Crash_Allocator allocator,
     gwp_asan::Crash* proto) {
   AllocatorState valid_state;
-  if (!GetAllocatorState(process_snapshot, crash_key, &valid_state))
+  if (!GetAllocatorState(process_snapshot, crash_key, allocator, &valid_state))
     return false;
 
   crashpad::VMAddress exception_addr =
@@ -169,7 +199,8 @@
           sizeof(AllocatorState::SlotMetadata) * valid_state.num_metadata,
           metadata_arr.get())) {
     proto->set_internal_error("Failed to read metadata.");
-    ReportHistogram(GwpAsanCrashAnalysisResult::kErrorFailedToReadSlotMetadata);
+    ReportHistogram(allocator,
+                    GwpAsanCrashAnalysisResult::kErrorFailedToReadSlotMetadata);
     return true;
   }
 
@@ -182,6 +213,7 @@
           slot_to_metadata.get())) {
     proto->set_internal_error("Failed to read slot_to_metadata.");
     ReportHistogram(
+        allocator,
         GwpAsanCrashAnalysisResult::kErrorFailedToReadSlotMetadataMapping);
     return true;
   }
@@ -192,11 +224,13 @@
       exception_addr, metadata_arr.get(), slot_to_metadata.get(), &metadata_idx,
       &error);
   if (ret == GetMetadataReturnType::kErrorBadSlot)
-    ReportHistogram(GwpAsanCrashAnalysisResult::kErrorBadSlot);
+    ReportHistogram(allocator, GwpAsanCrashAnalysisResult::kErrorBadSlot);
   if (ret == GetMetadataReturnType::kErrorBadMetadataIndex)
-    ReportHistogram(GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex);
+    ReportHistogram(allocator,
+                    GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex);
   if (ret == GetMetadataReturnType::kErrorOutdatedMetadataIndex)
-    ReportHistogram(GwpAsanCrashAnalysisResult::kErrorOutdatedMetadataIndex);
+    ReportHistogram(allocator,
+                    GwpAsanCrashAnalysisResult::kErrorOutdatedMetadataIndex);
   if (!error.empty()) {
     proto->set_internal_error(error);
     return true;
@@ -259,9 +293,5 @@
     output[i] = unpacked_stack_trace[i];
 }
 
-void CrashAnalyzer::ReportHistogram(GwpAsanCrashAnalysisResult result) {
-  UMA_HISTOGRAM_ENUMERATION(kCrashAnalysisHistogram, result);
-}
-
 }  // namespace internal
 }  // namespace gwp_asan
diff --git a/components/gwp_asan/crash_handler/crash_analyzer.h b/components/gwp_asan/crash_handler/crash_analyzer.h
index 22e94f5..726d5c3 100644
--- a/components/gwp_asan/crash_handler/crash_analyzer.h
+++ b/components/gwp_asan/crash_handler/crash_analyzer.h
@@ -7,7 +7,6 @@
 
 #include <stddef.h>
 
-#include "base/gtest_prod_util.h"
 #include "components/gwp_asan/common/allocator_state.h"
 #include "components/gwp_asan/crash_handler/crash.pb.h"
 #include "third_party/crashpad/crashpad/util/misc/address_types.h"
@@ -21,6 +20,43 @@
 namespace gwp_asan {
 namespace internal {
 
+// Captures the result of the GWP-ASan crash analyzer, whether the crash is
+// determined to be related or unrelated to GWP-ASan or if an error was
+// encountered analyzing the exception.
+//
+// These values are persisted via UMA--entries should not be renumbered and
+// numeric values should never be reused.
+enum class GwpAsanCrashAnalysisResult {
+  // The crash is not caused by GWP-ASan.
+  kUnrelatedCrash = 0,
+  // The crash is caused by GWP-ASan.
+  kGwpAsanCrash = 1,
+  // The ProcessMemory from the snapshot was null.
+  kErrorNullProcessMemory = 2,
+  // Failed to read the crashing process' memory of the global allocator.
+  kErrorFailedToReadAllocator = 3,
+  // The crashing process' global allocator members failed sanity checks.
+  kErrorAllocatorFailedSanityCheck = 4,
+  // Failed to read crash stack traces.
+  kErrorFailedToReadStackTrace = 5,
+  // The ExceptionSnapshot CPU context was null.
+  kErrorNullCpuContext = 6,
+  // The crashing process' bitness does not match the crash handler.
+  kErrorMismatchedBitness = 7,
+  // The allocator computed an invalid slot index.
+  kErrorBadSlot = 8,
+  // Failed to read the crashing process' memory of the SlotMetadata.
+  kErrorFailedToReadSlotMetadata = 9,
+  // The allocator stored an invalid metadata index for a given slot.
+  kErrorBadMetadataIndex = 10,
+  // The computed metadata index was outdated.
+  kErrorOutdatedMetadataIndex = 11,
+  // Failed to read the crashing process' slot to metadata mapping.
+  kErrorFailedToReadSlotMetadataMapping = 12,
+  // Number of values in this enumeration, required by UMA.
+  kMaxValue = kErrorFailedToReadSlotMetadataMapping
+};
+
 class CrashAnalyzer {
  public:
   // Given a ProcessSnapshot, determine if the exception is related to GWP-ASan.
@@ -33,46 +69,6 @@
  private:
   using SlotMetadata = AllocatorState::SlotMetadata;
 
-  static constexpr const char* kCrashAnalysisHistogram =
-      "GwpAsan.CrashAnalysisResult";
-
-  // Captures the result of the GWP-ASan crash analyzer, whether the crash is
-  // determined to be related or unrelated to GWP-ASan or if an error was
-  // encountered analyzing the exception.
-  //
-  // These values are persisted via UMA--entries should not be renumbered and
-  // numeric values should never be reused.
-  enum class GwpAsanCrashAnalysisResult {
-    // The crash is not caused by GWP-ASan.
-    kUnrelatedCrash = 0,
-    // The crash is caused by GWP-ASan.
-    kGwpAsanCrash = 1,
-    // The ProcessMemory from the snapshot was null.
-    kErrorNullProcessMemory = 2,
-    // Failed to read the crashing process' memory of the global allocator.
-    kErrorFailedToReadAllocator = 3,
-    // The crashing process' global allocator members failed sanity checks.
-    kErrorAllocatorFailedSanityCheck = 4,
-    // Failed to read crash stack traces.
-    kErrorFailedToReadStackTrace = 5,
-    // The ExceptionSnapshot CPU context was null.
-    kErrorNullCpuContext = 6,
-    // The crashing process' bitness does not match the crash handler.
-    kErrorMismatchedBitness = 7,
-    // The allocator computed an invalid slot index.
-    kErrorBadSlot = 8,
-    // Failed to read the crashing process' memory of the SlotMetadata.
-    kErrorFailedToReadSlotMetadata = 9,
-    // The allocator stored an invalid metadata index for a given slot.
-    kErrorBadMetadataIndex = 10,
-    // The computed metadata index was outdated.
-    kErrorOutdatedMetadataIndex = 11,
-    // Failed to read the crashing process' slot to metadata mapping.
-    kErrorFailedToReadSlotMetadataMapping = 12,
-    // Number of values in this enumeration, required by UMA.
-    kMaxValue = kErrorFailedToReadSlotMetadataMapping
-  };
-
   // Given an ExceptionSnapshot, return the address of where the exception
   // occurred (or null if it was not a data access exception.)
   static crashpad::VMAddress GetAccessAddress(
@@ -89,6 +85,7 @@
   static bool GetAllocatorState(
       const crashpad::ProcessSnapshot& process_snapshot,
       const char* crash_key,
+      Crash_Allocator allocator,
       AllocatorState* state);
 
   // This method implements the underlying logic for GetExceptionInfo(). It
@@ -106,12 +103,6 @@
                                  size_t stack_trace_offset,
                                  const SlotMetadata::AllocationInfo& slot_info,
                                  gwp_asan::Crash_AllocationInfo* proto_info);
-
-  // Report a GWP-ASan crash analysis result via UMA.
-  static void ReportHistogram(GwpAsanCrashAnalysisResult analysis_result);
-
-  FRIEND_TEST_ALL_PREFIXES(CrashAnalyzerTest, InternalError);
-  FRIEND_TEST_ALL_PREFIXES(CrashAnalyzerTest, StackTraceCollection);
 };
 
 }  // namespace internal
diff --git a/components/gwp_asan/crash_handler/crash_analyzer_unittest.cc b/components/gwp_asan/crash_handler/crash_analyzer_unittest.cc
index 64ae56b0..54061ae 100644
--- a/components/gwp_asan/crash_handler/crash_analyzer_unittest.cc
+++ b/components/gwp_asan/crash_handler/crash_analyzer_unittest.cc
@@ -31,6 +31,15 @@
 namespace gwp_asan {
 namespace internal {
 
+namespace {
+
+constexpr const char* kMallocHistogramName =
+    "GwpAsan.CrashAnalysisResult.Malloc";
+constexpr const char* kPartitionAllocHistogramName =
+    "GwpAsan.CrashAnalysisResult.PartitionAlloc";
+
+}  // namespace
+
 class CrashAnalyzerTest : public testing::Test {
  protected:
   void SetUp() final {
@@ -90,7 +99,8 @@
       CrashAnalyzer::GetExceptionInfo(process_snapshot_, &proto);
   ASSERT_TRUE(proto_present);
 
-  histogram_tester.ExpectTotalCount(CrashAnalyzer::kCrashAnalysisHistogram, 0);
+  histogram_tester.ExpectTotalCount(kMallocHistogramName, 0);
+  histogram_tester.ExpectTotalCount(kPartitionAllocHistogramName, 0);
 
   ASSERT_TRUE(proto.has_allocation());
   ASSERT_TRUE(proto.has_deallocation());
@@ -146,11 +156,11 @@
       CrashAnalyzer::GetExceptionInfo(process_snapshot_, &proto);
   ASSERT_TRUE(proto_present);
 
-  int result = static_cast<int>(
-      CrashAnalyzer::GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex);
-  EXPECT_THAT(
-      histogram_tester.GetAllSamples(CrashAnalyzer::kCrashAnalysisHistogram),
-      testing::ElementsAre(base::Bucket(result, 1)));
+  int result =
+      static_cast<int>(GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex);
+  EXPECT_THAT(histogram_tester.GetAllSamples(kMallocHistogramName),
+              testing::ElementsAre(base::Bucket(result, 1)));
+  histogram_tester.ExpectTotalCount(kPartitionAllocHistogramName, 0);
 
   EXPECT_TRUE(proto.has_internal_error());
   ASSERT_TRUE(proto.has_missing_metadata());
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index cc39838..36f811f 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -93,6 +93,68 @@
   }
 }
 
+// Background and implementation details:
+//
+// Each experiment group in any field trial can come with an optional set of
+// parameters (key-value pairs).  In the bundled omnibox experiment
+// (kBundledExperimentFieldTrialName), each experiment group comes with a
+// list of parameters in the form:
+//   key=<Rule>:
+//       <OmniboxEventProto::PageClassification (as an int)>:
+//       <whether Instant Extended is enabled (as a 1 or 0)>
+//     (note that there are no linebreaks in keys; this format is for
+//      presentation only>
+//   value=<arbitrary string>
+// Both the OmniboxEventProto::PageClassification and the Instant Extended
+// entries can be "*", which means this rule applies for all values of the
+// matching portion of the context.
+// One example parameter is
+//   key=SearchHistory:6:1
+//   value=PreventInlining
+// This means in page classification context 6 (a search result page doing
+// search term replacement) with Instant Extended enabled, the SearchHistory
+// experiment should PreventInlining.
+//
+// When an exact match to the rule in the current context is missing, we
+// give preference to a wildcard rule that matches the instant extended
+// context over a wildcard rule that matches the page classification
+// context.  Hopefully, though, users will write their field trial configs
+// so as not to rely on this fall back order.
+//
+// In short, this function tries to find the value associated with key
+// |rule|:|page_classification|:|instant_extended|, failing that it looks up
+// |rule|:*:|instant_extended|, failing that it looks up
+// |rule|:|page_classification|:*, failing that it looks up |rule|:*:*,
+// and failing that it returns the empty string.
+std::string GetValueForRuleInContextFromVariationParams(
+    const std::map<std::string, std::string>& params,
+    const std::string& rule,
+    OmniboxEventProto::PageClassification page_classification) {
+  if (params.empty())
+    return std::string();
+
+  const std::string page_classification_str =
+      base::NumberToString(static_cast<int>(page_classification));
+  const std::string instant_extended =
+      search::IsInstantExtendedAPIEnabled() ? "1" : "0";
+  // Look up rule in this exact context.
+  VariationParams::const_iterator it = params.find(
+      rule + ":" + page_classification_str + ":" + instant_extended);
+  if (it != params.end())
+    return it->second;
+  // Fall back to the global page classification context.
+  it = params.find(rule + ":*:" + instant_extended);
+  if (it != params.end())
+    return it->second;
+  // Fall back to the global instant extended context.
+  it = params.find(rule + ":" + page_classification_str + ":*");
+  if (it != params.end())
+    return it->second;
+  // Look up rule in the global context.
+  it = params.find(rule + ":*:*");
+  return (it != params.end()) ? it->second : std::string();
+}
+
 }  // namespace
 
 HUPScoringParams::ScoreBuckets::ScoreBuckets()
@@ -163,15 +225,14 @@
 
 bool OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial() {
   return InZeroSuggestMostVisitedWithoutSerpFieldTrial() ||
-         variations::GetVariationParamValue(kBundledExperimentFieldTrialName,
-                                            kZeroSuggestVariantRule) ==
+         base::GetFieldTrialParamValueByFeature(omnibox::kOnFocusSuggestions,
+                                                kZeroSuggestVariantRule) ==
              "MostVisited";
 }
 
 bool OmniboxFieldTrial::InZeroSuggestMostVisitedWithoutSerpFieldTrial() {
-  std::string variant(variations::GetVariationParamValue(
-      kBundledExperimentFieldTrialName,
-      kZeroSuggestVariantRule));
+  std::string variant = base::GetFieldTrialParamValueByFeature(
+      omnibox::kOnFocusSuggestions, kZeroSuggestVariantRule);
   if (variant == "MostVisitedWithoutSERP")
     return true;
 #if defined(OS_ANDROID)
@@ -187,8 +248,8 @@
 
 // static
 bool OmniboxFieldTrial::InZeroSuggestPersonalizedFieldTrial() {
-  return variations::GetVariationParamValue(kBundledExperimentFieldTrialName,
-                                            kZeroSuggestVariantRule) ==
+  return base::GetFieldTrialParamValueByFeature(omnibox::kOnFocusSuggestions,
+                                                kZeroSuggestVariantRule) ==
          "Personalized";
 }
 
@@ -695,65 +756,25 @@
 // static
 int OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs = 100;
 
-// Background and implementation details:
-//
-// Each experiment group in any field trial can come with an optional set of
-// parameters (key-value pairs).  In the bundled omnibox experiment
-// (kBundledExperimentFieldTrialName), each experiment group comes with a
-// list of parameters in the form:
-//   key=<Rule>:
-//       <OmniboxEventProto::PageClassification (as an int)>:
-//       <whether Instant Extended is enabled (as a 1 or 0)>
-//     (note that there are no linebreaks in keys; this format is for
-//      presentation only>
-//   value=<arbitrary string>
-// Both the OmniboxEventProto::PageClassification and the Instant Extended
-// entries can be "*", which means this rule applies for all values of the
-// matching portion of the context.
-// One example parameter is
-//   key=SearchHistory:6:1
-//   value=PreventInlining
-// This means in page classification context 6 (a search result page doing
-// search term replacement) with Instant Extended enabled, the SearchHistory
-// experiment should PreventInlining.
-//
-// When an exact match to the rule in the current context is missing, we
-// give preference to a wildcard rule that matches the instant extended
-// context over a wildcard rule that matches the page classification
-// context.  Hopefully, though, users will write their field trial configs
-// so as not to rely on this fall back order.
-//
-// In short, this function tries to find the value associated with key
-// |rule|:|page_classification|:|instant_extended|, failing that it looks up
-// |rule|:*:|instant_extended|, failing that it looks up
-// |rule|:|page_classification|:*, failing that it looks up |rule|:*:*,
-// and failing that it returns the empty string.
 std::string OmniboxFieldTrial::internal::GetValueForRuleInContext(
     const std::string& rule,
     OmniboxEventProto::PageClassification page_classification) {
   VariationParams params;
-  if (!variations::GetVariationParams(kBundledExperimentFieldTrialName,
-                                      &params)) {
+  if (!base::GetFieldTrialParams(kBundledExperimentFieldTrialName, &params))
     return std::string();
-  }
-  const std::string page_classification_str =
-      base::NumberToString(static_cast<int>(page_classification));
-  const std::string instant_extended =
-      search::IsInstantExtendedAPIEnabled() ? "1" : "0";
-  // Look up rule in this exact context.
-  VariationParams::const_iterator it = params.find(
-      rule + ":" + page_classification_str + ":" + instant_extended);
-  if (it != params.end())
-    return it->second;
-  // Fall back to the global page classification context.
-  it = params.find(rule + ":*:" + instant_extended);
-  if (it != params.end())
-    return it->second;
-  // Fall back to the global instant extended context.
-  it = params.find(rule + ":" + page_classification_str + ":*");
-  if (it != params.end())
-    return it->second;
-  // Look up rule in the global context.
-  it = params.find(rule + ":*:*");
-  return (it != params.end()) ? it->second : std::string();
+
+  return GetValueForRuleInContextFromVariationParams(params, rule,
+                                                     page_classification);
+}
+
+std::string OmniboxFieldTrial::internal::GetValueForRuleInContextByFeature(
+    const base::Feature& feature,
+    const std::string& rule,
+    metrics::OmniboxEventProto::PageClassification page_classification) {
+  VariationParams params;
+  if (!base::GetFieldTrialParamsByFeature(feature, &params))
+    return std::string();
+
+  return GetValueForRuleInContextFromVariationParams(params, rule,
+                                                     page_classification);
 }
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h
index 64d48ec..1a0da7d 100644
--- a/components/omnibox/browser/omnibox_field_trial.h
+++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -21,6 +21,7 @@
 
 namespace base {
 class TimeDelta;
+struct Feature;
 }
 
 // The set of parameters customizing the HUP scoring.
@@ -506,9 +507,21 @@
 // context, returns the empty string.  For more details, including how we
 // prioritize different wildcard contexts, see the implementation.  How to
 // interpret the value is left to the caller; this is rule-dependent.
+//
+// Deprecated. Use GetValueForRuleInContextByFeature instead.
 std::string GetValueForRuleInContext(
     const std::string& rule,
     metrics::OmniboxEventProto::PageClassification page_classification);
+
+// Same as GetValueForRuleInContext, but by |feature| instead of the bundled
+// omnibox experiment.  Prefer to use this method over GetValueForRuleInContext
+// when possible, as it can be useful to configure parameters outside of the
+// omnibox bundled experiment.
+std::string GetValueForRuleInContextByFeature(
+    const base::Feature& feature,
+    const std::string& rule,
+    metrics::OmniboxEventProto::PageClassification page_classification);
+
 }  // namespace internal
 
 }  // namespace OmniboxFieldTrial
diff --git a/components/omnibox/browser/omnibox_field_trial_unittest.cc b/components/omnibox/browser/omnibox_field_trial_unittest.cc
index 1a43850..dade926 100644
--- a/components/omnibox/browser/omnibox_field_trial_unittest.cc
+++ b/components/omnibox/browser/omnibox_field_trial_unittest.cc
@@ -11,7 +11,9 @@
 #include "base/macros.h"
 #include "base/metrics/field_trial.h"
 #include "base/strings/string16.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "components/omnibox/common/omnibox_features.h"
 #include "components/search/search.h"
 #include "components/variations/entropy_provider.h"
 #include "components/variations/variations_associated_data.h"
@@ -178,41 +180,22 @@
 // Test if InZeroSuggestFieldTrial*() properly parses various field trial
 // group names.
 TEST_F(OmniboxFieldTrialTest, ZeroSuggestFieldTrial) {
-  {
-    SCOPED_TRACE("Bundled field trial parameters.");
-    ResetFieldTrialList();
-    std::map<std::string, std::string> params;
-    ASSERT_TRUE(variations::AssociateVariationParams(
-        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
-    base::FieldTrialList::CreateFieldTrial(
-        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
+  ResetFieldTrialList();
 #if defined(OS_ANDROID) || defined(OS_IOS)
-    EXPECT_TRUE(OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial());
+  EXPECT_TRUE(OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial());
 #else
-    EXPECT_FALSE(OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial());
+  EXPECT_FALSE(OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial());
 #endif
 
-    ResetFieldTrialList();
-    params[std::string(OmniboxFieldTrial::kZeroSuggestVariantRule)] =
-        "MostVisited";
-    ASSERT_TRUE(variations::AssociateVariationParams(
-        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
-    base::FieldTrialList::CreateFieldTrial(
-        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
-    EXPECT_TRUE(OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial());
+  ResetFieldTrialList();
+  // ScopedFeatureList reuses the global field trial list already defined
+  // within ResetFieldTrialList.
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      omnibox::kOnFocusSuggestions,
+      {{OmniboxFieldTrial::kZeroSuggestVariantRule, "MostVisited"}});
 
-    ResetFieldTrialList();
-    params.erase(std::string(OmniboxFieldTrial::kZeroSuggestVariantRule));
-    base::FieldTrialList::CreateFieldTrial(
-        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
-    ASSERT_TRUE(variations::AssociateVariationParams(
-        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
-#if defined(OS_ANDROID) || defined(OS_IOS)
-    EXPECT_TRUE(OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial());
-#else
-    EXPECT_FALSE(OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial());
-#endif
-  }
+  EXPECT_TRUE(OmniboxFieldTrial::InZeroSuggestMostVisitedFieldTrial());
 }
 
 TEST_F(OmniboxFieldTrialTest, GetDemotionsByTypeWithFallback) {
diff --git a/components/omnibox/browser/zero_suggest_provider_unittest.cc b/components/omnibox/browser/zero_suggest_provider_unittest.cc
index 6ce4583..7122fd3 100644
--- a/components/omnibox/browser/zero_suggest_provider_unittest.cc
+++ b/components/omnibox/browser/zero_suggest_provider_unittest.cc
@@ -141,7 +141,7 @@
 class ZeroSuggestProviderTest : public testing::Test,
                                 public AutocompleteProviderListener {
  public:
-  ZeroSuggestProviderTest();
+  ZeroSuggestProviderTest() = default;
 
   void SetUp() override;
 
@@ -149,16 +149,12 @@
   // AutocompleteProviderListener:
   void OnProviderUpdate(bool updated_matches) override;
 
-  void ResetFieldTrialList();
-
   void CreatePersonalizedFieldTrial();
   void CreateMostVisitedFieldTrial();
   void CreateContextualSuggestFieldTrial();
 
   base::test::ScopedTaskEnvironment scoped_task_environment_;
-
-  // Needed for OmniboxFieldTrial::ActivateStaticTrials().
-  std::unique_ptr<base::FieldTrialList> field_trial_list_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 
   std::unique_ptr<FakeAutocompleteProviderClient> client_;
   scoped_refptr<ZeroSuggestProvider> provider_;
@@ -172,10 +168,6 @@
   DISALLOW_COPY_AND_ASSIGN(ZeroSuggestProviderTest);
 };
 
-ZeroSuggestProviderTest::ZeroSuggestProviderTest() {
-  ResetFieldTrialList();
-}
-
 void ZeroSuggestProviderTest::SetUp() {
   client_.reset(new FakeAutocompleteProviderClient());
 
@@ -195,43 +187,22 @@
 void ZeroSuggestProviderTest::OnProviderUpdate(bool updated_matches) {
 }
 
-void ZeroSuggestProviderTest::ResetFieldTrialList() {
-  // Destroy the existing FieldTrialList before creating a new one to avoid
-  // a DCHECK.
-  field_trial_list_.reset();
-  field_trial_list_.reset(new base::FieldTrialList(
-      std::make_unique<variations::SHA1EntropyProvider>("foo")));
-  variations::testing::ClearAllVariationParams();
-}
-
 void ZeroSuggestProviderTest::CreatePersonalizedFieldTrial() {
-  std::map<std::string, std::string> params;
-  params[std::string(OmniboxFieldTrial::kZeroSuggestVariantRule)] =
-      "Personalized";
-  variations::AssociateVariationParams(
-      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params);
-  base::FieldTrialList::CreateFieldTrial(
-      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
+  scoped_feature_list_.InitAndEnableFeatureWithParameters(
+      omnibox::kOnFocusSuggestions,
+      {{OmniboxFieldTrial::kZeroSuggestVariantRule, "Personalized"}});
 }
 
 void ZeroSuggestProviderTest::CreateMostVisitedFieldTrial() {
-  std::map<std::string, std::string> params;
-  params[std::string(OmniboxFieldTrial::kZeroSuggestVariantRule)] =
-      "MostVisitedWithoutSERP";
-  variations::AssociateVariationParams(
-      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params);
-  base::FieldTrialList::CreateFieldTrial(
-      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
+  scoped_feature_list_.InitAndEnableFeatureWithParameters(
+      omnibox::kOnFocusSuggestions,
+      {{OmniboxFieldTrial::kZeroSuggestVariantRule, "MostVisitedWithoutSERP"}});
 }
 
 void ZeroSuggestProviderTest::CreateContextualSuggestFieldTrial() {
-  std::map<std::string, std::string> params;
-  params[std::string(OmniboxFieldTrial::kZeroSuggestVariantRule)] =
-      "ContextualSuggestions";
-  variations::AssociateVariationParams(
-      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params);
-  base::FieldTrialList::CreateFieldTrial(
-      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
+  scoped_feature_list_.InitAndEnableFeatureWithParameters(
+      omnibox::kOnFocusSuggestions,
+      {{OmniboxFieldTrial::kZeroSuggestVariantRule, "ContextualSuggestions"}});
 }
 
 TEST_F(ZeroSuggestProviderTest, TestDoesNotReturnMatchesForPrefix) {
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index f18ec04..7821bc4 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -175,20 +175,6 @@
 #endif
 };
 
-// Feature used for the Zero Suggest Redirect to Chrome Field Trial.
-//
-// This feature is *enabled* in order to *disable* all forms of suggestions
-// based on the URL on-focus (whether from "redirect to Chrome" or the
-// default suggest server).  The actual disabling of redirect to Chrome
-// suggestions happens in contextual_suggestions_service.cc.  See comments
-// by kDefaultExperimentalServerAddress.
-//
-// If this feature were not enabled, Chrome would use the default suggest
-// server for suggestions based on the current URL on focus.  There is no
-// code in Chrome to disable that, so that why we took this route.
-const base::Feature kZeroSuggestRedirectToChrome{
-    "ZeroSuggestRedirectToChrome", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Feature used to display the title of the current URL match.
 const base::Feature kDisplayTitleForCurrentUrl{
   "OmniboxDisplayTitleForCurrentUrl",
@@ -334,6 +320,26 @@
 const base::Feature kOmniboxMaterialDesignWeatherIcons{
     "OmniboxMaterialDesignWeatherIcons", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Feature to configure on-focus suggestions provided by ZeroSuggestProvider.
+// This feature's main job is to contain the "ZeroSuggestVariant" field trial
+// parameter, which configures the global mode of ZeroSuggestProvider.
+const base::Feature kOnFocusSuggestions{"OmniboxOnFocusSuggestions",
+                                        base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Feature used for the Zero Suggest Redirect to Chrome Field Trial.
+//
+// This feature is *enabled* in order to *disable* all forms of suggestions
+// based on the URL on-focus (whether from "redirect to Chrome" or the
+// default suggest server).  The actual disabling of redirect to Chrome
+// suggestions happens in contextual_suggestions_service.cc.  See comments
+// by kDefaultExperimentalServerAddress.
+//
+// If this feature were not enabled, Chrome would use the default suggest
+// server for suggestions based on the current URL on focus.  There is no
+// code in Chrome to disable that, so that why we took this route.
+const base::Feature kZeroSuggestRedirectToChrome{
+    "ZeroSuggestRedirectToChrome", base::FEATURE_ENABLED_BY_DEFAULT};
+
 // Allow suggestions to be shown to the user on the New Tab Page upon focusing
 // URL bar (the omnibox).
 const base::Feature kZeroSuggestionsOnNTP{"OmniboxZeroSuggestionsOnNTP",
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index 699398a..2465068 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -33,7 +33,6 @@
 extern const base::Feature kEnableClipboardProviderTextSuggestions;
 extern const base::Feature kEnableClipboardProviderImageSuggestions;
 extern const base::Feature kSearchProviderWarmUpOnFocus;
-extern const base::Feature kZeroSuggestRedirectToChrome;
 extern const base::Feature kDisplayTitleForCurrentUrl;
 extern const base::Feature kQueryInOmnibox;
 extern const base::Feature kUIExperimentMaxAutocompleteMatches;
@@ -55,6 +54,10 @@
 extern const base::Feature kDedupeGoogleDriveURLs;
 extern const base::Feature kOmniboxPopupShortcutIconsInZeroState;
 extern const base::Feature kOmniboxMaterialDesignWeatherIcons;
+
+// On-Focus Suggestions a.k.a. ZeroSuggest.
+extern const base::Feature kOnFocusSuggestions;
+extern const base::Feature kZeroSuggestRedirectToChrome;
 extern const base::Feature kZeroSuggestionsOnNTP;
 
 }  // namespace omnibox
diff --git a/components/password_manager/content/browser/content_password_manager_driver.cc b/components/password_manager/content/browser/content_password_manager_driver.cc
index 56849f06..49b25ae 100644
--- a/components/password_manager/content/browser/content_password_manager_driver.cc
+++ b/components/password_manager/content/browser/content_password_manager_driver.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/callback.h"
+#include "base/metrics/histogram_macros.h"
 #include "components/autofill/content/browser/content_autofill_driver.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/password_form.h"
@@ -42,6 +43,13 @@
                     bounds_in_frame_coordinates.size());
 }
 
+void LogSiteIsolationMetricsForSubmittedForm(
+    content::RenderFrameHost* render_frame_host) {
+  UMA_HISTOGRAM_BOOLEAN(
+      "SiteIsolation.IsPasswordFormSubmittedInDedicatedProcess",
+      render_frame_host->GetSiteInstance()->RequiresDedicatedProcess());
+}
+
 }  // namespace
 
 namespace password_manager {
@@ -238,6 +246,8 @@
           BadMessageReason::CPMD_BAD_ORIGIN_FORM_SUBMITTED))
     return;
   GetPasswordManager()->OnPasswordFormSubmitted(this, password_form);
+
+  LogSiteIsolationMetricsForSubmittedForm(render_frame_host_);
 }
 
 void ContentPasswordManagerDriver::ShowManualFallbackForSaving(
@@ -270,6 +280,8 @@
           BadMessageReason::CPMD_BAD_ORIGIN_IN_PAGE_NAVIGATION))
     return;
   GetPasswordManager()->OnPasswordFormSubmittedNoChecks(this, password_form);
+
+  LogSiteIsolationMetricsForSubmittedForm(render_frame_host_);
 }
 
 void ContentPasswordManagerDriver::ShowPasswordSuggestions(
diff --git a/components/password_manager/content/common/credential_manager_mojom_traits.cc b/components/password_manager/content/common/credential_manager_mojom_traits.cc
index fe37c69..09a87af 100644
--- a/components/password_manager/content/common/credential_manager_mojom_traits.cc
+++ b/components/password_manager/content/common/credential_manager_mojom_traits.cc
@@ -92,6 +92,7 @@
         ANDROID_USER_VERIFICATION_UNSUPPORTED:
     case blink::mojom::CredentialManagerError::INVALID_DOMAIN:
     case blink::mojom::CredentialManagerError::CREDENTIAL_EXCLUDED:
+    case blink::mojom::CredentialManagerError::CREDENTIAL_NOT_RECOGNIZED:
     case blink::mojom::CredentialManagerError::NOT_IMPLEMENTED:
     case blink::mojom::CredentialManagerError::NOT_FOCUSED:
     case blink::mojom::CredentialManagerError::RESIDENT_CREDENTIALS_UNSUPPORTED:
diff --git a/components/ui_devtools/views/devtools_server_util.cc b/components/ui_devtools/views/devtools_server_util.cc
index aaadddd..2753785a 100644
--- a/components/ui_devtools/views/devtools_server_util.cc
+++ b/components/ui_devtools/views/devtools_server_util.cc
@@ -17,8 +17,6 @@
 #if defined(USE_AURA)
 #include "components/ui_devtools/views/dom_agent_aura.h"
 #include "components/ui_devtools/views/overlay_agent_aura.h"
-#include "ui/aura/env.h"
-#include "ui/aura/window.h"
 #endif
 
 namespace ui_devtools {
@@ -44,8 +42,6 @@
 #if defined(USE_AURA)
 void RegisterAdditionalRootWindowsAndEnv(std::vector<aura::Window*> roots) {
   DCHECK(!roots.empty());
-  OverlayAgentAura::GetInstance()->RegisterEnv(roots[0]->env());
-  DOMAgentAura::GetInstance()->RegisterEnv(roots[0]->env());
   for (auto* root : roots)
     DOMAgentAura::GetInstance()->RegisterRootWindow(root);
 }
diff --git a/components/ui_devtools/views/dom_agent_aura.cc b/components/ui_devtools/views/dom_agent_aura.cc
index 46cbe03..2dc310c 100644
--- a/components/ui_devtools/views/dom_agent_aura.cc
+++ b/components/ui_devtools/views/dom_agent_aura.cc
@@ -23,21 +23,15 @@
 DOMAgentAura::DOMAgentAura() {
   DCHECK(!dom_agent_aura_);
   dom_agent_aura_ = this;
-  RegisterEnv(aura::Env::GetInstance());
+  aura::Env::GetInstance()->AddObserver(this);
 }
 DOMAgentAura::~DOMAgentAura() {
   for (aura::Window* window : roots_)
     window->RemoveObserver(this);
-  for (auto* env : envs_)
-    env->RemoveObserver(this);
+  aura::Env::GetInstance()->RemoveObserver(this);
   dom_agent_aura_ = nullptr;
 }
 
-void DOMAgentAura::RegisterEnv(aura::Env* env) {
-  envs_.push_back(env);
-  env->AddObserver(this);
-}
-
 void DOMAgentAura::RegisterRootWindow(aura::Window* root) {
   roots_.push_back(root);
   root->AddObserver(this);
diff --git a/components/ui_devtools/views/dom_agent_aura.h b/components/ui_devtools/views/dom_agent_aura.h
index c92e07a..368b889 100644
--- a/components/ui_devtools/views/dom_agent_aura.h
+++ b/components/ui_devtools/views/dom_agent_aura.h
@@ -11,7 +11,6 @@
 #include "ui/aura/window_observer.h"
 
 namespace aura {
-class Env;
 class Window;
 }  // namespace aura
 
@@ -25,7 +24,6 @@
 
   ~DOMAgentAura() override;
   static DOMAgentAura* GetInstance() { return dom_agent_aura_; }
-  void RegisterEnv(aura::Env* env);
   void RegisterRootWindow(aura::Window* root);
 
   // DOMAgent
@@ -44,7 +42,6 @@
  private:
   static DOMAgentAura* dom_agent_aura_;
 
-  std::vector<aura::Env*> envs_;
   std::vector<aura::Window*> roots_;
 
   DISALLOW_COPY_AND_ASSIGN(DOMAgentAura);
diff --git a/components/ui_devtools/views/overlay_agent_aura.cc b/components/ui_devtools/views/overlay_agent_aura.cc
index a5953b2..6ca8983 100644
--- a/components/ui_devtools/views/overlay_agent_aura.cc
+++ b/components/ui_devtools/views/overlay_agent_aura.cc
@@ -17,7 +17,6 @@
 OverlayAgentAura::OverlayAgentAura(DOMAgent* dom_agent)
     : OverlayAgentViews(dom_agent) {
   DCHECK(!overlay_agent_aura_);
-  RegisterEnv(aura::Env::GetInstance());
   overlay_agent_aura_ = this;
 }
 
@@ -25,18 +24,13 @@
   overlay_agent_aura_ = nullptr;
 }
 
-void OverlayAgentAura::RegisterEnv(aura::Env* env) {
-  envs_.push_back(env);
-}
-
 void OverlayAgentAura::InstallPreTargetHandler() {
-  for (auto* env : envs_)
-    env->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
+  aura::Env::GetInstance()->AddPreTargetHandler(
+      this, ui::EventTarget::Priority::kSystem);
 }
 
 void OverlayAgentAura::RemovePreTargetHandler() {
-  for (auto* env : envs_)
-    env->RemovePreTargetHandler(this);
+  aura::Env::GetInstance()->RemovePreTargetHandler(this);
 }
 
 int OverlayAgentAura::FindElementIdTargetedByPoint(
diff --git a/components/ui_devtools/views/overlay_agent_aura.h b/components/ui_devtools/views/overlay_agent_aura.h
index 1af54dd..8449c30 100644
--- a/components/ui_devtools/views/overlay_agent_aura.h
+++ b/components/ui_devtools/views/overlay_agent_aura.h
@@ -30,7 +30,6 @@
 
   FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightWindow);
   FRIEND_TEST_ALL_PREFIXES(OverlayAgentTest, HighlightEmptyOrInvisibleWindow);
-  std::vector<aura::Env*> envs_;
 
   static OverlayAgentAura* overlay_agent_aura_;
 
diff --git a/components/viz/common/viz_utils.cc b/components/viz/common/viz_utils.cc
index cf216d6..53a2514 100644
--- a/components/viz/common/viz_utils.cc
+++ b/components/viz/common/viz_utils.cc
@@ -6,6 +6,8 @@
 
 #include "base/system/sys_info.h"
 #include "build/build_config.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/rrect_f.h"
 
 namespace viz {
 
@@ -16,4 +18,52 @@
   return false;
 }
 
+bool GetScaledRegion(const gfx::Rect& rect,
+                     const gfx::QuadF* clip,
+                     gfx::QuadF* scaled_region) {
+  if (!clip)
+    return false;
+
+  gfx::PointF p1(((clip->p1().x() - rect.x()) / rect.width()) - 0.5f,
+                 ((clip->p1().y() - rect.y()) / rect.height()) - 0.5f);
+  gfx::PointF p2(((clip->p2().x() - rect.x()) / rect.width()) - 0.5f,
+                 ((clip->p2().y() - rect.y()) / rect.height()) - 0.5f);
+  gfx::PointF p3(((clip->p3().x() - rect.x()) / rect.width()) - 0.5f,
+                 ((clip->p3().y() - rect.y()) / rect.height()) - 0.5f);
+  gfx::PointF p4(((clip->p4().x() - rect.x()) / rect.width()) - 0.5f,
+                 ((clip->p4().y() - rect.y()) / rect.height()) - 0.5f);
+  *scaled_region = gfx::QuadF(p1, p2, p3, p4);
+  return true;
+}
+
+bool GetScaledRRectF(const gfx::Rect& space,
+                     const gfx::RRectF& rect,
+                     gfx::RRectF* scaled_rect) {
+  float x_scale = 1.0f / space.width();
+  float y_scale = 1.0f / space.height();
+  float new_x = (rect.rect().x() - space.x()) * x_scale - 0.5f;
+  float new_y = (rect.rect().y() - space.y()) * y_scale - 0.5f;
+  *scaled_rect = rect;
+  scaled_rect->Scale(x_scale, y_scale);
+  scaled_rect->Offset(-scaled_rect->rect().origin().x(),
+                      -scaled_rect->rect().origin().y());
+  scaled_rect->Offset(new_x, new_y);
+  return true;
+}
+
+bool GetScaledUVs(const gfx::Rect& rect, const gfx::QuadF* clip, float uvs[8]) {
+  if (!clip)
+    return false;
+
+  uvs[0] = ((clip->p1().x() - rect.x()) / rect.width());
+  uvs[1] = ((clip->p1().y() - rect.y()) / rect.height());
+  uvs[2] = ((clip->p2().x() - rect.x()) / rect.width());
+  uvs[3] = ((clip->p2().y() - rect.y()) / rect.height());
+  uvs[4] = ((clip->p3().x() - rect.x()) / rect.width());
+  uvs[5] = ((clip->p3().y() - rect.y()) / rect.height());
+  uvs[6] = ((clip->p4().x() - rect.x()) / rect.width());
+  uvs[7] = ((clip->p4().y() - rect.y()) / rect.height());
+  return true;
+}
+
 }  // namespace viz
diff --git a/components/viz/common/viz_utils.h b/components/viz/common/viz_utils.h
index f840651..f290152 100644
--- a/components/viz/common/viz_utils.h
+++ b/components/viz/common/viz_utils.h
@@ -7,9 +7,30 @@
 
 #include "components/viz/common/viz_common_export.h"
 
+namespace gfx {
+class Rect;
+class RRectF;
+class QuadF;
+}  // namespace gfx
+
 namespace viz {
 
 VIZ_COMMON_EXPORT bool PreferRGB565ResourcesForDisplay();
+// This takes a gfx::Rect and a clip region quad in the same space,
+// and returns a quad with the same proportions in the space -0.5->0.5.
+VIZ_COMMON_EXPORT bool GetScaledRegion(const gfx::Rect& rect,
+                                       const gfx::QuadF* clip,
+                                       gfx::QuadF* scaled_region);
+// This takes a rounded rect and a rect that it lives in, and returns an
+// equivalent rounded rect in the space -0.5->0.5.
+VIZ_COMMON_EXPORT bool GetScaledRRectF(const gfx::Rect& space,
+                                       const gfx::RRectF& rect,
+                                       gfx::RRectF* scaled_rect);
+// This takes a gfx::Rect and a clip region quad in the same space,
+// and returns the proportional uv's in the space 0->1.
+VIZ_COMMON_EXPORT bool GetScaledUVs(const gfx::Rect& rect,
+                                    const gfx::QuadF* clip,
+                                    float uvs[8]);
 
 }  // namespace viz
 
diff --git a/components/viz/service/display/dc_layer_overlay.cc b/components/viz/service/display/dc_layer_overlay.cc
index d5bde06..c17ace6 100644
--- a/components/viz/service/display/dc_layer_overlay.cc
+++ b/components/viz/service/display/dc_layer_overlay.cc
@@ -202,9 +202,9 @@
     default;
 DCLayerOverlay::~DCLayerOverlay() = default;
 
-DCLayerOverlayProcessor::DCLayerOverlayProcessor(OutputSurface* surface) {
+DCLayerOverlayProcessor::DCLayerOverlayProcessor(
+    const ContextProvider* context_provider) {
 #if defined(OS_WIN)
-  auto* context_provider = surface->context_provider();
   if (context_provider) {
     has_hw_overlay_support_ =
         context_provider->ContextCapabilities().dc_layers &&
diff --git a/components/viz/service/display/dc_layer_overlay.h b/components/viz/service/display/dc_layer_overlay.h
index b005ccdb..d3a8983 100644
--- a/components/viz/service/display/dc_layer_overlay.h
+++ b/components/viz/service/display/dc_layer_overlay.h
@@ -16,7 +16,7 @@
 
 namespace viz {
 class DisplayResourceProvider;
-class OutputSurface;
+class ContextProvider;
 
 // Holds all information necessary to construct a DCLayer from a DrawQuad.
 class VIZ_SERVICE_EXPORT DCLayerOverlay {
@@ -69,7 +69,7 @@
 
 class DCLayerOverlayProcessor {
  public:
-  explicit DCLayerOverlayProcessor(OutputSurface* surface);
+  explicit DCLayerOverlayProcessor(const ContextProvider* context_provider);
   ~DCLayerOverlayProcessor();
 
   void Process(DisplayResourceProvider* resource_provider,
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index 7f52d50..5fbc63ee 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -109,7 +109,9 @@
     : settings_(settings),
       output_surface_(output_surface),
       resource_provider_(resource_provider),
-      overlay_processor_(std::make_unique<OverlayProcessor>(output_surface)) {}
+      overlay_processor_(std::make_unique<OverlayProcessor>(
+          output_surface->GetOverlayCandidateValidator(),
+          output_surface->context_provider())) {}
 
 DirectRenderer::~DirectRenderer() = default;
 
@@ -814,6 +816,10 @@
   return IsRenderPassResourceAllocated(render_pass_id);
 }
 
+bool DirectRenderer::OverlayNeedsSurfaceOccludingDamageRect() const {
+  return overlay_processor_->NeedsSurfaceOccludingDamageRect();
+}
+
 bool DirectRenderer::ShouldApplyRoundedCorner(const DrawQuad* quad) const {
   const SharedQuadState* sqs = quad->shared_quad_state;
   const gfx::RRectF& rounded_corner_bounds = sqs->rounded_corner_bounds;
diff --git a/components/viz/service/display/direct_renderer.h b/components/viz/service/display/direct_renderer.h
index e35a0f3..a55d343 100644
--- a/components/viz/service/display/direct_renderer.h
+++ b/components/viz/service/display/direct_renderer.h
@@ -101,6 +101,11 @@
     enlarge_pass_texture_amount_ = amount;
   }
 
+  bool has_overlay_validator() const {
+    return !!overlay_processor_->GetOverlayCandidateValidator();
+  }
+  bool OverlayNeedsSurfaceOccludingDamageRect() const;
+
  protected:
   friend class BspWalkActionDrawPolygon;
 
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index 0f83d4a..86a3f10 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -344,12 +344,10 @@
 
   // TODO(jbauman): Outputting an incomplete quad list doesn't work when using
   // overlays.
-  OverlayCandidateValidator* overlay_validator =
-      output_surface_->GetOverlayCandidateValidator();
   bool output_partial_list =
-      renderer_->use_partial_swap() && !overlay_validator;
+      renderer_->use_partial_swap() && !renderer_->has_overlay_validator();
   bool needs_surface_occluding_damage_rect =
-      overlay_validator && overlay_validator->NeedsSurfaceOccludingDamageRect();
+      renderer_->OverlayNeedsSurfaceOccludingDamageRect();
   aggregator_.reset(new SurfaceAggregator(
       surface_manager_, resource_provider_.get(), output_partial_list,
       needs_surface_occluding_damage_rect));
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index a70fb43..edf8e92 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -45,6 +45,7 @@
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "components/viz/common/resources/resource_id.h"
 #include "components/viz/common/skia_helper.h"
+#include "components/viz/common/viz_utils.h"
 #include "components/viz/service/display/draw_polygon.h"
 #include "components/viz/service/display/dynamic_geometry_binding.h"
 #include "components/viz/service/display/layer_quad.h"
@@ -701,65 +702,11 @@
   return true;
 }
 
-// This takes a gfx::Rect and a clip region quad in the same space,
-// and returns a quad with the same proportions in the space -0.5->0.5.
-bool GetScaledRegion(const gfx::Rect& rect,
-                     const gfx::QuadF* clip,
-                     gfx::QuadF* scaled_region) {
-  if (!clip)
-    return false;
-
-  gfx::PointF p1(((clip->p1().x() - rect.x()) / rect.width()) - 0.5f,
-                 ((clip->p1().y() - rect.y()) / rect.height()) - 0.5f);
-  gfx::PointF p2(((clip->p2().x() - rect.x()) / rect.width()) - 0.5f,
-                 ((clip->p2().y() - rect.y()) / rect.height()) - 0.5f);
-  gfx::PointF p3(((clip->p3().x() - rect.x()) / rect.width()) - 0.5f,
-                 ((clip->p3().y() - rect.y()) / rect.height()) - 0.5f);
-  gfx::PointF p4(((clip->p4().x() - rect.x()) / rect.width()) - 0.5f,
-                 ((clip->p4().y() - rect.y()) / rect.height()) - 0.5f);
-  *scaled_region = gfx::QuadF(p1, p2, p3, p4);
-  return true;
-}
-
-// This takes a rounded rect and a rect that it lives in, and returns an
-// equivalent rounded rect in the space -0.5->0.5.
-bool GetScaledRRectF(const gfx::Rect& space,
-                     const gfx::RRectF& rect,
-                     gfx::RRectF* scaled_rect) {
-  float x_scale = 1.0f / space.width();
-  float y_scale = 1.0f / space.height();
-  float new_x = (rect.rect().x() - space.x()) * x_scale - 0.5f;
-  float new_y = (rect.rect().y() - space.y()) * y_scale - 0.5f;
-  *scaled_rect = rect;
-  scaled_rect->Scale(x_scale, y_scale);
-  scaled_rect->Offset(-scaled_rect->rect().origin().x(),
-                      -scaled_rect->rect().origin().y());
-  scaled_rect->Offset(new_x, new_y);
-  return true;
-}
-
-// This takes a gfx::Rect and a clip region quad in the same space,
-// and returns the proportional uv's in the space 0->1.
-bool GetScaledUVs(const gfx::Rect& rect, const gfx::QuadF* clip, float uvs[8]) {
-  if (!clip)
-    return false;
-
-  uvs[0] = ((clip->p1().x() - rect.x()) / rect.width());
-  uvs[1] = ((clip->p1().y() - rect.y()) / rect.height());
-  uvs[2] = ((clip->p2().x() - rect.x()) / rect.width());
-  uvs[3] = ((clip->p2().y() - rect.y()) / rect.height());
-  uvs[4] = ((clip->p3().x() - rect.x()) / rect.width());
-  uvs[5] = ((clip->p3().y() - rect.y()) / rect.height());
-  uvs[6] = ((clip->p4().x() - rect.x()) / rect.width());
-  uvs[7] = ((clip->p4().y() - rect.y()) / rect.height());
-  return true;
-}
-
 gfx::Rect GLRenderer::GetBackdropBoundingBoxForRenderPassQuad(
     DrawRenderPassDrawQuadParams* params,
     gfx::Transform* backdrop_filter_bounds_transform,
     base::Optional<gfx::RRectF>* backdrop_filter_bounds,
-    gfx::Rect* unclipped_rect) {
+    gfx::Rect* unclipped_rect) const {
   DCHECK(backdrop_filter_bounds_transform);
   DCHECK(backdrop_filter_bounds);
   DCHECK(unclipped_rect);
@@ -776,7 +723,7 @@
   // represents |params->backdrop_filter_bounds| as a fraction of the space
   // defined by |quad->rect|, not including its offset.
   *backdrop_filter_bounds = gfx::RRectF();
-  if (!params->backdrop_filter_bounds.has_value() ||
+  if (!params->backdrop_filter_bounds ||
       !GetScaledRRectF(quad->rect, params->backdrop_filter_bounds.value(),
                        &backdrop_filter_bounds->value())) {
     backdrop_filter_bounds->reset();
diff --git a/components/viz/service/display/gl_renderer.h b/components/viz/service/display/gl_renderer.h
index 49293c4..ee36fe8 100644
--- a/components/viz/service/display/gl_renderer.h
+++ b/components/viz/service/display/gl_renderer.h
@@ -205,7 +205,7 @@
       DrawRenderPassDrawQuadParams* params,
       gfx::Transform* backdrop_filter_bounds_transform,
       base::Optional<gfx::RRectF>* backdrop_filter_bounds,
-      gfx::Rect* unclipped_rect);
+      gfx::Rect* unclipped_rect) const;
 
   // Allocates and returns a texture id that contains a copy of the contents
   // of the current RenderPass being drawn.
diff --git a/components/viz/service/display/gl_renderer_unittest.cc b/components/viz/service/display/gl_renderer_unittest.cc
index 29b910c..b211586 100644
--- a/components/viz/service/display/gl_renderer_unittest.cc
+++ b/components/viz/service/display/gl_renderer_unittest.cc
@@ -2077,9 +2077,9 @@
         DisplayResourceProvider::kGpu, output_surface_->context_provider(),
         shared_bitmap_manager_.get());
 
+    EXPECT_CALL(*output_surface_, GetOverlayCandidateValidator()).Times(1);
     renderer_.reset(new FakeRendererGL(&settings_, output_surface_.get(),
                                        resource_provider_.get()));
-    EXPECT_CALL(*output_surface_, GetOverlayCandidateValidator()).Times(1);
     renderer_->Initialize();
 
     EXPECT_CALL(*output_surface_, EnsureBackbuffer()).Times(1);
@@ -2171,8 +2171,9 @@
     void CheckOverlaySupport(OverlayCandidateList* surfaces) override {}
   };
 
-  explicit TestOverlayProcessor(OutputSurface* surface)
-      : OverlayProcessor(surface) {}
+  TestOverlayProcessor(OverlayCandidateValidator* overlay_validator,
+                       ContextProvider* context_provider)
+      : OverlayProcessor(overlay_validator, context_provider) {}
   ~TestOverlayProcessor() override = default;
 
   void Initialize() override {
@@ -2231,6 +2232,10 @@
       parent_resource_provider->GetChildToParentMap(child_id);
   ResourceId parent_resource_id = resource_map[list[0].id];
 
+  std::unique_ptr<TestOverlayProcessor::Validator> validator(
+      new TestOverlayProcessor::Validator);
+  output_surface->SetOverlayCandidateValidator(validator.get());
+
   RendererSettings settings;
   FakeRendererGL renderer(&settings, output_surface.get(),
                           parent_resource_provider.get(),
@@ -2238,13 +2243,10 @@
   renderer.Initialize();
   renderer.SetVisible(true);
 
-  TestOverlayProcessor* processor =
-      new TestOverlayProcessor(output_surface.get());
+  TestOverlayProcessor* processor = new TestOverlayProcessor(
+      validator.get(), output_surface->context_provider());
   processor->Initialize();
   renderer.SetOverlayProcessor(processor);
-  std::unique_ptr<TestOverlayProcessor::Validator> validator(
-      new TestOverlayProcessor::Validator);
-  output_surface->SetOverlayCandidateValidator(validator.get());
 
   gfx::Size viewport_size(1, 1);
   RenderPass* root_pass = cc::AddRenderPass(
@@ -2359,8 +2361,9 @@
     bool multiple_candidates_ = false;
   };
 
-  explicit SingleOverlayOnTopProcessor(OutputSurface* surface)
-      : OverlayProcessor(surface) {}
+  explicit SingleOverlayOnTopProcessor(OverlayCandidateValidator* validator,
+                                       ContextProvider* context_provider)
+      : OverlayProcessor(validator, context_provider) {}
 
   void Initialize() override {
     strategies_.push_back(
@@ -2452,8 +2455,9 @@
   renderer.Initialize();
   renderer.SetVisible(true);
 
-  SingleOverlayOnTopProcessor* processor =
-      new SingleOverlayOnTopProcessor(output_surface.get());
+  SingleOverlayOnTopProcessor* processor = new SingleOverlayOnTopProcessor(
+      output_surface->GetOverlayCandidateValidator(),
+      output_surface->context_provider());
   processor->Initialize();
   renderer.SetOverlayProcessor(processor);
 
@@ -2835,17 +2839,18 @@
 
   RendererSettings settings;
   settings.partial_swap_enabled = true;
+  std::unique_ptr<DCLayerValidator> validator(new DCLayerValidator);
+  output_surface->SetOverlayCandidateValidator(validator.get());
   FakeRendererGL renderer(&settings, output_surface.get(),
                           parent_resource_provider.get());
   renderer.Initialize();
   renderer.SetVisible(true);
   TestOverlayProcessor* processor =
-      new TestOverlayProcessor(output_surface.get());
+      new TestOverlayProcessor(output_surface->GetOverlayCandidateValidator(),
+                               output_surface->context_provider());
   processor->Initialize();
   processor->SetDCHasHwOverlaySupportForTesting();
   renderer.SetOverlayProcessor(processor);
-  std::unique_ptr<DCLayerValidator> validator(new DCLayerValidator);
-  output_surface->SetOverlayCandidateValidator(validator.get());
 
   gfx::Size viewport_size(100, 100);
 
@@ -2978,7 +2983,9 @@
 
   ContentBoundsOverlayProcessor(OutputSurface* surface,
                                 const std::vector<gfx::Rect>& content_bounds)
-      : OverlayProcessor(surface), content_bounds_(content_bounds) {}
+      : OverlayProcessor(surface->GetOverlayCandidateValidator(),
+                         surface->context_provider()),
+        content_bounds_(content_bounds) {}
 
   void Initialize() override {
     strategies_.push_back(
@@ -3121,8 +3128,9 @@
     renderer_->Initialize();
     renderer_->SetVisible(true);
 
-    TestOverlayProcessor* processor =
-        new TestOverlayProcessor(output_surface_.get());
+    TestOverlayProcessor* processor = new TestOverlayProcessor(
+        output_surface_->GetOverlayCandidateValidator(),
+        output_surface_->context_provider());
     processor->Initialize();
     renderer_->SetOverlayProcessor(processor);
   }
@@ -4063,7 +4071,9 @@
     renderer_->Initialize();
     renderer_->SetVisible(true);
 
-    auto* processor = new SingleOverlayOnTopProcessor(output_surface_.get());
+    auto* processor = new SingleOverlayOnTopProcessor(
+        output_surface_->GetOverlayCandidateValidator(),
+        output_surface_->context_provider());
     processor->AllowMultipleCandidates();
     processor->Initialize();
     renderer_->SetOverlayProcessor(processor);
diff --git a/components/viz/service/display/overlay_processor.cc b/components/viz/service/display/overlay_processor.cc
index 7554e21..2efdf5a4 100644
--- a/components/viz/service/display/overlay_processor.cc
+++ b/components/viz/service/display/overlay_processor.cc
@@ -97,15 +97,15 @@
   return OverlayStrategy::kUnknown;
 }
 
-OverlayProcessor::OverlayProcessor(OutputSurface* surface)
-    : surface_(surface), dc_processor_(surface) {}
+OverlayProcessor::OverlayProcessor(OverlayCandidateValidator* overlay_validator,
+                                   const ContextProvider* context_provider)
+    : overlay_validator_(overlay_validator),
+      dc_processor_(
+          std::make_unique<DCLayerOverlayProcessor>(context_provider)) {}
 
 void OverlayProcessor::Initialize() {
-  DCHECK(surface_);
-  OverlayCandidateValidator* validator =
-      surface_->GetOverlayCandidateValidator();
-  if (validator)
-    validator->GetStrategies(&strategies_);
+  if (overlay_validator_)
+    overlay_validator_->GetStrategies(&strategies_);
 }
 
 OverlayProcessor::~OverlayProcessor() {}
@@ -124,9 +124,7 @@
     OverlayCandidateList* overlay_candidates,
     CALayerOverlayList* ca_layer_overlays,
     gfx::Rect* damage_rect) {
-  OverlayCandidateValidator* overlay_validator =
-      surface_->GetOverlayCandidateValidator();
-  if (!overlay_validator || !overlay_validator->AllowCALayerOverlays())
+  if (!overlay_validator_ || !overlay_validator_->AllowCALayerOverlays())
     return false;
 
   if (!ProcessForCALayerOverlays(
@@ -152,14 +150,12 @@
     OverlayCandidateList* overlay_candidates,
     DCLayerOverlayList* dc_layer_overlays,
     gfx::Rect* damage_rect) {
-  OverlayCandidateValidator* overlay_validator =
-      surface_->GetOverlayCandidateValidator();
-  if (!overlay_validator || !overlay_validator->AllowDCLayerOverlays())
+  if (!overlay_validator_ || !overlay_validator_->AllowDCLayerOverlays())
     return false;
 
-  dc_processor_.Process(resource_provider,
-                        gfx::RectF(render_passes->back()->output_rect),
-                        render_passes, damage_rect, dc_layer_overlays);
+  dc_processor_->Process(resource_provider,
+                         gfx::RectF(render_passes->back()->output_rect),
+                         render_passes, damage_rect, dc_layer_overlays);
 
   DCHECK(overlay_candidates->empty());
   return true;
@@ -199,10 +195,10 @@
   // contents.
   if (!render_pass->copy_requests.empty()) {
     damage_rect->Union(
-        dc_processor_.previous_frame_overlay_damage_contribution());
+        dc_processor_->previous_frame_overlay_damage_contribution());
     // Update damage rect before calling ClearOverlayState, otherwise
     // previous_frame_overlay_rect_union will be empty.
-    dc_processor_.ClearOverlayState();
+    dc_processor_->ClearOverlayState();
 
     return;
   }
@@ -242,12 +238,11 @@
     // If no strategy worked the only remaining overlay in the list is the one
     // backed by the OutputSurface. Make sure the OverlayCandidateValidator
     // applies any modifications if needed.
-    if (!candidates->empty() && surface_->GetOverlayCandidateValidator()) {
+    if (!candidates->empty() && overlay_validator_) {
       DCHECK_EQ(candidates->size(), 1u);
       DCHECK(candidates->back().use_output_surface_for_resource);
 
-      surface_->GetOverlayCandidateValidator()->AdjustOutputSurfaceOverlay(
-          &candidates->back());
+      overlay_validator_->AdjustOutputSurfaceOverlay(&candidates->back());
       DCHECK_EQ(candidates->size(), 1u);
     }
   }
@@ -339,4 +334,9 @@
   damage_rect->Union(output_surface_overlay_damage_rect);
 }
 
+bool OverlayProcessor::NeedsSurfaceOccludingDamageRect() const {
+  return overlay_validator_ &&
+         overlay_validator_->NeedsSurfaceOccludingDamageRect();
+}
+
 }  // namespace viz
diff --git a/components/viz/service/display/overlay_processor.h b/components/viz/service/display/overlay_processor.h
index 7b01c0e..93b513b 100644
--- a/components/viz/service/display/overlay_processor.h
+++ b/components/viz/service/display/overlay_processor.h
@@ -21,7 +21,7 @@
 }
 
 namespace viz {
-class OutputSurface;
+class OverlayCandidateValidator;
 
 class VIZ_SERVICE_EXPORT OverlayProcessor {
  public:
@@ -54,13 +54,21 @@
   };
   using StrategyList = std::vector<std::unique_ptr<Strategy>>;
 
-  explicit OverlayProcessor(OutputSurface* surface);
+  // TODO(weiliangc): Take ownership of validator here.
+  OverlayProcessor(OverlayCandidateValidator* overlay_validator,
+                   const ContextProvider* context_provider);
   virtual ~OverlayProcessor();
   // Virtual to allow testing different strategies.
   virtual void Initialize();
 
   gfx::Rect GetAndResetOverlayDamage();
 
+  const OverlayCandidateValidator* GetOverlayCandidateValidator() const {
+    return overlay_validator_;
+  }
+
+  bool NeedsSurfaceOccludingDamageRect() const;
+
   // Attempt to replace quads from the specified root render pass with overlays
   // or CALayers. This must be called every frame.
   void ProcessForOverlays(
@@ -76,12 +84,12 @@
       std::vector<gfx::Rect>* content_bounds);
 
   void SetDCHasHwOverlaySupportForTesting() {
-    dc_processor_.SetHasHwOverlaySupport();
+    dc_processor_->SetHasHwOverlaySupport();
   }
 
  protected:
   StrategyList strategies_;
-  OutputSurface* surface_;
+  OverlayCandidateValidator* overlay_validator_;
   gfx::Rect overlay_damage_rect_;
   gfx::Rect previous_frame_underlay_rect_;
   bool previous_frame_underlay_was_unoccluded_ = false;
@@ -110,7 +118,7 @@
                         const QuadList* quad_list,
                         gfx::Rect* damage_rect);
 
-  DCLayerOverlayProcessor dc_processor_;
+  std::unique_ptr<DCLayerOverlayProcessor> dc_processor_;
 
   DISALLOW_COPY_AND_ASSIGN(OverlayProcessor);
 };
diff --git a/components/viz/service/display/overlay_unittest.cc b/components/viz/service/display/overlay_unittest.cc
index 5fc0f02..99a1ded 100644
--- a/components/viz/service/display/overlay_unittest.cc
+++ b/components/viz/service/display/overlay_unittest.cc
@@ -180,12 +180,15 @@
 
 class DefaultOverlayProcessor : public OverlayProcessor {
  public:
-  explicit DefaultOverlayProcessor(OutputSurface* surface);
+  DefaultOverlayProcessor(OverlayCandidateValidator* overlay_validator,
+                          ContextProvider* context_provider);
   size_t GetStrategyCount();
 };
 
-DefaultOverlayProcessor::DefaultOverlayProcessor(OutputSurface* surface)
-    : OverlayProcessor(surface) {}
+DefaultOverlayProcessor::DefaultOverlayProcessor(
+    OverlayCandidateValidator* overlay_validator,
+    ContextProvider* context_provider)
+    : OverlayProcessor(overlay_validator, context_provider) {}
 
 size_t DefaultOverlayProcessor::GetStrategyCount() {
   return strategies_.size();
@@ -252,6 +255,21 @@
   unsigned bind_framebuffer_count_ = 0;
 };
 
+template <typename OverlayCandidateValidatorType>
+class TypedOverlayProcessor : public OverlayProcessor {
+ public:
+  TypedOverlayProcessor(OverlayCandidateValidatorType* validator,
+                        const ContextProvider* context_provider)
+      : OverlayProcessor(validator, context_provider), validator_(validator) {}
+
+  OverlayCandidateValidatorType* GetTypedOverlayCandidateValidator() {
+    return validator_;
+  }
+
+ private:
+  OverlayCandidateValidatorType* validator_;
+};
+
 std::unique_ptr<RenderPass> CreateRenderPass() {
   int render_pass_id = 1;
   gfx::Rect output_rect(0, 0, 256, 256);
@@ -522,6 +540,8 @@
 template <typename OverlayCandidateValidatorType>
 class OverlayTest : public testing::Test {
   using OutputSurfaceType = OverlayOutputSurface<OverlayCandidateValidatorType>;
+  using OverlayProcessorType =
+      TypedOverlayProcessor<OverlayCandidateValidatorType>;
 
  protected:
   void SetUp() override {
@@ -541,8 +561,9 @@
     child_provider_->BindToCurrentThread();
     child_resource_provider_ = std::make_unique<ClientResourceProvider>(true);
 
-    overlay_processor_ =
-        std::make_unique<OverlayProcessor>(output_surface_.get());
+    overlay_processor_ = std::make_unique<OverlayProcessorType>(
+        output_surface_->GetOverlayCandidateValidator(),
+        output_surface_->context_provider());
     overlay_processor_->Initialize();
     overlay_processor_->SetDCHasHwOverlaySupportForTesting();
   }
@@ -558,6 +579,11 @@
     provider_ = nullptr;
   }
 
+  void AddExpectedRectToOverlayValidator(const gfx::RectF& rect) {
+    overlay_processor_->GetTypedOverlayCandidateValidator()->AddExpectedRect(
+        rect);
+  }
+
   scoped_refptr<TestContextProvider> provider_;
   std::unique_ptr<OutputSurfaceType> output_surface_;
   cc::FakeOutputSurfaceClient client_;
@@ -565,7 +591,7 @@
   std::unique_ptr<DisplayResourceProvider> resource_provider_;
   scoped_refptr<TestContextProvider> child_provider_;
   std::unique_ptr<ClientResourceProvider> child_resource_provider_;
-  std::unique_ptr<OverlayProcessor> overlay_processor_;
+  std::unique_ptr<OverlayProcessorType> overlay_processor_;
   gfx::Rect damage_rect_;
   std::vector<gfx::Rect> content_bounds_;
 };
@@ -601,8 +627,9 @@
                                                 provider.get(),
                                                 shared_bitmap_manager.get());
 
-  auto overlay_processor =
-      std::make_unique<DefaultOverlayProcessor>(&output_surface);
+  auto overlay_processor = std::make_unique<DefaultOverlayProcessor>(
+      output_surface.GetOverlayCandidateValidator(),
+      output_surface.context_provider());
   overlay_processor->Initialize();
   EXPECT_GE(2U, overlay_processor->GetStrategyCount());
 }
@@ -861,8 +888,7 @@
                         child_resource_provider_.get(), child_provider_.get(),
                         pass->shared_quad_state_list.back(), pass.get(),
                         kSmallCandidateRect);
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kSmallCandidateRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kSmallCandidateRect));
 
   // Add a bigger quad below the previous one, but not occluded.
   const auto kBigCandidateRect = gfx::Rect(20, 20, 32, 32);
@@ -870,8 +896,7 @@
       resource_provider_.get(), child_resource_provider_.get(),
       child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
       kBigCandidateRect);
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kBigCandidateRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kBigCandidateRect));
 
   unsigned resource_big = quad_big->resource_id();
 
@@ -1448,8 +1473,7 @@
 }
 
 TEST_F(SingleOverlayOnTopTest, AllowNotTopIfNotOccluded) {
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kOverlayBottomRightRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kOverlayBottomRightRect));
 
   std::unique_ptr<RenderPass> pass = CreateRenderPass();
   CreateOpaqueQuadAt(resource_provider_.get(),
@@ -1473,8 +1497,7 @@
 }
 
 TEST_F(SingleOverlayOnTopTest, AllowTransparentOnTop) {
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kOverlayBottomRightRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kOverlayBottomRightRect));
 
   std::unique_ptr<RenderPass> pass = CreateRenderPass();
   SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState();
@@ -1500,8 +1523,7 @@
 }
 
 TEST_F(SingleOverlayOnTopTest, AllowTransparentColorOnTop) {
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kOverlayBottomRightRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kOverlayBottomRightRect));
 
   std::unique_ptr<RenderPass> pass = CreateRenderPass();
   CreateSolidColorQuadAt(pass->shared_quad_state_list.back(),
@@ -1635,8 +1657,7 @@
 }
 
 TEST_F(UnderlayTest, OverlayLayerUnderMainLayer) {
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kOverlayBottomRightRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kOverlayBottomRightRect));
 
   std::unique_ptr<RenderPass> pass = CreateRenderPass();
   CreateFullscreenOpaqueQuad(resource_provider_.get(),
@@ -1756,8 +1777,7 @@
 TEST_F(UnderlayTest, DamageNotSubtractedForNonIdenticalConsecutiveUnderlays) {
   gfx::Rect overlay_rects[] = {kOverlayBottomRightRect, kOverlayRect};
   for (int i = 0; i < 2; ++i) {
-    output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-        gfx::RectF(overlay_rects[i]));
+    AddExpectedRectToOverlayValidator(gfx::RectF(overlay_rects[i]));
 
     std::unique_ptr<RenderPass> pass = CreateRenderPass();
 
@@ -1881,8 +1901,7 @@
 }
 
 TEST_F(UnderlayTest, DamageSubtractedWhenQuadsAboveDontOverlap) {
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kOverlayBottomRightRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kOverlayBottomRightRect));
 
   for (int i = 0; i < 2; ++i) {
     std::unique_ptr<RenderPass> pass = CreateRenderPass();
@@ -1943,8 +1962,7 @@
 }
 
 TEST_F(UnderlayTest, UpdateDamageWhenChangingUnderlays) {
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kOverlayTopLeftRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kOverlayTopLeftRect));
 
   for (size_t i = 0; i < 2; ++i) {
     std::unique_ptr<RenderPass> pass = CreateRenderPass();
@@ -2102,8 +2120,7 @@
 }
 
 TEST_F(UnderlayCastTest, BlackOutsideOverlayContentBounds) {
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kOverlayBottomRightRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kOverlayBottomRightRect));
 
   const gfx::Rect kLeftSide(0, 0, 128, 256);
   const gfx::Rect kTopRight(128, 0, 128, 128);
@@ -2183,8 +2200,7 @@
   // Check rounding behaviour on overlay quads.  Be conservative (content
   // potentially visible on boundary).
   const gfx::Rect overlay_rect(1, 1, 8, 8);
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(1.5f, 1.5f, 8, 8));
+  AddExpectedRectToOverlayValidator(gfx::RectF(1.5f, 1.5f, 8, 8));
 
   gfx::Transform transform;
   transform.Translate(0.5f, 0.5f);
@@ -2215,8 +2231,7 @@
   // rect).
   gfx::Rect overlay_rect = kOverlayRect;
   overlay_rect.Inset(0, 0, 1, 1);
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(0.5f, 0.5f, 255, 255));
+  AddExpectedRectToOverlayValidator(gfx::RectF(0.5f, 0.5f, 255, 255));
 
   gfx::Transform transform;
   transform.Translate(0.5f, 0.5f);
@@ -3105,6 +3120,7 @@
 
 class GLRendererWithOverlaysTest : public testing::Test {
   using OutputSurfaceType = OverlayOutputSurface<SingleOverlayValidator>;
+  using OverlayProcessorType = TypedOverlayProcessor<SingleOverlayValidator>;
 
  protected:
   GLRendererWithOverlaysTest() {
@@ -3128,8 +3144,12 @@
   }
 
   void Init(bool use_validator) {
-    if (use_validator)
+    if (use_validator) {
       output_surface_->SetOverlayCandidateValidator(new SingleOverlayValidator);
+      overlay_processor_ = std::make_unique<OverlayProcessorType>(
+          output_surface_->GetOverlayCandidateValidator(),
+          output_surface_->context_provider());
+    }
 
     renderer_ = std::make_unique<OverlayInfoRendererGL>(
         &settings_, output_surface_.get(), resource_provider_.get());
@@ -3159,11 +3179,17 @@
     renderer_->DidReceiveTextureInUseResponses(responses);
   }
 
+  void AddExpectedRectToOverlayValidator(const gfx::RectF& rect) {
+    overlay_processor_->GetTypedOverlayCandidateValidator()->AddExpectedRect(
+        rect);
+  }
+
   RendererSettings settings_;
   cc::FakeOutputSurfaceClient output_surface_client_;
   std::unique_ptr<OutputSurfaceType> output_surface_;
   std::unique_ptr<DisplayResourceProvider> resource_provider_;
   std::unique_ptr<OverlayInfoRendererGL> renderer_;
+  std::unique_ptr<OverlayProcessorType> overlay_processor_;
   scoped_refptr<TestContextProvider> provider_;
   scoped_refptr<TestContextProvider> child_provider_;
   std::unique_ptr<ClientResourceProvider> child_resource_provider_;
@@ -3174,8 +3200,7 @@
   bool use_validator = true;
   Init(use_validator);
   renderer_->set_expect_overlays(true);
-  output_surface_->GetOverlayCandidateValidator()->AddExpectedRect(
-      gfx::RectF(kOverlayBottomRightRect));
+  AddExpectedRectToOverlayValidator(gfx::RectF(kOverlayBottomRightRect));
 
   std::unique_ptr<RenderPass> pass = CreateRenderPass();
 
diff --git a/components/viz/service/display/software_renderer.cc b/components/viz/service/display/software_renderer.cc
index 5c806a8..990b032 100644
--- a/components/viz/service/display/software_renderer.cc
+++ b/components/viz/service/display/software_renderer.cc
@@ -17,6 +17,7 @@
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
 #include "components/viz/common/quads/tile_draw_quad.h"
+#include "components/viz/common/viz_utils.h"
 #include "components/viz/service/display/output_surface.h"
 #include "components/viz/service/display/output_surface_frame.h"
 #include "components/viz/service/display/renderer_utils.h"
@@ -709,23 +710,61 @@
 
 gfx::Rect SoftwareRenderer::GetBackdropBoundingBoxForRenderPassQuad(
     const RenderPassDrawQuad* quad,
-    const gfx::Transform& contents_device_transform,
     const cc::FilterOperations* backdrop_filters,
+    const cc::FilterOperations* regular_filters,
+    base::Optional<gfx::RRectF> backdrop_filter_bounds_input,
+    gfx::Transform contents_device_transform,
+    gfx::Transform* backdrop_filter_bounds_transform,
+    base::Optional<gfx::RRectF>* backdrop_filter_bounds,
     gfx::Rect* unclipped_rect) const {
-  // TODO(916318): This function needs to compute the backdrop
-  // filter clip rect and return it.
-  DCHECK(ShouldApplyBackdropFilters(backdrop_filters));
-  gfx::Rect backdrop_rect = gfx::ToEnclosingRect(cc::MathUtil::MapClippedRect(
-      contents_device_transform, QuadVertexRect()));
+  DCHECK(backdrop_filter_bounds_transform);
+  DCHECK(backdrop_filter_bounds);
+  DCHECK(unclipped_rect);
 
-  SkMatrix matrix;
-  matrix.setScale(quad->filters_scale.x(), quad->filters_scale.y());
-  backdrop_rect = backdrop_filters->MapRectReverse(backdrop_rect, matrix);
+  // |backdrop_filter_bounds| is a rounded rect in [-0.5,0.5] space that
+  // represents |backdrop_filter_bounds_input| as a fraction of the space
+  // defined by |quad->rect|, not including its offset.
+  *backdrop_filter_bounds = gfx::RRectF();
+  if (!backdrop_filter_bounds_input ||
+      !GetScaledRRectF(quad->rect, backdrop_filter_bounds_input.value(),
+                       &backdrop_filter_bounds->value())) {
+    backdrop_filter_bounds->reset();
+  }
+
+  // |backdrop_rect| is now the bounding box of clip_region, in window pixel
+  // coordinates, and with flip applied.
+  gfx::Rect backdrop_rect = gfx::ToEnclosingRect(cc::MathUtil::MapClippedRect(
+      contents_device_transform, gfx::RectF(-0.5f, -0.5f, 1.f, 1.f)));
+
+  if (ShouldApplyBackdropFilters(backdrop_filters)) {
+    SkMatrix matrix;
+    // |filters_scale| is the ratio of render pass physical pixels to root layer
+    // layer space, including content-to-target-space scale and device pixel
+    // ratio.
+    matrix.setScale(quad->filters_scale.x(), quad->filters_scale.y());
+    // |backdrop_rect| is now expanded for pixel moving backdrop_filters, offset
+    // by any backdrop-filter drop-shadow offset. Note that scale is not applied
+    // to the backdrop_rect itself, only the sigma or x/y offset of filters.
+    backdrop_rect = backdrop_filters->MapRectReverse(backdrop_rect, matrix);
+  }
+
+  if (regular_filters) {
+    DCHECK(!regular_filters->IsEmpty());
+    // If we have regular filters, grab an extra one-pixel border around the
+    // background, so texture edge clamping gives us a transparent border
+    // in case the filter expands the result.
+    backdrop_rect.Inset(-1, -1, -1, -1);
+  }
 
   *unclipped_rect = backdrop_rect;
   backdrop_rect.Intersect(MoveFromDrawToWindowSpace(
       current_frame()->current_render_pass->output_rect));
 
+  // Shift to the space of the captured backdrop image.
+  *backdrop_filter_bounds_transform = contents_device_transform;
+  backdrop_filter_bounds_transform->PostTranslate(-backdrop_rect.x(),
+                                                  -backdrop_rect.y());
+
   return backdrop_rect;
 }
 
@@ -736,6 +775,8 @@
       BackdropFiltersForPass(quad->render_pass_id);
   if (!ShouldApplyBackdropFilters(backdrop_filters))
     return nullptr;
+  base::Optional<gfx::RRectF> backdrop_filter_bounds_input =
+      BackdropFilterBoundsForPass(quad->render_pass_id);
   const cc::FilterOperations* regular_filters =
       FiltersForPass(quad->render_pass_id);
 
@@ -748,9 +789,13 @@
       quad_rect_matrix;
   contents_device_transform.FlattenTo2d();
 
+  base::Optional<gfx::RRectF> backdrop_filter_bounds;
+  gfx::Transform backdrop_filter_bounds_transform;
   gfx::Rect unclipped_rect;
   gfx::Rect backdrop_rect = GetBackdropBoundingBoxForRenderPassQuad(
-      quad, contents_device_transform, backdrop_filters, &unclipped_rect);
+      quad, backdrop_filters, regular_filters, backdrop_filter_bounds_input,
+      contents_device_transform, &backdrop_filter_bounds_transform,
+      &backdrop_filter_bounds, &unclipped_rect);
 
   // Figure out the transformations to move it back to pixel space.
   gfx::Transform contents_device_transform_inverse;
@@ -780,22 +825,40 @@
             quad->shared_quad_state->opacity));
   }
 
-  // TODO(916318): This function needs to apply the backdrop filter
-  // clip rect to the image.
+  gfx::Rect bitmap_rect =
+      gfx::Rect(0, 0, backdrop_bitmap.width(), backdrop_bitmap.height());
   sk_sp<SkImageFilter> filter =
       cc::RenderSurfaceFilters::BuildImageFilter(
           backdrop_filters_plus_effects,
-          gfx::SizeF(backdrop_bitmap.width(), backdrop_bitmap.height()),
+          gfx::SizeF(bitmap_rect.width(), bitmap_rect.height()),
           clipping_offset)
           ->cached_sk_filter_;
-  sk_sp<SkImage> filter_backdrop_image =
+  sk_sp<SkImage> filtered_image =
       ApplyImageFilter(filter.get(), quad, backdrop_bitmap, nullptr);
-
-  if (!filter_backdrop_image)
+  if (!filtered_image)
     return nullptr;
 
-  return filter_backdrop_image->makeShader(content_tile_mode, content_tile_mode,
-                                           &filter_backdrop_transform);
+  // Use an SkBitmap to paint the rrect-clipped filtered image.
+  SkImageInfo info =
+      SkImageInfo::MakeN32Premul(bitmap_rect.width(), bitmap_rect.height());
+  SkBitmap bitmap;
+  bitmap.allocPixels(info, info.minRowBytes());
+  SkCanvas canvas(bitmap);
+
+  // Clip the filtered image to the (rounded) bounding box of the element.
+  if (backdrop_filter_bounds) {
+    canvas.setMatrix(backdrop_filter_bounds_transform.matrix());
+    canvas.clipRRect(SkRRect(*backdrop_filter_bounds), SkClipOp::kIntersect,
+                     true /* antialias */);
+    canvas.resetMatrix();
+  }
+
+  // Now paint the pre-filtered image onto the canvas.
+  SkRect bitmap_skrect = RectToSkRect(bitmap_rect);
+  canvas.drawImageRect(filtered_image, bitmap_skrect, bitmap_skrect, nullptr);
+
+  return SkImage::MakeFromBitmap(bitmap)->makeShader(
+      content_tile_mode, content_tile_mode, &filter_backdrop_transform);
 }
 
 void SoftwareRenderer::UpdateRenderPassTextures(
diff --git a/components/viz/service/display/software_renderer.h b/components/viz/service/display/software_renderer.h
index c4fac4b..9ac27ed 100644
--- a/components/viz/service/display/software_renderer.h
+++ b/components/viz/service/display/software_renderer.h
@@ -87,9 +87,14 @@
                                   SkIRect* auto_bounds) const;
   gfx::Rect GetBackdropBoundingBoxForRenderPassQuad(
       const RenderPassDrawQuad* quad,
-      const gfx::Transform& contents_device_transform,
       const cc::FilterOperations* backdrop_filters,
+      const cc::FilterOperations* regular_filters,
+      base::Optional<gfx::RRectF> backdrop_filter_bounds_input,
+      gfx::Transform contents_device_transform,
+      gfx::Transform* backdrop_filter_bounds_transform,
+      base::Optional<gfx::RRectF>* backdrop_filter_bounds,
       gfx::Rect* unclipped_rect) const;
+
   SkBitmap GetBackdropBitmap(const gfx::Rect& bounding_rect) const;
   sk_sp<SkShader> GetBackdropFilterShader(const RenderPassDrawQuad* quad,
                                           SkTileMode content_tile_mode) const;
diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc
index 4341dff..b683e51 100644
--- a/components/viz/service/gl/gpu_service_impl.cc
+++ b/components/viz/service/gl/gpu_service_impl.cc
@@ -167,14 +167,22 @@
   if (vulkan_implementation_) {
     vulkan_context_provider_ =
         VulkanInProcessContextProvider::Create(vulkan_implementation_);
-    if (!vulkan_context_provider_)
+    if (vulkan_context_provider_) {
+      // If Vulkan is supported, then OOP-R is supported.
+      gpu_info_.oop_rasterization_supported = true;
+      gpu_feature_info_.status_values[gpu::GPU_FEATURE_TYPE_OOP_RASTERIZATION] =
+          gpu::kGpuFeatureStatusEnabled;
+    } else {
       DLOG(WARNING) << "Failed to create Vulkan context provider.";
+    }
   }
 #endif
 
 #if defined(OS_MACOSX)
-  if (gpu_preferences.enable_metal)
+  if (gpu_feature_info_.status_values[gpu::GPU_FEATURE_TYPE_METAL] ==
+      gpu::kGpuFeatureStatusEnabled) {
     metal_context_provider_ = MetalContextProvider::Create();
+  }
 #endif
 
   gpu_memory_buffer_factory_ =
diff --git a/components/viz/test/data/backdrop_filter_rotated_sw.png b/components/viz/test/data/backdrop_filter_rotated_sw.png
index 338e697..0a4fe04 100644
--- a/components/viz/test/data/backdrop_filter_rotated_sw.png
+++ b/components/viz/test/data/backdrop_filter_rotated_sw.png
Binary files differ
diff --git a/content/browser/DEPS b/content/browser/DEPS
index af8662b..b0b4efb 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -134,6 +134,5 @@
   # To share values of UMA enums between product code and tests.
   "cross_site_document_blocking_browsertest.cc": [
     "+services/network/cross_origin_read_blocking.h",
-    "+services/network/initiator_lock_compatibility.h",
   ],
 }
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index 3bff2b9..dc54c09 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -695,7 +695,10 @@
   }
   {
     TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:NetworkChangeNotifier");
-    network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
+    // On Android if reduced mode started network service this would already be
+    //  created.
+    if (!net::NetworkChangeNotifier::HasNetworkChangeNotifier())
+      network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
   }
   {
     TRACE_EVENT0("startup", "BrowserMainLoop::Subsystem:ScreenlockMonitor");
diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h
index 1fae724..c0ae415e 100644
--- a/content/browser/browser_main_loop.h
+++ b/content/browser/browser_main_loop.h
@@ -175,14 +175,16 @@
   media::UserInputMonitor* user_input_monitor() const {
     return user_input_monitor_.get();
   }
-  net::NetworkChangeNotifier* network_change_notifier() const {
-    return network_change_notifier_.get();
-  }
   MediaKeysListenerManagerImpl* media_keys_listener_manager() const {
     return media_keys_listener_manager_.get();
   }
 
 #if defined(OS_CHROMEOS)
+  // Only expose this on ChromeOS since it's only needed there. On Android this
+  // be null if this process started in reduced mode.
+  net::NetworkChangeNotifier* network_change_notifier() const {
+    return network_change_notifier_.get();
+  }
   KeyboardMicRegistration* keyboard_mic_registration() {
     return &keyboard_mic_registration_;
   }
diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc
index c00d975..12e1c5c 100644
--- a/content/browser/browsing_instance.cc
+++ b/content/browser/browsing_instance.cc
@@ -26,7 +26,8 @@
           BrowsingInstanceId::FromUnsafeValue(next_browsing_instance_id_++),
           BrowserOrResourceContext(browser_context)),
       active_contents_count_(0u),
-      default_process_(nullptr) {
+      default_process_(nullptr),
+      default_site_instance_(nullptr) {
   DCHECK(browser_context);
 }
 
@@ -51,8 +52,7 @@
 
 bool BrowsingInstance::IsDefaultSiteInstance(
     const SiteInstanceImpl* site_instance) const {
-  return site_instance != nullptr &&
-         site_instance == default_site_instance_.get();
+  return site_instance != nullptr && site_instance == default_site_instance_;
 }
 
 bool BrowsingInstance::HasSiteInstance(const GURL& url) {
@@ -117,11 +117,22 @@
       !SiteInstanceImpl::DoesSiteRequireDedicatedProcess(isolation_context_,
                                                          url)) {
     DCHECK(!default_process_);
-    if (!default_site_instance_) {
-      default_site_instance_ = new SiteInstanceImpl(this);
-      default_site_instance_->SetSite(SiteInstanceImpl::GetDefaultSiteURL());
+    scoped_refptr<SiteInstanceImpl> site_instance = default_site_instance_;
+    if (!site_instance) {
+      site_instance = new SiteInstanceImpl(this);
+
+      // Keep a copy of the pointer so it can be used for other URLs. This is
+      // safe because the SiteInstanceImpl destructor will call
+      // UnregisterSiteInstance() to clear this copy when the last
+      // reference to |site_instance| is destroyed.
+      // Note: This assignment MUST happen before the SetSite() call to ensure
+      // this instance is not added to |site_instance_map_| when SetSite()
+      // calls RegisterSiteInstance().
+      default_site_instance_ = site_instance.get();
+
+      site_instance->SetSite(SiteInstanceImpl::GetDefaultSiteURL());
     }
-    return default_site_instance_;
+    return site_instance;
   }
 
   return nullptr;
@@ -133,7 +144,7 @@
 
   // Explicitly prevent the |default_site_instance_| from being added since
   // the map is only supposed to contain instances that map to a single site.
-  if (site_instance == default_site_instance_.get())
+  if (site_instance == default_site_instance_)
     return;
 
   std::string site = site_instance->GetSiteURL().possibly_invalid_spec();
@@ -153,6 +164,12 @@
 void BrowsingInstance::UnregisterSiteInstance(SiteInstanceImpl* site_instance) {
   DCHECK(site_instance->browsing_instance_.get() == this);
   DCHECK(site_instance->HasSite());
+
+  if (site_instance == default_site_instance_) {
+    // The last reference to the default SiteInstance is being destroyed.
+    default_site_instance_ = nullptr;
+  }
+
   std::string site = site_instance->GetSiteURL().possibly_invalid_spec();
 
   // Only unregister the SiteInstance if it is the same one that is registered
@@ -175,6 +192,7 @@
   // us are gone.
   DCHECK(site_instance_map_.empty());
   DCHECK_EQ(0u, active_contents_count_);
+  DCHECK(!default_site_instance_);
   if (default_process_)
     default_process_->RemoveObserver(this);
 }
diff --git a/content/browser/browsing_instance.h b/content/browser/browsing_instance.h
index d371ea1..775b64a 100644
--- a/content/browser/browsing_instance.h
+++ b/content/browser/browsing_instance.h
@@ -203,8 +203,9 @@
   // |site_instance_map_| and it does not require a dedicated process.
   // This field and |default_process_| are mutually exclusive and this field
   // should only be set if kProcessSharingWithStrictSiteInstances is not
-  // enabled.
-  scoped_refptr<SiteInstanceImpl> default_site_instance_;
+  // enabled. This is a raw pointer to avoid a reference cycle between the
+  // BrowsingInstance and the SiteInstanceImpl.
+  SiteInstanceImpl* default_site_instance_;
 
   DISALLOW_COPY_AND_ASSIGN(BrowsingInstance);
 };
diff --git a/content/browser/compositor/reflector_impl_unittest.cc b/content/browser/compositor/reflector_impl_unittest.cc
index a56dc08..7d74c54 100644
--- a/content/browser/compositor/reflector_impl_unittest.cc
+++ b/content/browser/compositor/reflector_impl_unittest.cc
@@ -182,6 +182,13 @@
     reflector_->OnSourcePostSubBuffer(kSubRect, kSurfaceSize);
   }
 
+#if defined(USE_OZONE)
+  void CheckOverlaySupport(viz::OverlayCandidateList* surfaces) {
+    output_surface_->GetOverlayCandidateValidator()->CheckOverlaySupport(
+        surfaces);
+  }
+#endif  // defined(USE_OZONE)
+
  protected:
   std::unique_ptr<ui::TestContextFactories> context_factories_;
   scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_;
@@ -234,7 +241,7 @@
   plane_2.plane_z_order = 1;
   list.push_back(plane_1);
   list.push_back(plane_2);
-  output_surface_->GetOverlayCandidateValidator()->CheckOverlaySupport(&list);
+  CheckOverlaySupport(&list);
   EXPECT_TRUE(list[0].overlay_handled);
 }
 
@@ -250,7 +257,7 @@
   plane_2.plane_z_order = 1;
   list.push_back(plane_1);
   list.push_back(plane_2);
-  output_surface_->GetOverlayCandidateValidator()->CheckOverlaySupport(&list);
+  CheckOverlaySupport(&list);
   EXPECT_FALSE(list[0].overlay_handled);
 }
 #endif  // defined(USE_OZONE)
diff --git a/content/browser/devtools/protocol/system_info_handler.cc b/content/browser/devtools/protocol/system_info_handler.cc
index bdd7562..83b9de3c 100644
--- a/content/browser/devtools/protocol/system_info_handler.cc
+++ b/content/browser/devtools/protocol/system_info_handler.cc
@@ -94,10 +94,6 @@
 
   void EndImageDecodeAcceleratorSupportedProfile() override {}
 
-  void BeginOverlayCapability() override {}
-
-  void EndOverlayCapability() override {}
-
   void BeginDx12VulkanVersionInfo() override {}
 
   void EndDx12VulkanVersionInfo() override {}
diff --git a/content/browser/gpu/compositor_util.cc b/content/browser/gpu/compositor_util.cc
index 75be2ce..e850ca4 100644
--- a/content/browser/gpu/compositor_util.cc
+++ b/content/browser/gpu/compositor_util.cc
@@ -169,12 +169,13 @@
        "Out-of-process accelerated rasterization has been disabled, either "
        "via blacklist, about:flags or the command line.",
        false, true},
-      {"metal", gpu::kGpuFeatureStatusEnabled,
+      {"metal",
+       SafeGetFeatureStatus(gpu_feature_info, gpu::GPU_FEATURE_TYPE_METAL),
 #if defined(OS_MACOSX)
        !base::FeatureList::IsEnabled(features::kMetal) /* disabled */,
        "Metal is not enabled by default.",
 #else
-       true /* disabled */, "Metal only available on macOS.",
+       true /* disabled */, "Metal is only available on macOS.",
 #endif
        false /* fallback_to_software */, false /* needs_gpu_access */},
       {"multiple_raster_threads", gpu::kGpuFeatureStatusEnabled,
@@ -239,12 +240,6 @@
     std::string status;
     if (gpu_feature_data.name == "surface_synchronization") {
       status = (!gpu_feature_data.disabled ? "enabled_on" : "disabled_off");
-    } else if (gpu_feature_data.name == "metal") {
-#if defined(OS_MACOSX)
-      status = (!gpu_feature_data.disabled ? "enabled_on" : "disabled_off");
-#else
-      status = "unavailable_off";
-#endif
     } else if (gpu_feature_data.name == "viz_display_compositor") {
       status = (!gpu_feature_data.disabled ? "enabled_on" : "disabled_off");
     } else if (gpu_feature_data.disabled || gpu_access_blocked ||
@@ -284,6 +279,8 @@
         if (features::IsVizHitTestingSurfaceLayerEnabled())
           status += "_on";
       }
+      if (gpu_feature_data.name == "metal")
+        status += "_on";
     }
     feature_status_dict->SetString(gpu_feature_data.name, status);
   }
diff --git a/content/browser/gpu/gpu_internals_ui.cc b/content/browser/gpu/gpu_internals_ui.cc
index 59f38b7..68187d7 100644
--- a/content/browser/gpu/gpu_internals_ui.cc
+++ b/content/browser/gpu/gpu_internals_ui.cc
@@ -187,15 +187,12 @@
   basic_info->Append(NewDescriptionValuePair(
       "Supports overlays",
       std::make_unique<base::Value>(gpu_info.supports_overlays)));
-
-  auto overlay_capabilities = std::make_unique<base::ListValue>();
-  for (const auto& cap : gpu_info.overlay_capabilities) {
-    overlay_capabilities->Append(NewDescriptionValuePair(
-        gpu::OverlayFormatToString(cap.format),
-        cap.is_scaling_supported ? "SCALING" : "DIRECT"));
-  }
-  basic_info->Append(NewDescriptionValuePair("Overlay capabilities",
-                                             std::move(overlay_capabilities)));
+  basic_info->Append(NewDescriptionValuePair(
+      "YUY2 overlay support",
+      gpu::OverlaySupportToString(gpu_info.yuy2_overlay_support)));
+  basic_info->Append(NewDescriptionValuePair(
+      "NV12 overlay support",
+      gpu::OverlaySupportToString(gpu_info.nv12_overlay_support)));
 
   std::vector<gfx::PhysicalDisplaySize> display_sizes =
       gfx::GetPhysicalSizeForDisplays();
diff --git a/content/browser/loader/cross_site_document_blocking_browsertest.cc b/content/browser/loader/cross_site_document_blocking_browsertest.cc
index 8007638..0d65276 100644
--- a/content/browser/loader/cross_site_document_blocking_browsertest.cc
+++ b/content/browser/loader/cross_site_document_blocking_browsertest.cc
@@ -56,8 +56,6 @@
 using Action = network::CrossOriginReadBlocking::Action;
 using RequestInitiatorOriginLockCompatibility =
     network::InitiatorLockCompatibility;
-using CorbVsInitiatorLock =
-    network::CrossOriginReadBlocking::CorbResultVsInitiatorLockCompatibility;
 
 namespace {
 
@@ -116,37 +114,6 @@
     histograms.ExpectUniqueSample(
         "NetworkService.URLLoader.RequestInitiatorOriginLockCompatibility",
         expected_lock_compatibility, 1);
-
-    if (0 != (expectations & kShouldBeBlocked)) {
-      auto benign_blocking = base::Bucket(
-          static_cast<int>(CorbVsInitiatorLock::kBenignBlocking), 1);
-      auto compatible_blocking = base::Bucket(
-          static_cast<int>(CorbVsInitiatorLock::kBlockingWhenCompatibleLock),
-          1);
-      auto other_blocking = base::Bucket(
-          static_cast<int>(CorbVsInitiatorLock::kBlockingWhenOtherLock), 1);
-
-      ::testing::Matcher<std::vector<base::Bucket>> expected_buckets;
-      if (special_request_initiator_origin_lock_check_for_appcache) {
-        expected_buckets = ::testing::ElementsAre(other_blocking);
-      } else {
-        // Important part of the verification is that we never expect to
-        // encounted kBlockingWhenIncorrectLock (outside of HTML Import
-        // scenarios covered by separate, explicit tests below).
-        expected_buckets =
-            ::testing::AnyOf(::testing::ElementsAre(benign_blocking),
-                             ::testing::ElementsAre(compatible_blocking));
-      }
-
-      EXPECT_THAT(
-          histograms.GetAllSamples(
-              "SiteIsolation.XSD.NetworkService.InitiatorLockCompatibility"),
-          expected_buckets);
-    } else {  // ~kAllowed
-      histograms.ExpectUniqueSample(
-          "SiteIsolation.XSD.NetworkService.InitiatorLockCompatibility",
-          CorbVsInitiatorLock::kNoBlocking, 1);
-    }
   }
 
   std::string bucket;
@@ -1337,28 +1304,6 @@
       // which means that legitimate fetches from HTML Imported scripts may get
       // incorrectly blocked.
       interceptor.Verify(CorbExpectations::kShouldBeBlockedWithoutSniffing);
-
-      // The main purpose of the test is not verifying the incorrect behavior
-      // above, but making sure that the UMA that records the incorrect behavior
-      // is logged.  Hopefully the incorrect behavior will rarely occur in
-      // practice.
-      FetchHistogramsFromChildProcesses();
-      auto incorrect_lock_blocking = base::Bucket(
-          static_cast<int>(CorbVsInitiatorLock::kBlockingWhenIncorrectLock), 1);
-
-      // ExecuteScriptAsync covers 2 fetches:
-      // - Fetching cross_site_document_blocking/html_import.html
-      // - Fetching site_isolation/nosniff.json (via <script src=...>
-      //   from html_import.html)
-      // The second one is done with kIncorrectLock, but the assertion below
-      // needs to also cover the first one:
-      auto no_blocking =
-          base::Bucket(static_cast<int>(CorbVsInitiatorLock::kNoBlocking), 1);
-      EXPECT_THAT(
-          histograms.GetAllSamples(
-              "SiteIsolation.XSD.NetworkService.InitiatorLockCompatibility"),
-          ::testing::UnorderedElementsAre(no_blocking,
-                                          incorrect_lock_blocking));
     } else {
       // Without |request_initiator_site_lock| no CORB blocking is expected.
       interceptor.Verify(CorbExpectations::kShouldBeAllowedWithoutSniffing);
@@ -1429,22 +1374,6 @@
     std::string fetch_result;
     EXPECT_TRUE(msg_queue.WaitForMessage(&fetch_result));
     EXPECT_THAT(fetch_result, ::testing::HasSubstr("BODY: runMe"));
-
-    if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-      FetchHistogramsFromChildProcesses();
-
-      // ExecuteScriptAsync covers 3 fetches:
-      // - Fetching cross_site_document_blocking/html_import.html
-      // - Fetching cross_site_document_blocking/fetch_nosniff_json.js
-      // - Fetching site_isolation/nosniff.json
-      // All of them should result in no CORB blocking.
-      auto no_blocking =
-          base::Bucket(static_cast<int>(CorbVsInitiatorLock::kNoBlocking), 3);
-      EXPECT_THAT(
-          histograms.GetAllSamples(
-              "SiteIsolation.XSD.NetworkService.InitiatorLockCompatibility"),
-          ::testing::UnorderedElementsAre(no_blocking));
-    }
   }
 }
 
@@ -1511,25 +1440,6 @@
     std::string fetch_result;
     EXPECT_TRUE(msg_queue.WaitForMessage(&fetch_result));
     EXPECT_THAT(fetch_result, ::testing::HasSubstr("BODY: runMe"));
-
-    if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-      // The main purpose of the test is not verifying the incorrect behavior
-      // above, but making sure that the UMA that records the incorrect behavior
-      // is logged.  Hopefully the incorrect behavior will rarely occur in
-      // practice.
-      FetchHistogramsFromChildProcesses();
-
-      // ExecuteScriptAsync covers 3 fetches:
-      // - Fetching cross_site_document_blocking/html_import.html
-      // - Fetching site_isolation/nosniff.json
-      // All of them should result in no CORB blocking.
-      auto no_blocking =
-          base::Bucket(static_cast<int>(CorbVsInitiatorLock::kNoBlocking), 2);
-      EXPECT_THAT(
-          histograms.GetAllSamples(
-              "SiteIsolation.XSD.NetworkService.InitiatorLockCompatibility"),
-          ::testing::UnorderedElementsAre(no_blocking));
-    }
   }
 }
 
diff --git a/content/browser/media/media_web_contents_observer.cc b/content/browser/media/media_web_contents_observer.cc
index b5dfc64..5c029100 100644
--- a/content/browser/media/media_web_contents_observer.cc
+++ b/content/browser/media/media_web_contents_observer.cc
@@ -9,10 +9,8 @@
 #include "build/build_config.h"
 #include "content/browser/media/audible_metrics.h"
 #include "content/browser/media/audio_stream_monitor.h"
-#include "content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/media/media_player_delegate_messages.h"
-#include "content/public/browser/picture_in_picture_window_controller.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "ipc/ipc_message_macros.h"
diff --git a/content/browser/network_service_instance.cc b/content/browser/network_service_instance.cc
index 5f6b139..3b8f4483 100644
--- a/content/browser/network_service_instance.cc
+++ b/content/browser/network_service_instance.cc
@@ -243,9 +243,11 @@
   return g_network_service;
 }
 
+#if defined(OS_CHROMEOS)
 net::NetworkChangeNotifier* GetNetworkChangeNotifier() {
   return BrowserMainLoop::GetInstance()->network_change_notifier();
 }
+#endif
 
 void FlushNetworkServiceInstanceForTesting() {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
diff --git a/content/browser/picture_in_picture/picture_in_picture_session.cc b/content/browser/picture_in_picture/picture_in_picture_session.cc
index 641f514..0e6c05f 100644
--- a/content/browser/picture_in_picture/picture_in_picture_session.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_session.cc
@@ -7,7 +7,6 @@
 #include "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
 #include "content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
-#include "content/common/media/media_player_delegate_messages.h"
 
 namespace content {
 
@@ -77,7 +76,7 @@
   if (is_stopping_)
     return;
 
-  StopInternal(base::DoNothing());
+  StopInternal(base::NullCallback());
 }
 
 void PictureInPictureSession::StopInternal(StopCallback callback) {
@@ -87,13 +86,12 @@
 
   GetWebContentsImpl()->ExitPictureInPicture();
 
-  std::move(callback).Run();
-
-  // TODO(mlamouri): move to observer_->Stop();
-  player_id_->render_frame_host->Send(
-      new MediaPlayerDelegateMsg_EndPictureInPictureMode(
-          player_id_->render_frame_host->GetRoutingID(),
-          player_id_->delegate_id));
+  // `OnStopped()` should only be called if there is no callback to run, as a
+  // contract in the API.
+  if (callback)
+    std::move(callback).Run();
+  else
+    observer_->OnStopped();
 
   if (auto* controller = GetController())
     controller->SetActiveSession(nullptr);
@@ -104,7 +102,7 @@
 
 void PictureInPictureSession::OnConnectionError() {
   // StopInternal() will self destruct which will close the bindings.
-  StopInternal(base::DoNothing());
+  StopInternal(base::NullCallback());
 }
 
 WebContentsImpl* PictureInPictureSession::GetWebContentsImpl() {
diff --git a/content/browser/renderer_host/input/scroll_latency_browsertest.cc b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
index 79824eca..91e82b0 100644
--- a/content/browser/renderer_host/input/scroll_latency_browsertest.cc
+++ b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
@@ -355,12 +355,6 @@
       0, "Event.Latency.ScrollBegin.Touch.TimeToScrollUpdateSwapBegin4"));
 }
 
-IN_PROC_BROWSER_TEST_F(ScrollLatencyBrowserTest, ScrollbarButtonLatency) {
-  LoadURL();
-
-  RunScrollbarButtonLatencyTest();
-}
-
 class ScrollLatencyCompositedScrollbarBrowserTest
     : public ScrollLatencyBrowserTest {
  public:
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index 083957ab9..03fb996 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/run_loop.h"
 #include "base/strings/string16.h"
 #include "base/test/mock_log.h"
+#include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
 #include "content/browser/browsing_instance.h"
 #include "content/browser/child_process_security_policy_impl.h"
@@ -240,6 +241,43 @@
   // contents is now deleted, along with instance and browsing_instance
 }
 
+// Ensure that default SiteInstances are deleted when all references to them
+// are gone.
+TEST_F(SiteInstanceTest, DefaultSiteInstanceDestruction) {
+  // Skip this test case if the --site-per-process switch is present (e.g. on
+  // Site Isolation Android chromium.fyi bot).
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kSitePerProcess)) {
+    return;
+  }
+
+  TestBrowserContext browser_context;
+  base::test::ScopedCommandLine scoped_command_line;
+
+  // Disable site isolation so we can get default SiteInstances on all
+  // platforms.
+  scoped_command_line.GetProcessCommandLine()->AppendSwitch(
+      switches::kDisableSiteIsolation);
+
+  // Ensure that default SiteInstances are deleted when all references to them
+  // are gone.
+  auto site_instance1 =
+      SiteInstanceImpl::CreateForURL(&browser_context, GURL("http://foo.com"));
+  // TODO(958060): Remove the creation of this second instance and update
+  // the deletion count below once CreateForURL() starts returning a default
+  // SiteInstance for sites that don't require a dedicated process.
+  auto site_instance2 =
+      site_instance1->GetRelatedSiteInstance(GURL("http://bar.com"));
+  EXPECT_FALSE(site_instance1->IsDefaultSiteInstance());
+  EXPECT_TRUE(static_cast<SiteInstanceImpl*>(site_instance2.get())
+                  ->IsDefaultSiteInstance());
+  site_instance1.reset();
+  site_instance2.reset();
+
+  EXPECT_EQ(2, browser_client()->GetAndClearSiteInstanceDeleteCount());
+  EXPECT_EQ(1, browser_client()->GetAndClearBrowsingInstanceDeleteCount());
+}
+
 // Test to ensure GetProcess returns and creates processes correctly.
 TEST_F(SiteInstanceTest, GetProcess) {
   // Ensure that GetProcess returns a process.
diff --git a/content/browser/webauth/authenticator_common.cc b/content/browser/webauth/authenticator_common.cc
index 60a0dec..21eca3df 100644
--- a/content/browser/webauth/authenticator_common.cc
+++ b/content/browser/webauth/authenticator_common.cc
@@ -1304,7 +1304,7 @@
       break;
     case AuthenticatorRequestClientDelegate::InterestingFailureReason::
         kKeyNotRegistered:
-      status = blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR;
+      status = blink::mojom::AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED;
       break;
     case AuthenticatorRequestClientDelegate::InterestingFailureReason::kTimeout:
       status = blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR;
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index dc973fc..8d292a1 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -728,7 +728,7 @@
     SCOPED_TRACE(std::string(test_case.origin) + " " +
                  std::string(test_case.claimed_authority));
 
-    EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
+    EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
               TryAuthenticationWithAppId(test_case.origin,
                                          test_case.claimed_authority));
   }
@@ -1055,7 +1055,7 @@
     if (should_be_valid) {
       EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
     } else {
-      EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
+      EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
                 callback_receiver.status());
     }
   }
@@ -1140,7 +1140,7 @@
       // The virtual device will return an error because
       // |reject_silent_authentication_requests| is true and then it'll
       // immediately resolve the touch request.
-      EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
+      EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
                 callback_receiver.status());
     }
   }
@@ -1365,7 +1365,7 @@
     authenticator->GetAssertion(GetTestPublicKeyCredentialRequestOptions(),
                                 callback_receiver.callback());
     callback_receiver.WaitForCallback();
-    EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
+    EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
               callback_receiver.status());
     // The user must have pressed the authenticator for the operation to
     // resolve.
@@ -2476,7 +2476,8 @@
                               callback_receiver.callback());
 
   callback_receiver.WaitForCallback();
-  EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status());
+  EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
+            callback_receiver.status());
 
   ASSERT_TRUE(failure_reason_receiver.was_called());
   EXPECT_EQ(content::AuthenticatorRequestClientDelegate::
@@ -2632,8 +2633,9 @@
     base::RunLoop().RunUntilIdle();
     callback_receiver.WaitForCallback();
     EXPECT_EQ(callback_receiver.status(),
-              has_allowed_credential ? AuthenticatorStatus::SUCCESS
-                                     : AuthenticatorStatus::NOT_ALLOWED_ERROR);
+              has_allowed_credential
+                  ? AuthenticatorStatus::SUCCESS
+                  : AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED);
   }
 }
 
@@ -3052,7 +3054,7 @@
 
         switch (expected[support_level][uv_level]) {
           case kFailure:
-            EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
+            EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
                       callback_receiver.status());
             break;
 
@@ -3239,7 +3241,7 @@
       callback_receiver.WaitForCallback();
 
       if (should_be_unrecognized) {
-        EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
+        EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
                   callback_receiver.status());
       } else {
         EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
diff --git a/content/browser/webauth/webauth_browsertest.cc b/content/browser/webauth/webauth_browsertest.cc
index 3e147cf..138f417 100644
--- a/content/browser/webauth/webauth_browsertest.cc
+++ b/content/browser/webauth/webauth_browsertest.cc
@@ -75,7 +75,7 @@
     "webauth: NotSupportedError: Required parameters missing in "
     "`options.publicKey`.";
 
-constexpr char kNotAllowedErrorMessage[] =
+constexpr char kTimeoutErrorMessage[] =
     "webauth: NotAllowedError: The operation either timed out or was not "
     "allowed. See: https://w3c.github.io/webauthn/#sec-assertion-privacy.";
 
@@ -93,6 +93,10 @@
 constexpr char kRelyingPartyRpIconUrlSecurityErrorMessage[] =
     "webauth: SecurityError: 'rp.icon' should be a secure URL";
 
+constexpr char kInvalidStateError[] =
+    "webauth: InvalidStateError: The user attempted to use an authenticator "
+    "that recognized none of the provided credentials.";
+
 constexpr char kAbortErrorMessage[] =
     "webauth: AbortError: The user aborted a request.";
 
@@ -810,7 +814,7 @@
     ASSERT_TRUE(content::ExecuteScriptAndExtractString(
         shell()->web_contents()->GetMainFrame(),
         BuildCreateCallWithParameters(parameters), &result));
-    ASSERT_EQ(kNotAllowedErrorMessage, result);
+    ASSERT_EQ(kTimeoutErrorMessage, result);
   }
 }
 
@@ -848,7 +852,7 @@
         shell()->web_contents()->GetMainFrame(),
         BuildCreateCallWithParameters(parameters), &result));
 
-    ASSERT_EQ(kNotAllowedErrorMessage, result);
+    ASSERT_EQ(kTimeoutErrorMessage, result);
   }
 }
 
@@ -867,7 +871,7 @@
         shell()->web_contents()->GetMainFrame(),
         BuildCreateCallWithParameters(parameters), &result));
 
-    ASSERT_EQ(kNotAllowedErrorMessage, result);
+    ASSERT_EQ(kTimeoutErrorMessage, result);
   }
 }
 // Tests that when navigator.credentials.create() is called with abort
@@ -945,7 +949,7 @@
     ASSERT_TRUE(content::ExecuteScriptAndExtractString(
         shell()->web_contents()->GetMainFrame(),
         BuildGetCallWithParameters(parameters), &result));
-    ASSERT_EQ(kNotAllowedErrorMessage, result);
+    ASSERT_EQ(kInvalidStateError, result);
   }
 }
 
@@ -964,7 +968,7 @@
 }
 
 // Tests that when navigator.credentials.get() is called with abort
-// signal's aborted flag not set, we get a NOT_ALLOWED_ERROR, because the
+// signal's aborted flag not set, we get a kInvalidStateError, because the
 // virtual device does not have any registered credentials.
 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
                        GetPublicKeyCredentialWithAbortNotSet) {
@@ -982,7 +986,7 @@
 
     ASSERT_TRUE(content::ExecuteScriptAndExtractString(
         shell()->web_contents()->GetMainFrame(), script, &result));
-    ASSERT_EQ(kNotAllowedErrorMessage, result);
+    ASSERT_EQ(kInvalidStateError, result);
   }
 }
 
@@ -1202,7 +1206,7 @@
       shell()->web_contents(),
       BuildCreateCallWithParameters(CreateParameters()), "webauth: ");
   ASSERT_TRUE(result);
-  ASSERT_EQ(kNotAllowedErrorMessage, *result);
+  ASSERT_EQ(kTimeoutErrorMessage, *result);
 }
 
 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, WinGetAssertion) {
@@ -1224,7 +1228,7 @@
       shell()->web_contents(), BuildGetCallWithParameters(GetParameters()),
       "webauth: ");
   ASSERT_TRUE(result);
-  ASSERT_EQ(kNotAllowedErrorMessage, *result);
+  ASSERT_EQ(kTimeoutErrorMessage, *result);
 }
 
 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
@@ -1240,7 +1244,7 @@
       shell()->web_contents(), BuildGetCallWithParameters(GetParameters()),
       "webauth: ");
   ASSERT_TRUE(result);
-  ASSERT_EQ(kNotAllowedErrorMessage, *result);
+  ASSERT_EQ(kTimeoutErrorMessage, *result);
 }
 #endif
 
@@ -1327,7 +1331,7 @@
     authenticator()->GetAssertion(std::move(get_assertion_request_params),
                                   get_callback_receiver.callback());
     get_callback_receiver.WaitForCallback();
-    EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
+    EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
               get_callback_receiver.status());
   }
 }
diff --git a/content/child/blink_platform_impl.cc b/content/child/blink_platform_impl.cc
index 7a09b30..34d96aa 100644
--- a/content/child/blink_platform_impl.cc
+++ b/content/child/blink_platform_impl.cc
@@ -50,6 +50,7 @@
 #include "third_party/blink/public/resources/grit/blink_resources.h"
 #include "third_party/zlib/google/compression_utils.h"
 #include "ui/base/layout.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/events/gestures/blink/web_gesture_curve_impl.h"
 
 #if defined(OS_ANDROID)
@@ -77,6 +78,8 @@
 #if defined(OS_ANDROID)
   return std::make_unique<WebThemeEngineAndroid>();
 #elif defined(OS_MACOSX)
+  if (features::IsFormControlsRefreshEnabled())
+    return std::make_unique<WebThemeEngineDefault>();
   return std::make_unique<WebThemeEngineMac>();
 #else
   return std::make_unique<WebThemeEngineDefault>();
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index b6a8d8a..160d89d 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -23,6 +23,7 @@
 #include "services/network/public/cpp/features.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/platform/web_runtime_features.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/events/blink/blink_features.h"
 #include "ui/gfx/switches.h"
 #include "ui/gl/gl_switches.h"
@@ -501,6 +502,9 @@
 
   WebRuntimeFeatures::EnableDisplayLocking(
       base::FeatureList::IsEnabled(blink::features::kDisplayLocking));
+
+  WebRuntimeFeatures::EnableFormControlsRefresh(
+      features::IsFormControlsRefreshEnabled());
 }
 
 }  // namespace
diff --git a/content/common/media/media_player_delegate_messages.h b/content/common/media/media_player_delegate_messages.h
index 706adc9..e9219e9 100644
--- a/content/common/media/media_player_delegate_messages.h
+++ b/content/common/media/media_player_delegate_messages.h
@@ -13,7 +13,6 @@
 
 #include <stdint.h>
 
-#include "components/viz/common/surfaces/surface_id.h"
 #include "content/common/content_export.h"
 #include "ipc/ipc_message_macros.h"
 #include "media/base/media_content_type.h"
@@ -61,9 +60,6 @@
                     int /* delegate_id, distinguishes instances */,
                     double /* is_persistent */)
 
-IPC_MESSAGE_ROUTED1(MediaPlayerDelegateMsg_EndPictureInPictureMode,
-                    int /* delegate_id, distinguishes instances */)
-
 // ----------------------------------------------------------------------------
 // Messages from the renderer notifying the browser of playback state changes.
 // ----------------------------------------------------------------------------
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessRanking.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessRanking.java
index e3e1987..846d347 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessRanking.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessRanking.java
@@ -4,6 +4,8 @@
 
 package org.chromium.content.browser;
 
+import android.os.Handler;
+
 import org.chromium.base.BuildConfig;
 import org.chromium.base.process_launcher.ChildProcessConnection;
 import org.chromium.content_public.browser.ChildProcessImportance;
@@ -26,6 +28,12 @@
     // so should support 2^16 connections, which should be way more than enough.
     private static final int FROM_RIGHT = 32768;
 
+    // Delay after group is rebound so that higher ranked processes are more recent.
+    // Note the post and delay is to avoid extra rebinds when rank for multiple connections
+    // change together, eg if visibility changes for a tab with a number of out-of-process
+    // iframes.
+    private static final int REBIND_DELAY_MS = 1000;
+
     private static class ConnectionWithRank {
         public final ChildProcessConnection connection;
 
@@ -161,13 +169,17 @@
 
     private static final RankComparator COMPARATOR = new RankComparator();
 
+    private final Handler mHandler = new Handler();
     // |mMaxSize| can be -1 to indicate there can be arbitrary number of connections.
     private final int mMaxSize;
     // ArrayList is not the most theoretically efficient data structure, but is good enough
     // for sizes in production and more memory efficient than linked data structures.
     private final List<ConnectionWithRank> mRankings = new ArrayList<>();
 
+    private final Runnable mRebindRunnable = this::rebindHighRankConnections;
+
     private boolean mEnableServiceGroupImportance;
+    private boolean mRebindRunnablePending;
 
     public ChildProcessRanking() {
         mMaxSize = -1;
@@ -185,6 +197,7 @@
         assert !mEnableServiceGroupImportance;
         mEnableServiceGroupImportance = true;
         reshuffleGroupImportance();
+        postRebindHighRankConnectionsIfNeeded();
         if (ENABLE_CHECKS) checkGroupImportance();
     }
 
@@ -308,6 +321,7 @@
             reshuffleGroupImportance();
         }
 
+        postRebindHighRankConnectionsIfNeeded();
         if (ENABLE_CHECKS) checkGroupImportance();
     }
 
@@ -321,6 +335,21 @@
         }
     }
 
+    private void postRebindHighRankConnectionsIfNeeded() {
+        if (mRebindRunnablePending) return;
+        mHandler.postDelayed(mRebindRunnable, REBIND_DELAY_MS);
+        mRebindRunnablePending = true;
+    }
+
+    private void rebindHighRankConnections() {
+        mRebindRunnablePending = false;
+        for (int i = mRankings.size() - 1; i >= 0; --i) {
+            ConnectionWithRank connection = mRankings.get(i);
+            if (connection.shouldBeInLowRankGroup()) continue;
+            connection.connection.rebind();
+        }
+    }
+
     private void checkOrder() {
         boolean crossedLowRankGroupCutoff = false;
         for (int i = 0; i < mRankings.size(); ++i) {
diff --git a/content/public/android/junit/src/org/chromium/content/browser/ChildProcessRankingTest.java b/content/public/android/junit/src/org/chromium/content/browser/ChildProcessRankingTest.java
index 4bbcb10f..be1cf48 100644
--- a/content/public/android/junit/src/org/chromium/content/browser/ChildProcessRankingTest.java
+++ b/content/public/android/junit/src/org/chromium/content/browser/ChildProcessRankingTest.java
@@ -10,6 +10,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
 
 import org.chromium.base.process_launcher.ChildProcessConnection;
 import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -20,7 +21,7 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class ChildProcessRankingTest {
-    private ChildProcessConnection createConnection() {
+    private TestChildProcessConnection createConnection() {
         TestChildProcessConnection connection = new TestChildProcessConnection(
                 new ComponentName("pkg", "cls"), false /* bindToCallerCheck */,
                 false /* bindAsExternalService */, null /* serviceBundle */);
@@ -269,4 +270,34 @@
         }
         Assert.assertTrue(exceptionThrown);
     }
+
+    @Test
+    public void testRebindHighRankConnection() {
+        ChildProcessRanking ranking = new ChildProcessRanking();
+        ranking.enableServiceGroupImportance();
+
+        TestChildProcessConnection c1 = createConnection();
+        TestChildProcessConnection c2 = createConnection();
+        TestChildProcessConnection c3 = createConnection();
+
+        ranking.addConnection(c1, true /* foreground */, 0 /* frameDepth */,
+                false /* intersectsViewport */, ChildProcessImportance.IMPORTANT);
+        ranking.addConnection(c2, true /* foreground */, 2 /* frameDepth */,
+                false /* intersectsViewport */, ChildProcessImportance.NORMAL);
+        ranking.addConnection(c3, true /* foreground */, 3 /* frameDepth */,
+                false /* intersectsViewport */, ChildProcessImportance.NORMAL);
+
+        assertNotInGroup(new ChildProcessConnection[] {c1});
+        assertInGroupOrderedByImportance(new ChildProcessConnection[] {c2, c3});
+
+        c1.getAndResetRebindCalled();
+        ranking.updateConnection(c3, true /* foreground */, 1 /* frameDepth */,
+                false /* intersectsViewport */, ChildProcessImportance.NORMAL);
+        assertNotInGroup(new ChildProcessConnection[] {c1});
+        assertInGroupOrderedByImportance(new ChildProcessConnection[] {c3, c2});
+        Assert.assertFalse(c1.getAndResetRebindCalled());
+
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+        Assert.assertTrue(c1.getAndResetRebindCalled());
+    }
 }
diff --git a/content/public/browser/network_service_instance.h b/content/public/browser/network_service_instance.h
index 3bed383..b79141e 100644
--- a/content/public/browser/network_service_instance.h
+++ b/content/public/browser/network_service_instance.h
@@ -9,6 +9,7 @@
 
 #include "base/callback.h"
 #include "base/callback_list.h"
+#include "build/build_config.h"
 #include "content/common/content_export.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
 
@@ -63,8 +64,11 @@
 // service is enabled.
 CONTENT_EXPORT network::NetworkService* GetNetworkServiceImpl();
 
+// Only on ChromeOS since it's only used there.
+#if defined(OS_CHROMEOS)
 // Returns the global NetworkChangeNotifier instance.
 CONTENT_EXPORT net::NetworkChangeNotifier* GetNetworkChangeNotifier();
+#endif
 
 // Call |FlushForTesting()| on cached |NetworkServicePtr|. For testing only.
 // Must only be called on the UI thread.
diff --git a/content/renderer/input/render_widget_input_handler.cc b/content/renderer/input/render_widget_input_handler.cc
index f4783bf..ff1ec5f 100644
--- a/content/renderer/input/render_widget_input_handler.cc
+++ b/content/renderer/input/render_widget_input_handler.cc
@@ -216,8 +216,7 @@
       handling_event_overscroll_(nullptr),
       handling_injected_scroll_params_(nullptr),
       handling_event_type_(WebInputEvent::kUndefined),
-      suppress_next_char_events_(false),
-      last_injected_gesture_was_begin_(false) {
+      suppress_next_char_events_(false) {
   DCHECK(delegate);
   DCHECK(widget);
   delegate->SetInputHandler(this);
@@ -442,9 +441,23 @@
   // scroll gestures back into blink, e.g., a mousedown on a scrollbar. We
   // do this here so that we can attribute lateny information from the mouse as
   // a scroll interaction, instead of just classifying as mouse input.
-  if (injected_scroll_params && injected_scroll_params->size()) {
-    HandleInjectedScrollGestures(*injected_scroll_params, input_event,
-                                 latency_info);
+  if (injected_scroll_params) {
+    // TODO(dlibby): Create a copy of latency_info with new type
+    // and set a new swap promise monitor.
+    WebFloatPoint position = ui::PositionInWidgetFromInputEvent(input_event);
+    for (const InjectScrollGestureParams& params : *injected_scroll_params) {
+      std::unique_ptr<WebGestureEvent> gesture_event =
+          ui::GenerateInjectedScrollGesture(
+              params.type, input_event.TimeStamp(), params.device, position,
+              params.scroll_delta, params.granularity);
+      if (params.type == WebInputEvent::Type::kGestureScrollBegin) {
+        gesture_event->data.scroll_begin.scrollable_area_element_id =
+            params.scrollable_area_element_id.GetInternalValue();
+      }
+
+      widget_->GetWebWidget()->HandleInputEvent(
+          blink::WebCoalescedInputEvent(*gesture_event.get()));
+    }
   }
 
   // Send gesture scroll events and their dispositions to the compositor thread,
@@ -526,7 +539,6 @@
 }
 
 void RenderWidgetInputHandler::InjectGestureScrollEvent(
-    blink::WebGestureDevice device,
     const blink::WebFloatSize& delta,
     blink::WebScrollGranularity granularity,
     cc::ElementId scrollable_area_element_id,
@@ -552,15 +564,16 @@
           std::make_unique<std::vector<InjectScrollGestureParams>>();
     }
 
-    InjectScrollGestureParams params{device, delta, granularity,
-                                     scrollable_area_element_id, injected_type};
+    InjectScrollGestureParams params{blink::WebGestureDevice::kScrollbar, delta,
+                                     granularity, scrollable_area_element_id,
+                                     injected_type};
     (*handling_injected_scroll_params_)->push_back(params);
   } else {
     base::TimeTicks now = base::TimeTicks::Now();
     std::unique_ptr<WebGestureEvent> gesture_event =
-        ui::GenerateInjectedScrollGesture(injected_type, now, device,
-                                          WebFloatPoint(0, 0), delta,
-                                          granularity);
+        ui::GenerateInjectedScrollGesture(
+            injected_type, now, blink::WebGestureDevice::kScrollbar,
+            WebFloatPoint(0, 0), delta, granularity);
     if (injected_type == WebInputEvent::Type::kGestureScrollBegin) {
       gesture_event->data.scroll_begin.scrollable_area_element_id =
           scrollable_area_element_id.GetInternalValue();
@@ -590,69 +603,6 @@
   }
 }
 
-void RenderWidgetInputHandler::HandleInjectedScrollGestures(
-    std::vector<InjectScrollGestureParams> injected_scroll_params,
-    const WebInputEvent& input_event,
-    const ui::LatencyInfo& original_latency_info) {
-  DCHECK(injected_scroll_params.size());
-
-  base::TimeTicks original_timestamp;
-  bool found_original_component = original_latency_info.FindLatency(
-      ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, &original_timestamp);
-  DCHECK(found_original_component);
-
-  WebFloatPoint position = ui::PositionInWidgetFromInputEvent(input_event);
-  for (const InjectScrollGestureParams& params : injected_scroll_params) {
-    // Set up a new LatencyInfo for the injected scroll - this is the original
-    // LatencyInfo for the input event that was being handled when the scroll
-    // was injected. This new LatencyInfo will have a modified type, and an
-    // additional scroll update component. Also set up a SwapPromiseMonitor that
-    // will cause the LatencyInfo to be sent up with the compositor frame, if
-    // the GSU causes a commit. This allows end to end latency to be logged for
-    // the injected scroll, annotated with the correct type.
-    ui::LatencyInfo scrollbar_latency_info(original_latency_info);
-
-    // Currently only scrollbar is supported - if this DCHECK hits due to a
-    // new type being injected, please modify the type passed to
-    // |set_source_event_type()|.
-    DCHECK(params.device == blink::WebGestureDevice::kScrollbar);
-    scrollbar_latency_info.set_source_event_type(
-        ui::SourceEventType::SCROLLBAR);
-    scrollbar_latency_info.AddLatencyNumber(
-        ui::LatencyComponentType::INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT);
-
-    if (params.type == WebInputEvent::Type::kGestureScrollUpdate) {
-      scrollbar_latency_info.AddLatencyNumberWithTimestamp(
-          last_injected_gesture_was_begin_
-              ? ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT
-              : ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
-          original_timestamp, 1);
-    }
-
-    std::unique_ptr<cc::SwapPromiseMonitor> swap_promise_monitor;
-    if (widget_->layer_tree_view()) {
-      swap_promise_monitor =
-          widget_->layer_tree_view()->CreateLatencyInfoSwapPromiseMonitor(
-              &scrollbar_latency_info);
-    }
-
-    std::unique_ptr<WebGestureEvent> gesture_event =
-        ui::GenerateInjectedScrollGesture(
-            params.type, input_event.TimeStamp(), params.device, position,
-            params.scroll_delta, params.granularity);
-    if (params.type == WebInputEvent::Type::kGestureScrollBegin) {
-      gesture_event->data.scroll_begin.scrollable_area_element_id =
-          params.scrollable_area_element_id.GetInternalValue();
-      last_injected_gesture_was_begin_ = true;
-    } else {
-      last_injected_gesture_was_begin_ = false;
-    }
-
-    widget_->GetWebWidget()->HandleInputEvent(
-        blink::WebCoalescedInputEvent(*gesture_event.get()));
-  }
-}
-
 bool RenderWidgetInputHandler::DidChangeCursor(const WebCursor& cursor) {
   if (current_cursor_.has_value() && current_cursor_.value() == cursor)
     return false;
diff --git a/content/renderer/input/render_widget_input_handler.h b/content/renderer/input/render_widget_input_handler.h
index bf67706..8a8c5942 100644
--- a/content/renderer/input/render_widget_input_handler.h
+++ b/content/renderer/input/render_widget_input_handler.h
@@ -67,8 +67,7 @@
                               const blink::WebFloatSize& velocity,
                               const cc::OverscrollBehavior& behavior);
 
-  void InjectGestureScrollEvent(blink::WebGestureDevice device,
-                                const blink::WebFloatSize& delta,
+  void InjectGestureScrollEvent(const blink::WebFloatSize& delta,
                                 blink::WebScrollGranularity granularity,
                                 cc::ElementId scrollable_area_element_id,
                                 blink::WebInputEvent::Type injected_type);
@@ -98,11 +97,6 @@
   blink::WebInputEventResult HandleTouchEvent(
       const blink::WebCoalescedInputEvent& coalesced_event);
 
-  void HandleInjectedScrollGestures(
-      std::vector<InjectScrollGestureParams> injected_scroll_params,
-      const WebInputEvent& input_event,
-      const ui::LatencyInfo& original_latency_info);
-
   RenderWidgetInputHandlerDelegate* const delegate_;
 
   RenderWidget* const widget_;
@@ -134,11 +128,6 @@
   // Indicates if the next sequence of Char events should be suppressed or not.
   bool suppress_next_char_events_;
 
-  // Whether the last injected scroll gesture was a GestureScrollBegin. Used to
-  // determine which GestureScrollUpdate is the first in a gesture sequence for
-  // latency classification.
-  bool last_injected_gesture_was_begin_;
-
   DISALLOW_COPY_AND_ASSIGN(RenderWidgetInputHandler);
 };
 
diff --git a/content/renderer/media/renderer_webmediaplayer_delegate.cc b/content/renderer/media/renderer_webmediaplayer_delegate.cc
index 4b8faa4..d8cbaf1 100644
--- a/content/renderer/media/renderer_webmediaplayer_delegate.cc
+++ b/content/renderer/media/renderer_webmediaplayer_delegate.cc
@@ -238,8 +238,6 @@
                         OnMediaDelegateVolumeMultiplierUpdate)
     IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_BecamePersistentVideo,
                         OnMediaDelegateBecamePersistentVideo)
-    IPC_MESSAGE_HANDLER(MediaPlayerDelegateMsg_EndPictureInPictureMode,
-                        OnPictureInPictureModeEnded)
     IPC_MESSAGE_UNHANDLED(return false)
   IPC_END_MESSAGE_MAP()
   return true;
@@ -355,13 +353,6 @@
     observer->OnBecamePersistentVideo(value);
 }
 
-void RendererWebMediaPlayerDelegate::OnPictureInPictureModeEnded(
-    int player_id) {
-  Observer* observer = id_map_.Lookup(player_id);
-  if (observer)
-    observer->OnPictureInPictureModeEnded();
-}
-
 void RendererWebMediaPlayerDelegate::ScheduleUpdateTask() {
   if (!pending_update_task_) {
     base::ThreadTaskRunnerHandle::Get()->PostTask(
diff --git a/content/renderer/media/renderer_webmediaplayer_delegate.h b/content/renderer/media/renderer_webmediaplayer_delegate.h
index 3e3921e..fc39ff3 100644
--- a/content/renderer/media/renderer_webmediaplayer_delegate.h
+++ b/content/renderer/media/renderer_webmediaplayer_delegate.h
@@ -95,7 +95,6 @@
   void OnMediaDelegateSuspendAllMediaPlayers();
   void OnMediaDelegateVolumeMultiplierUpdate(int player_id, double multiplier);
   void OnMediaDelegateBecamePersistentVideo(int player_id, bool value);
-  void OnPictureInPictureModeEnded(int player_id);
 
   // Schedules UpdateTask() to run soon.
   void ScheduleUpdateTask();
diff --git a/content/renderer/media/stream/webmediaplayer_ms.cc b/content/renderer/media/stream/webmediaplayer_ms.cc
index 92d19cd..b7a85e9 100644
--- a/content/renderer/media/stream/webmediaplayer_ms.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms.cc
@@ -927,15 +927,6 @@
   get_client()->OnBecamePersistentVideo(value);
 }
 
-void WebMediaPlayerMS::OnPictureInPictureModeEnded() {
-  // It is possible for this method to be called when the player is no longer in
-  // Picture-in-Picture mode.
-  if (!client_ || !IsInPictureInPicture())
-    return;
-
-  client_->PictureInPictureStopped();
-}
-
 bool WebMediaPlayerMS::CopyVideoTextureToPlatformTexture(
     gpu::gles2::GLES2Interface* gl,
     unsigned target,
diff --git a/content/renderer/media/stream/webmediaplayer_ms.h b/content/renderer/media/stream/webmediaplayer_ms.h
index 5ca7055..dc148fd 100644
--- a/content/renderer/media/stream/webmediaplayer_ms.h
+++ b/content/renderer/media/stream/webmediaplayer_ms.h
@@ -181,7 +181,6 @@
   void OnSeekBackward(double seconds) override;
   void OnVolumeMultiplierUpdate(double multiplier) override;
   void OnBecamePersistentVideo(bool value) override;
-  void OnPictureInPictureModeEnded() override;
 
   void OnFirstFrameReceived(media::VideoRotation video_rotation,
                             bool is_opaque);
diff --git a/content/renderer/media/stream/webmediaplayer_ms_compositor.cc b/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
index 0b4437a..023faa7 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms_compositor.cc
@@ -521,49 +521,75 @@
     ++dropped_frame_count_;
   current_frame_rendered_ = false;
 
-  scoped_refptr<media::VideoFrame> old_frame = std::move(current_frame_);
-  current_frame_ = frame;
+  // Compare current frame with |frame|. Initialize values as if there is no
+  // current frame.
+  bool is_first_frame = true;
+  bool has_frame_size_changed = false;
+  base::Optional<media::VideoRotation> new_rotation = media::VIDEO_ROTATION_0;
+  base::Optional<bool> new_opacity;
+
+  new_opacity = media::IsOpaque(frame->format());
+  media::VideoRotation current_video_rotation;
+  if (frame->metadata()->GetRotation(media::VideoFrameMetadata::ROTATION,
+                                     &current_video_rotation)) {
+    new_rotation = current_video_rotation;
+  }
+
+  if (current_frame_) {
+    // We have a current frame, so determine what has changed.
+    is_first_frame = false;
+
+    if (!current_frame_->metadata()->GetRotation(
+            media::VideoFrameMetadata::ROTATION, &current_video_rotation) ||
+        current_video_rotation == *new_rotation) {
+      new_rotation.reset();
+    }
+
+    if (*new_opacity == media::IsOpaque(current_frame_->format()))
+      new_opacity.reset();
+
+    has_frame_size_changed =
+        frame->natural_size() != current_frame_->natural_size();
+  }
+
+  current_frame_ = std::move(frame);
 
   // Complete the checks after |current_frame_| is accessible to avoid
   // deadlocks, see https://crbug.com/901744.
   video_frame_compositor_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&WebMediaPlayerMSCompositor::CheckForFrameChanges, this,
-                     std::move(old_frame), std::move(frame)));
+                     is_first_frame, has_frame_size_changed,
+                     std::move(new_rotation), std::move(new_opacity)));
 }
 
 void WebMediaPlayerMSCompositor::CheckForFrameChanges(
-    scoped_refptr<media::VideoFrame> old_frame,
-    scoped_refptr<media::VideoFrame> new_frame) {
+    bool is_first_frame,
+    bool has_frame_size_changed,
+    base::Optional<media::VideoRotation> new_frame_rotation,
+    base::Optional<bool> new_frame_opacity) {
   DCHECK(video_frame_compositor_task_runner_->BelongsToCurrentThread());
 
-  const bool new_frame_is_opaque = media::IsOpaque(new_frame->format());
-  media::VideoRotation new_frame_video_rotation = media::VIDEO_ROTATION_0;
-  ignore_result(new_frame->metadata()->GetRotation(
-      media::VideoFrameMetadata::ROTATION, &new_frame_video_rotation));
-  if (!old_frame) {
+  if (is_first_frame) {
     main_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&WebMediaPlayerMS::OnFirstFrameReceived, player_,
-                       new_frame_video_rotation, new_frame_is_opaque));
+                       *new_frame_rotation, *new_frame_opacity));
     return;
   }
-  media::VideoRotation old_frame_video_rotation = media::VIDEO_ROTATION_0;
-  ignore_result(old_frame->metadata()->GetRotation(
-      media::VideoFrameMetadata::ROTATION, &old_frame_video_rotation));
-  if (new_frame_video_rotation != old_frame_video_rotation) {
+  if (new_frame_rotation.has_value()) {
     main_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&WebMediaPlayerMS::OnRotationChanged, player_,
-                                  new_frame_video_rotation));
+                                  *new_frame_rotation));
     if (submitter_)
-      submitter_->SetRotation(new_frame_video_rotation);
+      submitter_->SetRotation(*new_frame_rotation);
   }
-  if (new_frame_is_opaque != media::IsOpaque(old_frame->format())) {
+  if (new_frame_opacity.has_value()) {
     main_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&WebMediaPlayerMS::OnOpacityChanged, player_,
-                                  new_frame_is_opaque));
+                                  *new_frame_opacity));
   }
-  if (old_frame->natural_size() != new_frame->natural_size()) {
+  if (has_frame_size_changed) {
     main_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&WebMediaPlayerMS::TriggerResize, player_));
   }
diff --git a/content/renderer/media/stream/webmediaplayer_ms_compositor.h b/content/renderer/media/stream/webmediaplayer_ms_compositor.h
index 551e044..bb1b8dc 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_compositor.h
+++ b/content/renderer/media/stream/webmediaplayer_ms_compositor.h
@@ -13,6 +13,7 @@
 
 #include "base/memory/ref_counted_delete_on_sequence.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
 #include "cc/layers/surface_layer.h"
@@ -147,8 +148,11 @@
   void SetCurrentFrame(scoped_refptr<media::VideoFrame> frame);
   // Following the update to |current_frame_|, this will check for changes that
   // require updating video layer.
-  void CheckForFrameChanges(scoped_refptr<media::VideoFrame> old_frame,
-                            scoped_refptr<media::VideoFrame> new_frame);
+  void CheckForFrameChanges(
+      bool is_first_frame,
+      bool has_frame_size_changed,
+      base::Optional<media::VideoRotation> new_frame_rotation,
+      base::Optional<bool> new_frame_opacity);
 
   void StartRenderingInternal();
   void StopRenderingInternal();
diff --git a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
index ea2e9486..3deac07 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
@@ -577,7 +577,6 @@
       const blink::WebString& remote_device_friendly_name) override {}
   void MediaRemotingStopped(
       blink::WebLocalizedString::Name error_msg) override {}
-  void PictureInPictureStopped() override {}
   void RequestPlay() override {}
   void RequestPause() override {}
   void RequestMuted(bool muted) override {}
diff --git a/content/renderer/render_process_impl.cc b/content/renderer/render_process_impl.cc
index 38f08a8..42ab7a3 100644
--- a/content/renderer/render_process_impl.cc
+++ b/content/renderer/render_process_impl.cc
@@ -25,6 +25,7 @@
 #include "base/debug/stack_trace.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
+#include "base/strings/string_split.h"
 #include "base/system/sys_info.h"
 #include "base/task/thread_pool/initialization_util.h"
 #include "base/task/thread_pool/thread_pool.h"
@@ -208,9 +209,13 @@
   }
 
   if (command_line.HasSwitch(switches::kJavaScriptFlags)) {
-    std::string flags(
-        command_line.GetSwitchValueASCII(switches::kJavaScriptFlags));
-    v8::V8::SetFlagsFromString(flags.c_str(), flags.size());
+    std::string js_flags =
+        command_line.GetSwitchValueASCII(switches::kJavaScriptFlags);
+    std::vector<base::StringPiece> flag_list = base::SplitStringPiece(
+        js_flags, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    for (const auto& flag : flag_list) {
+      v8::V8::SetFlagsFromString(flag.as_string().c_str(), flag.size());
+    }
   }
 
   if (command_line.HasSwitch(switches::kDomAutomationController))
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 5a4d20a..0f7faf6 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -2763,13 +2763,12 @@
 }
 
 void RenderWidget::InjectGestureScrollEvent(
-    blink::WebGestureDevice device,
     const blink::WebFloatSize& delta,
     blink::WebScrollGranularity granularity,
     cc::ElementId scrollable_area_element_id,
     blink::WebInputEvent::Type injected_type) {
   input_handler_->InjectGestureScrollEvent(
-      device, delta, granularity, scrollable_area_element_id, injected_type);
+      delta, granularity, scrollable_area_element_id, injected_type);
 }
 
 void RenderWidget::SetOverscrollBehavior(
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 2c91755..515a94d 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -79,6 +79,7 @@
 namespace scheduler {
 class WebRenderWidgetSchedulingState;
 }
+enum class WebInjectedScrollSequenceType;
 struct WebDeviceEmulationParams;
 class WebDragData;
 class WebFrameWidget;
@@ -409,7 +410,6 @@
                      const blink::WebFloatPoint& position,
                      const blink::WebFloatSize& velocity) override;
   void InjectGestureScrollEvent(
-      blink::WebGestureDevice device,
       const blink::WebFloatSize& delta,
       blink::WebScrollGranularity granularity,
       cc::ElementId scrollable_area_element_id,
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 3825c68..3dd1dab 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -762,6 +762,7 @@
 
     framework_contents = [
       "Helpers",
+      "Libraries",
       "Resources",
     ]
 
@@ -866,7 +867,7 @@
   }
 
   if (use_egl) {
-    # Add the ANGLE .dylibs in the MODULE_DIR of Chromium.app
+    # Add the ANGLE .dylibs in the Libraries directory of the Framework.
     bundle_data("content_shell_angle_binaries") {
       sources = [
         "$root_out_dir/egl_intermediates/libEGL.dylib",
@@ -880,7 +881,7 @@
       ]
     }
 
-    # Add the SwiftShader .dylibs in the MODULE_DIR of Chromium.app
+    # Add the SwiftShader .dylibs in the Libraries directory of the Framework.
     bundle_data("content_shell_swiftshader_binaries") {
       sources = [
         "$root_out_dir/egl_intermediates/libswiftshader_libEGL.dylib",
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index 68289d1..472232a70 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -249,8 +249,8 @@
     config = {
       'direct_composition': False,
       'supports_overlays': False,
-      'overlay_cap_yuy2': 'NONE',
-      'overlay_cap_nv12': 'NONE',
+      'yuy2_overlay_support': 'NONE',
+      'nv12_overlay_support': 'NONE',
     }
     assert os_version in _SUPPORTED_WIN_VERSIONS
     assert gpu_vendor_id in _SUPPORTED_WIN_GPU_VENDORS
@@ -260,9 +260,9 @@
         config['supports_overlays'] = True
         assert gpu_device_id in _SUPPORTED_WIN_INTEL_GPUS
         if gpu_device_id in _SUPPORTED_WIN_INTEL_GPUS_WITH_YUY2_OVERLAYS:
-          config['overlay_cap_yuy2'] = 'SCALING'
+          config['yuy2_overlay_support'] = 'SCALING'
         if gpu_device_id in _SUPPORTED_WIN_INTEL_GPUS_WITH_NV12_OVERLAYS:
-          config['overlay_cap_nv12'] = 'SCALING'
+          config['nv12_overlay_support'] = 'SCALING'
     return config
 
   @classmethod
diff --git a/content/test/gpu/gpu_tests/trace_integration_test.py b/content/test/gpu/gpu_tests/trace_integration_test.py
index d37671c..b3c13a8 100644
--- a/content/test/gpu/gpu_tests/trace_integration_test.py
+++ b/content/test/gpu/gpu_tests/trace_integration_test.py
@@ -81,7 +81,7 @@
 # The following is defined for Chromium testing internal use.
 _SWAP_CHAIN_GET_FRAME_STATISTICS_MEDIA_FAILED = -1
 
-# Pixel format enums match OverlayFormat in config/gpu/gpu_info.h
+# Pixel format enums match OverlayFormat in DirectCompositionSurfaceWin.
 _SWAP_CHAIN_PIXEL_FORMAT_BGRA = 0
 _SWAP_CHAIN_PIXEL_FORMAT_YUY2 = 1
 _SWAP_CHAIN_PIXEL_FORMAT_NV12 = 2
@@ -287,9 +287,9 @@
     supports_nv12_overlays = False
     if overlay_bot_config.get('supports_overlays', False):
       supports_yuy2_overlays = False
-      if overlay_bot_config.get('overlay_cap_yuy2', 'NONE') != 'NONE':
+      if overlay_bot_config['yuy2_overlay_support'] != 'NONE':
         supports_yuy2_overlays = True
-      if overlay_bot_config.get('overlay_cap_nv12', 'NONE') != 'NONE':
+      if overlay_bot_config['nv12_overlay_support'] != 'NONE':
         supports_nv12_overlays = True
       assert supports_yuy2_overlays or supports_nv12_overlays
       if expect_yuy2 or not supports_nv12_overlays:
diff --git a/device/vr/openvr/openvr_api_wrapper.cc b/device/vr/openvr/openvr_api_wrapper.cc
index 897a8a2..9a48eeb 100644
--- a/device/vr/openvr/openvr_api_wrapper.cc
+++ b/device/vr/openvr/openvr_api_wrapper.cc
@@ -98,14 +98,14 @@
 ServiceTestHook* OpenVRWrapper::service_test_hook_ = nullptr;
 
 std::string GetOpenVRString(vr::IVRSystem* vr_system,
-                            vr::TrackedDeviceProperty prop) {
+                            vr::TrackedDeviceProperty prop,
+                            uint32_t device_index) {
   std::string out;
 
   vr::TrackedPropertyError error = vr::TrackedProp_Success;
   char openvr_string[vr::k_unMaxPropertyStringSize];
   vr_system->GetStringTrackedDeviceProperty(
-      vr::k_unTrackedDeviceIndex_Hmd, prop, openvr_string,
-      vr::k_unMaxPropertyStringSize, &error);
+      device_index, prop, openvr_string, vr::k_unMaxPropertyStringSize, &error);
 
   if (error == vr::TrackedProp_Success)
     out = openvr_string;
diff --git a/device/vr/openvr/openvr_api_wrapper.h b/device/vr/openvr/openvr_api_wrapper.h
index a1b11fd..8fb60ec 100644
--- a/device/vr/openvr/openvr_api_wrapper.h
+++ b/device/vr/openvr/openvr_api_wrapper.h
@@ -45,8 +45,10 @@
   static bool any_initialized_;
 };
 
-std::string GetOpenVRString(vr::IVRSystem* vr_system,
-                            vr::TrackedDeviceProperty prop);
+std::string GetOpenVRString(
+    vr::IVRSystem* vr_system,
+    vr::TrackedDeviceProperty prop,
+    uint32_t device_index = vr::k_unTrackedDeviceIndex_Hmd);
 
 }  // namespace device
 
diff --git a/device/vr/openvr/openvr_gamepad_helper.cc b/device/vr/openvr/openvr_gamepad_helper.cc
index 00c5edb..63389f2 100644
--- a/device/vr/openvr/openvr_gamepad_helper.cc
+++ b/device/vr/openvr/openvr_gamepad_helper.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "device/vr/openvr/openvr_gamepad_helper.h"
+#include "device/vr/openvr/openvr_api_wrapper.h"
 
 #include <memory>
 #include <unordered_set>
@@ -243,13 +244,13 @@
     kRequired = 1,
   };
 
-  // TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue
-  // #550 (https://github.com/immersive-web/webxr/issues/550) is resolved.
   OpenVRGamepadBuilder(vr::IVRSystem* vr_system,
                        uint32_t controller_id,
                        vr::VRControllerState_t controller_state,
                        device::mojom::XRHandedness handedness)
-      : GamepadBuilder("openvr", GamepadMapping::kXRStandard, handedness),
+      : GamepadBuilder(GetGamepadId(vr_system, controller_id),
+                       GamepadMapping::kXRStandard,
+                       handedness),
         controller_state_(controller_state) {
     supported_buttons_ = vr_system->GetUint64TrackedDeviceProperty(
         controller_id, vr::Prop_SupportedButtons_Uint64);
@@ -309,6 +310,26 @@
   }
 
  private:
+  static bool IsControllerHTCVive(vr::IVRSystem* vr_system,
+                                  uint32_t controller_id) {
+    std::string model =
+        GetOpenVRString(vr_system, vr::Prop_ModelNumber_String, controller_id);
+    std::string manufacturer = GetOpenVRString(
+        vr_system, vr::Prop_ManufacturerName_String, controller_id);
+    return (model == "Vive Controller MV") && (manufacturer == "HTC");
+  }
+
+  // TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue
+  // #550 (https://github.com/immersive-web/webxr/issues/550) is resolved.
+  static std::string GetGamepadId(vr::IVRSystem* vr_system,
+                                  uint32_t controller_id) {
+    if (IsControllerHTCVive(vr_system, controller_id)) {
+      return "htc-vive";
+    }
+
+    return "openvr";
+  }
+
   bool IsUsed(vr::EVRButtonId button_id) {
     auto it = used_axes_.find(button_id);
     return it != used_axes_.end();
diff --git a/docs/testing/json_test_results_format.md b/docs/testing/json_test_results_format.md
index 92933e3..51d1f60 100644
--- a/docs/testing/json_test_results_format.md
+++ b/docs/testing/json_test_results_format.md
@@ -114,6 +114,7 @@
 |  `expected` | string | **Required.** An unordered space-separated list of the result types expected for the test, e.g. `FAIL PASS` means that a test is expected to either pass or fail. A test that contains multiple values is expected to be flaky. |
 |  `artifacts` | dict | **Optional.** A dictionary describing test artifacts generated by the execution of the test. The dictionary maps the name of the artifact (`screenshot`, `crash_log`) to a list of relative locations of the artifact (`screenshot/page.png`, `logs/crash.txt`). Any '/' characters in the file paths are meant to be platform agnostic; tools will replace them with the appropriate per platform path separators. There is one entry in the list per test execution. If `artifact_permanent_location` is specified, then this location is relative to that path. Otherwise, the path is assumed to be relative to the location of the json file which contains this. |
 |  `bugs` | string | **Optional.** A comma-separated list of URLs to bug database entries associated with each test. |
+|  `shard` | int | **Optional.** The 0-based index of the shard that the test ran on, if the test suite was sharded across multiple bots. |
 |  `is_flaky` | bool | **Optional.** If present and true, the test was run multiple times and produced more than one kind of result. If false (or if the key is not present at all), the test either only ran once or produced the same result every time. |
 |  `is_regression` | bool | **Optional.** If present and true, the test failed unexpectedly. If false (or if the key is not present at all), the test either ran as expected or passed unexpectedly. |
 |  `is_unexpected` | bool | **Optional.** If present and true, the test result was unexpected. This might include an unexpected pass, i.e., it is not necessarily a regression. If false (or if the key is not present at all), the test produced the expected result. |
diff --git a/fuchsia/base/BUILD.gn b/fuchsia/base/BUILD.gn
index 06e662e..f3db8b8 100644
--- a/fuchsia/base/BUILD.gn
+++ b/fuchsia/base/BUILD.gn
@@ -40,6 +40,23 @@
   ]
 }
 
+# FIDL/Mojo adapters for MessagePorts.
+source_set("message_port") {
+  sources = [
+    "message_port.cc",
+  ]
+  public = [
+    "message_port.h",
+  ]
+  deps = [
+    ":base",
+    "//base",
+    "//mojo/public/cpp/bindings",
+    "//third_party/blink/public/common",
+    "//third_party/fuchsia-sdk/sdk:web",
+  ]
+}
+
 source_set("test_support") {
   testonly = true
   sources = [
diff --git a/fuchsia/base/DEPS b/fuchsia/base/DEPS
new file mode 100644
index 0000000..9455f2c
--- /dev/null
+++ b/fuchsia/base/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+mojo/public",
+  "+third_party/blink/public/common/messaging",
+  "+third_party/blink/public/mojom/messaging",
+]
diff --git a/fuchsia/base/message_port.cc b/fuchsia/base/message_port.cc
new file mode 100644
index 0000000..55d3820
--- /dev/null
+++ b/fuchsia/base/message_port.cc
@@ -0,0 +1,354 @@
+// 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 "fuchsia/base/message_port.h"
+
+#include <stdint.h>
+
+#include <lib/fit/function.h>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/macros.h"
+#include "fuchsia/base/mem_buffer_util.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "third_party/blink/public/common/messaging/message_port_channel.h"
+#include "third_party/blink/public/common/messaging/string_message_codec.h"
+#include "third_party/blink/public/common/messaging/transferable_message_struct_traits.h"
+#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
+
+namespace cr_fuchsia {
+namespace {
+
+base::Optional<fuchsia::web::FrameError> MojoMessageFromFidl(
+    fuchsia::web::WebMessage fidl_message,
+    mojo::Message* mojo_message) {
+  if (!fidl_message.has_data()) {
+    return fuchsia::web::FrameError::NO_DATA_IN_MESSAGE;
+  }
+
+  base::string16 data_utf16;
+  if (!cr_fuchsia::ReadUTF8FromVMOAsUTF16(fidl_message.data(), &data_utf16)) {
+    return fuchsia::web::FrameError::BUFFER_NOT_UTF8;
+  }
+
+  blink::TransferableMessage transferable_message;
+  if (fidl_message.has_outgoing_transfer()) {
+    for (fuchsia::web::OutgoingTransferable& outgoing :
+         *fidl_message.mutable_outgoing_transfer()) {
+      transferable_message.ports.emplace_back(
+          MessagePortFromFidl(std::move(outgoing.message_port())));
+    }
+  }
+
+  transferable_message.owned_encoded_message =
+      blink::EncodeStringMessage(data_utf16);
+  transferable_message.encoded_message =
+      transferable_message.owned_encoded_message;
+  *mojo_message = blink::mojom::TransferableMessage::SerializeAsMessage(
+      &transferable_message);
+
+  return {};
+}
+
+base::Optional<fuchsia::web::WebMessage> FidlWebMessageFromMojo(
+    mojo::Message mojo_message) {
+  blink::TransferableMessage transferable_message;
+  if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
+          std::move(mojo_message), &transferable_message)) {
+    return {};
+  }
+
+  fuchsia::web::WebMessage fidl_message;
+  if (!transferable_message.ports.empty()) {
+    std::vector<fuchsia::web::IncomingTransferable> transferables;
+    for (const blink::MessagePortChannel& port : transferable_message.ports) {
+      fuchsia::web::IncomingTransferable incoming;
+      incoming.set_message_port(MessagePortFromMojo(port.ReleaseHandle()));
+      transferables.emplace_back(std::move(incoming));
+    }
+    fidl_message.set_incoming_transfer(std::move(transferables));
+  }
+
+  base::string16 data_utf16;
+  if (!blink::DecodeStringMessage(transferable_message.encoded_message,
+                                  &data_utf16)) {
+    return {};
+  }
+
+  std::string data_utf8;
+  if (!base::UTF16ToUTF8(data_utf16.data(), data_utf16.size(), &data_utf8))
+    return {};
+
+  base::STLClearObject(&data_utf16);
+
+  fuchsia::mem::Buffer data = cr_fuchsia::MemBufferFromString(data_utf8);
+  if (!data.vmo)
+    return {};
+
+  fidl_message.set_data(std::move(data));
+  return fidl_message;
+}
+
+// Defines the implementation of a MessagePort which routes messages from
+// FIDL clients to web content, or vice versa. Every MessagePort has a FIDL
+// port and a Mojo port.
+//
+// MessagePort instances are self-managed; they destroy themselves when
+// the connection is terminated from either the Mojo or FIDL side.
+class MessagePort : public mojo::MessageReceiver {
+ protected:
+  explicit MessagePort(mojo::ScopedMessagePipeHandle mojo_port) {
+    mojo_port_ = std::make_unique<mojo::Connector>(
+        std::move(mojo_port), mojo::Connector::SINGLE_THREADED_SEND,
+        base::ThreadTaskRunnerHandle::Get());
+    mojo_port_->set_incoming_receiver(this);
+    mojo_port_->set_connection_error_handler(
+        base::BindOnce(&MessagePort::Destroy, base::Unretained(this)));
+  }
+
+  ~MessagePort() override = default;
+
+  // Deletes |this|, implicitly disconnecting the FIDL and Mojo ports.
+  void Destroy() {
+    // |mojo_port_| and |binding_| are implicitly unbound.
+    delete this;
+  }
+
+  // Sends a message to |mojo_port_|.
+  void SendMojoMessage(mojo::Message* message) {
+    CHECK(mojo_port_->Accept(message));
+  }
+
+  // Called by |mojo_port_| when a Mojo message was received.
+  virtual void DeliverMessageToFidl() = 0;
+
+  // Returns the next messagefrom Mojo, or an empty value if there
+  // are no more messages in the incoming queue.
+  base::Optional<fuchsia::web::WebMessage> GetNextMojoMessage() {
+    if (message_queue_.empty())
+      return {};
+
+    return std::move(message_queue_.front());
+  }
+
+  void OnDeliverMessageToFidlComplete() {
+    DCHECK(!message_queue_.empty());
+
+    message_queue_.pop_front();
+  }
+
+ private:
+  // mojo::MessageReceiver implementation.
+  bool Accept(mojo::Message* message) override {
+    base::Optional<fuchsia::web::WebMessage> message_converted =
+        FidlWebMessageFromMojo(std::move(*message));
+    if (!message_converted) {
+      DLOG(ERROR) << "Couldn't decode MessageChannel from Mojo pipe.";
+      Destroy();
+      return false;
+    }
+    message_queue_.emplace_back(std::move(*message_converted));
+
+    // Start draining the queue if it was empty beforehand.
+    if (message_queue_.size() == 1u)
+      DeliverMessageToFidl();
+
+    return true;
+  }
+
+  base::circular_deque<fuchsia::web::WebMessage> message_queue_;
+  std::unique_ptr<mojo::Connector> mojo_port_;
+
+  DISALLOW_COPY_AND_ASSIGN(MessagePort);
+};
+
+// Binds a handle to a remote MessagePort to a Mojo MessagePipe.
+class FidlMessagePortClient : public MessagePort {
+ public:
+  FidlMessagePortClient(
+      mojo::ScopedMessagePipeHandle mojo_port,
+      fidl::InterfaceHandle<fuchsia::web::MessagePort> fidl_port)
+      : MessagePort(std::move(mojo_port)), port_(fidl_port.Bind()) {
+    ReadMessageFromFidl();
+
+    port_.set_error_handler([this](zx_status_t status) {
+      ZX_LOG_IF(ERROR,
+                status != ZX_ERR_PEER_CLOSED && status != ZX_ERR_CANCELED,
+                status)
+          << " MessagePort disconnected.";
+      Destroy();
+    });
+  }
+
+ private:
+  ~FidlMessagePortClient() override = default;
+
+  void ReadMessageFromFidl() {
+    port_->ReceiveMessage(
+        fit::bind_member(this, &FidlMessagePortClient::OnMessageReceived));
+  }
+
+  void OnMessageReceived(fuchsia::web::WebMessage message) {
+    mojo::Message mojo_message;
+    base::Optional<fuchsia::web::FrameError> result =
+        MojoMessageFromFidl(std::move(message), &mojo_message);
+    if (result) {
+      LOG(WARNING) << "Received bad message, error: "
+                   << static_cast<int32_t>(*result);
+      Destroy();
+      return;
+    }
+
+    SendMojoMessage(&mojo_message);
+
+    ReadMessageFromFidl();
+  }
+
+  void OnMessagePosted(fuchsia::web::MessagePort_PostMessage_Result result) {
+    if (result.is_err()) {
+      LOG(ERROR) << "PostMessage failed, reason: "
+                 << static_cast<int32_t>(result.err());
+      Destroy();
+      return;
+    }
+
+    DeliverMessageToFidl();
+  }
+
+  // cr_fuchsia::MessagePort implementation.
+  void DeliverMessageToFidl() override {
+    base::Optional<fuchsia::web::WebMessage> message = GetNextMojoMessage();
+    if (!message)
+      return;
+
+    port_->PostMessage(
+        std::move(*message),
+        fit::bind_member(this, &FidlMessagePortClient::OnMessagePosted));
+
+    OnDeliverMessageToFidlComplete();
+  }
+
+  fuchsia::web::MessagePortPtr port_;
+
+  DISALLOW_COPY_AND_ASSIGN(FidlMessagePortClient);
+};
+
+// Binds a MessagePort FIDL service from a Mojo MessagePipe.
+class FidlMessagePortServer : public fuchsia::web::MessagePort,
+                              public MessagePort {
+ public:
+  explicit FidlMessagePortServer(mojo::ScopedMessagePipeHandle mojo_port)
+      : cr_fuchsia::MessagePort(std::move(mojo_port)), binding_(this) {
+    binding_.set_error_handler([this](zx_status_t status) {
+      ZX_LOG_IF(ERROR,
+                status != ZX_ERR_PEER_CLOSED && status != ZX_ERR_CANCELED,
+                status)
+          << " MessagePort disconnected.";
+      Destroy();
+    });
+  }
+
+  FidlMessagePortServer(
+      mojo::ScopedMessagePipeHandle mojo_port,
+      fidl::InterfaceRequest<fuchsia::web::MessagePort> request)
+      : FidlMessagePortServer(std::move(mojo_port)) {
+    binding_.Bind(std::move(request));
+  }
+
+  fidl::InterfaceHandle<fuchsia::web::MessagePort> NewBinding() {
+    return binding_.NewBinding();
+  }
+
+ private:
+  ~FidlMessagePortServer() override = default;
+
+  // MessagePort implementation.
+  void DeliverMessageToFidl() override {
+    // Do nothing if the client hasn't requested a read, or if there's nothing
+    // to read.
+    if (!pending_receive_message_callback_)
+      return;
+
+    base::Optional<fuchsia::web::WebMessage> message = GetNextMojoMessage();
+    if (!message)
+      return;
+
+    pending_receive_message_callback_(std::move(*message));
+    pending_receive_message_callback_ = {};
+    OnDeliverMessageToFidlComplete();
+  }
+
+  // fuchsia::web::MessagePort implementation.
+  void PostMessage(fuchsia::web::WebMessage message,
+                   PostMessageCallback callback) override {
+    mojo::Message mojo_message;
+    base::Optional<fuchsia::web::FrameError> status =
+        MojoMessageFromFidl(std::move(message), &mojo_message);
+
+    if (status) {
+      LOG(ERROR) << "Error when reading message from FIDL: "
+                 << static_cast<int32_t>(*status);
+      Destroy();
+      return;
+    }
+
+    SendMojoMessage(&mojo_message);
+    fuchsia::web::MessagePort_PostMessage_Result result;
+    result.set_response(fuchsia::web::MessagePort_PostMessage_Response());
+    callback(std::move(result));
+  }
+
+  void ReceiveMessage(ReceiveMessageCallback callback) override {
+    if (pending_receive_message_callback_) {
+      LOG(WARNING)
+          << "ReceiveMessage called multiple times without acknowledgement.";
+      Destroy();
+      return;
+    }
+    pending_receive_message_callback_ = std::move(callback);
+    DeliverMessageToFidl();
+  }
+
+  PostMessageCallback post_message_ack_;
+  ReceiveMessageCallback pending_receive_message_callback_;
+  fidl::Binding<fuchsia::web::MessagePort> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(FidlMessagePortServer);
+};
+
+}  // namespace
+
+mojo::ScopedMessagePipeHandle MessagePortFromFidl(
+    fidl::InterfaceRequest<fuchsia::web::MessagePort> port) {
+  mojo::ScopedMessagePipeHandle client_port;
+  mojo::ScopedMessagePipeHandle content_port;
+  mojo::CreateMessagePipe(0, &content_port, &client_port);
+
+  new FidlMessagePortServer(std::move(client_port), std::move(port));
+
+  return content_port;
+}
+
+mojo::ScopedMessagePipeHandle MessagePortFromFidl(
+    fidl::InterfaceHandle<fuchsia::web::MessagePort> port) {
+  mojo::ScopedMessagePipeHandle client_port;
+  mojo::ScopedMessagePipeHandle content_port;
+  mojo::CreateMessagePipe(0, &content_port, &client_port);
+
+  new FidlMessagePortClient(std::move(content_port), std::move(port));
+
+  return client_port;
+}
+
+fidl::InterfaceHandle<fuchsia::web::MessagePort> MessagePortFromMojo(
+    mojo::ScopedMessagePipeHandle port) {
+  return (new FidlMessagePortServer(std::move(port)))->NewBinding();
+}
+
+}  // namespace cr_fuchsia
diff --git a/fuchsia/base/message_port.h b/fuchsia/base/message_port.h
new file mode 100644
index 0000000..df2d9da
--- /dev/null
+++ b/fuchsia/base/message_port.h
@@ -0,0 +1,38 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_BASE_MESSAGE_PORT_H_
+#define FUCHSIA_BASE_MESSAGE_PORT_H_
+
+#include <fuchsia/web/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+#include <memory>
+
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/connector.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace cr_fuchsia {
+
+// Creates a connected MessagePort from a FIDL MessagePort request and
+// returns a handle to its peer Mojo pipe.
+mojo::ScopedMessagePipeHandle MessagePortFromFidl(
+    fidl::InterfaceRequest<fuchsia::web::MessagePort> fidl_port);
+
+// Creates a connected MessagePort from a remote FIDL MessagePort handle,
+// returns a handle to its peer Mojo pipe.
+mojo::ScopedMessagePipeHandle MessagePortFromFidl(
+    fidl::InterfaceHandle<fuchsia::web::MessagePort> fidl_port);
+
+// Creates a connected MessagePort from a transferred Mojo MessagePort and
+// returns a handle to its FIDL interface peer.
+fidl::InterfaceHandle<fuchsia::web::MessagePort> MessagePortFromMojo(
+    mojo::ScopedMessagePipeHandle mojo_port);
+
+}  // namespace cr_fuchsia
+
+#endif  // FUCHSIA_BASE_MESSAGE_PORT_H_
diff --git a/fuchsia/engine/BUILD.gn b/fuchsia/engine/BUILD.gn
index f5cb6e0..5dd737d 100644
--- a/fuchsia/engine/BUILD.gn
+++ b/fuchsia/engine/BUILD.gn
@@ -72,6 +72,7 @@
     "//content/public/common",
     "//content/public/renderer",
     "//fuchsia/base",
+    "//fuchsia/base:message_port",
     "//fuchsia/base:modular",
     "//mojo/public/cpp/bindings",
     "//services/network/public/cpp",
@@ -104,8 +105,6 @@
     "browser/discarding_event_filter.h",
     "browser/frame_impl.cc",
     "browser/frame_impl.h",
-    "browser/message_port_impl.cc",
-    "browser/message_port_impl.h",
     "browser/web_engine_browser_context.cc",
     "browser/web_engine_browser_context.h",
     "browser/web_engine_browser_main.cc",
diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc
index 61e3f2b..10f2152 100644
--- a/fuchsia/engine/browser/frame_impl.cc
+++ b/fuchsia/engine/browser/frame_impl.cc
@@ -19,8 +19,8 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/was_activated_option.h"
 #include "fuchsia/base/mem_buffer_util.h"
+#include "fuchsia/base/message_port.h"
 #include "fuchsia/engine/browser/context_impl.h"
-#include "fuchsia/engine/browser/message_port_impl.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/logging/logging_utils.h"
@@ -393,7 +393,7 @@
     for (fuchsia::web::OutgoingTransferable& outgoing :
          *message.mutable_outgoing_transfer()) {
       mojo::ScopedMessagePipeHandle port =
-          MessagePortImpl::FromFidl(std::move(outgoing.message_port()));
+          cr_fuchsia::MessagePortFromFidl(std::move(outgoing.message_port()));
       if (!port) {
         result.set_err(fuchsia::web::FrameError::INTERNAL_ERROR);
         callback(std::move(result));
diff --git a/fuchsia/engine/browser/message_port_impl.cc b/fuchsia/engine/browser/message_port_impl.cc
deleted file mode 100644
index 4016b72..0000000
--- a/fuchsia/engine/browser/message_port_impl.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "fuchsia/engine/browser/message_port_impl.h"
-
-#include <stdint.h>
-
-#include <lib/fit/function.h>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/fuchsia/fuchsia_logging.h"
-#include "base/macros.h"
-#include "fuchsia/base/mem_buffer_util.h"
-#include "mojo/public/cpp/system/message_pipe.h"
-#include "third_party/blink/public/common/messaging/message_port_channel.h"
-#include "third_party/blink/public/common/messaging/string_message_codec.h"
-#include "third_party/blink/public/common/messaging/transferable_message_struct_traits.h"
-#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
-
-namespace {
-
-// Converts a message posted to a JS MessagePort to a WebMessage.
-// Returns an unset Optional<> if the message could not be converted.
-base::Optional<fuchsia::web::WebMessage> FromMojoMessage(
-    mojo::Message message) {
-  fuchsia::web::WebMessage converted;
-
-  blink::TransferableMessage transferable_message;
-  if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
-          std::move(message), &transferable_message))
-    return {};
-
-  if (!transferable_message.ports.empty()) {
-    std::vector<fuchsia::web::IncomingTransferable> transferables;
-    for (const blink::MessagePortChannel& port : transferable_message.ports) {
-      fuchsia::web::IncomingTransferable incoming;
-      incoming.set_message_port(
-          MessagePortImpl::FromMojo(port.ReleaseHandle()));
-      transferables.emplace_back(std::move(incoming));
-    }
-    converted.set_incoming_transfer(std::move(transferables));
-  }
-
-  base::string16 data_utf16;
-  if (!blink::DecodeStringMessage(transferable_message.encoded_message,
-                                  &data_utf16)) {
-    return {};
-  }
-
-  std::string data_utf8;
-  if (!base::UTF16ToUTF8(data_utf16.data(), data_utf16.size(), &data_utf8))
-    return {};
-  base::STLClearObject(&data_utf16);
-
-  fuchsia::mem::Buffer data = cr_fuchsia::MemBufferFromString(data_utf8);
-  if (!data.vmo) {
-    return {};
-  }
-
-  converted.set_data(std::move(data));
-  return converted;
-}
-
-}  // namespace
-
-MessagePortImpl::MessagePortImpl(mojo::ScopedMessagePipeHandle mojo_port)
-    : binding_(this) {
-  connector_ = std::make_unique<mojo::Connector>(
-      std::move(mojo_port), mojo::Connector::SINGLE_THREADED_SEND,
-      base::ThreadTaskRunnerHandle::Get());
-  connector_->set_incoming_receiver(this);
-  connector_->set_connection_error_handler(
-      base::BindOnce(&MessagePortImpl::OnDisconnected, base::Unretained(this)));
-  binding_.set_error_handler([this](zx_status_t status) {
-    ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status)
-        << " MessagePort disconnected.";
-    OnDisconnected();
-  });
-}
-
-MessagePortImpl::~MessagePortImpl() = default;
-
-void MessagePortImpl::OnDisconnected() {
-  // |connector_| and |binding_| are implicitly unbound.
-  delete this;
-}
-
-void MessagePortImpl::PostMessage(fuchsia::web::WebMessage message,
-                                  PostMessageCallback callback) {
-  fuchsia::web::MessagePort_PostMessage_Result result;
-  if (!message.has_data()) {
-    result.set_err(fuchsia::web::FrameError::NO_DATA_IN_MESSAGE);
-    callback(std::move(result));
-    return;
-  }
-
-  base::string16 data_utf16;
-  if (!cr_fuchsia::ReadUTF8FromVMOAsUTF16(message.data(), &data_utf16)) {
-    result.set_err(fuchsia::web::FrameError::BUFFER_NOT_UTF8);
-    callback(std::move(result));
-    return;
-  }
-
-  blink::TransferableMessage transfer_message;
-  if (message.has_outgoing_transfer()) {
-    for (fuchsia::web::OutgoingTransferable& outgoing :
-         *message.mutable_outgoing_transfer()) {
-      transfer_message.ports.emplace_back(
-          MessagePortImpl::FromFidl(std::move(outgoing.message_port())));
-    }
-  }
-
-  transfer_message.owned_encoded_message =
-      blink::EncodeStringMessage(data_utf16);
-  transfer_message.encoded_message = transfer_message.owned_encoded_message;
-  mojo::Message mojo_message =
-      blink::mojom::TransferableMessage::SerializeAsMessage(&transfer_message);
-
-  CHECK(connector_->Accept(&mojo_message));
-  result.set_response(fuchsia::web::MessagePort_PostMessage_Response());
-  callback(std::move(result));
-}
-
-void MessagePortImpl::ReceiveMessage(
-    fuchsia::web::MessagePort::ReceiveMessageCallback callback) {
-  pending_client_read_cb_ = std::move(callback);
-  MaybeDeliverToClient();
-}
-
-void MessagePortImpl::MaybeDeliverToClient() {
-  // Do nothing if the client hasn't requested a read, or if there's nothing
-  // to read.
-  if (!pending_client_read_cb_ || message_queue_.empty()) {
-    return;
-  }
-
-  auto pending_client_read_cb = std::exchange(pending_client_read_cb_, nullptr);
-  pending_client_read_cb(std::move(message_queue_.front()));
-  message_queue_.pop_front();
-}
-
-bool MessagePortImpl::Accept(mojo::Message* message) {
-  base::Optional<fuchsia::web::WebMessage> message_converted =
-      FromMojoMessage(std::move(*message));
-  if (!message_converted) {
-    DLOG(ERROR) << "Couldn't decode MessageChannel from Mojo pipe.";
-    return false;
-  }
-  message_queue_.emplace_back(std::move(message_converted.value()));
-  MaybeDeliverToClient();
-  return true;
-}
-
-// static
-mojo::ScopedMessagePipeHandle MessagePortImpl::FromFidl(
-    fidl::InterfaceRequest<fuchsia::web::MessagePort> port) {
-  mojo::ScopedMessagePipeHandle client_port;
-  mojo::ScopedMessagePipeHandle content_port;
-  mojo::CreateMessagePipe(0, &content_port, &client_port);
-
-  MessagePortImpl* port_impl = new MessagePortImpl(std::move(client_port));
-  port_impl->binding_.Bind(std::move(port));
-
-  return content_port;
-}
-
-// static
-fidl::InterfaceHandle<fuchsia::web::MessagePort> MessagePortImpl::FromMojo(
-    mojo::ScopedMessagePipeHandle port) {
-  MessagePortImpl* created_port = new MessagePortImpl(std::move(port));
-  return created_port->binding_.NewBinding();
-}
diff --git a/fuchsia/engine/browser/message_port_impl.h b/fuchsia/engine/browser/message_port_impl.h
deleted file mode 100644
index 92afb84..0000000
--- a/fuchsia/engine/browser/message_port_impl.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef FUCHSIA_ENGINE_BROWSER_MESSAGE_PORT_IMPL_H_
-#define FUCHSIA_ENGINE_BROWSER_MESSAGE_PORT_IMPL_H_
-
-#include <fuchsia/web/cpp/fidl.h>
-#include <lib/fidl/cpp/binding.h>
-#include <memory>
-
-#include "base/containers/circular_deque.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "mojo/public/cpp/bindings/connector.h"
-#include "mojo/public/cpp/bindings/message.h"
-#include "mojo/public/cpp/system/message_pipe.h"
-
-// Defines the implementation of a MessagePort which routes messages from
-// FIDL clients to web content, or vice versa. Every MessagePortImpl has a FIDL
-// port and a Mojo port.
-//
-// MessagePortImpl instances are self-managed; they destroy themselves when
-// the connection is terminated from either the Mojo or FIDL side.
-class MessagePortImpl : public fuchsia::web::MessagePort,
-                        public mojo::MessageReceiver {
- public:
-  // Creates a connected MessagePort from a FIDL MessagePort request and
-  // returns a handle to its peer Mojo pipe.
-  static mojo::ScopedMessagePipeHandle FromFidl(
-      fidl::InterfaceRequest<fuchsia::web::MessagePort> port);
-
-  // Creates a connected MessagePort from a transferred Mojo MessagePort and
-  // returns a handle to its FIDL interface peer.
-  static fidl::InterfaceHandle<fuchsia::web::MessagePort> FromMojo(
-      mojo::ScopedMessagePipeHandle port);
-
- private:
-  explicit MessagePortImpl(mojo::ScopedMessagePipeHandle mojo_port);
-
-  // Non-public to ensure that only this object may destroy itself.
-  ~MessagePortImpl() override;
-
-  // fuchsia::web::MessagePort implementation.
-  void PostMessage(fuchsia::web::WebMessage message,
-                   PostMessageCallback callback) override;
-  void ReceiveMessage(ReceiveMessageCallback callback) override;
-
-  // Called when the connection to Blink or FIDL is terminated.
-  void OnDisconnected();
-
-  // Sends the next message enqueued in |message_queue_| to the client,
-  // if the client has requested a message.
-  void MaybeDeliverToClient();
-
-  // mojo::MessageReceiver implementation.
-  bool Accept(mojo::Message* message) override;
-
-  fidl::Binding<fuchsia::web::MessagePort> binding_;
-  base::circular_deque<fuchsia::web::WebMessage> message_queue_;
-  ReceiveMessageCallback pending_client_read_cb_;
-  std::unique_ptr<mojo::Connector> connector_;
-
-  DISALLOW_COPY_AND_ASSIGN(MessagePortImpl);
-};
-
-#endif  // FUCHSIA_ENGINE_BROWSER_MESSAGE_PORT_IMPL_H_
diff --git a/fuchsia/runners/BUILD.gn b/fuchsia/runners/BUILD.gn
index 1da5594..6d2a024 100644
--- a/fuchsia/runners/BUILD.gn
+++ b/fuchsia/runners/BUILD.gn
@@ -32,6 +32,8 @@
 
 source_set("cast_runner_core") {
   sources = [
+    "cast/api_bindings_client.cc",
+    "cast/api_bindings_client.h",
     "cast/cast_channel_bindings.cc",
     "cast/cast_channel_bindings.h",
     "cast/cast_component.cc",
@@ -103,6 +105,8 @@
     "cast/fake_application_config_manager.h",
     "cast/fake_queryable_data.cc",
     "cast/fake_queryable_data.h",
+    "cast/test_api_bindings.cc",
+    "cast/test_api_bindings.h",
   ]
   deps = [
     ":cast_runner_core",
@@ -140,6 +144,7 @@
 
 test("cast_runner_browsertests") {
   sources = [
+    "cast/api_bindings_client_browsertest.cc",
     "cast/cast_channel_bindings_browsertest.cc",
     "cast/named_message_port_connector_browsertest.cc",
     "cast/not_implemented_api_bindings_browsertest.cc",
diff --git a/fuchsia/runners/cast/api_bindings_client.cc b/fuchsia/runners/cast/api_bindings_client.cc
new file mode 100644
index 0000000..27e1cfd
--- /dev/null
+++ b/fuchsia/runners/cast/api_bindings_client.cc
@@ -0,0 +1,109 @@
+// 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 "fuchsia/runners/cast/api_bindings_client.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/strings/string_piece.h"
+
+namespace {
+
+uint64_t kBindingsIdStart = 0xFF0000;
+
+}  // namespace
+
+ApiBindingsClient::ApiBindingsClient(
+    fidl::InterfaceHandle<chromium::cast::ApiBindings> bindings_service,
+    base::OnceClosure on_bindings_received_callback)
+    : bindings_service_(bindings_service.Bind()),
+      on_bindings_received_callback_(std::move(on_bindings_received_callback)) {
+  DCHECK(bindings_service_);
+  DCHECK(on_bindings_received_callback_);
+
+  bindings_service_->GetAll(
+      fit::bind_member(this, &ApiBindingsClient::OnBindingsReceived));
+
+  bindings_service_.set_error_handler([this](zx_status_t status) mutable {
+    ZX_LOG_IF(ERROR, status != ZX_OK, status)
+        << "ApiBindings disconnected before bindings were read.";
+
+    if (!bindings_) {
+      // The Agent disconnected before sending a bindings response,
+      // so it's possible that the Agent doesn't yet implement the ApiBindings
+      // service. Populate the bindings with an empty set so initialization may
+      // continue.
+      // TODO(crbug.com/953958): Remove this fallback once the Agent implements
+      // ApiBindings.
+      LOG(WARNING)
+          << "Couldn't receive bindings from Agent, proceeding anyway.";
+      OnBindingsReceived({});
+    }
+  });
+}
+
+void ApiBindingsClient::OnBindingsReceived(
+    std::vector<chromium::cast::ApiBinding> bindings) {
+  DCHECK(on_bindings_received_callback_);
+
+  bindings_ = std::move(bindings);
+  std::move(on_bindings_received_callback_).Run();
+}
+
+void ApiBindingsClient::AttachToFrame(fuchsia::web::Frame* frame,
+                                      NamedMessagePortConnector* connector,
+                                      base::OnceClosure on_error_callback) {
+  DCHECK(!frame_) << "AttachToFrame() was called twice.";
+  DCHECK(frame);
+  DCHECK(connector);
+  DCHECK(bindings_)
+      << "AttachToFrame() was called before bindings were received.";
+
+  connector_ = connector;
+  frame_ = frame;
+
+  bindings_service_.set_error_handler([on_error_callback =
+                                           std::move(on_error_callback)](
+                                          zx_status_t status) mutable {
+    ZX_LOG_IF(ERROR, status != ZX_OK, status) << "ApiBindings disconnected.";
+    std::move(on_error_callback).Run();
+  });
+
+  connector_->RegisterDefaultHandler(base::BindRepeating(
+      &ApiBindingsClient::OnPortConnected, base::Unretained(this)));
+
+  // Enumerate and inject all scripts in |bindings|.
+  uint64_t bindings_id = kBindingsIdStart;
+  for (chromium::cast::ApiBinding& entry : *bindings_) {
+    frame_->AddBeforeLoadJavaScript(
+        bindings_id++, {"*"}, std::move(*entry.mutable_before_load_script()),
+        [](fuchsia::web::Frame_AddBeforeLoadJavaScript_Result result) {
+          CHECK(result.is_response()) << "JavaScript injection error: "
+                                      << static_cast<uint32_t>(result.err());
+        });
+  }
+}
+
+ApiBindingsClient::~ApiBindingsClient() {
+  if (connector_ && frame_) {
+    connector_->RegisterDefaultHandler({});
+
+    // Remove all injected scripts using their automatically enumerated IDs.
+    for (uint64_t i = 0; i < bindings_->size(); ++i)
+      frame_->RemoveBeforeLoadJavaScript(kBindingsIdStart + i);
+  }
+}
+
+void ApiBindingsClient::OnPortConnected(
+    base::StringPiece port_name,
+    fidl::InterfaceHandle<fuchsia::web::MessagePort> port) {
+  if (bindings_service_)
+    bindings_service_->Connect(port_name.as_string(), std::move(port));
+}
+
+bool ApiBindingsClient::HasBindings() const {
+  return bindings_.has_value();
+}
diff --git a/fuchsia/runners/cast/api_bindings_client.h b/fuchsia/runners/cast/api_bindings_client.h
new file mode 100644
index 0000000..a6695e9
--- /dev/null
+++ b/fuchsia/runners/cast/api_bindings_client.h
@@ -0,0 +1,57 @@
+// 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 FUCHSIA_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
+#define FUCHSIA_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
+
+#include <fuchsia/web/cpp/fidl.h>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia/runners/cast/named_message_port_connector.h"
+
+// Injects scripts received from the ApiBindings service, and provides connected
+// ports to the Agent.
+class ApiBindingsClient {
+ public:
+  // Reads bindings definitions from |bindings_service_| at construction time.
+  // Invokes |on_bindings_received_callback| once the definitions are received,
+  // after which the client may invoke AttachToFrame().
+  ApiBindingsClient(
+      fidl::InterfaceHandle<chromium::cast::ApiBindings> bindings_service,
+      base::OnceClosure on_bindings_received_callback);
+  ~ApiBindingsClient();
+
+  // Injects APIs and handles channel connections on |frame|.
+  // |on_error_closure|: Invoked in the event of an unrecoverable error (e.g.
+  //                     lost connection to the Agent). The callback must
+  //                     remain valid for the entire lifetime of |this|.
+  void AttachToFrame(fuchsia::web::Frame* frame,
+                     NamedMessagePortConnector* connector,
+                     base::OnceClosure on_error_callback);
+
+  // Indicates that bindings were successfully received from
+  // |bindings_service_|.
+  bool HasBindings() const;
+
+ private:
+  // Called when ApiBindings::GetAll() has responded.
+  void OnBindingsReceived(std::vector<chromium::cast::ApiBinding> bindings);
+
+  // Called when |connector_| has connected a port.
+  void OnPortConnected(base::StringPiece port_name,
+                       fidl::InterfaceHandle<fuchsia::web::MessagePort> port);
+
+  base::Optional<std::vector<chromium::cast::ApiBinding>> bindings_;
+  fuchsia::web::Frame* frame_ = nullptr;
+  NamedMessagePortConnector* connector_ = nullptr;
+  chromium::cast::ApiBindingsPtr bindings_service_;
+  base::OnceClosure on_bindings_received_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ApiBindingsClient);
+};
+
+#endif  // FUCHSIA_RUNNERS_CAST_API_BINDINGS_CLIENT_H_
diff --git a/fuchsia/runners/cast/api_bindings_client_browsertest.cc b/fuchsia/runners/cast/api_bindings_client_browsertest.cc
new file mode 100644
index 0000000..947886f1
--- /dev/null
+++ b/fuchsia/runners/cast/api_bindings_client_browsertest.cc
@@ -0,0 +1,114 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuchsia/web/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/test/bind_test_util.h"
+#include "base/test/test_timeouts.h"
+#include "fuchsia/base/fit_adapter.h"
+#include "fuchsia/base/frame_test_util.h"
+#include "fuchsia/base/mem_buffer_util.h"
+#include "fuchsia/base/result_receiver.h"
+#include "fuchsia/base/test_navigation_listener.h"
+#include "fuchsia/engine/test/web_engine_browser_test.h"
+#include "fuchsia/runners/cast/api_bindings_client.h"
+#include "fuchsia/runners/cast/test_api_bindings.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class ApiBindingsClientTest : public cr_fuchsia::WebEngineBrowserTest {
+ public:
+  ApiBindingsClientTest()
+      : api_service_binding_(&api_service_),
+        run_timeout_(TestTimeouts::action_timeout(),
+                     base::MakeExpectedNotRunClosure(FROM_HERE)) {
+    set_test_server_root(base::FilePath("fuchsia/runners/cast/testdata"));
+  }
+
+  ~ApiBindingsClientTest() override = default;
+
+ protected:
+  void StartClient() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+
+    // Get the bindings from |api_service_|.
+    base::RunLoop run_loop;
+    client_ = std::make_unique<ApiBindingsClient>(
+        api_service_binding_.NewBinding(), run_loop.QuitClosure());
+    EXPECT_FALSE(client_->HasBindings());
+    run_loop.Run();
+    EXPECT_TRUE(client_->HasBindings());
+
+    frame_ = WebEngineBrowserTest::CreateFrame(&navigation_listener_);
+    frame_->GetNavigationController(controller_.NewRequest());
+    connector_ = std::make_unique<NamedMessagePortConnector>(frame_.get());
+    client_->AttachToFrame(frame_.get(), connector_.get(),
+                           base::MakeExpectedNotRunClosure(FROM_HERE));
+  }
+
+  void SetUpOnMainThread() override {
+    cr_fuchsia::WebEngineBrowserTest::SetUpOnMainThread();
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+  void TearDownOnMainThread() override {
+    // Destroy |client_| before the MessageLoop is destroyed.
+    client_.reset();
+  }
+
+  fuchsia::web::FramePtr frame_;
+  std::unique_ptr<NamedMessagePortConnector> connector_;
+  TestApiBindings api_service_;
+  fidl::Binding<chromium::cast::ApiBindings> api_service_binding_;
+  std::unique_ptr<ApiBindingsClient> client_;
+  cr_fuchsia::TestNavigationListener navigation_listener_;
+  fuchsia::web::NavigationControllerPtr controller_;
+
+ private:
+  const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
+
+  DISALLOW_COPY_AND_ASSIGN(ApiBindingsClientTest);
+};
+
+IN_PROC_BROWSER_TEST_F(ApiBindingsClientTest, EndToEnd) {
+  std::vector<chromium::cast::ApiBinding> binding_list;
+  chromium::cast::ApiBinding echo_binding;
+  echo_binding.set_before_load_script(cr_fuchsia::MemBufferFromString(
+      "window.echo = cast.__platform__.PortConnector.bind('echoService');"));
+  binding_list.emplace_back(std::move(echo_binding));
+  api_service_.set_bindings(std::move(binding_list));
+  StartClient();
+
+  const GURL test_url = embedded_test_server()->GetURL("/echo.html");
+  EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(
+      controller_.get(), fuchsia::web::LoadUrlParams(), test_url.spec()));
+  navigation_listener_.RunUntilUrlEquals(test_url);
+  connector_->OnPageLoad();
+
+  fuchsia::web::MessagePortPtr port =
+      api_service_.RunUntilMessagePortReceived("echoService").Bind();
+
+  fuchsia::web::WebMessage message;
+  message.set_data(cr_fuchsia::MemBufferFromString("ping"));
+  port->PostMessage(std::move(message),
+                    [](fuchsia::web::MessagePort_PostMessage_Result result) {
+                      EXPECT_TRUE(result.is_response());
+                    });
+
+  base::RunLoop response_loop;
+  cr_fuchsia::ResultReceiver<fuchsia::web::WebMessage> response(
+      response_loop.QuitClosure());
+  port->ReceiveMessage(
+      cr_fuchsia::CallbackToFitFunction(response.GetReceiveCallback()));
+  response_loop.Run();
+
+  std::string response_string;
+  EXPECT_TRUE(
+      cr_fuchsia::StringFromMemBuffer(response->data(), &response_string));
+  EXPECT_EQ("ack ping", response_string);
+}
+
+}  // namespace
diff --git a/fuchsia/runners/cast/cast_channel_bindings.cc b/fuchsia/runners/cast/cast_channel_bindings.cc
index a65cb2b..cd36f9b 100644
--- a/fuchsia/runners/cast/cast_channel_bindings.cc
+++ b/fuchsia/runners/cast/cast_channel_bindings.cc
@@ -26,18 +26,15 @@
 CastChannelBindings::CastChannelBindings(
     fuchsia::web::Frame* frame,
     NamedMessagePortConnector* connector,
-    chromium::cast::CastChannelPtr channel_consumer,
-    base::OnceClosure on_error_closure)
+    chromium::cast::CastChannelPtr channel_consumer)
     : frame_(frame),
       connector_(connector),
-      on_error_closure_(std::move(on_error_closure)),
       channel_consumer_(std::move(channel_consumer)) {
   DCHECK(connector_);
   DCHECK(frame_);
 
-  channel_consumer_.set_error_handler([this](zx_status_t status) mutable {
-    ZX_LOG(ERROR, status) << " Agent disconnected";
-    std::move(on_error_closure_).Run();
+  channel_consumer_.set_error_handler([](zx_status_t status) mutable {
+    ZX_LOG(ERROR, status) << "Cast Channel FIDL client disconnected";
   });
 
   connector->Register(
diff --git a/fuchsia/runners/cast/cast_channel_bindings.h b/fuchsia/runners/cast/cast_channel_bindings.h
index 414250d..79c4739 100644
--- a/fuchsia/runners/cast/cast_channel_bindings.h
+++ b/fuchsia/runners/cast/cast_channel_bindings.h
@@ -23,15 +23,11 @@
   // |frame|: The frame to be provided with a CastChannel.
   // |connector|: The NamedMessagePortConnector to use for establishing
   // transport.
-  // |on_error_closure|: Invoked in the event of an unrecoverable error (e.g.
-  //                     lost connection to the Agent). The callback must
-  //                     remain valid for the entire lifetime of |this|.
   // |channel_consumer|: A FIDL service which receives opened Cast Channels.
   // Both |frame| and |connector| must outlive |this|.
   CastChannelBindings(fuchsia::web::Frame* frame,
                       NamedMessagePortConnector* connector,
-                      chromium::cast::CastChannelPtr channel_consumer,
-                      base::OnceClosure on_error_closure);
+                      chromium::cast::CastChannelPtr channel_consumer);
   ~CastChannelBindings();
 
  private:
@@ -65,8 +61,6 @@
 
   fuchsia::mem::Buffer bindings_script_;
 
-  base::OnceClosure on_error_closure_;
-
   // The service which will receive connected Cast Channels.
   chromium::cast::CastChannelPtr channel_consumer_;
 
diff --git a/fuchsia/runners/cast/cast_channel_bindings_browsertest.cc b/fuchsia/runners/cast/cast_channel_bindings_browsertest.cc
index ed554fd5..e3db04e 100644
--- a/fuchsia/runners/cast/cast_channel_bindings_browsertest.cc
+++ b/fuchsia/runners/cast/cast_channel_bindings_browsertest.cc
@@ -129,8 +129,7 @@
   frame_->GetNavigationController(controller.NewRequest());
 
   CastChannelBindings cast_channel_instance(
-      frame_.get(), connector_.get(), receiver_binding_.NewBinding().Bind(),
-      base::MakeExpectedNotRunClosure(FROM_HERE));
+      frame_.get(), connector_.get(), receiver_binding_.NewBinding().Bind());
 
   // Verify that CastChannelBindings can properly handle message, connect,
   // disconnect, and MessagePort disconnection events.
@@ -156,8 +155,7 @@
   frame_->GetNavigationController(controller.NewRequest());
 
   CastChannelBindings cast_channel_instance(
-      frame_.get(), connector_.get(), receiver_binding_.NewBinding().Bind(),
-      base::MakeExpectedNotRunClosure(FROM_HERE));
+      frame_.get(), connector_.get(), receiver_binding_.NewBinding().Bind());
 
   // Verify that CastChannelBindings can properly handle message, connect,
   // disconnect, and MessagePort disconnection events.
diff --git a/fuchsia/runners/cast/cast_component.cc b/fuchsia/runners/cast/cast_component.cc
index a482c6e..d0f0aa7 100644
--- a/fuchsia/runners/cast/cast_component.cc
+++ b/fuchsia/runners/cast/cast_component.cc
@@ -41,6 +41,7 @@
 CastComponent::CastComponent(
     CastRunner* runner,
     chromium::cast::ApplicationConfig application_config,
+    std::unique_ptr<ApiBindingsClient> api_bindings_client,
     std::unique_ptr<base::fuchsia::StartupContext> context,
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
         controller_request,
@@ -51,6 +52,7 @@
       touch_input_policy_(
           TouchInputPolicyFromApplicationConfig(application_config_)),
       connector_(frame()),
+      api_bindings_client_(std::move(api_bindings_client)),
       navigation_listener_binding_(this) {
   base::AutoReset<bool> constructor_active_reset(&constructor_active_, true);
 
@@ -59,6 +61,11 @@
 
   frame()->SetNavigationEventListener(
       navigation_listener_binding_.NewBinding());
+  api_bindings_client_->AttachToFrame(
+      frame(), &connector_,
+      base::BindOnce(&CastComponent::DestroyComponent, base::Unretained(this),
+                     kBindingsFailureExitCode,
+                     fuchsia::sys::TerminationReason::INTERNAL_ERROR));
 }
 
 CastComponent::~CastComponent() = default;
@@ -96,10 +103,7 @@
   cast_channel_ = std::make_unique<CastChannelBindings>(
       frame(), &connector_,
       agent_manager_->ConnectToAgentService<chromium::cast::CastChannel>(
-          CastRunner::kAgentComponentUrl),
-      base::BindOnce(&CastComponent::DestroyComponent, base::Unretained(this),
-                     kBindingsFailureExitCode,
-                     fuchsia::sys::TerminationReason::INTERNAL_ERROR));
+          CastRunner::kAgentComponentUrl));
 
   queryable_data_ = std::make_unique<QueryableDataBindings>(
       frame(),
diff --git a/fuchsia/runners/cast/cast_component.h b/fuchsia/runners/cast/cast_component.h
index e8ee7ad..7ccf279 100644
--- a/fuchsia/runners/cast/cast_component.h
+++ b/fuchsia/runners/cast/cast_component.h
@@ -10,6 +10,7 @@
 
 #include "base/fuchsia/service_directory.h"
 #include "fuchsia/base/agent_manager.h"
+#include "fuchsia/runners/cast/api_bindings_client.h"
 #include "fuchsia/runners/cast/cast_channel_bindings.h"
 #include "fuchsia/runners/cast/named_message_port_connector.h"
 #include "fuchsia/runners/cast/queryable_data_bindings.h"
@@ -24,6 +25,7 @@
  public:
   CastComponent(CastRunner* runner,
                 chromium::cast::ApplicationConfig application_config,
+                std::unique_ptr<ApiBindingsClient> bindings_manager,
                 std::unique_ptr<base::fuchsia::StartupContext> startup_context,
                 fidl::InterfaceRequest<fuchsia::sys::ComponentController>
                     controller_request,
@@ -31,6 +33,7 @@
   ~CastComponent() override;
 
  private:
+  // TODO(crbug.com/953958): Remove this.
   void InitializeCastPlatformBindings();
 
   // WebComponent overrides.
@@ -52,6 +55,7 @@
   std::unique_ptr<CastChannelBindings> cast_channel_;
   std::unique_ptr<QueryableDataBindings> queryable_data_;
   std::unique_ptr<TouchInputBindings> touch_input_;
+  std::unique_ptr<ApiBindingsClient> api_bindings_client_;
 
   fidl::Binding<fuchsia::web::NavigationEventListener>
       navigation_listener_binding_;
diff --git a/fuchsia/runners/cast/cast_runner.cc b/fuchsia/runners/cast/cast_runner.cc
index b161cd1..32c636b 100644
--- a/fuchsia/runners/cast/cast_runner.cc
+++ b/fuchsia/runners/cast/cast_runner.cc
@@ -26,7 +26,9 @@
   chromium::cast::ApplicationConfigManagerPtr app_config_manager;
   std::unique_ptr<base::fuchsia::StartupContext> startup_context;
   std::unique_ptr<cr_fuchsia::AgentManager> agent_manager;
+  std::unique_ptr<ApiBindingsClient> bindings_manager;
   fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller_request;
+  chromium::cast::ApplicationConfig app_config;
 };
 
 void CastRunner::StartComponent(
@@ -67,6 +69,15 @@
                           chromium::cast::ApplicationConfig());
       });
 
+  // Get binding details from the Agent.
+  fidl::InterfaceHandle<chromium::cast::ApiBindings> api_bindings_client;
+  pending_component->agent_manager->ConnectToAgentService(
+      kAgentComponentUrl, api_bindings_client.NewRequest());
+  pending_component->bindings_manager = std::make_unique<ApiBindingsClient>(
+      std::move(api_bindings_client),
+      base::BindOnce(&CastRunner::MaybeStartComponent, base::Unretained(this),
+                     base::Unretained(pending_component.get())));
+
   const std::string cast_app_id(cast_url.GetContent());
   pending_component->app_config_manager->GetConfig(
       cast_app_id, [this, pending_component = pending_component.get()](
@@ -97,15 +108,31 @@
     return;
   }
 
+  pending_component->app_config = std::move(app_config);
+
+  MaybeStartComponent(pending_component);
+}
+
+void CastRunner::MaybeStartComponent(PendingComponent* pending_component) {
+  // Called after the completion of GetConfigCallback() or
+  // ApiBindingsClient::OnBindingsReceived().
+  if (pending_component->app_config.IsEmpty() ||
+      !pending_component->bindings_manager->HasBindings()) {
+    // Don't proceed unless both the application config and API bindings are
+    // received.
+    return;
+  }
+
   // Create a component based on the returned configuration, and pass it the
   // fields stashed in PendingComponent.
-  GURL cast_app_url(app_config.web_url());
+  GURL cast_app_url(pending_component->app_config.web_url());
   auto component = std::make_unique<CastComponent>(
-      this, std::move(app_config),
+      this, std::move(pending_component->app_config),
+      std::move(pending_component->bindings_manager),
       std::move(pending_component->startup_context),
       std::move(pending_component->controller_request),
       std::move(pending_component->agent_manager));
-  pending_components_.erase(it);
+  pending_components_.erase(pending_component);
 
   component->LoadUrl(std::move(cast_app_url));
   RegisterComponent(std::move(component));
diff --git a/fuchsia/runners/cast/cast_runner.h b/fuchsia/runners/cast/cast_runner.h
index 7a50775a..fe44c2d 100644
--- a/fuchsia/runners/cast/cast_runner.h
+++ b/fuchsia/runners/cast/cast_runner.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <set>
+#include <vector>
 
 #include "base/callback.h"
 #include "base/containers/flat_set.h"
@@ -37,6 +38,11 @@
 
   void GetConfigCallback(PendingComponent* pending_component,
                          chromium::cast::ApplicationConfig app_config);
+  void GetBindingsCallback(PendingComponent* pending_component,
+                           std::vector<chromium::cast::ApiBinding> bindings);
+
+  // Starts a component once all configuration data is available.
+  void MaybeStartComponent(PendingComponent* pending_component);
 
   // Holds StartComponent() requests while the ApplicationConfig is being
   // fetched from the ApplicationConfigManager.
diff --git a/fuchsia/runners/cast/cast_runner_integration_test.cc b/fuchsia/runners/cast/cast_runner_integration_test.cc
index 888e50c..37415e3 100644
--- a/fuchsia/runners/cast/cast_runner_integration_test.cc
+++ b/fuchsia/runners/cast/cast_runner_integration_test.cc
@@ -21,6 +21,7 @@
 #include "fuchsia/base/result_receiver.h"
 #include "fuchsia/runners/cast/cast_runner.h"
 #include "fuchsia/runners/cast/fake_application_config_manager.h"
+#include "fuchsia/runners/cast/test_api_bindings.h"
 #include "fuchsia/runners/common/web_component.h"
 #include "fuchsia/runners/common/web_content_runner.h"
 #include "net/test/embedded_test_server/default_handlers.h"
@@ -78,10 +79,16 @@
  public:
   FakeComponentState(
       base::StringPiece component_url,
-      chromium::cast::ApplicationConfigManager* app_config_manager)
+      chromium::cast::ApplicationConfigManager* app_config_manager,
+      chromium::cast::ApiBindings* bindings_manager)
       : ComponentStateBase(component_url),
         app_config_binding_(service_directory(), app_config_manager),
-        cast_channel_(std::make_unique<FakeCastChannel>(service_directory())) {}
+        cast_channel_(std::make_unique<FakeCastChannel>(service_directory())) {
+    if (bindings_manager)
+      bindings_manager_binding_ = std::make_unique<
+          base::fuchsia::ScopedServiceBinding<chromium::cast::ApiBindings>>(
+          service_directory(), bindings_manager);
+  }
   ~FakeComponentState() override {
     if (on_delete_)
       std::move(on_delete_).Run();
@@ -98,6 +105,9 @@
   const base::fuchsia::ScopedServiceBinding<
       chromium::cast::ApplicationConfigManager>
       app_config_binding_;
+  std::unique_ptr<
+      base::fuchsia::ScopedServiceBinding<chromium::cast::ApiBindings>>
+      bindings_manager_binding_;
   std::unique_ptr<FakeCastChannel> cast_channel_;
   base::OnceClosure on_delete_;
 
@@ -196,7 +206,8 @@
   std::unique_ptr<cr_fuchsia::AgentImpl::ComponentStateBase> OnComponentConnect(
       base::StringPiece component_url) {
     auto component_state = std::make_unique<FakeComponentState>(
-        component_url, &app_config_manager_);
+        component_url, &app_config_manager_,
+        (provide_api_bindings_ ? &api_bindings_ : nullptr));
     component_state_ = component_state.get();
     return component_state;
   }
@@ -208,6 +219,11 @@
   // Returns fake Cast application information to the CastRunner.
   FakeApplicationConfigManager app_config_manager_;
 
+  TestApiBindings api_bindings_;
+
+  // If set, publishes an ApiBindings service from the Agent.
+  bool provide_api_bindings_ = true;
+
   // Incoming service directory, ComponentContext and per-component state.
   std::unique_ptr<base::fuchsia::ServiceDirectory> component_services_;
   std::unique_ptr<cr_fuchsia::FakeComponentContext> component_context_;
@@ -270,6 +286,56 @@
   run_loop.Run();
 }
 
+// Ensures that the runner will continue to work during the transitional period
+// when the Agent does not supply an ApiBindings.
+// TODO(crbug.com/953958): Remove this.
+TEST_F(CastRunnerIntegrationTest, NoApiBindings) {
+  provide_api_bindings_ = false;
+  const char kBlankAppId[] = "00000000";
+  const char kBlankAppPath[] = "/defaultresponse";
+  app_config_manager_.AddAppMapping(kBlankAppId,
+                                    test_server_.GetURL(kBlankAppPath));
+
+  // Launch the test-app component.
+  fuchsia::sys::ComponentControllerPtr component_controller =
+      StartCastComponent(base::StringPrintf("cast:%s", kBlankAppId));
+  component_controller.set_error_handler(&ComponentErrorHandler);
+
+  // Access the NavigationController from the WebComponent. The test will hang
+  // here if no WebComponent was created.
+  fuchsia::web::NavigationControllerPtr nav_controller;
+  {
+    base::RunLoop run_loop;
+    cr_fuchsia::ResultReceiver<WebComponent*> web_component(
+        run_loop.QuitClosure());
+    cast_runner_->GetWebComponentForTest(web_component.GetReceiveCallback());
+    run_loop.Run();
+    ASSERT_NE(*web_component, nullptr);
+    (*web_component)
+        ->frame()
+        ->GetNavigationController(nav_controller.NewRequest());
+  }
+
+  // Ensure the NavigationState has the expected URL.
+  {
+    base::RunLoop run_loop;
+    cr_fuchsia::ResultReceiver<fuchsia::web::NavigationState> nav_entry(
+        run_loop.QuitClosure());
+    nav_controller->GetVisibleEntry(
+        cr_fuchsia::CallbackToFitFunction(nav_entry.GetReceiveCallback()));
+    run_loop.Run();
+    ASSERT_TRUE(nav_entry->has_url());
+    EXPECT_EQ(nav_entry->url(), test_server_.GetURL(kBlankAppPath).spec());
+  }
+
+  // Verify that the component is torn down when |component_controller| is
+  // unbound.
+  base::RunLoop run_loop;
+  component_state_->set_on_delete(run_loop.QuitClosure());
+  component_controller.Unbind();
+  run_loop.Run();
+}
+
 TEST_F(CastRunnerIntegrationTest, IncorrectCastAppId) {
   // Launch the a component with an invalid Cast app Id.
   fuchsia::sys::ComponentControllerPtr component_controller =
@@ -353,37 +419,6 @@
   run_loop.Run();
 }
 
-TEST_F(CastRunnerIntegrationTest, CastChannelConsumerDropped) {
-  const char kCastChannelAppId[] = "00000001";
-  const char kCastChannelAppPath[] = "/cast_channel.html";
-  app_config_manager_.AddAppMapping(kCastChannelAppId,
-                                    test_server_.GetURL(kCastChannelAppPath));
-
-  // Launch the test-app component.
-  fuchsia::sys::ComponentControllerPtr component_controller =
-      StartCastComponent(base::StringPrintf("cast:%s", kCastChannelAppId));
-
-  // Spin the message loop to handle creation of the component state.
-  base::RunLoop().RunUntilIdle();
-  ASSERT_TRUE(component_state_);
-
-  // Disconnecting the CastChannel should trigger the component to teardown,
-  // resulting in our ComponentControllerPtr being dropped, and the component
-  // state held by the agent being deleted.
-  component_state_->set_on_delete(base::MakeExpectedRunClosure(FROM_HERE));
-  component_state_->ClearCastChannel();
-  base::RunLoop run_loop;
-  component_controller.set_error_handler([&run_loop](zx_status_t status) {
-    EXPECT_EQ(status, ZX_ERR_PEER_CLOSED);
-    run_loop.Quit();
-  });
-  run_loop.Run();
-  EXPECT_FALSE(component_controller.is_bound());
-
-  // Give the fake agent an opportunity to perform teardown cleanup.
-  base::RunLoop().RunUntilIdle();
-}
-
 TEST_F(CastRunnerIntegrationTest, CastChannelComponentControllerDropped) {
   const char kCastChannelAppId[] = "00000001";
   const char kCastChannelAppPath[] = "/cast_channel.html";
diff --git a/fuchsia/runners/cast/named_message_port_connector.cc b/fuchsia/runners/cast/named_message_port_connector.cc
index 4b59300..e8d338d 100644
--- a/fuchsia/runners/cast/named_message_port_connector.cc
+++ b/fuchsia/runners/cast/named_message_port_connector.cc
@@ -57,6 +57,11 @@
   DCHECK(port_connected_handlers_.empty());
 }
 
+void NamedMessagePortConnector::RegisterDefaultHandler(
+    DefaultPortConnectedCallback handler) {
+  default_handler_ = std::move(handler);
+}
+
 void NamedMessagePortConnector::Register(const std::string& port_name,
                                          PortConnectedCallback handler) {
   DCHECK(handler);
@@ -106,13 +111,6 @@
     return;
   }
 
-  if (port_connected_handlers_.find(port_name) ==
-      port_connected_handlers_.end()) {
-    LOG(ERROR) << "No registration for port: " << port_name;
-    control_port_.Unbind();
-    return;
-  }
-
   if (message.incoming_transfer().size() != 1) {
     LOG(ERROR) << "Expected one Transferable, got "
                << message.incoming_transfer().size() << " instead.";
@@ -127,8 +125,24 @@
     control_port_.Unbind();
     return;
   }
-  port_connected_handlers_[port_name].Run(
-      std::move(transferable.message_port()));
 
-  ReceiveNextConnectRequest();
+  if (default_handler_ && port_connected_handlers_.find(port_name) ==
+                              port_connected_handlers_.end()) {
+    default_handler_.Run(port_name, std::move(transferable.message_port()));
+  } else {
+    // TODO(crbug.com/953958): Deprecated, remove this once all APIs are
+    // migrated.
+
+    if (port_connected_handlers_.find(port_name) ==
+        port_connected_handlers_.end()) {
+      LOG(ERROR) << "No registration for port: " << port_name;
+      control_port_.Unbind();
+      return;
+    }
+
+    port_connected_handlers_[port_name].Run(
+        std::move(transferable.message_port()));
+
+    ReceiveNextConnectRequest();
+  }
 }
diff --git a/fuchsia/runners/cast/named_message_port_connector.h b/fuchsia/runners/cast/named_message_port_connector.h
index 54ca2fa..a698021 100644
--- a/fuchsia/runners/cast/named_message_port_connector.h
+++ b/fuchsia/runners/cast/named_message_port_connector.h
@@ -17,18 +17,31 @@
 // or more services registered by the caller.
 class NamedMessagePortConnector {
  public:
+  using DefaultPortConnectedCallback = base::RepeatingCallback<void(
+      base::StringPiece,
+      fidl::InterfaceHandle<fuchsia::web::MessagePort>)>;
+
+  // TODO(crbug.com/953958): Deprecated, remove this.
   using PortConnectedCallback = base::RepeatingCallback<void(
       fidl::InterfaceHandle<fuchsia::web::MessagePort>)>;
 
   explicit NamedMessagePortConnector(fuchsia::web::Frame* frame);
   ~NamedMessagePortConnector();
 
+  // Sets the handler that is called for connected ports which aren't
+  // registered in advance.
+  // TODO(crbug.com/953958): Rename this to Register() when the transition is
+  // complete.
+  void RegisterDefaultHandler(DefaultPortConnectedCallback handler);
+
   // Registers a |handler| which will receive MessagePorts originating from
   // |frame_|'s web content. |port_name| is a non-empty, alphanumeric string
   // shared with the native backends.
+  // TODO(crbug.com/953958): Remove this method.
   void Register(const std::string& port_name, PortConnectedCallback handler);
 
   // Unregisters a handler.
+  // TODO(crbug.com/953958): Remove this method.
   void Unregister(const std::string& port_name);
 
   // Invoked by the caller after every |frame_| page load.
@@ -43,7 +56,14 @@
   void OnConnectRequest(fuchsia::web::WebMessage message);
 
   fuchsia::web::Frame* const frame_;
+
+  // Invoked for ports which weren't previously Register()'ed.
+  DefaultPortConnectedCallback default_handler_;
+
+  // Deprecated.
+  // TODO(crbug.com/953958): Remove this.
   std::map<std::string, PortConnectedCallback> port_connected_handlers_;
+
   fuchsia::web::MessagePortPtr control_port_;
 
   DISALLOW_COPY_AND_ASSIGN(NamedMessagePortConnector);
diff --git a/fuchsia/runners/cast/test_api_bindings.cc b/fuchsia/runners/cast/test_api_bindings.cc
new file mode 100644
index 0000000..ef99298
--- /dev/null
+++ b/fuchsia/runners/cast/test_api_bindings.cc
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fuchsia/runners/cast/test_api_bindings.h"
+
+#include "base/run_loop.h"
+
+TestApiBindings::TestApiBindings() = default;
+
+TestApiBindings::~TestApiBindings() = default;
+
+fidl::InterfaceHandle<::fuchsia::web::MessagePort>
+TestApiBindings::RunUntilMessagePortReceived(base::StringPiece port_name) {
+  while (ports_.find(port_name.as_string()) == ports_.end()) {
+    base::RunLoop run_loop;
+    port_received_closure_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  fidl::InterfaceHandle<::fuchsia::web::MessagePort> port =
+      std::move(ports_[port_name.as_string()]);
+  ports_.erase(port_name.as_string());
+  return port;
+}
+
+void TestApiBindings::GetAll(GetAllCallback callback) {
+  callback(std::move(bindings_));
+}
+
+void TestApiBindings::Connect(
+    std::string port_name,
+    fidl::InterfaceHandle<::fuchsia::web::MessagePort> message_port) {
+  ports_[port_name] = std::move(message_port);
+
+  if (port_received_closure_)
+    std::move(port_received_closure_).Run();
+}
diff --git a/fuchsia/runners/cast/test_api_bindings.h b/fuchsia/runners/cast/test_api_bindings.h
new file mode 100644
index 0000000..eb556bc
--- /dev/null
+++ b/fuchsia/runners/cast/test_api_bindings.h
@@ -0,0 +1,48 @@
+// 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 FUCHSIA_RUNNERS_CAST_TEST_API_BINDINGS_H_
+#define FUCHSIA_RUNNERS_CAST_TEST_API_BINDINGS_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+
+// Simple implementation of the ApiBindings service, for use by tests.
+class TestApiBindings : public chromium::cast::ApiBindings {
+ public:
+  TestApiBindings();
+  ~TestApiBindings() override;
+
+  // Spins a RunLoop until a port named |port_name| is received.
+  fidl::InterfaceHandle<::fuchsia::web::MessagePort>
+  RunUntilMessagePortReceived(base::StringPiece port_name);
+
+  // Sets the list of bindings which will be returned by GetAll().
+  void set_bindings(std::vector<chromium::cast::ApiBinding> bindings) {
+    bindings_ = std::move(bindings);
+  }
+
+ private:
+  // chromium::cast::ApiBindingsManager implementation.
+  void GetAll(GetAllCallback callback) override;
+  void Connect(
+      std::string channel_id,
+      fidl::InterfaceHandle<::fuchsia::web::MessagePort> message_port) override;
+
+  std::map<std::string, fidl::InterfaceHandle<::fuchsia::web::MessagePort>>
+      ports_;
+  std::vector<chromium::cast::ApiBinding> bindings_;
+  base::OnceClosure port_received_closure_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestApiBindings);
+};
+
+#endif  // FUCHSIA_RUNNERS_CAST_TEST_API_BINDINGS_H_
diff --git a/fuchsia/runners/cast/testdata/echo.html b/fuchsia/runners/cast/testdata/echo.html
new file mode 100644
index 0000000..bfc14c7
--- /dev/null
+++ b/fuchsia/runners/cast/testdata/echo.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <head><title>echo</title></head>
+  <body>
+    <script>
+      // Use the "echo" port that was bound to the scripting context by the
+      // browsertest.
+      echo.onmessage = function(msg) {
+        echo.postMessage('ack ' + msg.data);
+      }
+    </script>
+  </body>
+</html>
diff --git a/gpu/command_buffer/tests/gl_test_utils.cc b/gpu/command_buffer/tests/gl_test_utils.cc
index 4881834..25346d7 100644
--- a/gpu/command_buffer/tests/gl_test_utils.cc
+++ b/gpu/command_buffer/tests/gl_test_utils.cc
@@ -374,7 +374,7 @@
     }
 
     gpu::GPUInfo gpu_info;
-    gpu::CollectContextGraphicsInfo(&gpu_info, gpu::GpuPreferences());
+    gpu::CollectContextGraphicsInfo(&gpu_info);
     // See crbug.com/822716, the ATI proprietary driver has eglGetProcAddress
     // but eglInitialize crashes with x11.
     if (gpu_info.gl_vendor.find("ATI Technologies Inc.") != std::string::npos) {
diff --git a/gpu/config/gpu_blacklist.cc b/gpu/config/gpu_blacklist.cc
index e7e24c8..66eebdd 100644
--- a/gpu/config/gpu_blacklist.cc
+++ b/gpu/config/gpu_blacklist.cc
@@ -47,7 +47,7 @@
                             GPU_FEATURE_TYPE_OOP_RASTERIZATION);
   list->AddSupportedFeature("android_surface_control",
                             GPU_FEATURE_TYPE_ANDROID_SURFACE_CONTROL);
-
+  list->AddSupportedFeature("metal", GPU_FEATURE_TYPE_METAL);
   return list;
 }
 
diff --git a/gpu/config/gpu_feature_type.h b/gpu/config/gpu_feature_type.h
index a9684b9..5ac3ab5 100644
--- a/gpu/config/gpu_feature_type.h
+++ b/gpu/config/gpu_feature_type.h
@@ -23,6 +23,7 @@
   GPU_FEATURE_TYPE_PROTECTED_VIDEO_DECODE,
   GPU_FEATURE_TYPE_OOP_RASTERIZATION,
   GPU_FEATURE_TYPE_ANDROID_SURFACE_CONTROL,
+  GPU_FEATURE_TYPE_METAL,
   NUMBER_OF_GPU_FEATURE_TYPES
 };
 
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index f5d598c..e64de67 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -63,13 +63,6 @@
 const base::Feature kDefaultPassthroughCommandDecoder{
     "DefaultPassthroughCommandDecoder", base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kDirectCompositionGpuVSync{
-    "DirectCompositionGpuVSync", base::FEATURE_DISABLED_BY_DEFAULT};
-
-// Overrides preferred overlay format to NV12 instead of YUY2.
-const base::Feature kDirectCompositionPreferNV12Overlays{
-    "DirectCompositionPreferNV12Overlays", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Allow putting a video swapchain underneath the main swapchain, so overlays
 // can be used even if there are controls on top of the video. It can be
 // enabled only when overlay is supported.
@@ -99,11 +92,6 @@
     "UseDCOverlaysForSoftwareProtectedVideo",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Use decode swap chain created from compatible video decoder buffers.
-const base::Feature kDirectCompositionUseNV12DecodeSwapChain{
-    "DirectCompositionUseNV12DecodeSwapChain",
-    base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Controls the decode acceleration of JPEG images (as opposed to camera
 // captures) in Chrome OS using the VA-API.
 // TODO(andrescj): remove or enable by default in Chrome OS once
diff --git a/gpu/config/gpu_finch_features.h b/gpu/config/gpu_finch_features.h
index 08ce217..3d22294 100644
--- a/gpu/config/gpu_finch_features.h
+++ b/gpu/config/gpu_finch_features.h
@@ -27,10 +27,6 @@
 
 GPU_EXPORT extern const base::Feature kDefaultPassthroughCommandDecoder;
 
-GPU_EXPORT extern const base::Feature kDirectCompositionGpuVSync;
-
-GPU_EXPORT extern const base::Feature kDirectCompositionPreferNV12Overlays;
-
 GPU_EXPORT extern const base::Feature kDirectCompositionUnderlays;
 
 GPU_EXPORT extern const base::Feature
@@ -44,8 +40,6 @@
 
 GPU_EXPORT extern const base::Feature kUseDCOverlaysForSoftwareProtectedVideo;
 
-GPU_EXPORT extern const base::Feature kDirectCompositionUseNV12DecodeSwapChain;
-
 GPU_EXPORT extern const base::Feature kVaapiJpegImageDecodeAcceleration;
 
 #if defined(OS_ANDROID)
diff --git a/gpu/config/gpu_info.cc b/gpu/config/gpu_info.cc
index 15e6b30..01769df 100644
--- a/gpu/config/gpu_info.cc
+++ b/gpu/config/gpu_info.cc
@@ -94,16 +94,6 @@
 }
 
 #if defined(OS_WIN)
-void EnumerateOverlayCapability(const gpu::OverlayCapability& cap,
-                                gpu::GPUInfo::Enumerator* enumerator) {
-  std::string key_string = "overlayCap";
-  key_string += OverlayFormatToString(cap.format);
-  enumerator->BeginOverlayCapability();
-  enumerator->AddString(key_string.c_str(),
-                        cap.is_scaling_supported ? "SCALING" : "DIRECT");
-  enumerator->EndOverlayCapability();
-}
-
 void EnumerateDx12VulkanVersionInfo(const gpu::Dx12VulkanVersionInfo& info,
                                     gpu::GPUInfo::Enumerator* enumerator) {
   enumerator->BeginDx12VulkanVersionInfo();
@@ -121,22 +111,17 @@
 namespace gpu {
 
 #if defined(OS_WIN)
-const char* OverlayFormatToString(OverlayFormat format) {
-  switch (format) {
-    case OverlayFormat::kBGRA:
-      return "BGRA";
-    case OverlayFormat::kYUY2:
-      return "YUY2";
-    case OverlayFormat::kNV12:
-      return "NV12";
+const char* OverlaySupportToString(gpu::OverlaySupport support) {
+  switch (support) {
+    case gpu::OverlaySupport::kNone:
+      return "NONE";
+    case gpu::OverlaySupport::kDirect:
+      return "DIRECT";
+    case gpu::OverlaySupport::kScaling:
+      return "SCALING";
   }
 }
-
-bool OverlayCapability::operator==(const OverlayCapability& other) const {
-  return format == other.format &&
-         is_scaling_supported == other.is_scaling_supported;
-}
-#endif
+#endif  // OS_WIN
 
 VideoDecodeAcceleratorCapabilities::VideoDecodeAcceleratorCapabilities()
     : flags(0) {}
@@ -252,7 +237,8 @@
 #if defined(OS_WIN)
     bool direct_composition;
     bool supports_overlays;
-    OverlayCapabilities overlay_capabilities;
+    OverlaySupport yuy2_overlay_support;
+    OverlaySupport nv12_overlay_support;
     DxDiagNode dx_diagnostics;
     Dx12VulkanVersionInfo dx12_vulkan_version_info;
 #endif
@@ -317,8 +303,10 @@
 #if defined(OS_WIN)
   enumerator->AddBool("directComposition", direct_composition);
   enumerator->AddBool("supportsOverlays", supports_overlays);
-  for (const auto& cap : overlay_capabilities)
-    EnumerateOverlayCapability(cap, enumerator);
+  enumerator->AddString("yuy2OverlaySupport",
+                        OverlaySupportToString(yuy2_overlay_support));
+  enumerator->AddString("nv12OverlaySupport",
+                        OverlaySupportToString(nv12_overlay_support));
   EnumerateDx12VulkanVersionInfo(dx12_vulkan_version_info, enumerator);
 #endif
   enumerator->AddInt("videoDecodeAcceleratorFlags",
diff --git a/gpu/config/gpu_info.h b/gpu/config/gpu_info.h
index 15a2b8d..8aef772 100644
--- a/gpu/config/gpu_info.h
+++ b/gpu/config/gpu_info.h
@@ -154,19 +154,9 @@
     std::vector<ImageDecodeAcceleratorSupportedProfile>;
 
 #if defined(OS_WIN)
-// Common overlay formats that we're interested in. Must match the OverlayFormat
-// enum in //tools/metrics/histograms/enums.xml. Mapped to corresponding DXGI
-// formats in DirectCompositionSurfaceWin.
-enum class OverlayFormat { kBGRA = 0, kYUY2 = 1, kNV12 = 2, kMaxValue = kNV12 };
+enum class OverlaySupport { kNone = 0, kDirect = 1, kScaling = 2 };
 
-GPU_EXPORT const char* OverlayFormatToString(OverlayFormat format);
-
-struct GPU_EXPORT OverlayCapability {
-  OverlayFormat format;
-  bool is_scaling_supported;
-  bool operator==(const OverlayCapability& other) const;
-};
-using OverlayCapabilities = std::vector<OverlayCapability>;
+GPU_EXPORT const char* OverlaySupportToString(OverlaySupport support);
 
 struct GPU_EXPORT Dx12VulkanVersionInfo {
   bool IsEmpty() const { return !d3d12_feature_level && !vulkan_version; }
@@ -323,8 +313,8 @@
 
   // True if we use direct composition surface overlays on Windows.
   bool supports_overlays = false;
-
-  OverlayCapabilities overlay_capabilities;
+  OverlaySupport yuy2_overlay_support = OverlaySupport::kNone;
+  OverlaySupport nv12_overlay_support = OverlaySupport::kNone;
 
   // The information returned by the DirectX Diagnostics Tool.
   DxDiagNode dx_diagnostics;
@@ -391,9 +381,6 @@
     virtual void BeginAuxAttributes() = 0;
     virtual void EndAuxAttributes() = 0;
 
-    virtual void BeginOverlayCapability() = 0;
-    virtual void EndOverlayCapability() = 0;
-
     virtual void BeginDx12VulkanVersionInfo() = 0;
     virtual void EndDx12VulkanVersionInfo() = 0;
 
diff --git a/gpu/config/gpu_info_collector.cc b/gpu/config/gpu_info_collector.cc
index 3e1eced..ad0e520 100644
--- a/gpu/config/gpu_info_collector.cc
+++ b/gpu/config/gpu_info_collector.cc
@@ -20,7 +20,6 @@
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/trace_event/trace_event.h"
-#include "gpu/config/gpu_preferences.h"
 #include "gpu/config/gpu_switches.h"
 #include "third_party/angle/src/gpu_info_util/SystemInfo.h"  // nogncheck
 #include "third_party/skia/include/core/SkGraphics.h"
@@ -171,8 +170,7 @@
   return CollectBasicGraphicsInfo(gpu_info);
 }
 
-bool CollectGraphicsInfoGL(GPUInfo* gpu_info,
-                           const GpuPreferences& gpu_preferences) {
+bool CollectGraphicsInfoGL(GPUInfo* gpu_info) {
   TRACE_EVENT0("startup", "gpu_info_collector::CollectGraphicsInfoGL");
   DCHECK_NE(gl::GetGLImplementation(), gl::kGLImplementationNone);
 
@@ -380,7 +378,7 @@
 void CollectGraphicsInfoForTesting(GPUInfo* gpu_info) {
   DCHECK(gpu_info);
 #if defined(OS_ANDROID)
-  CollectContextGraphicsInfo(gpu_info, GpuPreferences());
+  CollectContextGraphicsInfo(gpu_info);
 #else
   CollectBasicGraphicsInfo(gpu_info);
 #endif  // OS_ANDROID
diff --git a/gpu/config/gpu_info_collector.h b/gpu/config/gpu_info_collector.h
index 9421bab..10d3fef 100644
--- a/gpu/config/gpu_info_collector.h
+++ b/gpu/config/gpu_info_collector.h
@@ -20,7 +20,6 @@
 }
 
 namespace gpu {
-struct GpuPreferences;
 
 // Collects basic GPU info without creating a GL/DirectX context (and without
 // the danger of crashing), including vendor_id and device_id.
@@ -35,9 +34,7 @@
 
 // Create a GL/DirectX context and collect related info.
 // This is called at GPU process startup time.
-GPU_EXPORT bool CollectContextGraphicsInfo(
-    GPUInfo* gpu_info,
-    const GpuPreferences& gpu_preferences);
+GPU_EXPORT bool CollectContextGraphicsInfo(GPUInfo* gpu_info);
 
 #if defined(OS_WIN)
 // Collect the DirectX Disagnostics information about the attached displays.